Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `apm install --target intellij` now configures JetBrains Copilot MCP support
while routing package file primitives through the Copilot profile.
(by @sergio-sisternes-epam; closes #1957) (#2041)

### Fixed

- Azure DevOps marketplace checks now preserve suffix-free `/_git/<repo>` URLs
and pass Azure CLI bearer authentication through to `git ls-remote`. (closes #2119)
- Kiro hooks now emit the official Kiro v1 runtime shape
(`{"version": "v1", "hooks": [...]}`) that Kiro 1.0 reads natively, replacing
the superseded pre-1.0 schema that Kiro could not consume. Portable hooks gain
matchers, hook-level timeouts, and `enabled` toggles, and native v1 files
preserve their supported fields. The contract is recorded in the OpenAPM v0.1
specification. (closes #2071) (#2095)

## [0.24.1] - 2026-07-10

Expand Down
15 changes: 13 additions & 2 deletions CONFORMANCE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1107,12 +1107,23 @@
"tests": [
"tests/spec_conformance/test_manifest_reqs.py::test_consumer_deploys_antigravity_rules_with_expected_dedup"
]
},
{
"conformance_class": "consumer",
"id": "req-tg-006",
"keyword": "MUST",
"section": "8.5",
"status": "active",
"test_count": 1,
"tests": [
"tests/spec_conformance/test_manifest_reqs.py::test_consumer_deploys_kiro_v1_hook_schema"
]
}
],
"spec_version": "v0.1.1",
"summary_by_class": {
"consumer": {
"active": 69,
"active": 70,
"skipped": 1,
"unbound": 0,
"xfail": 0
Expand All @@ -1136,5 +1147,5 @@
"xfail": 0
}
},
"total_requirements": 98
"total_requirements": 99
}
3 changes: 2 additions & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ All four conformance classes (Producer, Consumer, Registry, Governance) carry ac
| Class | Active | Skipped | Xfail | Unbound |
|-------|-------:|--------:|------:|--------:|
| Producer | 12 | 0 | 0 | 0 |
| Consumer | 69 | 1 | 0 | 0 |
| Consumer | 70 | 1 | 0 | 0 |
| Registry | 1 | 0 | 0 | 0 |
| Governance | 15 | 0 | 0 | 0 |

Expand Down Expand Up @@ -125,6 +125,7 @@ All four conformance classes (Producer, Consumer, Registry, Governance) carry ac
| [req-tg-003](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-003) | MUST | 8.5 | consumer | active | 1 |
| [req-tg-004](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-004) | MUST | 4.2.1 | consumer | active | 1 |
| [req-tg-005](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-005) | MUST | 8.5 | consumer | active | 1 |
| [req-tg-006](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-006) | MUST | 8.5 | consumer | active | 1 |

## Waivers

