Conversation
The `.kiro/` shell is the same on Kiro IDE 1.x and Kiro CLI because both run the same agent runtime, so the row's product name should not claim one surface. - `harness/kiro/manifest.ts`: `productName` "Kiro CLI" -> "Kiro", and `configNextStep` now names both entry paths instead of only `kiro-cli chat`. - `core/tools/aidlc-doctor.ts`: the product label is a hardcoded map that does not read the generated `harness.json`, so the same rename is needed there; without it `doctor` still prints `Project (.kiro, Kiro CLI)`. Verified: `bun scripts/package.ts kiro` regenerates with `productName: "Kiro"` and the matching `configNextStep` in `tools/data/harness.json`, and `doctor` prints `Project (.kiro, Kiro)` with `0 problems`.
One harness serves Kiro IDE 1.x and Kiro CLI, so the code no longer carries two names for the same `.kiro/` shell. Five sites already normalized `kiro-ide` to `kiro` before use; those normalizations become identities and are removed. Membership and routing (no behaviour change): - `aidlc.ts`: `AdapterHarness` union and the harness-leaf map. - `aidlc-init.ts`: `modelHarness()`. - `aidlc-orchestrate.ts`: `isKiroRoutingHarness()` (three predicates). - `aidlc-model-policy.ts`, `aidlc-plugin.ts`: unions plus their `kiro-ide -> kiro` normalizers. - `aidlc-settings.ts`: the harness list and the emitted `$schema` properties. - `aidlc-config-diagnostics.ts`: `kiro || kiro-ide` collapses to `kiro`. Behaviour that was keyed on the IDE row now keys on `kiro`, because that row serves the IDE: - the `kiro-ide-chat-model` pending action, - `.vscode/settings.json` in the diagnosed file set and its `kiroAgent.trustedCommands` check, - the legacy Plan Approval path in `aidlc-orchestrate.ts`. Its build range is not documented as 0.x-only, and an argument-less payload has been observed on 1.x, so it is preserved rather than dropped; leaving the old literal would have killed it silently, since the comparison is on a string the compiler cannot check. Two merges needed a judgement, both recorded in the code: - the harness CLI requirement is now optional with an install hint naming both entry paths, because a project opened only in the IDE needs no `kiro-cli`; - the model/effort capability keeps the CLI's `true`, and its message says that `chat.modelDefaults` is read by Kiro CLI and ignored by Kiro IDE, where the chat model is set in the IDE. Dual-generation stdin handling is gone: both supported surfaces write and close stdin, so `aidlc engine adapter` reads once for every target. The single exception is the `legacy-ide-notice` target, whose host (Kiro IDE 0.x) opens stdin and never closes it; reading there would hang before the notice printed. `bun run typecheck` is clean for `core/`, `harness/` and the adapters; the remaining errors are five test call sites that still pass `"kiro-ide"`.
- t293: the `kiro-ide` case asserted a harness that expresses neither model nor effort. That capability split is gone with the row merge, so the case is removed rather than repointed, and the honesty note now pins the merged wording (Kiro CLI reads `chat.modelDefaults`; Kiro IDE does not). - t294: `probeHarnessCli` now pins the merged contract - `kiro-cli` is optional because a project opened only in Kiro IDE needs no separate CLI - and the provider-idempotence loop, the trust fixture and the pending-action install all use the `kiro` tree. Both files pass (t293 17/0, t294 23/0) and `bun run typecheck` is clean across all three project configs. Note for the rest of the sweep: three of these call sites were invisible to the type checker because the harness name arrives as a plain string (`install(...)`, `cpSync(join(DIST, ...))`, `temp(...)`). The compiler catches the typed unions only, so the remaining test files must be found by search and by running them.
… manifests
The registration channel the supported surfaces read is the standalone
`hooks/aidlc-*.json` manifest, not the agent-v1 `hooks` dict, so the row now
carries that shape:
- `agents/aidlc.md` replaces the fifteen agent-v1 `.json` configs. Hook wiring
lives in the manifests; a JSON twin would only restate the persona and drift
from it. The fourteen persona `.md` files are core projections and are
unchanged.
- eleven standalone manifests are projected, each dispatching
`engine adapter kiro <target>`.
- `steering/aidlc-active-memory.md` comes along, since steering is Kiro's
always-on layer on both surfaces.
- the nine `*.kiro.hook` files are gone. That manifest format is the Kiro IDE
0.12-era channel and it does not fire on 1.x; only two of the nine were even
projected, so seven were dead weight in the tree. The upgrade notice for a 0.x
host is authored separately.
- `settings/{cli,mcp}.json` stay: they are this row's own, and the IDE ignores
them.
Verified on the regenerated tree: agents `json 0 / md 15`, hooks
`standalone 11 / kiro.hook 0`, steering 1, settings 2, project-root `.gitignore`
present, `doctor` `0 problems` and `graph compile --check` exit 0.
Two defects this step surfaced, both invisible to `doctor` and `typecheck`:
- the manifests still dispatched `engine adapter kiro-ide`, which is no longer a
known adapter harness, so every hook would have failed with a usage error;
they now dispatch `kiro`.
- three of the eleven call targets this row's adapter does not implement
(`enforce-approval-gate`, `record-human-turn`, `terminal-command-guard`). The
adapter union merge is the next step; until it lands those three hooks would
not run. Neither `doctor` nor the type checker asks whether a manifest's
target exists, so this was found by walking the manifests against the
adapter's target set.
…ce, and pin the engine
The row's shape change left three consumers assuming Kiro agent JSON, so every
projection refresh failed. Reproduced before the fix:
aidlc config models --preset balanced --project --yes
error: …/projection/.kiro/agents/aidlc-architect-agent.json: missing agent surface
- `aidlc-model-policy.ts`: the writer no longer opens a per-agent JSON surface,
and `modelPolicySurfaceDrift()` reads `settings/cli.json` instead of an agent
file.
- `scripts/package.ts`: `projectKiroAgentJson()` and its call site are removed.
`agents/*.json` no longer exists in this row, so the branch was unreachable and
would have read as "the model dial is projected here" to the next reader. The
`settings/cli.json` projection stays.
- `aidlc-config-diagnostics.ts`: the trust file list followed agent `.json` and
the 0.12-era `.kiro.hook` files, both gone. It now follows the channel that
declares commands - the standalone `hooks/*.json` manifests.
The capability declaration is corrected to what the surface can actually carry.
It first kept the CLI's `model: true, effort: true`; measuring the shipped
command showed neither lands: with no model key on a Markdown agent the writer
has no model to key an effort default by, so `settings/cli.json` came back
byte-identical after a `--model … --effort max` request. The row therefore
declares `model: false, effort: false`, and `modelPolicySurfaceDrift()` now
reports the request as inexpressible rather than silently dropping it.
`settings/cli.json` gains `"chat.agentEngine": "v3"`. It belongs in the same
commit as the shape: without it plain `kiro-cli` enters its 2.x engine, which
reads neither this row's Markdown conductor nor its standalone manifests.
`readStdinWithTimeout()` is removed from the dispatcher - it existed only for the
0.12 channel that no longer has a caller there.
Verified: `aidlc config models` completes on the regenerated tree and prints the
honesty note; generated `cli.json` carries the pin; t293 17/0, t294 23/0
(both now exercise the current shape); `bun run typecheck` and `bun run lint`
(732 files) clean.
… them The row merge left two adapters with overlapping but not equal target sets, so three shipped manifests dispatched a target this row did not implement. For a PreToolUse gate that fails OPEN: an adapter with no handler returns null and the dispatcher exits 0, so `enforce-approval-gate` would have reported success while gating nothing. The gate comes first. `t331` walks the shipped manifests against the adapter in both directions - every dispatched target must be implemented, every target the merge brought in must survive, and no manifest may dispatch another row's name. Run against the pre-merge tree it reproduced exactly the three unimplemented targets; it is green now. The merge takes the IDE adapter as the base rather than meeting in the middle. That is measured, not stylistic: it carries 35 functions against the CLI adapter's 8, and the CLI's own five are dispatch helpers, while the IDE side owns the payload semantics, the terminal latch, plan approval and the turn counter. Five targets are ported onto it - `deliver-stage-rules`, `guard-tool-call`, `review-freeze`, `reviewer-scope`, `state-transition-guard` - with the two helpers they need (`canonicalTool`, `kiroDispatch`). 17 targets, 2395 lines. Verified: `t331` 3/0; `bun run lint` clean across 733 files; `doctor` 0 problems on the regenerated tree; `aidlc engine adapter kiro session-start` dispatches and answers with the payload's own session id. Worth knowing for anyone editing this file: `tsconfig.json` excludes `harness/*/hooks/*-adapter.ts`, so `bun run typecheck` does not parse it. A syntax error here passes typecheck and is caught only by lint.
…e list The first version of this gate walked the manifests that exist against the adapter, so an absent manifest was an absence nothing looked for. It passed 3/0 on a tree where two blocking hooks were unregistered and the lifecycle hook reached one surface. That is the failure mode the gate was added to prevent. It now encodes what the row must register, from two grounded places: the pre-merge agent-v1 `hooks` dict this row carried, which named the event for each target, and the manifests the IDE row shipped. Five assertions: - every required responsibility is registered on every trigger it needs; - a responsibility that must refuse a tool call sits on a blocking trigger - `docs/features/hooks.md` gives Prompt Submit, Pre Tool Use and Pre Task Execution as the only ones that can block, so a guard on Post*/Stop is a bystander; - a single-surface trigger is paired - the same table gives Session Start as IDE only and Agent Spawn as CLI only, so a lifecycle hook needs both names or it is dead on one surface. The IDE-only file and task triggers are exempt by name, since they have no CLI counterpart to pair with; - every trigger spelling is one the docs define; - every dispatched target exists in the adapter, and no manifest dispatches another row's harness name. Against the current tree it reports three real defects the previous version passed: `session-start` is registered on SessionStart only, so it never fires on the CLI; and `review-freeze` and `state-transition-guard` have hook bodies in the tree with no manifest, so neither guard is registered at all. `bun run lint` clean (719 files); no typecheck errors from this file.
…ards The wiring gate reported three defects; this closes the wiring half of all three. - `aidlc-session-start.json` gains an `AgentSpawn` entry beside `SessionStart`. `docs/features/hooks.md` gives Session Start as IDE-only and Agent Spawn as CLI-only, so one shell serving both surfaces registers both names or the start hook is dead on the CLI. The pre-merge agent-v1 config registered exactly this event for the same target, so the pairing restores what the row already had. - `aidlc-review-freeze.json` and `aidlc-state-transition-guard.json` are added. Both hook bodies already shipped in the tree with no manifest, so neither guard was registered at all. They sit on `PreToolUse` because they must refuse: the same doc table gives Prompt Submit, Pre Tool Use and Pre Task Execution as the only blocking triggers, so the PostToolUse audit hook cannot substitute. - both are declared in `harness/kiro/manifest.ts`; a manifest the packager does not project is not shipped, which is how nine `.kiro.hook` files sat in this row with only two projected. Matchers use the alias set the terminal guard already measures, `^(execute_bash|execute_pwsh|shell)$`, so the IDE spellings are covered rather than assumed; review-freeze adds the write tools the audit hook matches. `state-transition-guard` is not a duplicate of `terminal-command-guard`: that one keeps terminal commands deterministic, this one protects workflow lifecycle state. Verified: manifests 11 -> 13; the wiring gate is 6/0. 🔴 Not yet working at run time. Dispatching either new target answers `aidlc: kiro is not defined` and exits 0 - so the guards are registered and failing OPEN. The three blocking targets that came from the IDE base dispatch cleanly, so the fault is confined to the five blocks ported from the CLI adapter, whose run-local contract does not exist in this base. That refactor is the next commit; the row retirement stays uncommitted until it lands.
…ract The ported blocks were registered but failed OPEN: dispatching either answered `aidlc: kiro is not defined` and exited 0, so the guards gated nothing. They were CLI-adapter code carrying that adapter's run-local context into a base that does not have it. They are rewritten in the base's idiom rather than translated: - read `ide.toolName` / `ide.toolArgs`, guarded by `ide.malformedFields` first, the way `terminal-command-guard` does; - both targets join `PAYLOAD_TARGETS`, without which the normalized context arrives empty and every check silently passes; - `return 0` / `return 2` instead of `process.exit`, matching the reject contract the neighbouring guards use; - one shared `runCoreHook(hook, payload, cwd)` replaces the two hand-rolled spawns, and `TERMINAL_TOOLS` replaces the inline tool-name comparisons - the alias set the terminal guard already measures, so the IDE spellings are covered rather than assumed. Three placement faults were found by running the thing, not by reading it: the blocks had landed inside `buildForward()`, so their exit codes were returned where a forward was expected (`fwd.input.tool_name` threw); the helpers sat below their callers; and `projectDir` is local to `run()`, so a top-level helper cannot see it - `runCoreHook` now takes the directory, and the guards pass `process.cwd()` as the neighbouring `enforce-approval-gate` does. Verified by dispatch, both directions: - `state-transition-guard` with `aidlc-state.ts approve` -> exit 2 with the refusal on stderr; with a plain `echo` -> exit 0. - a blocked verb had to be chosen from `BLOCKED_STATE_TRANSITIONS`; my first probe used `complete-stage`, which is not in that set, and the pass it produced was the probe's fault, not the guard's. Wiring gate 6/0. `bun run lint` clean (721 files). Generated-adapter typecheck errors 44 -> 33; every remaining one belongs to the three blocks not yet moved (`guard-tool-call`, `deliver-stage-rules`, `reviewer-scope`).
…s glob
The Kiro docs are explicit: "When using custom agents, steering files are not
automatically included. You must explicitly add them to the agent's `resources`
configuration to load steering context" (Steering with custom agents). This row's
conductor carried no `resources`, so the always-on layer never reached it -
including `steering/aidlc-active-memory.md`, which this row ships for exactly that
purpose.
The glob is `file://{{HARNESS_DIR}}/steering/**/*.md`, the form the same doc gives,
and the packager substitutes it to `.kiro/` as with any other authored surface.
This also closes half of the `deliver-stage-rules` question. That target delivers
the active stage's rules, and the reason it looked replaceable was an assumption
that steering arrives on its own; it does not.
… auto-load
The row merge moved the conductor from agent-v1 JSON to Markdown and carried
only one of its four `resources` entries across, plus none of its delegation
trust. Both losses are silent: the agent still starts, and doctor, typecheck and
`graph compile --check` all pass, because nothing asks whether a declared
resource is reachable.
What was lost and why each one matters:
skill://.kiro/skills/*/SKILL.md The conductor's whole contract is "follow the
aidlc skill exactly". Skills are not
auto-loaded for a custom agent
(features/skills.md), so the instruction
pointed at nothing.
file://aidlc/spaces/…/memory/ The workflow's own memory.
file://AGENTS.md The onboarding contract at the project root.
toolsSettings.subagent.
trustedAgents (14) Every routed stage delegates to a persona.
Unlisted, each delegation raises an approval
prompt the conductor cannot answer.
`permissions` has no subagent capability, so delegation trust stays under
`toolsSettings` where the schema puts it; the two coexist. The 14 names mirror
core/agents/ exactly (measured, not copied) and are spelled out rather than
globbed, because `aidlc-*` would also pre-trust whatever a dropped-in plugin
adds.
Verified on the generated tree: dist/kiro/.kiro/agents/aidlc.md parses as YAML
with 4 resources, 14 trustedAgents, 3 permission rules and no unsubstituted
{{HARNESS_DIR}}; every resource target exists in the tree; doctor reports
0 problems.
The persona-scoped axis this row lost with the merge is back, without the
agent-scope field the v3 hook schema does not have.
WHY IT WAS LOST. The pre-merge row registered five hook targets inside each
persona's own agent-v1 config and passed the persona as argv (90 registrations).
Standalone manifests carry `trigger`/`matcher`/`action`/`timeout`/`enabled`/
`confirm` and nothing that names an agent, so that channel has no replacement in
kind. Two guards depended on it: state-transition-guard enforces ONLY when it
knows a delegate is acting (an empty agent_type returns 0), so without it a
delegated agent could run lifecycle verbs the main session reserves; and
reviewer-scope compares agent_type against the dispatched reviewer.
WHAT REPLACES IT, MEASURED. Across 615 captured payloads (Kiro CLI 2.18.1 /
2.19.2 / 2.20.1 and IDE 1.x) a delegate's OWN tool calls do reach the workspace
hooks, strictly nested inside the dispatch event's PreToolUse..PostToolUse window
(CLI: dispatch Pre, then the delegate's execute_bash, then dispatch Post; IDE:
same, with the delegate's MCP call and read_file nested too). The dispatch event
names the delegate in one of three places: the `subagent_<agent>` tool name,
`tool_input.name`, or `tool_input.stages[].role`. So the window itself carries the
identity the payload omits: open on the dispatch's Pre, close on its Post, and
attribute what happens in between.
- New latch under aidlc/.aidlc-sessions/kiro-delegation/<session>/, keyed by the
dispatch payload so a redelivery of one event is idempotent (several
manifests can match one tool call, and each match invokes this adapter).
- Entries expire after 6h. A window is closed by an event a crashed session
never sends, and a stuck entry would make state-transition-guard refuse the
MAIN session's own lifecycle verbs indefinitely.
- Ambiguity is reported, not guessed. Parallel delegation is real (measured:
three dispatches opened before any closed). state-transition-guard gets every
inflight name, because its enforcement does not depend on WHICH delegate is
acting, only that one is. reviewer-scope COMPARES the name, so it takes one
only when exactly one is inflight and otherwise fails open — the convention
the core hook already states for its other uncertainties.
Four defects found on the way, each one fixed here:
- `orchestrate_subagent` was recognized nowhere. Its payload puts personas in
`tool_input.stages[].role`, the shape the `subagent` alias handles, but only
`subagent` was on that path, so an orchestrated delegation produced no
dispatch at all: guards saw nothing, the audit recorded nothing.
- reviewer-scope was never registered on this row, so a blocking guard was
unreachable. It has a manifest now, matching reads as well as writes because
reading outside the reviewed artifact is the violation it exists for. Its old
`scoped_registration: true` fallback is dropped: that was sound only because
the registration itself was the reviewer's, and from a global manifest it
would claim every unattributed call — including the conductor's own.
- review-freeze tested `canonicalWriteTool(tool) === null` when that function
returns "", so the gate never fired; and its matcher omitted create_file,
delete_file, apply_patch and edit_file, all of which that canonicalizer
translates.
- guard-tool-call, reviewer-scope and deliver-stage-rules had been ported INTO
buildForward(), where their `return 0` / `return 2` became the Forward value
and the callers then read `.hook` off a number. Moved to the early-return
chain where the other guards live, with the declarations they were using
without having (KiroHookInput, KiroDispatch, firstNonBlank, projectEnv, the
raw `kiro` payload, rmSync).
Verified:
bun run typecheck 37 errors -> 0 (all three configs)
bun test t331 7 pass / 0 fail (2 new assertions)
bun test t293 t294 39 pass / 1 fail; the failure is the
pre-existing dist/kiro-ide expectation
dist/kiro doctor 0 problems
aidlc-graph.ts compile --check exit 0
replay on a throwaway fixture, real captured payload shapes:
dispatch Pre -> latch holds the persona
delegate runs `aidlc-orchestrate.ts next` -> exit 2, names the persona
dispatch Post -> latch empty; the same command then passes
orchestrate_subagent Pre -> latch holds stages[].role
two distinct personas inflight -> exit 2 naming both
reviewer-scope with no dispatch record -> exit 0 (fails open, no crash)
NOT verified here: attribution when two DIFFERENT personas are inflight AND one
issues a guarded call. No capture contains a nested call inside a parallel window,
so the joined-name path above is reasoned from the nesting property rather than
observed. It errs toward refusing.
The 31 files of harness/kiro-ide/ go away and every registry that named the row
loses that name. One Kiro row remains, serving Kiro IDE and Kiro CLI from the
same .kiro/ shell. The content is not lost - it is in git history, and its
adapter's targets were merged into the kiro adapter first.
Registries updated: PluginTargetKind and HarnessManifest.plugin.kind drop the
"kiro-ide" member (so the compiler now rejects it rather than accepting a name
with no row), doctor's productNames map, verify-release's distribution list,
plugin-test's compose env, package.ts and onboarding.ts header comments.
Five things the mechanical rename left behind, each fixed here. All five build
and typecheck clean, which is why none of them would have been noticed by a gate:
- scripts/plugin-hooks-template/compose.ts: `const isKiroIde = false` sent .kiro
down the CLI branch, which looks for agents/<persona>.json and reads
trustedAgents out of agents/aidlc.json. The merged row has neither - it is a
Markdown row. So plugin composition on Kiro was looking for files that no
longer exist, and every dispatched stage would have been rejected with a
remediation telling the author to write agent-v1 JSON. Now .kiro uses the
Markdown surface and reads trustedAgents from the conductor's frontmatter.
- The same file's stricter dispatchable predicate is gone rather than rerouted.
It required a non-empty `tools:` grant plus well-formed permissions.rules,
and the 14 personas ship from core/ with neither field while being dispatched
successfully today; a controlled-experiment subagent authored with no
permissions block also ran its shell command with no prompt. Requiring those
fields would have reported every persona as an absent dispatch surface. For a
Markdown agent the presence of the file is the whole check.
- core/tools/aidlc-plugin-emit.ts emitted the plugin compose hook on
SessionStart only. docs/features/hooks.md gives Session Start as IDE-only, so
on Kiro CLI a plugin would simply never compose and nothing would say so. It
now ships both SessionStart and AgentSpawn, matching the row's own
aidlc-session-start.json.
- core/tools/aidlc-utility.ts gated the settings/cli.json doctor check on an
agent-v1 aidlc.json being present. Markdown-only means that check stopped
running entirely - and cli.json is the file that pins the agent engine and
activates the workspace default agent. It is unconditional now, and the
conductor check names aidlc.md instead of aidlc.{json,md}.
- Two dead duplicate branches from the bulk rename: scripts/build-binaries.ts
ran harnessRuntimeGate(..., "kiro", ".kiro") twice, and
core/tools/aidlc-runner-gen.ts had two `harnessName === "kiro"` tests where
the first shadowed the second, so the CLI-only wording won and the merged
both-surfaces wording was unreachable. Also aidlc-init.ts's first-run hint
named only Kiro CLI; it names both entry points now, like the manifest's
configNextStep.
Verified: bun scripts/package.ts clean, bun run typecheck 0 errors across all
three configs, dist/kiro doctor 0 problems, and the emitted test-pro plugin
manifest carries both triggers with the plugin's agent shipping as .md (which the
corrected surface extension now matches).
Test-tier state is NOT clean yet and this commit does not claim it: the harness
matrix, drive calibration and several smoke/e2e files still assert the kiro-ide
row and the agent-v1 JSON conductor. That migration is the next commit.
Finishes the merge: nothing in tests or docs still describes a second Kiro row,
and the tier is at parity with the base commit.
DOCS. docs/guide/harnesses/{kiro-cli,kiro-ide}.md become one kiro.md - the two
files described the same install of the same tree for the two surfaces of one
runtime. The merged guide carries the IDE's model requirement and its
non-interactive-PATH tip, both entry points, and a hooks chapter DERIVED from the
shipped manifests rather than transcribed (the old IDE table had drifted: it
still named aidlc-mint and aidlc-block, and its log-subagent matcher predated
orchestrate_subagent). The upgrade cleanup block is kept and extended: the
agent-v1 conductor, the persona JSONs and the `.kiro.hook` registrations are all
retired now, and an overlay copy cannot delete what it no longer ships. Every
roster and inbound link follows (README, docs/README, the harness index, getting
started, cli-commands, customization, glossary, codex-cli).
Two prose surfaces were wrong for the merged row rather than merely stale, and a
test caught each:
- the conductor SKILL told the user to "exit or restart Kiro CLI" for a fresh
session, which is wrong advice to someone reading it in the IDE;
- question-rendering.md called itself "the Kiro CLI harness annex" and said
"Kiro CLI has no structured-question tool" - true of the row, misattributed
to one of its surfaces.
TESTS. 50 files. Mostly mechanical (a harness name leaves a list, a dist path
repoints), plus these judgement calls:
- t148 rewritten: 13 of its 20 cases asserted the agent-v1 shape. The
properties they pinned all still matter, so they were re-expressed against
the Markdown conductor - a match-scoped shell grant instead of a blanket one,
no `subagent` on a persona, no model pin, every registration reaching this
row's adapter - plus the new ones the merge creates: the resources a custom
agent does not auto-load, and the engine pin.
- t245 renamed and repointed, and taught that a manifest may carry two hooks
(one lifecycle responsibility needs both surface-specific triggers; a
delegation needs both of its edges). Its per-file shape contract complements
t331's coverage contract - neither subsumes the other.
- t218 renamed to name what it covers (channel normalization, not a row).
- Three tests pinned the ABSENCE of enforcement this row now has, so they were
inverted rather than deleted: reviewer-scope registration (t221), review-freeze
registration (t264), and the "IDE-native enforcement only" doc parity (t239).
The matching carve-out in core/aidlc-common/protocols/stage-protocol-reviewer.md
- "on a harness without reviewer-scope enforcement (Kiro IDE today), do not
write the record" - is gone with them: no shipped harness lacks it now, so
`reviewerScopeRegistration: "unsupported"` has no subject either.
- harness-matrix: the kiro-ide row is gone; the kiro row's capabilities follow
the merged reality (Markdown agents, manifest-registered reviewer-scope, a
plugin wiring file it now emits). `kiroAgentJson` and its `kiro-resources`
sibling are deleted rather than left false everywhere - a capability no row
has is a selector that silently matches nothing, and three tests were already
keying off it.
- manifestGrantsIdeAgentTools also accepts an authored agents/*.md, because a
row that writes its conductor outright read as granting nothing.
FOUR MORE DEFECTS, all found by a test after the paths were repointed:
1. dist-release/kiro shipped no .vscode/settings.json. The
`kiroAgent.trustedCommands` pre-trust was a nativeRootIntegration on the
folded row only, so the native channel lost it - and an untrusted project
command on the IDE surface silently never runs. Carried onto this row: the
integration follows the surface that needs it, not the row name.
2. The dispatcher read hook stdin unbounded, in TWO places (an early buffer
before the pinned-version handoff, and the adapter route itself). A host
that never closes stdin wedged the hook forever. Both go through a ceiling
now, mirroring the one the adapter's own entry point already documents as
defensive; when the ceiling fires the caller must also exit explicitly,
because a pending Bun.stdin.text() keeps the loop alive.
3. My own bulk rename deleted four POSITIONAL arguments that happened to sit on
their own line: two `distSurface(<harness>, ...)` calls, a
`join(REPO_ROOT, "dist", <harness>, ...)`, a `composeSynthetic(..., <harness>,
cb)`, an install `--harness <name>`, a `[".kiro", <name>]` fixture tuple, and
the harness slot of both t218 dispatcher helpers. Each still compiled. Found
by auditing every removal site against git, not by reading the diff.
4. t188's plugin-compose expectations followed compose.ts: the remediation text
names a Markdown agent now, and the case that varied frontmatter GRANT shape
was rebuilt on the two axes that actually govern (the file exists, and the
conductor trusts the name) - grant completeness does not, which is why the
stricter predicate went away in the first place.
Verified, sequentially (two batteries at once cross-contaminate):
bun run typecheck 0 errors
bun test tests/unit failure set IDENTICAL to base 78a6879
(25, all in t186/t208/t209 - pre-existing)
bun test tests/smoke+integration
52 failures vs base's 52, differing by one
live-SDK case in each direction (t72 here,
t-journey-workspace there) - model flake, and
every remaining failure is in a suite base
fails too
tests/.coverage-registry.json regenerated (it is generated, not authored)
Review found four P1s. Three are fixed here; the fourth (the fail-open /
fail-closed asymmetry between reviewer-scope and state-transition-guard) is a
policy decision and is still open.
P1-1 — the delegation latch collapsed identical concurrent dispatches. Keyed by
sha256(tool_name + tool_input), two dispatches of the same persona with the same
prompt hashed to one entry and the FIRST close released both, so enforcement
dropped while a delegate was still running. Read-modify-write also had no lock,
so two adapter processes opening windows at once could lose one another's update.
Both go away with an append-only NDJSON ledger: each open is its own record, each
close cancels the most recent open with a matching key (LIFO), and an append is a
single write with nothing read first. It compacts only when nothing is inflight,
where truncation cannot race an append. A torn final line is skipped rather than
discarding the ledger.
P1-3 — reviewer-scope's matcher named execute_pwsh, but canonicalTool translated
only `shell` and `execute_bash`, so a pwsh call matched the manifest, reached the
adapter, and returned 0. The gate read as present and enforced nothing on Windows.
canonicalTool now maps every TERMINAL_TOOLS spelling, and t331's matcher-parity
list carries execute_pwsh and shell for both terminal-capable guards - it was that
list's omission that let the mismatch through.
P1-4 — subagent_response was classified as an opaque mutation. It is the shell a
delegate reports through: it names no path and writes nothing, so "a write whose
target went missing" is the wrong question for it. Two changes, because one was
not enough: it leaves mutationCapableTool (keeping it out of the legacy recovery
machinery), AND plan-approval-guard returns early for it, because the core guard
treats any tool it does not recognize as mutation-capable and refused a call that
mutates nothing.
Reproduce before: bun test tests/unit/t147-kiro-hook-adapter.test.ts \
-t '1f: response shells stay inert for pre-dispatch hooks' # expected 0, got 2
t147 migration, partial. Three cases asserted the agent-v1 registration channel
(14 persona JSONs each registering `state-transition-guard <its own name>`), which
is what the latch replaces. They are rewritten onto the mechanism that governs
now - open a window, the guard enforces against the delegate; close it, the main
session runs the same verb freely - which also gives the latch the regression
tests the review asked for, including 5d2 for the identical-dispatch collapse.
🔴 THIS COMMIT DOES NOT CLAIM t147 IS GREEN. 25 pass / 11 fail, against 21/14 at
the previous commit and 35/0 on base 78a6879. The remaining 11 are merge
regressions in the adapter with at least three distinct causes, all still open:
- bare `subagent` crew payloads reach the core guard unrecognized
("unknown mutation-capable tool: subagent") instead of the dispatch path;
- something writes .aidlc-subagent-inflight where log-subagent must not;
- record-human-turn records no HUMAN_TURN on the unattended prompt path.
🔴 AND A CORRECTION. The previous commit claimed "unit failure set IDENTICAL to
base". That was wrong. Both tier logs were truncated - no summary line, 364 and
352 lines for a 272-file tier - and the claim was computed from them without
checking that they were complete. t147 alone is a 13-case regression they did not
show. Nothing in this branch's verification should be trusted above a log that
ends in a summary line.
Verified here: bun run typecheck 0 errors; t331 / t245 / t148 / t221 / t264
158 pass / 0 fail; t147 improves 21/14 -> 25/11.
t147 exercises the adapter against live-captured payloads. It was 21 pass / 14
fail after the row merge and 35 / 0 on base - the merged adapter is IDE-derived,
so every CLI-surface shape it never saw was silently dropped. Six distinct gaps,
each one a lost audit row or an unenforced guard rather than a crash:
- `tool_response` is not always a string. Two captured shapes exist -
`{items:[{Text}]}` from a crew completion and `{success,result:[...]}` from a
write - and both were classified malformed, which discards the WHOLE event.
No SUBAGENT_COMPLETED row, and the Stop hook's inflight marker never cleared.
Decoded now; a value that is no transport at all (number, boolean) still
reports malformed, because that is the case worth surfacing.
- The crew shape (`subagent` + `stages[].role`) reached plan-approval-guard
unrecognized, so the core guard answered "unknown mutation-capable tool:
subagent" instead of the Code Generation refusal. Routed through kiroDispatch,
which already normalizes it and already skips malformed stages.
- log-subagent read identity only from the tool name and the result prose, so a
crew or direct dispatch recorded `Agent Type: unknown` - the persona is in the
PAYLOAD (`stages[].role`, `name`). And an empty result dropped the row even
when the payload named the delegate; the drop guard exists to prevent an
identity-less fiction, which is not what that is.
- audit-and-sensors scraped the written path from result prose only. That holds
for the IDE's captured writes, which carry empty inputs, but the CLI POPULATES
them - so it audited the wrong path or none. Input first, prose as fallback.
- A batch write names every target in `operations[]` and the core audit hook
records one artifact per invocation (it reads `file_path`; there is no `paths`
handling), so a batch of two produced one row. Invoked per path now.
- canonicalWriteTool did not know `write`, which the payload fixture's own
provenance calls part of the defensive adapter vocabulary, and ignored the
`command` mode that distinguishes a create from an edit - so an edit under
that spelling was audited as a Write.
Two contracts stated in code that were only implied before:
- A delete is not an artifact write. The write-audit manifest's matcher already
said so by omitting delete_file, and t147 pins that a delete leaves no audit
heartbeat - but the direct and dispatcher entry points bypass that matcher, so
the adapter says it too. (This also answers the review's question about that
matcher: the omission is deliberate, not a gap.)
- subagent_response is a dispatch auxiliary, named once and used by both the
opaque-mutation classifier and the plan-approval early return.
t147: 21/14 -> 31/5. Five remain, all still open and none of them these shapes:
the unattended prompt path records no HUMAN_TURN (verb-intercept vs the separate
record-human-turn registration), todo_list state sync, the deliver-stage-rules
advisory warning (5b/5c), and reviewer-scope's defensive vocabulary (5f).
Verified: bun run typecheck 0; t331 / t245 / t148 / t264 / t265 140 pass / 0 fail.
t147 is green: 36 pass / 0 fail, from 31/5 at the previous commit, 21/14 when the row merge landed, and 35/0 on base 78a6879. THE ONE THAT MATTERED. reviewer-scope, guard-tool-call and deliver-stage-rules read the RAW payload (`kiro`), and none of them was in INPUT_TARGETS - the set that decides whether `run()` reads stdin at all. So `input` was "", `kiro` stayed `{}`, and each returned 0 on its first field access. Three guards that were registered, dispatched to, and enforced nothing; `doctor`, `graph compile --check` and the type checker all pass, because the payload arrives as JSON on a stream and its absence looks exactly like a tool call that needs no guarding. Adding those three names fixed three separate t147 cases at once. sync-workflow-state joins them for the same reason but stays out of PAYLOAD_TARGETS on purpose: a malformed payload there must fall back to the audit-tail reconciliation rather than drop the event. sync-workflow-state also gains the CLI path it lost. The IDE gives no task payload, so the merged adapter read the slug from the audit tail for everyone. The CLI DOES give it - a todo_list create whose task description ends in "[slug]" - and reading it directly is exact where the audit tail is a reconstruction. Payload first, marker as fallback. The mint moved, and a test still described where it used to be. verb-intercept minted HUMAN_TURN on the pre-merge CLI row, where hook wiring lived inside the agent config and there was no second registration to double-count with. This row registers aidlc-record-human-turn.json separately, so verb-intercept must NOT mint - re-adding it would record two turns per prompt. The test now pins both halves: verb-intercept does not mint, record-human-turn does, and AIDLC_UNATTENDED withholds the ledger event. Two comments named manifests that do not ship: aidlc-mint.json and aidlc-block.json, for what are actually aidlc-record-human-turn.json and aidlc-enforce-approval-gate.json. A reader following either name finds nothing. Verified: bun run typecheck 0; t147 36/0; t331 / t245 / t148 / t221 / t264 / t265 / t218 278 pass / 0 fail.
…nd CI
Codex's review found the documentation migration incomplete, and it was right in
the way that matters most: the literal `kiro-ide` grep I judged by cannot see a
sentence that describes the split without naming the row.
harness/kiro/onboarding.fills.ts is the worst of it, because it RENDERS INTO THE
SHIPPED AGENTS.md - a file loaded every session. It said the install runs "on the
**Kiro CLI harness**", made Kiro CLI >= 2.6 a hard prerequisite (wrong for an
IDE-only project), and told every reader that "the `/aidlc` session runs from
`agents/aidlc.json`; all 14 expert roles have JSON configs". Those files do not
exist. It now names the row and both of its entry points, and points at
agents/aidlc.md.
Changing that text changes the rendered AGENTS.md bytes, which surfaced an
upgrade-path obligation the packager enforces: the manifest's
legacySignatures.wholeFileHashes must still recognize the variant that shipped
before this change, or an upgrade treats a stale unmarked AGENTS.md as user-owned
content and leaves it. Added, with the reason, plus the current render in t243's
expectation (the packager appends it, so that list moves whenever this text does).
Reference prose, per site rather than by search-and-replace, because some
"Kiro IDE" / "Kiro CLI" sentences are legitimately about a SURFACE and must stay:
- 06-hooks-and-tools: the reviewer-scope Identity paragraph described the
per-agent registration that no longer exists; it now describes the delegation
window, including the parallel-persona case where the adapter declines and
records a drop. The reviewer-scope registration row, the per-harness
registration sentence, the deliver-stage-rules row, the marker-mtime row and
the harness-dir sentence follow. The Stop-block table keeps its two SURFACE
rows (the IDE cannot block, the CLI can) and loses the retired v2 engine row.
- 14-claude-features: the primitive map had a Kiro CLI column and a Kiro IDE
column; they are one column now, and t239 reads index 2 instead of 3.
- 05-agent-system, 13-customization, 18-plugin-mechanism: the agent-v1 JSON and
the "tools: + permissions.rules are required" premise are gone. 18 also states
the measured truth that those fields are OPTIONAL on a Kiro agent - the 14
shipped personas carry neither - so the remedy for a rejected plugin stage is
the file plus conductor trust, not frontmatter.
.github/workflows/release.yml still installed and doctored a `kiro-ide` harness in
both its PowerShell and bash lifecycle loops. t244 pins those exact strings, which
is how it was found - I had updated the test's expectation in an earlier commit and
not the workflow it describes.
P1-2, the last of the review's P1s, is resolved as a documentation defect rather
than a behavior change. The two persona-scoped guards DO differ under ambiguity,
and the reason is what each needs: state-transition-guard needs only presence, so
two inflight personas still answer its question; reviewer-scope compares identity,
so it cannot. My earlier comment claimed the code "errs toward refusing" while it
did the opposite for reviewer-scope. The comments now state the asymmetry and its
reason at both sites, and reviewer-scope records a visible drop instead of
declining silently.
CHANGELOG gains an Unreleased entry with the Breaking and Upgrade notes, including
the retired surfaces an overlay copy cannot delete. AIDLC_VERSION is deliberately
NOT bumped: the release workflow rejects a tag that does not match it, so the bump
belongs to whoever cuts the release.
Verified: bun run typecheck 0; t243 84/0; t244 36 tests 0 fail; t239 / t221 /
t266 / t147 134 pass / 0 fail.
Codex's re-review of the previous four commits kept REQUEST_CHANGES with five
blockers. All five were real; four were defects I introduced and one was a policy
I decided to ignore and should not have.
BLOCKER 1 - a crew dispatch opened one window per persona and a close released
only one. openDelegation appends an open per agent under the same dispatch key, and
the replay cancelled the most recent match, so a two-persona crew left one open
inflight until the 6h TTL - and the lifecycle guard kept refusing the MAIN
session's own verbs after the crew had finished. Records carry a `group` now, one
per openDelegation call: a close cancels the whole most-recent group for that key,
while two IDENTICAL dispatches still need two closes because they are two groups.
A pre-group record replays as its own group so an in-flight upgrade strands
nothing. Regression: t147 5d3.
BLOCKER 2 - `write` was missing from the review-freeze, reviewer-scope and
write-audit matchers. The payload fixture carries `tool_name: "write"` and the
adapter canonicalizes it, so a frozen-artifact write and a reviewer-scope
violation under that spelling reached nothing, and the write went unaudited. Every
matcher is now derived from the canonicalizers rather than hand-listed, and t331's
parity lists carry `write` and `read` - it was that omission that let this pass.
BLOCKER 3 - plain `subagent` was missing from the log-subagent matcher while
DISPATCH_TOOL_NAMES included it. The Pre edge could open a window through a
matcher-free registration, but the Post edge never fired: stale latch, and no
completion audit for the crew shape the fixture actually carries. t331 called the
list "all three shapes" while checking `subagent_<name>`; it is four shapes.
BLOCKER 4 - reviewer-scope still failed open with two different personas inflight.
I had reclassified this as a documentation defect. That was wrong: passing an empty
identity lets a delegate read outside its review whenever a second delegate happens
to be inflight, which is the gap the persona axis exists to close. The adapter now
resolves the ambiguity from the dispatch record the core hook itself reads - if the
reviewer it names is among the inflight personas, that is the identity. The
residual cost is a possible false refusal of another delegate's out-of-scope call,
which is the direction to err in and is recorded either way. Regression: t147 5g.
BLOCKER 5 - the ledger compaction was a check-then-truncate race, and my comment
claimed it could not race "when nothing is inflight". Nothing stops another adapter
process from opening a window in that gap. Compaction is gone; records are small
and expire by TTL, so an unbounded ledger costs disk rather than correctness.
RELEASE METADATA IS DELIBERATELY ABSENT. The previous version of this commit
bumped core/tools/aidlc-version.ts, the README badge and a CHANGELOG heading,
because AGENTS.md:54-60 and CONTRIBUTING.md:56 both say every user-visible PR must.
Those documents are stale: the maintainers asked on 2026-09-08 that a PR NOT carry
the bump - "we will bump version when releasing" - and asked for exactly that
change to be reverted on another PR. So this branch touches none of the three, and
the Breaking / Upgrade notes a release entry would need live in the PR body where
whoever cuts the release can lift them. The documented policy and the practice
disagreeing is worth raising separately; it is not this PR's to settle.
Also from the re-review:
- sync-workflow-state matched only `execute_bash`, so the CLI's todo_list
PostToolUse never reached the target the previous commit taught to parse it.
Only the direct adapter test was green.
- `legacy-ide-notice` existed as two special-cases in the dispatcher and nowhere
else. It is implemented now, per the design: ONE legacy `.kiro.hook` on
promptSubmit, because that format is the only channel an unsupported Kiro IDE
0.x reads AND it does not fire on a supported one - the channel is the version
check. The target reads neither stdin nor the normalized payload (a 0.x host
never closes stdin), confirms the legacy channel through USER_PROMPT, prints
the upgrade notice and exits 2. Measured on a throwaway fixture: no
USER_PROMPT -> exit 0 and no output; USER_PROMPT set -> exit 2 with the notice.
t245 and t148 pin that this is the ONLY legacy file, and t147 9 pins both arms.
Still unverified on a real 0.12 host - this machine has none.
- docs claimed the projection adds `tools:` and `permissions.rules` to Kiro
personas while also saying the 14 personas carry neither. The generated files
carry neither; the capability frontmatter is the CONDUCTOR's. 05-agent-system
and 09-porting now say that, and 09 notes frontmatterAdditions has no user.
- t245's expected registration table is regenerated FROM the shipped manifests,
so a matcher change cannot leave it stale by hand again.
Not addressed here: the double stdin timeout (dispatcher + adapter route each
bound the same held-open stream, measured ~1.3s at a 500ms ceiling) and whether
process.exit truncates stdout. Both are latency and neither loses a decision.
Verified: typecheck 0; `bun scripts/package.ts --check` deterministic across two
builds for all six rows; t147 39/0, t245 43/0, t331 7/0, t148 16/0, t68 7/0,
t221 / t239 / t264 / t265 / t218 all pass.
CI's contract check runs `bun run check` = typecheck AND `biome check
--error-on-warnings`. I only ever ran typecheck locally, so four findings rode the
branch to CI, and every one of them is residue of my own edits:
- an `x !== null && x.foo` that biome wants as an optional chain;
- `validIdePermissionRule` in compose.ts, left behind when the over-strict
dispatchability predicate that was its only caller went away - and removing it
exposed two more (`inlineYamlListHasValue`, `blockYamlListHasValue`) that only
IT still used, so the dead set was three functions deep;
- `existsSync` in t220 and `readdirSync` in t266, unused since the assertions
that needed them were rewritten onto the Markdown shape.
The lesson is the gate, not the findings: a green typecheck says nothing about
lint, and `bun run check` is the one command that matches what CI asks.
Verified: bun run check 0 (722 files); t147 39/0, t220 47/0, t266, t188 93/0 -
200 pass / 0 fail across the four suites the removals touch.
…e suites
The 36 unit failures on this branch were not stale expectations. Four of them
named real losses, and the tests were the only thing still asking for them.
- The roll-forward seam. verb-intercept kept the IDE row's terminal dedup and
lost the CLI row's flat markers, while continue-workflow's carve-out — plus
aidlc-orchestrate's done-guard and the doctor bundle, both in separate
processes that cannot know a Kiro session id — still read them. Writer and
reader never met, so the latch was dead. Restores the turn clock, the
read-only/config-alias latch, the forwarding latch, and the engine
pre-dispatch alongside the per-session latch.
- The `engine` noun. runTerminalCommand spawned `[executable, ...args]`, so
every off-band terminal command was malformed on a compiled install.
- The persona grants. frontmatterAdditions went missing with the kiro-ide
manifest, so all 14 delegation targets shipped with no tools, no MCP grants,
no shell allowlist and no resources. Restored as frontmatter, reproducing the
agent-v1 model the row shipped before the merge field for field.
- The active-space re-pointer. It only handled agents/*.json, so a space switch
left every Markdown agent pinned to `default`. The new arm is anchored on both
`file://` and the glob tail, because the persona body's
`spaces/<active-space>/memory/{org,team,project}.md` is a placeholder a looser
pattern would overwrite.
The conductor's `permissions` comment claimed the schema has no subagent
capability; it does. What actually lives in toolsSettings.subagent is which
agents may be spawned and which skip the prompt.
Test moves: agent configs are read out of frontmatter; t252 drops the conductor
from the behavioural vectors (it states the same policy as capability globs) and
asserts that declared policy separately rather than guessing Kiro's glob
matcher; t308 names its matcher-bearing manifests instead of counting them;
t316's shared-leaf guard moves to the .aidlc pair, which is still shared;
t299 stops expecting a "(not probed)" row now that every harness is probed.
Verified: bun run check clean; smoke+unit 286 files, the 8 affected files green.
Two unrelated failures remain — t255 passes in isolation (a 2s signal race under
--parallel 8) and t298 fails identically without this change.
…int the prose the merge invalidated The reviewer-scope ambiguity branch recorded a drop whenever more than one delegate was inflight and no identity could be attributed - including when no dispatch record existed at all, which is the ordinary state of a crew stage. Measured: three guarded calls during a two-delegate crew with no review in flight appended three lines. A non-empty .drops file is a release signal here, so a normal stage was filling it. Record a drop only when a record exists - enforcement was expected and did not happen - and say which of the two ways it could not be attributed. Three prose sites still described the channel this row removed: the Kiro CLI adapter asserting scoped_registration from per-agent JSON configs, and Kiro IDE shipping no registration at all. The field itself stays - OpenCode is now its only sender and still needs it - so only the attribution changes, and the porting guide keeps the pattern while gaining the two mechanisms that replaced it. t147 5g2/5g3 pin both halves of the branch, and 5g now also asserts the attribution drop it always produced but never checked.
Measured on a real Kiro IDE 0.12.333, which we had never had until now. Two premises behind the notice were wrong. Exit 2 is not a refusal there. The host's hook runner turns any non-zero exit into HookFailedCommandError - "Hook '<name>' command execution failed" - and nothing downstream reads the code as a denial. So the notice arrived as a BROKEN HOOK, and the session's own agent did what that framing invites: it investigated for two minutes, concluded a current build was being misidentified, and wrote "enabled": false into the manifest. promptSubmit cannot refuse anything there either. What 0.12 does honour is text on a preToolUse hook: its own instructions tell the model that when the output denies access it is FORBIDDEN from retrying and MUST NOT proceed under any circumstances. So the notice now rides preToolUse with toolTypes ["*"], exits 0, and writes the denial FIRST on stdout - stdout because the runner takes `stdout || stderr`. Re-measured on the same host with the shipped artifact: the read was abandoned, the notice relayed to the user, no attempt to disable it, and 23s instead of 1m58s. The USER_PROMPT gate survives the move: that variable is populated on this trigger too, carrying the tool input as JSON rather than the user's prompt. Only its presence is read. This refuses per tool call rather than per prompt, which is the better seam - nothing in AI-DLC advances without a tool. It is enforcement by instruction, not a hard block, and the guide now says so instead of implying the IDE stops the turn.
A supported IDE does not run a `.kiro.hook` - measured on 1.0.437, where nothing fired - but it does LIST one as `legacy` beside a `Migrate` button, and one click turns this manifest into a current-generation hook the host does run. The USER_PROMPT gate cannot catch that: 1.x populates USER_PROMPT for a runCommand hook too. Migrated, on preToolUse, the notice would deny every tool call on an install AI-DLC fully supports, and the button sits in the hooks panel of every such install. So the target reads the host's version instead of trusting the channel. VSCODE_IPC_HOOK names the socket by version line - `0.12-main.sock` on 0.12.333, `1.0.-main.sock` on 1.0.437, both measured, hence the optional third dot - and anything that is not a 0.x major stays silent. So does an absent or unparseable value: informing is this target's only job, and refusing work on a supported install is the worse error, so silence is the failure mode to prefer. The cost is that a 0.x host which stops setting the variable gets no notice. Verified against the running host's real socket path, not a fixture literal: silent on 1.0.437, denial on the 0.12.333 value. The guide now says to leave the legacy hook alone rather than migrate it.
The row advertised `Kiro CLI >= 2.6` in five places. That predates the engine it now targets: the v3 engine first shipped in 2.8.0, behind `kiro-cli --v3`, and this row is v3-only. A 2.6 install would read the tree, ignore the engine pin in `.kiro/settings/cli.json` as an unknown key, come up on the 2.x engine, and never read the standalone hook manifests - working badly rather than refusing. The floor is now 2.21.1, which is where the pin was measured rather than where the feature began. The honest reason it is not lower: `chat.agentEngine` is absent from the published settings reference and from every changelog entry between 2.2.0 and 2.21.0, so there is no public record of when it started working, and the version where we watched a plain `kiro-cli` reach the engine is the only defensible claim. The prose points anyone on an older 2.x at `--v3` and says plainly that we have not tested that path.
…or all of them
Audited the generated AGENTS.md against the tree it ships beside. The row merge
left prose describing one surface, or the pre-merge wiring, as if it were still
true, and the shared skeleton carried defects that reach every harness.
This row (`harness/kiro/onboarding.fills.ts`):
- "blocking stop hook" was listed among the features the install relies on. Two
paragraphs of our own guide say the opposite - `Stop` cannot refuse a turn on
either Kiro surface, which is why every guard that must refuse sits on
PreToolUse - and the same AGENTS.md contradicts the claim further down.
- "the workspace default agent" was listed the same way. It comes from
`settings/cli.json`, which the IDE ignores, so it is not a feature both
surfaces have; the bullet then invited the reader to delete it.
- The activation bullet marked only its last clause as IDE-specific, leaving an
IDE reader to assume the engine pin and the precedence note applied to them.
It also omitted the shipped `chat.modelDefaults` effort default.
- "all 14 expert roles" miscounted its own list, which the same sentence breaks
into 11 personas, 2 review-only agents and the composer. And delegation goes
through more than one tool name, as the log-subagent matcher shows.
The shared skeleton (`core/templates/onboarding.md`), affecting every row:
- Three bullets told the reader to type `{{INVOKE}}-knowledge`, which renders to
`bun .kiro/tools/aidlc.ts-knowledge` - a command that cannot exist. The
user-facing token is `{{SKILL_INVOKE}}`, so these are `/aidlc-knowledge` and
friends. Nine spellings fixed.
- The DocumentKB bullet shipped twice, and the two copies disagreed: one
documented `summarize`, the other did not. Kept the one that does.
- "All 17 hooks are TypeScript (`.ts`)" is true of no row here: hooks are
declared as data, and the count differs per harness. The sentence's real point
- no executable bits - survives without either claim.
- "unlike the three above" pointed at the wrong bullets; they are below.
- The skill directory was said to hold the stage protocol and per-phase stage
files. It holds the orchestrator and question rendering; those files ship
under `aidlc-common/`.
- Both `{{SLOT:...}}` section markers sat with no blank line above, so a filled
slot glued its first heading to the preceding paragraph and an emptied one
glued the next template heading. Every row shipped two glued headings; all six
are now clean.
The conductor's shell grant was `bun .kiro/tools/aidlc-*`. Shell match patterns
are globs where `*` is any sequence (features/permissions.md), so that requires a
literal `-` after `aidlc` and never matches `bun .kiro/tools/aidlc.ts` — which is
the only shell command the orchestrator skill issues, 22 spellings out of 22. The
conductor therefore prompted on every turn of its own forwarding loop.
The glob text is stock, inherited from `harness/kiro-ide/agents/aidlc.md`, but the
effect is this branch's: before the merge the CLI row's conductor was an agent-v1
config whose `allowedCommands` regex — `bun (run )?["']?\.kiro/tools/[A-Za-z0-9._-]+\.ts…`
— did cover the dispatcher. Keeping the IDE row's Markdown agent kept the narrower
grant, so CLI users lost a pre-approval they had.
Restored as `{{INVOKE}} engine *` rather than by widening the glob to `aidlc*`.
The native channel pre-approves only `aidlc engine *` and leaves the other route
namespaces (`config`, `compose`, `intent`, `space`) prompting; expressing the same
boundary here keeps the two channels honest instead of trusting more on one of
them. A path glob cannot say it, because the namespace lives in the arguments.
The token spelling is what makes both channels work: `{{INVOKE}}` projects to the
bun dispatcher on one and to `aidlc` on the other, where it collapses onto the
line the tool glob already becomes — so the release rewrite now drops the
duplicate. Writing the bun spelling literally instead made the release projection
produce `aidlc engine engine *`, which its own guard rejected.
The prose bullet described the personas' allowlist, not this agent's: it claimed a
`bun run` spelling and quoted paths that only the persona regex has, called the
grant read-only when the agent holds `write` and `shell`, and said "ONLY" while
omitting the filesystem writes and the 14 pre-trusted personas. Rewritten from the
file, with the deliberate prompts named and the IDE's Agent Autonomy caveat added
— that setting is evaluated before any of this, so Supervised mode prompts anyway.
Both were mine, from the round that fixed the onboarding prose. `Hooks are declared as data` is false for the opencode row, which registers its hooks through an auto-discovered TypeScript plugin rather than a wiring file (`harness/opencode/manifest.ts:19` calls it exactly that). The sentence exists to say no hook needs an executable bit, so it now says only that, and claims no mechanism on behalf of six different harnesses. `the only command the orchestrator skill issues` overstated a real measurement. The skill issues 22 shell calls: 19 on the dispatcher's engine route, and three `config` routes that the grant deliberately leaves prompting on both channels, since they rewrite the install. The comment now says which and how many.
Changing the shared onboarding text moved the root file's hash, and the install descriptor's `legacySignatures.wholeFileHashes` is how `aidlc config` recognizes a tree it has written before. The packager appends the CURRENT render to whatever the manifest declares, so a text change silently drops the previous render from that set unless it is promoted — an install carrying it would stop being recognized as ours. The precedent is in this branch already: the last commit to touch this text (`fcf3aed6`) promoted the outgoing hash into the manifest and added the incoming one to t243. Same two moves here, for the kiro and codex rows — the only ones whose `AGENTS.md` is a managed-block root integration. t243 now passes 84/0. It was the only suite that asked this question, and none of the targeted runs around the prose commits included it.
…t has none The prose said 0.12 has no exit-code refusal at all. Our own control run says otherwise: `kiro-test/kiro-ide-0.x-test/RETEST-0.12.333.md` records a preToolUse hook exiting 2 refusing a read outright, with no retry. The mistake was mine and it was a generalisation from the wrong seam — I read the host's hook runner as far as `HookFailedCommandError` and concluded "never a refusal", without following the rejection into the preToolUse caller. What 0.12 actually does is seam-specific: exit 2 on `promptSubmit` stops nothing (measured — the turn continued and the notice arrived framed as a broken hook), while exit 2 on `preToolUse` refuses the call. So the notice now carries both halves. The exit code is the enforcement, honoured by the host rather than by the model's goodwill; the denial text is the explanation, and it still leads, because this host's preToolUse contract tells the model that output denying access forbids retrying. Written to both streams deliberately: `stdout || stderr` is the success path's rule and does not apply to a non-zero exit, and the delivery we actually measured on a non-zero exit was via stderr. Unchanged: the USER_PROMPT gate, and the host-version guard that keeps a migrated copy of this hook silent on a supported IDE. That guard matters more now, not less — with exit 2 a migrated hook would refuse every tool call there. Verified on the shipped tree: 0.x socket + payload → exit 2 with the denial leading both streams; supported socket → exit 0, silent. t147 41/0, check clean. The behaviour on a real 0.12.333 host is the user's re-test, not yet run.
…ranch Clean: 0 conflicts. `awslabs#1185` touches five files this branch had just edited (`aidlc-config-diagnostics.ts`, `aidlc-init.ts`, `15-troubleshooting.md`, `t296`, `t304`) but on other lines — a comment extension, a new troubleshooting row, and new assertions. Checked for the semantic collision the textual merge cannot report, because the last absorption hid exactly this: after merging, no test asserts the product name `Kiro CLI` and no prose describes Kiro as two harness rows, so upstream's new `t296`/`t304` assertions read this row's own wording. This branch's provider prose and the two-server registry text both survived. The `kiro-ide -> kiro` upgrade wiring in `aidlc-init.ts` is intact: five `distributionUpgradesTo` call sites, two `currentDistribution`, and the two read-only diagnostics sites still untouched. Gates: install, package, typecheck, lint and `package.ts --check` rc=0, the last deterministic across all six rows. t296+t304+t281 40/0, t243 88/0.
…its surfaces `t299` asserted `In Kiro CLI, effort dials do not apply`, but that sentence interpolates `descriptor.productName` (aidlc-init.ts), and this row declares `Kiro` because one row serves both surfaces. CI caught it on shard 2/4. Third instance of the same class after `comes with Kiro IDE` in t294 and `comes with Kiro CLI` in t296/t304, and the one my grep missed: I had searched for `with Kiro CLI`, which does not match `In Kiro CLI`. The net that would have caught all four is every test expectation carrying `Kiro CLI` or `Kiro IDE` as a product name; swept now, and the remaining hits are test names, skip messages, and assertions that name a surface on purpose (how to start a session on each, the 0.x unsupported notice), which CI confirms still hold. t299 14 pass / 1 skip.
rafaelgsr
left a comment
There was a problem hiding this comment.
Thanks for consolidating the Kiro CLI and IDE distributions.
I reviewed the current head 4c5c2cf7094f20462c0a6089cf0e957ed808d2ea against base a517a06166448a456d3934d68a216b627b65547a.
The direction aligns with the project's one-core/many-harness architecture and replaces PR #1063's scope. I found three issues:
-
[P1] Preview-release lifecycle checks still include
kiro-ide..github/workflows/preview-release.yml:440and:574invoke a distribution that this PR removes. The command exits4, so both Windows and Unix preview-release jobs will fail. Please remove that row and add coverage that keeps preview and stable release rosters synchronized. -
[P2] Persisted machine defaults are not migrated.
core/tools/aidlc-init.ts:4170comparesdefault-harnessdirectly without applying the retired-distribution successor map. A machine retainingkiro-idecannot configure a fresh project without explicitly passing--harness kiro. Please normalize or migrate the saved default and cover this upgrade path. -
[P3] Plugin documentation still calls
.kiroambiguous.docs/harness-engineering/10-authoring-a-plugin.md:577still distinguishes Kiro CLI from Kiro IDE. Please update it for the unified distribution.
I made no changes to the author branch.
Validation performed:
bun scripts/package.ts --check— passed- TypeScript checks — passed
- Focused Kiro adapter, manifest, MCP, smoke, and diagnostic tests — passed
- Current remote CI — all required checks green
- Both blocking upgrade/release failures reproduced manually on the current head
One remaining risk is that a completed live Kiro workflow tail has not been independently verified.
leandrodamascena
left a comment
There was a problem hiding this comment.
Thanks for consolidating the Kiro IDE and CLI distributions and for carrying the upstream fixes into the merged row.
I reviewed the current head 4c5c2cf against current main 891669aa9, with merge base a517a061. The repository already treats both Kiro surfaces as the same .kiro runtime, so the consolidation matches the current architecture. I found three blocking compatibility and release defects.
-
P1: Preview publication still invokes the retired distribution.
.github/workflows/preview-release.yml:440and:574still includekiro-ide. Both lifecycle jobs invokeaidlc config --harness kiro-ide, which now exits 4. The next scheduled or manually dispatched preview will fail before publication. Remove the retired entry from both lists and add coverage for the preview workflow inventory. -
P1: Previously valid model settings prevent migration.
core/tools/aidlc-settings.ts:199-212now rejectskiro-idein per-agent model maps. The previous release accepted that key. Duringaidlc config --harness kiro, settings are resolved before the project stamp changes, so the command exits 4 withunknown harness "kiro-ide". Read the retired key as a compatibility alias forkiro, define the conflict behavior when both keys exist, and add an upgrade regression test. -
P2: Read-only configuration and the interactive setup walk reject retired project stamps.
selectedDiagnosticHarness,prepareModelsSection, andrunSetupWalkpass the persistedkiro-ideidentity directly tomodelHarness, which now rejects it. I reproduced exit 2 from bothconfig providers --showandconfig models --show. Apply the one-way successor mapping when reading existing project identity and when matching--harness kiro, while continuing to writekiro.
Validation performed on a local merge with current main: bun run typecheck passed, bun scripts/package.ts --check passed, reviewer-scope tests passed 120/120, and the focused Kiro/configuration suites passed 117/117. The current GitHub CI is green. The disclosed absence of a completed end-to-end Construction tail remains a release risk after these blockers are fixed.
Review round on 4c5c2cf found six issues across two reviewers; five share one cause. The first cut of the upgrade path fixed the places that PERFORM the upgrade and left every place that READS an identity a previous release wrote, so a user holding the retired row was refused before reaching the upgrade at all. - `modelHarness` (core/tools/aidlc-init.ts) resolves the retired id at the gate. That is one change for the diagnostics sections, the models section and the setup walk, all three of which reviewers reproduced as exit 2 from `config providers --show` and `config models --show`. Resolving at the gate rather than at its 20-odd call sites is the point: the previous attempt patched call sites and missed three. - `normalizeHarnessModelMap` (core/tools/aidlc-settings.ts) reads the retired key as its successor instead of throwing `unknown harness "kiro-ide"`. Throwing there put the upgrade out of reach, because settings resolve before the stamp is rewritten — the command that would remove the key could not run while the key was present. Conflict behaviour is defined: the current key wins, the retired one is applied only in its absence, and the outcome does not depend on key order. A key that never existed is still a hard error, named as written. - The configured machine default is normalized, so a machine that recorded the retired row configures a fresh project without an explicit `--harness`. - `.github/workflows/preview-release.yml` no longer invokes the retired row in either shell. `release.yml` had already been updated and this one had not, which is precisely how it stayed invisible; `t332` now reads both rosters and the shipped one. - `docs/harness-engineering/10-authoring-a-plugin.md` no longer calls `.kiro` ambiguous. The successor map moves to `aidlc-model-policy.ts` beside the harness roster. It was local to `aidlc-init.ts`, which is why consumers elsewhere never saw it. Regression tests, each verified failing at the parent with the message the reviewers quoted: `t298` (retired key read, conflict order, unknown key still throws), `t243` (both read-only sections exit 0 and report `kiro`; a machine default of the retired row still lands `kiro`), `t332` (preview and stable rosters match the shipped one — `Expected - 0 / Received + 1` before). Gates: typecheck, lint and `package.ts --check` rc=0, deterministic across all six rows. t243 89/0 · t294+t296+t304 58/0 · t298+t332+t330+t293+t299 69/0.
…ranch Clean: 0 conflicts. `awslabs#1179` touches `core/tools/aidlc-sensor-traceability.ts` and its suite, neither of which this branch had edited. Checked for the semantic collision a textual merge cannot report, since the last two absorptions each hid one: the shipped rosters and product-name assertions still read this row's own wording, and the `kiro-ide -> kiro` resolution added in the review round is intact. `tests/unit/t281-*.test.ts` carries two files sharing the id — the MCP registry one and the traceability one. Both are present upstream and at the merge base, so that collision is not this branch's and is left alone. Gates: install, package, typecheck, lint and `package.ts --check` rc=0, the last deterministic across all six rows. t281 (both) + t332 + t298 55/0.
… path CI on 5750cf4 failed four checks — three unit shards and the deterministic integration job — and every failing case spawns a hook or a subprocess: t07, t131, t144, t221, t241, t265b. One cause: the previous commit turned `aidlc-settings.ts`'s `import type` of `aidlc-model-policy.ts` into a runtime import, and `aidlc-lib.ts` is loaded by every spawned hook. `t144`'s `LIB_SIBLINGS` is the list of modules the lib needs at runtime; that module is not in it, because until now the edge was erased at compile time. The child failed at import and exited 1. Adding the module to `LIB_SIBLINGS` would have turned the checks green while widening the graph every hook loads, which is the opposite of where awslabs#1180 is taking high-frequency hooks. So the resolver moves to `aidlc-runtime-paths.ts` instead: that module already owns distribution identity (`legacyDistribution`, `harnessIdentity`), imports nothing from this project so it cannot cycle, and is already a lib sibling — no hook loads anything new. That is the third home for these three lines (local to aidlc-init.ts, where no other consumer saw them; aidlc-model-policy.ts, which broke the hook path; now here). The comment records the route so the next move has the reasons. Gates: typecheck, lint and `package.ts --check` rc=0. t144+t07 26/0 · t131+t241+t221 157/0 · t265+t298+t332+t294 111/0.
|
Thank you both — the reproductions made these quick to confirm, and five of the six turned out to share one cause. The first cut of the upgrade path fixed the places that perform the upgrade and left every place that reads an identity a previous release wrote. So a user holding the retired row was refused before reaching the upgrade at all — the worst shape this could take, and I had scoped it that way deliberately: I left the read-only sections alone on the reasoning that they should keep reporting the id the project actually carries, and wrote that reasoning into a comment. Reproducing your exit 2 settled it. Current head is @rafaelgsr 1 / @leandrodamascena 1 — preview publication invokes the retired row. Removed from both shells in @leandrodamascena 2 — previously valid model settings block migration. @leandrodamascena 3 — read-only configuration and the setup walk reject retired stamps. Resolved at the gate rather than at the call sites: @rafaelgsr 2 — persisted machine defaults are not migrated. The configured default is normalized, so a machine that recorded the retired row configures a fresh project without an explicit @rafaelgsr 3 — plugin documentation still calls The successor map moved to On the residual risk you both named. It is real and I am not going to claim it away. I do not think this PR is the right place to close it: validating a full Construction tail takes days of wall clock, |
|
Thanks for the fast turnaround on the first round and for the reproductions in the reply — the six items are fixed where you said they are, and moving the successor map into I reviewed the current head Blocking1. P1 — A refused dispatch leaks a delegation window and wedges the main session. 2. P2 — 3. P2 — 4. P2 — A failed write with a populated input path is audited as 5. P2 — 6. P2 — Source Non-blocking
One questionDoes IDE 1.0.437 populate ValidationTemporary worktrees only; no changes to the branch. Remaining riskNo completed Construction tail on either surface — #1 sits exactly there. An unclean manual overlay leaves the twelve old |
@apackeer reproduced six defects against dist/kiro built from bff9376. All six are real on this head; each fix below carries a test that fails at its parent. 1. A refused dispatch leaked a delegation window and wedged the main session. log-subagent opens the window on the dispatch's PreToolUse edge, before any sibling guard has decided. When a guard refuses, Kiro blocks the tool and sends no PostToolUse, so the only close that dispatch would ever get never arrived and the open lived for DELEGATION_TTL_MS. Every ledger consumer then read a delegate that was never running: the main session's own `next` was refused with the delegate named as the caller, and a later human turn did not clear it. The window is now cancelled at the two places this adapter turns a refusal into Kiro's reject contract, so a guard added later inherits it, and liveDelegationOpens counts a close whose open is not on the ledger yet - hook order across sibling registrations is not guaranteed. The pending close consumes the whole group, not its first persona, or a crew dispatch stayed live. (t147 5d5) 2. guard-tool-call was not registered anywhere. Both of its exit-2 branches - the first-`next` argument fidelity check and the same-turn roll-forward backstop - were reachable only by a hand-typed dispatcher call, while t180 kept asserting them by invoking the target directly. All six registered execute_bash PreToolUse guards return 0 on a bare `next` that the latch says dropped the user's arguments. Registered with the terminal-tool matcher and pinned in t331 (responsibility + matcher) and t245 (manifest schema). 3. doctor crashed on an unrefreshed kiro-ide project: the runtime probe indexed HARNESS_CLI with the raw stamp and threw `undefined is not an object (evaluating 'spec.command')`, so the user holding the row that needs upgrading could not ask what state it was in. The stamp is a READ, so it now resolves through currentDistribution like every other reader, and the probe reports an unknown id instead of throwing - a diagnostic must never be the thing that crashes. (t243, in the retired-stamp regression) 4. A failed write with a populated input path was audited as ARTIFACT_UPDATED. The failure classification lived inside the `!rawPath` branch, which was sound only while the path came from prose: preferring the tool input broke that proxy, because the CLI populates the input even when the tool refuses. Classify the failure first, whatever the path came from. (t218 F4g) 5. Three guards read process.cwd() rather than the resolved project, so the same guarded payload was refused from the project root and allowed from anywhere else - enforce-approval-gate, state-transition-guard and review-freeze. All three use projectDir now, and the comment that justified the cwd read is corrected. Arming this floor takes the runner's off-switch removed, a stage at [?], and an audit that tracks presence without a HUMAN_TURN. (t147 5h) 6. Source kiro installs failed `config trust --check` while doctor on the same tree reported zero problems. `.vscode/settings.json` trusts the literal `aidlc engine *`, which only the native channel runs, and the manifest emits the file from nativeRootIntegrations only. The check is gated on the projection descriptor - where the release build declares that file - so the channel that ships it is the channel judged on it. (t294) Also: the new hook json had to enter every roster that names these files, not just the two the fix touched - t308 pins the matcher-bearing manifests by name, and the kiro harness guide's hook table listed all the others.
…ranch # Conflicts: # tests/unit/gen-coverage-registry.test.ts
|
Thank you — six reproductions with exact coordinates, and every one of them held when I re-measured it. Two of your six also overturned a judgement I had already written down, which I have said plainly below rather than quietly fixing. Current head is 1 (P1) — a refused dispatch leaked a delegation window. Confirmed exactly as described, including that a later Your walk of the other leak paths was more complete than mine, and I want to record what I did not fix: cancellation, a crash, a malformed 2 (P2) — Adding it also had to enter a roster neither of us named: 3 (P2) — 4 (P2) — a failed write with a populated input path is audited as 5 (P2) — One correction to my own test scaffolding, in case it matters elsewhere: the adapter's 6 (P2) — source One thing I did not do: Non-blocking items. I verified all five and none of them is fixed in this round: the Remaining risk. Unchanged from what I told @rafaelgsr and @leandrodamascena: no completed Construction tail on either surface, and I am not claiming it away. My plan is still to validate it against a preview release once this lands, and to bring what it surfaces as focused follow-up pull requests. Your point that |
|
Thanks — four of the six are fixed and verified by reproduction (#2, #3, #5, #6), and the honesty about the leak paths you did not take is appreciated. I reviewed the current head Blocking1. P1 — #1 is only partially fixed: 2. P2 — the pending-close accounting introduced in 3. P2 — #4 still audits a structured failure with a populated path. Non-blocking
Verified#2: manifest on Remaining riskUnchanged — no completed Construction tail, and #1/#2 above sit in it. CI on Recommendation: changes requested for the three blocking items. |
Round 3 found my round-2 fix wrong in both directions, and both are real. The
delegation window is now opened only for a dispatch that was allowed to start,
which removes the need for the accounting that caused the second defect.
1. P1 — the window still leaked, through a refuser I had not considered.
`enforce-approval-gate` carries no matcher, so it fires on every dispatch, and
its exit 2 is far upstream of the two places I had hooked. Choosing call sites
is what produced both this and the round-1 defect, so the fix is not a third
site: the log-subagent PreToolUse edge - the only target whose matcher is
exactly the dispatch tools, and which sees both edges - now decides admission
and appends the open only after it passes. The human-presence floor moved to
one predicate (`approvalFloorRefuses`) with two callers, and the Plan Approval
verdict comes from the same core hook the sibling guard would have run. Both
matcherless gates stand down on a dispatch shape so they cannot refuse one
after this edge admitted it; `enforce-approval-gate` joins INPUT_TARGETS for
that reason alone. (t147 5d5, rewritten)
2. P2 — the pending-close accounting I added failed open. An expired open is
skipped at replay, so its close matched nothing and was remembered as a credit
for that key; the next byte-identical dispatch's open was then consumed by it
and never went live, hiding a running delegate from state-transition-guard and
reviewer-scope. Surplus closes reached the same state within the TTL, and
fixing item 1 by adding cancel sites would have made that live. The accounting
is gone: a refusal is not a ledger fact, a close means the dispatch reported,
and an unmatched close is dropped. Telling "the same event twice" from "two
identical events" needs an id this payload does not carry, so nothing depends
on one. (t147 5d6)
3. P2 — a structured failure with a populated path was still audited. The
transport's flag can sit inside the result envelope (`{success:false,
result:['']}`) and the text collector walked only the text keys, so this shape
carried neither a flag nor recognisable error prose. The nested flag now fills
the same `toolSuccess` the top-level one does, so the existing explicit-false
branch decides it - no new policy. Honouring a nested `true` over the prose
heuristic is the same reading the top-level flag already gets. (t218 F4h)
Non-blocking, all four with the coordinates from the review: an unreadable
projection descriptor is now its own trust issue instead of silently meaning "this
channel owes nothing" (it made a native install with the file deleted report
clean), `trust --show` no longer lists `.vscode/settings.json` for a source
projection, and the two remaining raw-stamp readers go through
`currentDistribution` - `doctor` was prescribing `--harness kiro-ide`, which exits
4, and reporting a retired stamp as an unsupported policy surface. Two stale
comments corrected: the orphaned banner above `canonicalTool`, whose claims about
t331 were both false, and the record-human-turn note that still said
`process.cwd()`.
Per the reviewer's read on awslabs#1: this is the failure a completed Construction tail
would have hit, which is why it took two rounds to find.
|
Thank you — and the framing of #2 as a fail-open that my own fix introduced is exactly right. I have taken a different route than the one you proposed for #1, so I want to put that difference first rather than bury it. Current head is #1 and #2 together, because the second is what rules out the obvious fix for the firstYou proposed routing every exit-2 on a dispatch-shaped So the window is no longer opened before admission. The Two consequences worth checking, since both are behaviour changes and not refactors:
The
One test moved rather than being added: #3Fixed, but deliberately not as a new rule about nested flags. The nested Non-blocking, all four
The five round-2 non-blocking items are still unchanged, and the What I did not verify locally this roundI ran Remaining riskUnchanged, and your point stands: no completed Construction tail, and both #1 and #2 live in it. #2 in particular is a failure I introduced while fixing #1, which is the strongest argument yet that this area needs the tail rather than another round of reading. My plan is still to validate it against a preview release once this lands and to bring what it surfaces as separate pull requests. |
|
Thanks — the redesign is the right instinct: a refusal should never be a ledger fact, and the three round-3 blockers now have passing reproductions with failing round-2 controls ( I reviewed the current head Blocking1. P1 — The stand-downs turn two independent refusers into zero. The requirement, rather than a prescription: a gate must not fail open when the admitter did not run, and a refusal after admission must not strand the window. Making the admitter and the gates evaluate the same predicate on the same identity (suffix authoritative, the same 2. P2 — Admission depends on an exact 3. P2 — Non-blocking
VerifiedR3 #1/#2/#3 fixed with controls; retired-stamp Remaining riskUnchanged: no completed Construction tail, and #1 sits exactly where round 3's did. The author-admitted crash/cancel/malformed-Post leaks are neither better nor worse. CI on |
Round 4 found my round-3 fix fail-open four ways, all verified here: the two stand-downs returned 0 for any dispatch shape without asking whether the admitter had run. This is the third consecutive reversal in this seam, so the invariant is now stated rather than implied — a gate refuses on its own evidence, and the recoverable cost of a duplicate refusal is accepted. 1. P1 — both stand-downs are gone. `enforce-approval-gate` and the plan guard judge a dispatch like any other tool call again. With the log-subagent manifest absent, its matcher not firing, or its own block skipped because the payload was malformed, the floor previously had no owner at all; the plan guard also skipped its legacy opaque-write arm on every dispatch. The `INPUT_TARGETS` entry added only to power the stand-down is removed with it. admission-before-open stays, but as an early refusal and a narrower window — not as the sole owner of the decision. A gate refusing after admission leaves a window with no close, and that is now reclaimed rather than prevented (below). (t147 5d5 rewritten, 5d7 malformed bypass, t218 dispatch assertion restored to 2) 2. P1 (same finding, identity half) — one derivation, and it is the ledger's. `kiroDispatch` treats the `subagent_` suffix as authoritative with arguments as fallback, matching this harness's rule that a platform-provided identity outranks an agent-authored one. My admission check derived it argument-first, so `subagent_aidlc-developer-agent` carrying `tool_input.name: "aidlc-quality-agent"` asked core about the quality agent and then had its window attributed to the developer. The `invoke_sub_agent` developer fallback stays OUT of kiroDispatch: it is a fail-closed policy, not an identity, and putting it there would let a completion audit record the developer for a delegate named in prose. (t147 5d8) 3. Windows are reclaimed by a `sweep` boundary, not by more closes. A close cancels the most recent group for its key, so appending one at a human turn would cancel a dispatch the NEXT turn legitimately opened with the same bytes — round 3's defect again. A sweep record abandons every open appended before it and leaves later ones untouched. `record-human-turn` appends it, which is sound because a dispatch on this row is blocking: anything still believed inflight when a human speaks is finished or gone. This also covers the crash/cancel/malformed-Post leaks I admitted in round 3 and left open. (t147 5d6, 5d9 both hook orders) 4. P2 — `hook_event_name` is normalised at parse (`canonicalHookEvent`). The hosts measured so far send PascalCase, but this repo's captured fixtures and Kiro's docs spell `preToolUse`, and my admission edge branched on an exact match — putting a security decision on the spelling of one field. Unknown names pass through so a new trigger shows up in a drop rather than being silently renamed. Same lesson as the USER_PROMPT/stdin channel fix: accept both, do not replace one. 5. P2 — the admission path fails CLOSED on unreadable state while the advisory gate keeps failing open. `approvalFloorRefuses` takes the answer as an argument so neither caller inherits the other's default; `humanActedSinceGate` learned the same lesson (ENOENT-only skip) after treating "could not read" as "was empty" inverted it to fail-open. Not done, and not silently: `planApprovalRefusesDispatch` still discards the core hook's stdout/stderr (round-4 awslabs#3), so the AIDLC-UNIT remedy is not relayed. The concurrent-hook case the reviewer asked for is also not written yet.
Round 4 awslabs#3, and the concurrency pin the review asked for. - `runCoreHook` already piped stdout and dropped it. It now returns it, and the dispatch admission relays both streams: the core guard's stderr carries the AIDLC-UNIT / AIDLC-TESTING-CONTRACT remedy, which is the conductor's only deterministic retry, and its stdout carries the one-time "N file(s) changed since this plan was approved" notice that relaxed Change Control prints exactly once. My generic line stays only as the fallback for a guard that said nothing. Adding a field leaves the two other callers byte-identical. (t147 5d10, fails at the parent) - t147 5d11 pins the concurrent case: two adapter PROCESSES admitting byte-identical dispatches at the same moment leave two opens in two groups. This one PASSES at the parent by design — it is a regression pin for the property the append-only ledger already provides (a keyed map once collapsed them and the first close released both), not evidence of a new defect.
Open since round 2 of the awslabs#1157 review. The axis is engine GENERATION, not surface — "IDE versus CLI" is the wrong way to reason about it, and that is the part worth writing down. Counted across every capture on this machine: - Kiro CLI, agent-v1 engine (2.6.1, 2.18.1): camelCase. Source is our own fixture, whose `_provenance` records both captures and states "payload field names are verbatim". - Kiro IDE 1.x, every build from 1.0.89 to 1.0.337: PascalCase, camelCase 0 — 1.0.89 (41), 1.0.116 (26), 1.0.138 (31), 1.0.165 (108), 1.0.203 (61), 1.0.212 (73), 1.0.309 (275), 1.0.337 (111), from the per-version archives in the kiro-ide-1.x-test controlled-experiment tree. - Current unified row, both surfaces: PascalCase, camelCase 0 (CLI 140/139/7/3, IDE 129/127/6/2). 🔴 This corrects the first draft of this section, which guessed the camelCase fixture was a 0.12-era capture. It is not: IDE 0.12 carries no `hook_event_name` field at all (its contract is USER_PROMPT with camelCase FIELD names), and the fixture's own provenance names kiro-cli. The published hook docs spell it camelCase too, which matches agent-v1 rather than anything measured here. The adapter does not depend on the answer — `canonicalHookEvent` folds both at parse — but the admission edge branches on this value, so the next reader should not have to rediscover which spelling belongs to which generation.
…claims The first minor transition on the controlled-experiment tree (73 captured events) keeps PascalCase — ten builds now, camelCase 0 — but it moves which arm of the delegation identity carries the agent name: invoke_sub_agent returns with a populated argument object and subagent_<agent> is absent, so the suffix arm's precedence is a code guarantee rather than an observed shape.
…t claim t239 scans every doc for a "<N>-event" phrase and requires it to equal the audit registry's event count; "a 73-event capture" tripped that pin even though it counted hook firings, not audit event types.
Replaces #1063, closed unintentionally: a brief visibility change on the head fork detached it
from this repository's fork network, which closed the pull request and made reopening impossible.
Nothing about the change itself was reconsidered, and the head commit is the one #1063 carried
(711e4de).
What this does
kiroandkiro-idemaintain the same.kiro/shell twice. Kiro IDE and Kiro CLI run the same agent runtime, so this consolidates them into one row,kiro, serving both surfaces, and removeskiro-ide.Breaking:
aidlc config --harness kiro-ideno longer resolves — use--harness kiro.Why now
The two adapters implement the same contract twice and have never converged:
harness/kiroharness/kiro-ide690 → 3,104 lines. The point is not that the gap grows monotonically — it narrowed at 2.3.0 and flipped sign at 2.7.0 — but that the same contract is implemented twice and pays maintenance twice every generation. Five call sites already normalized
kiro-idetokirobefore this change (aidlc-model-policy.ts,aidlc-plugin.ts,aidlc-plugin-emit.ts,scripts/plugin-hooks-template/compose.ts).The merged adapter is 3,010 lines — the union, not a sum, because the duplicated halves collapse.
What the merge had to solve
The engine.
settings/cli.jsongains"chat.agentEngine": "v3", so a plainkiro-cliin the project reaches the agent runtime this row targets with no flag at the call site. Measured both ways on kiro-cli 2.21.1: with the key the session enters that engine, without it it does not.Hook registration. The row registers through standalone manifests (14 files, 16 registrations) that both surfaces read. Two triggers are surface-specific per
docs/features/hooks.md— Session Start is IDE-only, Agent Spawn is CLI-only — so lifecycle responsibilities register on both or they are dead on one surface. Only Prompt Submit, Pre Tool Use and Pre Task Execution can refuse, so every guard that must refuse sits onPreToolUse.The acting persona. This is the hard part. The pre-merge row registered five guard targets inside each persona's own agent-v1 config and passed the persona as argv — 90 registrations. A v3 hook manifest has no agent-scope field, so that channel has no replacement in kind, and two guards depend on it:
state-transition-guardenforces only when it knows a delegate is acting, andreviewer-scopecompares the identity against the dispatched reviewer.What replaces it is measured. Across 615 captured payloads (Kiro CLI 2.18.1 / 2.19.2 / 2.20.1 and IDE 1.x) a delegate's own tool calls do reach the workspace hooks, strictly nested inside the dispatch event's own
PreToolUse..PostToolUsewindow, and the dispatch event names the delegate in one of three places: thesubagent_<agent>tool name,tool_input.name, ortool_input.stages[].role. So the adapter opens a window on the dispatch's Pre, closes it on the matching Post, and attributes what happens in between. The ledger is append-only: each open is its own record, a close cancels the whole most-recent group for that dispatch key (a crew dispatch names several personas and must be released as one), and nothing is read before writing, so two adapter processes cannot lose one another's update.reviewer-scopeandreview-freezeare registered for the first time on this row. Both must be able to refuse, so both sit onPreToolUse.IDE 0.x
Not supported, and not silently dropped either: one legacy
.kiro.hookships whose only job is to say so. That format is the only channel a 0.x host reads and a supported one does not run it — measured on 1.0.437, where nothing fired — so the channel does most of the version check. Not all of it: a supported IDE lists the file aslegacybeside a Migrate button, and one click makes it a hook that host does run, so the notice also reads the host's own version before it speaks. Both halves are below.Which trigger, and which exit code, is not a free choice, and 0.12 answers differently per seam. On
promptSubmit, exit 2 stops nothing: the hook runner turns any non-zero exit intoHookFailedCommandError— "Hook '' command execution failed" — and that seam reads it as a broken hook rather than a refusal. Measured: the turn continued, and the session's own agent spent two minutes diagnosing the notice before writing"enabled": falseinto the manifest, which does not even disable it (that host keeps enablement in workspace state). OnpreToolUse, the same exit code does refuse the call. So the notice ridespreToolUsewithtoolTypes: ["*"], writes the denial first, and exits 2.Both halves carry weight. The exit code is the enforcement — measured on a real 0.12.333, the read was abandoned and the session reported it could not retry because the hook exited 2. The text is why, and it leads the output because this host's instructions tell the model that output denying access forbids retrying. It goes to both streams:
stdout || stderris the success path's rule and does not apply to a non-zero exit, and the delivery measured on a non-zero exit was stderr. The message states the supported versions inline rather than pointing at a file, since a reader in this state has every tool call refused and nodocs/tree in the install.This refuses per tool call rather than per prompt, which is the better seam: nothing in AI-DLC advances without a tool, and the refusal lands where work would have begun. The target still reads neither stdin nor the normalized payload (a 0.x host opens stdin and never closes it) and still confirms the legacy channel through
USER_PROMPT, which is populated on this trigger too, carrying the tool input as JSON rather than the user's prompt; only its presence is read.The channel alone is no longer proof of the host, so the target also checks one. A supported IDE does not run this file, but it lists it as
legacywith a Migrate button — one click makes it a current-generation hook that a supported host does run, andUSER_PROMPTis populated there too, so that gate would not catch it. Migrated, onpreToolUse, the notice would deny every tool call on a fully supported install, and the button is in the hooks panel of every one of them. So the target reads the version line out ofVSCODE_IPC_HOOK(0.12-main.sockon 0.12.333,1.0.-main.sockon 1.0.437, both measured) and speaks only for a 0.x major. An absent or unparseable value is silence as well: informing is this target's only job, and refusing work on a supported install is the worse failure, at the cost that a 0.x host which stops setting the variable gets no notice. The guide tells readers to leave the hook alone rather than migrate it.Defects found and fixed on the way
Each of these built and typechecked clean before the fix, which is why they are worth listing:
reviewer-scope,guard-tool-callanddeliver-stage-rulesread the raw payload, and none was in the set that decides whether the adapter reads stdin at all — so the payload was"", the parsed object was{}, and each returned 0 on its first field access.tool_responseis not always a string. Two captured shapes ({items:[{Text}]},{success,result:[...]}) were classified malformed, which discards the whole event: no audit row, and the Stop hook's inflight marker never cleared.execute_pwshthat the canonicalizer did not translate, so the call reached the adapter and returned 0 — the gate read as present and enforced nothing on Windows.writewas missing from three matchers while the adapter canonicalizes it, so a frozen-artifact write and a reviewer-scope violation under that spelling reached nothing.subagent_responsewas classified as an opaque mutation, diverting every delegate report during code-generation into the legacy recovery path.aidlc-plugin-emitemitted a plugin's compose hook onSessionStartonly, so on Kiro CLI a plugin never composed and nothing said so.dist-release/kiroshipped no.vscode/settings.json, so the native channel lost thekiroAgent.trustedCommandspre-trust and an untrusted project command silently never runs.continue-workflowcarve-out and, in other processes, the done-guard and the doctor bundle — still read the flat one, so the latch was dead. A compiled install lost theenginenoun from its argv, breaking every off-band terminal command. All fourteen personas shipped with notools, MCP or shell grants and noresources. And a space switch stopped re-pointing Markdown agents, so it could not put the conductor back.package.ts --check, all three typecheck configs and Biome passed throughout; the unit tier is what failed, and each of the four now has a regression test.harness/kiro-ide/while this branch was open, and none of what they added is IDE-0.x-specific, because this row still ships the legacy Plan Approval axis. Six things move here. The shell tool is namedexecute_bash,execute_pwsh(Windows) orshell, and ten branches tested a literal — one predicate now backs every shell decision, because an unrecognised name failed open on that host. The unattributable-mutation refusal is gated on an active Code Generation window, so thenextthat starts the loop is no longer refused with no workflow. The safe-read set gains the names the runtime actually sends. Legacy mediation records the planned workspace source, without which core refuses the decision and nothing is recorded at all. A failed mediation step is a refusal with a reason on stderr rather than a silent drop that left the write window latched. And source drift before approval no longer dead-ends: the canonical planning write — the remedy — stays permitted, and the refusal says to re-present the plan. The guide and the reference prose state that contract for one row rather than two, and the conductor's own skill carries the legacy contract it has to execute: which tools may write before approval, that the directive's protected choices are shown verbatim and mapped back to canonical answers and never copied into shared files, and that recovery needs an exact humanRecover Plan Approval.retainedTransportForCurrentStateskipped its reuse path for the legacy window by name (installedHarnessName() === "kiro-ide"). One row makes that literal permanently false, so a live legacy window took the reuse path and the publication that rotates its protected choices never happened:nextansweredrun-stagewhere ownership had to refuse (t327went 4/0 → 2/4). The carve-out exists because of the window, not the row, so it now asks the window — a derived legacy session whose host record matches, the same predicate the file already used a few hundred lines above. Reading the environment alone would have caught a supported IDE 1.x session and a CLI run from a VS Code terminal, both of which keep the reuse.core/is left with no"kiro-ide"literal anywhere, which is the state that makes this class of silence impossible rather than merely absent. Typecheck and the build never object to a literal like this; the suite is what caught it.PreToolUseagainst one onPostToolUse. So every dispatch opened two groups and closed one, and the leftover window kept the lifecycle guard refusing the main session's own verbs with a finished delegate named as the caller — measured on a live run, twice, where the only way forward was editing the ledger by hand. One target owns the ledger now: the only one registered on both edges of a dispatch. Typecheck, Biome andpackage.ts --checkare all blind to this; the regression test delivers one dispatch event to both targets and asserts one close releases it.uvxlaunchers. The merged row inherited Claude's registry, where four of the five entries spawn a local process from a floating@latestpackage. That is a heavier default than a row serving both surfaces wants, and the AWS documentation case it existed for is covered over plain HTTP, so the registry is nowcontext7plusaws-knowledge-mcp-server, bothtype: httpand both disabled. Claude keeps its five deliberately: the two harnesses provision MCP differently, and the guide and glossary prose describing the five is Claude-framed, so it stays true and is untouched. Two consequences the type checker does not see — the shared config diagnostics resolved its shipped-server list per harness, because one list would report a false defaults-drift on whichever harness ships fewer; and the persona frontmatter grants one token per entry, so all fourteen personas move from five grants to two.disclose_contextjoins the Plan Approval safe-read set, because denying it stopped a Windows customer mid-workflow and it has no write surface. Two hook manifests match all three shell spellings rather thanexecute_bashalone, since the engine verbs arrive through whichever one the host sends and a missing spelling silently stops that hook. fix: restore native Kiro IDE Plan Approval recovery and mediation #1111's new native-recovery suite pointed at the deleted row's dist, so its coverage moved to the merged row rather than disappearing. And its legacy spawns now route through the compiled binary's own subcommand: I had recorded that helper as "already covered here" on the strength of a grep count for the environment variable it reads, but those hits were elsewhere, and on a compiled install passing a source path to the binary makes the recovery fail closed. A grep count is not evidence that a code path uses what it counts.Tests
t331is new and encodes the wiring contract rather than the current file list: every required responsibility on every trigger it needs, a refusing responsibility only on a blocking trigger, a single-surface trigger always paired, every manifest target existing in the adapter, and every matcher covering the tool names the canonicalizers translate. That last assertion is what caught theexecute_pwshandwritegaps.t147(36 → 42 cases) keeps its live-captured payload fixtures and gains regression cases for the delegation window: one crew dispatch is one window however many personas it named; two identical dispatches need two closes;reviewer-scopestill enforces when a second persona is inflight; a crew with no review in flight leaves the drop log clean; a dispatch record naming a delegate that is not inflight is recorded as a drop; the 0.x notice fires only on the legacy channel.t245andt148were rewritten onto the Markdown shape — the properties they pinned (no blanket shell trust, no nested delegation, no model pin, every registration reaching this row's adapter) all still matter.t245's expected registration table is now generated from the shipped manifests so a matcher change cannot leave it stale by hand.Three tests pinned the absence of enforcement this row now has (
reviewer-scoperegistration,review-freezeregistration, the doc-parity claim) and were inverted rather than deleted. The matching carve-out instage-protocol-reviewer.md— "on a harness without reviewer-scope enforcement (Kiro IDE today), do not write the record" — is gone with them, after checking every shipped row's registration: no harness lacks it now.Verified
bun tests/run-tests.ts --smoke --unit, on this head withupstream/maina0ee4415merged in — 305 files, 8,321 assertions, one failing assertion.That failure is
t150-codex-packaging, in the case that assertsdoctorenforces Codex0.145.0as the compact-session reload floor. It is a 5,000 ms timeout: the doctor child process returned status −1 rather than 0, and the runner reported "killed 1 dangling process". The same file passes 13/0 in isolation on the same tree, in 9.47 s for the whole file, so the single case is under the limit when it is not competing for the machine. This branch does not modify that test, and while it does touchharness/codex/manifest.ts, the failing case checks a version floor indoctorrather than anything the manifest carries.The two suites the earlier body named as environment-sensitive both pass in this run:
t255-workspace-syncandt314-source-freshness-receipts. That is the first completed tier in which they pass together, which is what the earlier explanation predicted. The published figures before this run (297 files, 8,195 assertions) came from before the rebase, and the sentence that explained them blamed "the machine under--parallel 8" — wrongly, because the runner pins every smoke and unit file to serial execution, so that flag creates no concurrency in either tier. Barebun test tests/unitstill does not complete on this machine, which is why per-suite numbers come from isolated runs; the totals above come from the repository's own runner.The merge itself is clean — 0 conflicts — even though the overlap with
upstream/mainhas grown from six files to nine, three of them files this repository's own merged PRs changed (#1158 and #1160:core/aidlc-common/protocols/stage-protocol-reviewer.md,tests/unit/t266-review-class.test.ts,tests/unit/t279-reviewer-turn-budget.test.ts). On that merged treetypecheck,lintandpackage.ts --checkare clean.t279's per-harness assertion needed no roster edit becauseHARNESS_MATRIXis discovered from the tree rather than hardcoded; all six shipped rows carry the reviewer clause, andkiro-ideis absent by design.Isolated suites on the merged tree above:
t26615/0 ·t27915/0 ·t27147/0 ·t23913/0 ·t33125/0 ·t14745/0.Isolated suites measured earlier, before
upstream/mainwas merged in (counts have since grown where upstream added assertions):t24543/0 ·t14816/0 ·t21874/0 ·t22164/0 ·t24384/0 ·t2440 fail ·t687/0 ·t264·t265·t293·t294.Replayed on throwaway fixtures with real captured payload shapes: dispatch Pre → the window holds the persona; the delegate running
aidlc-orchestrate.ts next→ exit 2 naming it; dispatch Post → window empty and the same command passes;orchestrate_subagentPre → the window holdsstages[].role; two distinct personas inflight → exit 2 naming both; the 0.x notice → exit 0 with no output on a supported host, exit 2 with the notice whenUSER_PROMPTis set.Attribution inside a parallel delegation window, every branch measured on a throwaway fixture and pinned by a test in
t147:The 0.x notice, on a real Kiro IDE 0.12.333. Installed the shipped
kirotree into an empty project, opened it in that build, and asked for a file read. The hook fires at the tool call, the agent abandons the read, relays the notice, and does not try to work around it. Two earlier shapes were measured on the same host and rejected:promptSubmit+ exit 2 arrived as a failed hook, and the session's own agent then spent two minutes diagnosing it and wrote"enabled": falseinto the manifest — the same request now completes in ten seconds. Also measured there: the"enabled"field in a.kiro.hookdoes not gate execution (that host keeps per-hook enablement in workspace state), so editing it neither disables the notice nor is a supported way to.And on a supported one, IDE 1.0.437. The same tree, the same request: the read succeeds and no legacy hook runs — confirmed by two silent
.kiro.hookprobes that would have recorded a firing to a file, and by the shipped notice staying quiet. "The legacy channel does not fire on a supported host" is therefore measured on the current generation, not inherited from an older one. What that host does do is list the file aslegacywith a Migrate button, and it populatesUSER_PROMPTfor arunCommandhook exactly as 0.x does — so the notice reads the host version fromVSCODE_IPC_HOOKand stays silent unless the major is 0. Verified against the running host's real socket path rather than a fixture literal.The same session is incidental evidence for the row itself on the IDE surface:
session-start,record-human-turn,terminal-command,enforce-approval-gate,plan-approval-guard,reviewer-scopeandcontinue-workflowall fired from the standalone manifests, the engine advanced its turn counter and created the intent record, and.dropsstayed empty.A guarded call inside a parallel delegation window, on Kiro CLI
--v3. Three delegates opened before any of them closed, and guarded calls landed inside that window:No delegate closed between those rows, so the concurrency is the capture's own ordering rather than an inference.
execute_bashandfs_writecalls continued to land with two or three delegates inflight for the rest of the window. That is the combination the secondNot verifiedbullet said had only been replayed on a fixture, and it is now on the ledger of a live run.The branch that window feeds was exercised with it. Three delegates inflight and no reviewer dispatch record is the ambiguous case: the adapter resolves no identity, the core hook fails open, and — because a recordless crew is the ordinary state of that stage rather than a failure — nothing is written to
.drops. The run has nokiro-adapter.dropsfile at all, which is what the unit case for that branch asserts.The reviewer read-scope bound, enforcing, on both surfaces. Two live runs from this shipped tree reached CONSTRUCTION and ran per-unit reviews, which is what writes the dispatch record the guard needs. The record is written as the protocol specifies:
The guard then refused, and the refusals are on the ledger as
REVIEWER_SCOPE_BLOCKEDrows:shared-kernel,identity-access,ordering-session,customer-webBash, target<project-dir>— and one with target<record-root>auth-foundationBash, target<project-dir>The first refusal is representative: the reviewer ran
cd <project-root> && git status --porcelain, which reaches every unit's artifacts, and the hook stopped it — that row has aPreToolUsewith noPostToolUse, so the refusal is the hook's exit, not a tool result. One later refusal names<record-root>rather than the project root, so the bound is not merely a project-root check.What passed in the same window is the stronger half. Six
cd <record>/inception && …commands ran unrefused during those reviews — readingstories.md,requirements.md, the story map and specific FR blocks. All of them are on the dispatch record'sexemptlist. The guard therefore discriminates: declared upstream reads pass, sweeps of the record root or the project root do not.No workaround followed a refusal. After the block the reviewer read its own review file, returned its verdict, and the stage advanced (
log review→state unit complete --wave --unit shared-kernel). There is no later call that reaches the same breadth through another tool.One limit, stated because the ledger cannot show it otherwise: every refusal so far is on
Bash. The guard also matches the search and listing tools, and those matchers have not been exercised — not because the reviewer avoids them (the runs usegrep_search25 times,list_directory16, andfile_search7) but because inside these review windows the reviewer routes its searching through the shell instead: 53execute_bashcalls against 1grep_searchin one window, and 741 against 25 across the run. So the Bash matcher carries the load here, and the search matchers remain covered by tests rather than by this run.Not verified — please weigh this
/aidlcrun is included here. Two runs from this shipped tree — a Kiro CLI--v3project and an IDE 1.x project — have carried a workflow through IDEATION and INCEPTION and into CONSTRUCTION. On the CLI surface five units have been throughfunctional-designwith their own per-unit reviews, two of them across a NOT-READY → revision → READY cycle; on the IDE surface two units have. Every standalone hook fires, the rituals run, and one run surfaced a real defect in this row that is now fixed with a regression test (see the delegation-ledger bullet). What remains unverified is the tail of CONSTRUCTION —code-generation,build-and-testandci-pipeline— rather than the phase or the wiring.Release metadata
Deliberately absent: no
aidlc-version.tsbump, no README badge change, no CHANGELOG heading — following the maintainers' "we will bump version when releasing".AGENTS.mdandCONTRIBUTING.mdcurrently say the opposite; that divergence is worth settling separately and is not this PR's to decide. The entry a release would need:Follow-ups this PR does not take
permissions), the personas declare tool-settings regexes (toolsSettings). Both are supported —docs/features/custom-agents.mdstates existing JSON keeps working unmodified and the newer fields are optional — so each file keeps the shape it shipped with before the merge rather than being rewritten here. Unifying them is a separate, mechanical change.Review history
Reviewed adversarially five times before submission (Codex, in a separate harness). The last round's five blockers — crew-dispatch window accounting, the two missing matcher names,
reviewer-scopefailing open under ambiguity, and a check-then-truncate race in the ledger — are fixed here with regression tests. I am the reviewer on other PRs in this repo; treating my own work the same way seemed like the minimum.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.