Expand Down
4 changes: 4 additions & 0 deletions docs/public/specs/manifests/openapm-v0.1.requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ requirements:
keyword: MUST
section: "8.5"
conformance_class: consumer
- id: req-tg-006
keyword: MUST
section: "8.5"
conformance_class: consumer
- id: req-sc-001
keyword: MUST
section: "10.4"
Expand Down
10 changes: 7 additions & 3 deletions docs/src/content/docs/integrations/ide-tool-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ For server installation patterns, registry resolution, and trust model, see [MCP
instructions to `.kiro/steering/` and converts `applyTo:` scoping into Kiro
steering frontmatter (`inclusion: fileMatch`); unscoped instructions become
`inclusion: always`. Skills are copied verbatim to `.kiro/skills/`, hooks
become one JSON file per hook action in `.kiro/hooks/`, and MCP servers are
written to `.kiro/settings/mcp.json` or `~/.kiro/settings/mcp.json` for
`--global`.
become one Kiro v1 JSON file (`version: "v1"` with a `hooks` array) per hook
action in `.kiro/hooks/`, and MCP servers are written to
`.kiro/settings/mcp.json` or `~/.kiro/settings/mcp.json` for `--global`.
APM maps portable events to Kiro's PascalCase triggers and also accepts native
Kiro v1 hook documents for target-specific triggers. See
[Hooks and commands](../producer/author-primitives/hooks-and-commands/) for
the supported fields and action mappings.

This target covers the documented Kiro IDE layout. Kiro CLI configuration
differences are tracked separately; see [the targets matrix](../reference/targets-matrix/#kiro).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Claude (`PreToolUse`, `PostToolUse`) and Copilot (`preToolUse`,
"hooks": {
"PreToolUse": [
{
"matcher": "**/*.py",
"hooks": [
{"type": "command", "command": "${PLUGIN_ROOT}/scripts/validate.sh", "timeout": 10}
]
Expand Down Expand Up @@ -78,6 +79,36 @@ key is present but not a JSON object fails closed with a warning; a file
that parses cleanly but contributes zero entries also logs a warning so
authors notice empty merges during development.

Kiro-targeted packages may also use the native Kiro v1 shape. APM accepts
`{"version": "v1", "hooks": [...]}` and deploys each array entry as a
standalone v1 document. Portable Claude and Copilot events map to
Kiro's PascalCase `trigger` names, matcher groups become `matcher`, and command
actions become `action: {"type": "command", ...}`. In Kiro v1, `timeout` is a
hook-level field alongside `action`. Native `description`, `timeout`, and
`enabled` fields are preserved. Portable `askAgent` actions become Kiro v1
`agent` actions with the prompt preserved. `PreTaskExec` and `PostTaskExec`
also pass through from portable input; `PostFileCreate`, `PostFileSave`,
`PostFileDelete`, and `SessionStart` are Kiro-only triggers that require the
native v1 shape. Native v1 inputs support Kiro's `command` and `agent` action
types; files with no supported actions emit a warning. Kiro matchers are regex
patterns (for example `\\.py$`), unlike the glob patterns used in portable hook
matchers. For example, this Kiro-only hook runs the Ruff Python linter after a
file is saved:

```json
{
"version": "v1",
"hooks": [{
"name": "lint on save",
"trigger": "PostFileSave",
"matcher": "\\.py$",
"action": {"type": "command", "command": "ruff check ."},
"timeout": 30,
"enabled": true
}]
}
```

The `${PLUGIN_ROOT}`, `${CLAUDE_PLUGIN_ROOT}`, `${CURSOR_PLUGIN_ROOT}`, and `${KIRO_PLUGIN_ROOT}`
tokens resolve to the installed package root and are rewritten per
target. Plain `./script.sh` resolves relative to the hook file. If the hook
Expand Down Expand Up @@ -135,7 +166,7 @@ Supported targets and where the integrator writes:
| gemini | `.gemini/settings.json` | merged |
| codex | `.codex/hooks.json` | merged |
| windsurf | `.windsurf/hooks.json` | merged |
| kiro | `.kiro/hooks/<package-slug>-<hook-file-stem-slug>-<event-slug>-<n>.json` | one file per hook action |
| kiro | `.kiro/hooks/<package-slug>-<hook-file-stem-slug>-<event-slug>-<n>.json` | one Kiro v1 file per hook action |
| opencode | -- not supported -- | silently skipped |

Copilot hook files are namespaced with the source package name to avoid
Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/reference/targets-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ Kiro IDE.
- **File conventions.**
- instructions: `.kiro/steering/<name>.md` with `inclusion: always` or `inclusion: fileMatch` frontmatter
- skills: `.kiro/skills/<name>/SKILL.md`
- hooks: one JSON file per hook action under `.kiro/hooks/`
- hooks: one Kiro v1 document per hook action under `.kiro/hooks/`, with
top-level `version: "v1"` and a `hooks` array; see
[Hooks and commands](../producer/author-primitives/hooks-and-commands/)
- mcp: `.kiro/settings/mcp.json` (project) or `~/.kiro/settings/mcp.json` (user)
- **MCP shape.** JSON `mcpServers` entries use `command`/`args`/`env` for stdio and `url`/`headers` for remote servers. Kiro resolves `${VAR}` placeholders at runtime, so APM preserves them rather than writing secrets to disk.
- **Scope.** This is the documented Kiro IDE layout only. Kiro CLI differences are tracked separately and are not part of this target.
Expand Down
22 changes: 19 additions & 3 deletions docs/src/content/docs/specs/openapm-v0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ between the companion corpus and the implementation.

### 1.3 Document conventions

- OpenAPM v0.1 carries **98 normative statements** indexed in
- OpenAPM v0.1 carries **99 normative statements** indexed in
[Appendix C](#appendix-c-index-of-normative-statements).
- All on-disk files defined by this specification are **YAML 1.2**
parsed under the safe subset defined in
Expand Down Expand Up @@ -2073,6 +2073,19 @@ first install) as deployed rules; any other `.md` file under
`.agents/rules/` MUST NOT be treated as a deployed rule and MUST NOT
suppress instruction content in `AGENTS.md`.

<a id="req-tg-006"></a>
**[req-tg-006]** A conforming **consumer** implementation that deploys
target-native hook documents for Kiro MUST use the Kiro v1 hook schema and
MUST NOT emit a superseded schema. Each generated document MUST contain a
top-level `"version": "v1"` field and an array-valued `"hooks"` field. Each
hook entry MUST contain `name`, a PascalCase `trigger`, and an `action`; it MAY
contain a `matcher`, `description`, numeric `timeout`, and boolean `enabled`.
The consumer MUST NOT emit the superseded `when`/`then` hook shape. The
consumer MUST accept native Kiro v1 hook documents for Kiro-targeted packages
and preserve their supported names, descriptions, triggers, matchers, command or
agent actions, timeouts, and enabled state while applying the same path rewriting
and deployment constraints as portable hook inputs.

> **Editorial note.** [req-tg-005](#req-tg-005) names the `antigravity` deploy path
> and frontmatter keys in the normative text rather than delegating
> them to the non-normative Target Registry companion (contrast
Expand All @@ -2098,7 +2111,8 @@ without a spec revision. The current matrix is in the companion
- Consumer: [req-pr-001](#req-pr-001), [req-pr-002](#req-pr-002),
[req-pr-003](#req-pr-003), [req-tg-001](#req-tg-001),
[req-tg-002](#req-tg-002), [req-tg-003](#req-tg-003),
[req-tg-004](#req-tg-004), [req-tg-005](#req-tg-005).
[req-tg-004](#req-tg-004), [req-tg-005](#req-tg-005),
[req-tg-006](#req-tg-006).

---

Expand Down Expand Up @@ -2998,6 +3012,7 @@ renumbering of conformance classes.
| [req-tg-003](#req-tg-003) | MUST | 8.5 | consumer |
| [req-tg-004](#req-tg-004) | MUST | 4.2.1 | consumer |
| [req-tg-005](#req-tg-005) | MUST | 8.5 | consumer |
| [req-tg-006](#req-tg-006) | MUST | 8.5 | consumer |
| [req-sc-001](#req-sc-001) | MUST | 10.4 | consumer |
| [req-sc-002](#req-sc-002) | MUST | 10.9 | consumer |
| [req-sc-003](#req-sc-003) | MUST | 10.3 | consumer |
Expand All @@ -3014,7 +3029,7 @@ renumbering of conformance classes.
| [req-cf-001](#req-cf-001) | MUST | 12.5 | consumer |
| [req-cf-002](#req-cf-002) | MUST | 12.3 | consumer |

**Total normative statements: 98** (93 MUST, 5 SHOULD).
**Total normative statements: 99** (94 MUST, 5 SHOULD).

---

Expand All @@ -3036,6 +3051,7 @@ renumbering of conformance classes.
| 0.1.10 | 2026-07-04 | Spec-citation fold for Antigravity native instruction rules (closes the #1984 Mode-B silent-extension gate). Added [req-tg-005] (Section 8.5, consumer MUST): Antigravity instruction rules are deployed under `.agents/rules/<name>.md`, `applyTo` is rendered as `trigger: glob` plus `globs` (scalar or sequence), and compile-time deduplication only treats expected Antigravity rule filenames as deployed rules so unrelated `.md` files cannot suppress `AGENTS.md` content. Added `antigravity` to the Section 4.2.1 canonical target set and clarified that `all` excludes explicit-only targets. Statement count: 96 -> 97 (92 MUST, 5 SHOULD). |
| 0.1.11 | 2026-07-09 | Spec-guardian editorial+defensive fold on the Antigravity instruction-rule contract (no new normative statements; statement count remains 97 (92 MUST, 5 SHOULD)). Section 4.2.1: defined the **auto-detectable** vs **explicit-only** target taxonomy deterministically (a target is auto-detectable when the OpenAPM Target Registry publishes at least one detection predicate) and rewrote the `all` expansion to key off it, naming `agent-skills` and `antigravity` as the v0.1 explicit-only set (with a Section 8.4 cross-reference). [req-tg-001] extended: a target registered without a detection predicate MUST NOT be auto-detected and MUST be excluded from `all`, generalising the prior `agent-skills`-only clause to cover `antigravity`. [req-tg-005] extended: pinned a canonical `globs` representation (YAML scalar for exactly one glob, YAML block sequence for two or more, no frontmatter block when `applyTo` is absent) so deployed-file content hashes are reproducible across implementations; redefined the deduplication scope from "expected Antigravity rule filenames" to filenames derived from the currently-resolved instruction primitives recorded in `apm.lock.yaml` and the manifest, closing a fail-open interpretation where an unrelated `.agents/rules/*.md` file could suppress `AGENTS.md` content; lowercased the `antigravity` identifier and added an editorial note scoping the normative citation of the concrete deploy path. [req-tg-002] subdirectory-partition list updated to include `.agents/rules/`. No normative count change. |
| 0.1.12 | 2026-07-10 | Spec-citation fold for inactive-target lockfile reconciliation. Added [req-lk-020] (Section 5.2, consumer MUST): a non-frozen rewrite with a declared target set preserves paths attributable to current, another declared, or implementation-recognized targets that activate outside the manifest; removes prior paths attributable to none of them; applies the same decision to per-entry and top-level deployed-file lists and hash maps; and preserves prior paths when no target set is declared or attribution is indeterminate. Statement count: 97 -> 98 (93 MUST, 5 SHOULD). |
| 0.1.13 | 2026-07-10 | Spec-citation fold for Kiro v1 hook documents (closes #2071). Added [req-tg-006] (Section 8.5, consumer MUST): Kiro output uses the Kiro v1 schema with top-level `version: "v1"` plus an array-valued `hooks`, PascalCase triggers, optional matchers, v1 actions, and optional hook-level description, timeout, and enabled fields; the superseded `when`/`then` shape is forbidden; and native Kiro v1 inputs are accepted through the same path-rewrite and deployment constraints. Reconciled with the sibling 0.1.12 amendment (req-lk-020): cumulative statement count 98 -> 99 (94 MUST, 5 SHOULD). |

Errata (none at publication).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@ APM automatically normalises event names per target (e.g. `postToolUse`
becomes `PostToolUse` in Claude) and rewrites path variables
(`${PLUGIN_ROOT}`, `${CURSOR_PLUGIN_ROOT}`, `${CLAUDE_PLUGIN_ROOT}`) to
the correct target-specific form. Kiro materializes one JSON document per
hook action under `.kiro/hooks/`.
hook action under `.kiro/hooks/` using the Kiro v1 shape: top-level
`"version": "v1"`, an array-valued `"hooks"`, PascalCase `trigger`, optional
`matcher`, an `action` object, and optional hook-level `description`, `timeout`,
and `enabled` fields. Portable `askAgent` actions become Kiro v1 `agent`
actions. Kiro-targeted packages may author that native v1 shape directly when
they need Kiro-only triggers such as `PostFileSave` or `SessionStart`; APM
preserves those supported hook-level fields.

When a hook command references a script inside `hooks/` or `.apm/hooks/`,
APM deploys that hook source bundle so sibling helper files resolve at
Expand Down
51 changes: 32 additions & 19 deletions src/apm_cli/integration/hook_integrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,28 @@ class _MergeHookConfig:
"Stop": "SessionEnd",
},
"kiro": {
# Copilot / Claude -> Kiro camelCase events
"PreToolUse": "preToolUse",
"preToolUse": "preToolUse",
"PostToolUse": "postToolUse",
"postToolUse": "postToolUse",
"UserPromptSubmit": "promptSubmit",
"userPromptSubmit": "promptSubmit",
"promptSubmit": "promptSubmit",
"Stop": "agentStop",
"stop": "agentStop",
"AgentStop": "agentStop",
"agentStop": "agentStop",
"PreTaskExecution": "preTaskExecution",
"preTaskExecution": "preTaskExecution",
"PostTaskExecution": "postTaskExecution",
"postTaskExecution": "postTaskExecution",
# Copilot / Claude / legacy Kiro -> Kiro v1 PascalCase events
"PreToolUse": "PreToolUse",
"preToolUse": "PreToolUse",
"PostToolUse": "PostToolUse",
"postToolUse": "PostToolUse",
"UserPromptSubmit": "UserPromptSubmit",
"userPromptSubmit": "UserPromptSubmit",
"promptSubmit": "UserPromptSubmit",
"Stop": "Stop",
"stop": "Stop",
"AgentStop": "Stop",
"agentStop": "Stop",
"PreTaskExecution": "PreTaskExec",
"preTaskExecution": "PreTaskExec",
"PreTaskExec": "PreTaskExec",
"PostTaskExecution": "PostTaskExec",
"postTaskExecution": "PostTaskExec",
"PostTaskExec": "PostTaskExec",
"PostFileCreate": "PostFileCreate",
"PostFileSave": "PostFileSave",
"PostFileDelete": "PostFileDelete",
"SessionStart": "SessionStart",
},
}

Expand All @@ -179,7 +185,7 @@ class _MergeHookConfig:
"gemini": "PascalCase",
"antigravity": "PascalCase",
"windsurf": "PascalCase",
"kiro": "camelCase",
"kiro": "PascalCase",
}


Expand Down Expand Up @@ -627,7 +633,7 @@ def find_hook_files(self, package_path: Path) -> list[Path]:

return hook_files

def _parse_hook_json(self, hook_file: Path) -> dict | None:
def _parse_hook_json(self, hook_file: Path, *, allow_kiro_v1: bool = False) -> dict | None:
"""Parse a hook JSON file and return the data dict.

Accepts both the wrapped format (``{"hooks": {EventName: [...]}}``)
Expand All @@ -639,9 +645,10 @@ def _parse_hook_json(self, hook_file: Path) -> dict | None:

Args:
hook_file: Path to the hook JSON file
allow_kiro_v1: Accept Kiro's v1 array-valued ``hooks`` shape.

Returns:
Optional[Dict]: Parsed JSON dict (always wrapped), or None if invalid
Parsed JSON dict, or None if invalid.
"""
try:
with open(hook_file, encoding="utf-8") as f:
Expand All @@ -661,6 +668,12 @@ def _parse_hook_json(self, hook_file: Path) -> dict | None:
sorted(data.keys()),
)
data = {"hooks": data}
if (
allow_kiro_v1
and data.get("version") == "v1"
and isinstance(data.get("hooks"), list)
):
return data
# Fail closed on malformed shapes where "hooks" is present but not
# a dict (e.g. {"hooks": []}). Downstream code calls .items() on
# this value and would otherwise raise AttributeError mid-merge.
Expand Down
Loading
Loading