# Changelog — agent-workflow-kit

Semantically versioned ([semver](https://semver.org)), newest first. The `version:` in `SKILL.md`
is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
every `migrations/<version>-<slug>.md` newer than it, in semver order.

## 10.2.0 — a review can no longer pass a change it called broken (AD-122)

Ships antigravity-cli-bridge **5.4.0** in the bundled bridge tree. A consumer recorded a clean
`SHIP WITH NITS` receipt over a review body that named a correctness bug and a security violation
under `### Blocking` — the wrapper read the verdict line and nothing read the section. Now a
ship-class verdict beside a numbered Blocking item is a verdict-body contradiction and takes the
existing failed-review arm: exit 4, no receipt, both halves named, re-run the review. `REWORK` is
never refused — a veto needs no corroboration, only a pass does — an absent Blocking section is not
a contradiction, and a repeated heading terminates the scan, so exactly one section is ever judged.

The guard was written to a contract first (`docs/ai/specs/bridges/agy-review-verdict.md`, eight
scenarios), and the bridge's test suite was split in the same change: the shared fixture surface
moved to `bin/agy-review-harness.test.mjs`, the wrapper suite shrank to 2893 lines with all 213
existing arms green, and the new contract got its own topic file. Both new files ship with the
bridge tree, so the tarball count pin moves 257 → 259.

## 10.1.0 — no work without a specification, and a ratchet that does not trust its own file (AD-121)

A shipped tool that no LIVE contract claims is now a REFUSAL, not a preference. `tools/spec-coverage.mjs`
(the rule) and `tools/spec-coverage-cli.mjs` (argv, fs and the one write) read each contract's own
`## Module` list as the coverage map — coverage is never declared twice — and a project declares the
check as a gate. Measured at adoption in this repo: 122 tool modules, 14 governed by a contract.

The other 108 are owed, and what is owed is never STORED — it is a subtraction. `adopted` is the set
measured once at adoption and never rewritten; `settled` names the adopted paths whose contract has
since been written; the debt is the difference. So there is no list a hand can edit into a lie: the
only editable claim is "this one was paid", and every run checks that claim against the contracts
themselves — a settled path no live contract covers refuses, and refuses twice, because the tool is
uncovered again. A stored debt list was the first design and a review killed it: `--write-debt`
refusing to add a path guaranteed nothing while nobody was obliged to use it.

The scope file is validated before it is used: a wrong schema, an empty `roots` or `extensions`, a
non-string entry, or a census of ZERO files all refuse — a gate that passes because it looked at
nothing is not a pass. Exclusions are path-component boundaries, so excluding `x/fixtures` never also
hides `x/fixtures-escape.mjs`. `draft` and `retired` contracts cover nothing.

Also in this release: `tools/spec-check.mjs` counts a scenario marker as a WHOLE ordinal, so a store
reaching ten scenarios no longer refuses a correct binding while naming the wrong scenario
(`spec:x/S1` used to occur twice the moment `spec:x/S11` was written beside it).

## 10.0.0 — a symlinked docs file gets named instead of skipped (AD-119)

Mirrors the memory canon: `references/scripts/check-docs-size.mjs` no longer lets a symlinked docs
file leave the walk in silence. The predicate `entry.isFile()` is FALSE for a symlink and so is
`isDirectory()`, so a symlinked `*.md` under `docs/ai` fell through both arms of the walk and was not
even counted in the report's file total.

> ### ⚠ BREAKING — a docs tree that was green can now go RED
>
> A symlink under `docs/ai` named `*.md`, or resolving to a directory, is a NAMED error row and is
> never read.
>
> `--check-index` additionally runs the navigator's containment guard BEFORE it reads. On a chain
> the walk can TRAVERSE, that guard rejects a symlink at the project root, `docs`, `docs/ai` or
> `docs/ai/index.md` and exits 2 — where the mode used to compare and pass; it is the same guard
> `--write-index` and `--ensure-index` already applied. (A chain the walk cannot traverse at all
> still ends the run as it always did, before any guard — unchanged by this release.) It matters
> because `buildIndex` drops the navigator's own row, so a symlinked `index.md` whose target held
> the current bytes compared EQUAL and reported it fresh.
>
> **When it bites.** Only a deployment that actually has such a symlink, and only once these bytes
> reach its `scripts/`. `init` does NOT overwrite an existing deployed `check-docs-size.mjs`; the
> paths that do land it are a fresh bootstrap, a `migrate-adr-store` refresh, and the `specs` ensure's
> prior-matched refresh lane — which is what the catalog row below is for.
>
> **Remedy.** Replace it with a real file or a real directory, whichever it stands for, or move it out
> of `docs/ai`. The gate names the path and which case it is. For a symlink on the NAVIGATOR's own
> chain, note that regeneration is itself refused — `--write-index` cannot clear it, only replacing
> the link can.

**Deployments on the previous body stay upgradeable.** The prior catalog gains
`check-docs-size.mjs` `4.6.0..6.0.0`, so a project still carrying that body classifies `prior`
and remains inside the refresh lane instead of stranding as `custom` — which is what would otherwise
withhold every write that lane gates.

**Preserved, each pinned by its own arm.** A symlinked NON-`.md` regular file is still skipped; a
tree with no symlink under `docs/ai` writes a byte-identical navigator; an over-cap REAL `adr/`
record still collapses into the aggregate row, because the guard keys on the refusal and never on
`errors.length`; and `walkMarkdownFiles` keeps its exact historical one-argument signature, which is
why the parity-frozen spec file needed no edit.

**Only `ENOENT` and `ENOTDIR` mean absence.** Every other `stat` code — `EACCES`, `EIO`, `ELOOP` from
a symlink cycle — leaves the kind UNKNOWN and yields a named refusal carrying the code. Treating an
unclassifiable link as a skip is exactly how the subtree behind it escapes again.

## 9.0.0 — the archiver's cap table ships, and the migration seeds it behind its importer (AD-118)

Memory **6.0.0** replaces the changelog archiver's fixed cap literals with a stamp it can honour,
and this kit ships those bytes. One kit-owned behaviour moved with them, and it is the interesting
half: a new import by a refreshed archiver reopens a hole the companion seed exists to close.

> ### ⚠ BREAKING — inherited from memory 6.0.0
>
> The archiver REFUSES once a tier's line count goes past its ceiling, in every mode including
> `--check`, and META's floor rises 300 → 1500.
>
> **When it bites.** The new bytes reach a project's `scripts/` on a FRESH bootstrap, a
> `migrate-adr-store` refresh, or a hand copy — installing this kit does NOT overwrite an existing
> deployed `archive-changelog.mjs`. Once they land, a project whose archive has already outgrown a
> ceiling sees its `changelog-rotation` gate turn red with no edit of its own.

- **`references/scripts/archive-caps{,.test}.mjs`** — new, mirrored byte-identical from the memory
  canon; `references/scripts/archive-changelog{,.test}.mjs` refreshed to the 6.0.0 bodies.
  `sync-mirrors --check` green over all three copies.
- **`tools/migrate-adr-store.mjs` — `COMPANION_SEEDS` gains a per-entry condition.** The list exists
  so a refreshed archiver never crashes on a missing runtime import, and it was flat: every entry
  seeded on every migration. That was correct for what it held — the ONE runtime dependency
  `markdown-blocks.mjs`, which all three archivers import, plus its deploy-payload test riding along
  with it. `archive-caps.mjs` is imported by `archive-changelog.mjs` ALONE, so seeding it
  unconditionally would write a file with no importer during a plain ADR migration — the directional
  "never ADD a basename the consumer lacks" rule broken, and a log line saying something untrue. An
  entry may now NAME the importer it rides: the markdown pair stays unconditional, the `archive-caps`
  pair rides `archive-changelog.mjs`, and two arms pin both cases — importer present, importer absent.
- **`tools/known-footprint.mjs`** — `KIT_OWN_PATHS` gains both new script paths (30 → 32), so a
  HIDDEN deployment does not leave them visible in `git status`.
- Tarball sentinels move with the payload on their documented ladders: kit 250 → 252, memory 59 → 61.

## 8.0.0 — the bundled reader gets the scenario floor, and the refresh lane carries it out (AD-117)

Memory **5.0.0** gives the spec reader one new refusal — a `## Scenarios` section carrying no
scenario line — and this kit ships those bytes. Nothing in the kit's own logic changed shape; what
changed is what the deployed reader says about a document, and therefore what a project's spec gate
says about its store.

> ### ⚠ BREAKING — inherited from memory 5.0.0
>
> `spec-check` (both lanes) and the `spec-check` gate row relay the reader's verdict, so a spec that
> pins NOTHING now turns a deployment red with no edit of its own. The remedy is one line per
> scenario — `- S<N> <name> :: unbound` while no test pins it. The refusal is the migration signal:
> a contract that pins nothing is the one shape the layer exists to refuse.

- **`references/scripts/spec-schema{,.test}.mjs`** — refreshed to the 5.0.0 bodies; the four mirrored
  copies stay byte-identical, `sync-mirrors --check` green.
- **`tools/script-priors.mjs`** — the catalog gains the two OUTGOING `4.7.0..4.7.0` reader rows
  (`spec-schema.mjs`, `spec-schema.test.mjs`), read from the released bytes, so an unmodified 4.7.0
  deployment is recognised as unmodified and converges on the next kit touch instead of stranding as
  `custom`. The independent `FROZEN_PRIORS` pin in the test moves with it — 8 catalog rows, none
  equal to the bundled body.
- **Obligation discharged: KIT-SUBTREE-CHANGED-WITHOUT-A-BUMP.** Slice 4 wrote 19 spec-binding
  markers into this package's subtree and shipped no bump. This release carries them.


## 7.6.0 — `spec-check`: the spec store judged against what the session SAYS it changed (AD-115)

The feature-spec layer gets its structural checker. The change source is EXPLICIT and never git — a
session states what it did, and the checker decides whether the store on disk says the same thing.
"Well-formed" keeps its ONE definition: every per-document verdict is RELAYED from the shipped
reader's 7.5.0 `structure` extraction, and this release adds no second parser.

- **`tools/spec-check-ops.mjs` — one accepted spelling per document.** Four verbs
  (`add` | `modify` | `remove` | `rename=<old>:<new>`) over POSIX repo-relative `.md` paths inside
  `docs/ai/specs/` whose every segment is D-schema shaped. Nothing is normalized away: `./x.md`,
  `a//b.md`, `a/../b.md`, a backslash, a drive letter, an absolute path and a trailing-slash
  directory all REFUSE rather than resolving to some other path — so a dedup can be an equality, and
  because no accepted target can carry a `:` the rename separator is unambiguous. A path holds at
  most ONE role (self-rename, fan-out, fan-in, chain and any two-role clash refuse); the store root
  is never a target; any error empties the whole op list.
- **`tools/spec-check.mjs` — the judge, with no filesystem of its own.** `{read, probe, realpath,
  list}` are injected and an uninjected one throws. The SESSION lane judges the closure of the
  declared ops (targets plus each one's listing parent) on the post-state, the reader verdict, the
  per-kind line cap, the scenario bindings (the marker must occur EXACTLY once in the file the
  scenario names) and the containment of every path each document names. A `## Module` path must
  also EXIST with the kind it declares — a `dir/` root resolving to a directory, a file-list entry
  to a regular file — so a contract can no longer name code that is not there. `--all` judges the
  whole store instead and adds the four questions no document can answer about itself: an unlisted
  child (DISTINCT from an orphan), acyclicity, store-wide slug uniqueness and module overlap.
- **Fail-closed means PROVEN contained.** A path whose realpath does not resolve is never read.
  Containment asks the platform's own path model, because a textual prefix test reads
  `/repo\outside` as a child of `/repo` on POSIX and mis-reads a filesystem root in both directions.
  Every listed edge is resolved, contained and probed before it may enter the reachability graph — a
  phantom target would otherwise be marked reached and launder an orphan into a reached document.
  The census admits a CLOSED set — plain directories and regular `.md` files — and states everything
  else: an unlistable directory, one that resolves outside the root, any non-regular entry, and a
  regular file that is not a spec document. A census that observed nothing keeps its own refusal
  instead of collapsing into a usage error. The leaf read is descriptor-bound and no-follow.
- **`tools/spec-check-cli.mjs` — the IO shell.** `--op` (repeatable) unions `--ops-file`, `--all` is
  exclusive of both, `--root` defaults to the cwd. The register is never defaulted: guessing which
  file states the change set would attest a post-state nobody declared. Blank and `#` lines parse
  away, CRLF reads like LF, and every other line is passed on UNTRIMMED so both op sources accept
  exactly the same strings. Exit 0 clean, 1 findings, 2 usage.
- **`tools/procedures.mjs` — the advisor names it.** A `plan-execution` render now carries the
  populated `spec-check` commands for both lanes and the session register they read, plus the
  additive `specCheck` `--json` key. Advisory, like the finding-scope block beside it: nothing
  records that it ran.
- **Proof.** Every suite authored RED first and observed failing before its module existed; the
  D-scale gate measures the checker's OWN median (573 ms) beside the unchanged hook median against
  the 1500 ms budget, so a slower checker can never pass by sitting inside the hook's headroom.
  Tarball count 247 -> 250.

## 7.5.0 — the reader pair joins the known-prior refresh lane; the mirrored reader gains the structure verdict (AD-114)

Slice 2b's `spec-check` will need a structural read of a spec document, and the reader is the ONE
definition of well-formed — so the memory canon's reader (mirrored here byte-identically) now
returns an additive `structure` field. Because the bundled reader bytes move, the reader pair joins
the AD-113 known-prior refresh lane in the SAME release: without catalog rows for the outgoing
bodies, every 4.6.x deployment would classify `custom` against the new bundle and be stranded out of
the store seed forever.

- **`tools/script-priors.mjs` — the catalog spans both pairs.** `PRIOR_FILES` is now the FULL
  refreshable catalog (checker pair + reader pair); `SCRIPT_PRIORS` appends the outgoing
  4.6.0..4.6.1 reader-pair bodies (rows 4 -> 6, fixtures under `4.6.0/`, the literal frozen prefix
  extended with its first four rows byte-identical). The maintenance rule generalizes with it: a
  release that changes ANY catalog member appends the outgoing body's row.
- **`tools/ensure-specs.mjs` — the pairs decoupled, the reader refreshes.** `CHECKER_PAIR` was an
  alias of `PRIOR_FILES`, so the widened catalog would have double-surveyed the readers: the op now
  owns exported `READER_PAIR`/`CHECKER_PAIR` (the suite pins pairs-union === catalog domain), and
  `decideWrites` admits a reader REFRESH — reader seeds stay unconditional create-only (a custom
  neighbour never blocks them); a refresh runs only inside a pair whose every file is
  current | prior | absent; one custom file withholds its pair's REFRESHES and every downstream
  write, DIRECTIONALLY (a prior reader still refreshes beside a custom checker, which holds back
  only its own pair and the store). Order reader -> checker -> store unchanged; lines and tokens
  unchanged (`refreshed` now covers either pair).
- **Prose caught up at every live surface** — `references/modes/upgrade.md` (the configs summary
  and the spec-layer paragraph), the `ensure-configs` header + help, the vocabulary comment, the
  package-content ledger, and the memory twin's refresh-lane sentence all name BOTH pairs.
- **Proof.** The state table gains the reader-prior rows, the directional-withhold pin and dry-run
  rows; the E2E upgrades a real 4.6.x deployment (reader on the 4.6.0 bodies, checker current)
  whole under the real installed pre-commit hook, and the whole-chain prior+prior cell stays
  table-covered. Red-first throughout — three red-proofs minted on frozen test bytes against the
  pre-fix modules. Tarball count unchanged at 247.

## 7.4.0 — the spec layer reaches existing deployments: known-prior refresh, seed-behind-checker, the `specs` ensure (AD-113)

7.3.0 made a FRESH bootstrap deliver the feature-spec layer whole; this release gives every EXISTING
deployment the same layer on an equal-head upgrade, with no lineage bump. The shaping fact: the kit's
`index` ensure drives the BUNDLED navigator generator, which collapses `docs/ai/specs/` into one
counted row, while the project's pre-commit hook runs ITS deployed `check-docs-size.mjs` — a store
root seeded behind an older checker renders row by row and reds `--check-index` on the next commit.

- **`tools/script-priors.mjs` — a deployed script is refreshed only when its bytes are a body a
  release shipped.** An append-only catalog of sha256 digests of every `check-docs-size.mjs` body
  since memory 4.0.0 (three checker bodies + the one test body); `classifyDeployedScript` answers
  `current` · `prior` · `custom`. The fixtures are the shipped bytes verbatim
  (`test/fixtures/script-priors/<firstShipped>/<file>.txt`, outside the test glob, the source-size
  scope and `files[]`); the test holds catalog and fixtures equal BOTH ways and pins the rows as a
  literal immutable prefix — a count alone would let a row and its fixture be replaced together,
  turning a shipped body into `custom` on every deployment that carries it. No other deployed script
  is refreshed; a custom body is preserved verbatim and said so.
- **`tools/ensure-specs.mjs` — the sixth ensure op, and the store root seeds ONLY behind a checker
  pair proven current after the run.** `specs` sits after `scripts` and before `index`. It surveys
  the four script files and the store root into current | prior | custom | absent | wrong-kind
  BEFORE writing and admits each write through ONE conjunction: an absent reader file seeds
  create-only (nothing depends on it); the checker pair refreshes or seeds only when BOTH reader
  files are current after the seed and NEITHER checker file is custom; the store root only when the
  checker pair is then current. A custom file preserves itself AND withholds every dependent write,
  and the line names the remedy (copy the pair from `references/scripts/` by hand, re-run). A seed
  that loses the create-only race is re-PROVEN — the appeared file must classify `current`, else
  `race-unresolved` and nothing further; every line composes from the FATE of its write (written ·
  stood · would · withheld · stopped); the run token follows one precedence — `seeded` >
  `refreshed` > `customized-preserved` > `already-present`. The no-Node skip stays; there is NO
  legacy-ADR early return (the store has no ADR-layout dependency).
- **Vocabulary + wiring.** The closed vocabulary gains the relayed pair `refreshed` /
  `would-refresh` (doc-parity binds the `upgrade.md` step-3 line, the run-list registry mirrors the
  op, the composed-lines guard witnesses the tokens); `ENSURE_IMPLEMENTATIONS` moved from
  `ensure-ops.mjs` to `ensure-configs.mjs`, its only consumer, keeping the tools graph acyclic;
  `--only specs` works and the CLI counts 6 slots.
- **Proof.** The state table runs as a table-driven test over every cell with injected fs
  (`--dry-run` emits only `would-*`); the integration E2E upgrades a 4.5.4-byte deployment with
  `--reconcile` and the real installed hook — seeded/refreshed on the plain and legacy-ADR layouts,
  and the custom-checker variant keeps the checker verbatim, seeds NO store root and prints
  `customized-preserved`. Package-content pins 245 -> 247.

## 7.3.0 — the spec layer's delivery half: hidden from day one, mirrored, bootstrapped (AD-112)

The memory substrate ships the feature-spec layer in 4.6.0 and the engine its canon in 3.3.0; this
release is what makes a FRESH kit bootstrap deliver it whole and hidden.

- **Hidden mode, no new machinery.** `KIT_OWN_PATHS` gains `/scripts/spec-schema.mjs` and
  `/scripts/spec-schema.test.mjs` (sentinel 28 -> 30) — the reader the navigator checker now imports
  and its deployed test; `/docs/ai/` already subsumes the store. The integration suite proves it on a
  bootstrapped layout with real `git check-ignore` on both files and on `docs/ai/specs/x.md`: nothing
  of the layer is visible to git.
- **Mirrors.** `references/scripts/` carries the reader pair and the GROUPS-collapse checker
  byte-identical to the memory canon; `references/templates/` carries `specs/index.md` and
  `SPEC_TEMPLATE.md` (the mirror manifest names both; template-parity reverse-pins them). The kit's
  own fallback bootstrap excludes `SPEC_TEMPLATE.md` by NAME in its prose AND its E2E copy loop, and
  the finalized navigator carries ONE counted `specs/` row.
- **Templates.** `agent_rules.md` §1.2/§1.3 reworded (governing specs, adoption shim, the revision
  lands with the code) and its lens region re-rendered with the engine's `Spec-first` bullet;
  `AGENTS.md` routes `technical_specification.md` readers to the contracts under `docs/ai/specs/`.
- Package-content pins: 241 -> 245 (the reader pair + the two templates), each by NAME.

## 7.2.0 — `.mcp.json` joins the hidden-mode footprint, and a worktree never owns it (AD-111)

7.0.0 taught the kit to register its MCP server. It did not teach hidden mode about the file that
registration writes — so a hidden deployment that ran `/agent-workflow-kit mcp` left `.mcp.json`
sitting in `git status`, one `git add -A` away from a commit. That is exactly the leak the footprint
registry exists to prevent, and it shipped anyway.

- **`/.mcp.json` is in `KIT_OWN_PATHS`.** It joins `/.claude/settings.json` as a SHARED container the
  kit merges into rather than owns, on the same terms: the tracked→ASK arm means a project that
  already commits its own registration is asked, never silently un-tracked. Candidacy is
  unconditional on purpose — the path is CONSIDERED before a later `mcp --apply` writes the file
  rather than only after it appears, and is then hidden, dropped as already covered by a tracked
  `.gitignore`, or surfaced as an ASK. That is what removes the follow-up reconcile the bug depended
  on. The `contracts.md` prose mirror names it.
- **A worktree is never HANDED one, and never owns one.** The scope is the untracked lanes this tool
  drives — the registry copy set and `--include`; a TRACKED `.mcp.json` still arrives with
  `git worktree add`, because that is the checkout, not a copy. One frozen never-provisioned set,
  read at three sites because each needs it for a different reason: the copy set would hand a
  satellite a launcher pinned to a path consented for the MAIN checkout; the containment sweep would
  STOP the whole provision on an escaping symlink at that path (a device-node mask realpaths inside
  the repo and reaches the later special-file refusal instead); and `registryRoots` decides what
  `cleanup` may REMOVE. That third one was measured — without the guard, `cleanup` on a landed
  worktree carrying a satellite's own `.mcp.json` exited 0 and took the file with it. `--include` is
  refused pre-mutation for the same paths: it is the one door that bypasses the copy set, and it
  would have recorded the launcher as ours.
- **A settings copy this tool wrote never enables a server the worktree cannot declare.** The
  guarantee is exactly that narrow. `.claude/settings.json` rides `REBASE_TARGETS` into a worktree
  carrying `enabledMcpjsonServers`, which `mcp.md` itself calls a client error on every startup. An
  untracked copy still equal to MAIN loses our enable and our two derived allow rules — and ONLY
  those; every foreign server, rule and key survives, as do the file's EOL and the order of its
  non-index keys (`withoutRegistration`, the token-removal complement of `mergeSettings`, not its
  exact inverse — a JSON round-trip still hoists integer-like keys, as JS object semantics require).
  **Where a rewrite would LOSE something the kit does not own, it is refused whole and says why.**
  Three ways, each detected before anything is written: a number past double precision comes back a
  different value; a duplicate key at any depth collapses to the last one (both found on the source
  TEXT, because after the parse the evidence is gone); and invalid UTF-8 would become U+FFFD, so both
  rewriting lanes — the pin rebase and the token strip — decode with FATAL UTF-8 and refuse
  separately. The MAIN-equality proof compares BYTES, with the BOM preserved through the decode, so
  a BOM-carrying original and a BOM-less copy can never pass for the same file.
  The rewrite is admitted by ONE positive conjunction — untracked, bytes still MAIN's or their
  rebased form, and a launcher proven ABSENT — so a tracked or user-modified settings file keeps its
  registration tokens on purpose, as does one standing beside a launcher that is present or that
  cannot be read. Those states get a neutral line naming the tokens; orphanhood is only CLAIMED where
  it is proven.
- **All three output arms name the reconcile.** Applied, masked hand-off, and already-registered —
  the last one matters most, because a deployment registered under 7.0.0 learns about the reconcile
  on its next run. A settings write that fails after the entry landed carries the note too: that
  path never reaches the report, and it strands a standing registration.

**Known residual, stated:** a VISIBLE deployment still commits a launcher carrying an absolute
machine path. The fix is not symmetric with hidden mode — a visible `.mcp.json` may legitimately hold
a team's own committable servers — so it is its own queued decision, not a line in this release.

## 7.1.0 — `fold-scope`: the fold channel gets a checker that refuses a claim whose reference does not resolve (AD-110)

The engine canon now asks every finding to NAME the invariant its fix enforces before the edit
(engine **3.1.0**). Prose cannot check that a named invariant is really where you say it is.

- **`tools/fold-scope.mjs` + `tools/fold-scope-cli.mjs`** — the pure rule and its CLI half:
  `--class in-scope|new-invariant|blocking`, `--claim "<the invariant>"`, `--plan <plan-file>`,
  `--queue <queue-file>`. Exit 0 for every ACCEPT, non-zero for every REFUSE, one case per arm.
- **`--plan` and `--queue` are REQUIRED and never defaulted.** A project can have two registers that
  both look like "the queue" — the planning lifecycle's `docs/plans/queue.md` and a declared
  `flow.debtQueue` — and a checker that guessed would attest a deferral against the wrong file.
- **What it refuses**, each naming the lane out of it: an `in-scope` claim matching no acceptance
  bullet; a `new-invariant` whose invariant IS an acceptance bullet (route to fold); a deferral with
  no queue row, with several, whose row title carries `DONE`/`CLOSED`, that repeats a field label,
  that is missing a field (named, including an `origin` that is not a `file:line`), whose claim is
  not inside the row's `invariant` field, or whose residual exposure is declared live (route to
  blocking) or declares neither.
- **`/agent-workflow-kit procedures plan-execution` renders the populated command** — under solo,
  reviewed AND council, because the rule routes every finding and a Solo project must see the checker
  too — and NAMES which of the two registers it chose for `--queue`. Every PATH operand goes through
  the family's shell quoter (bare when the value is already safe, single-quoted when it is not), and
  the two placeholders are single-quoted literals so their angle brackets and pipe are inert — the
  line is safe to paste as rendered. `--json` gains an additive `foldScope` array.
- **Markdown is read through `references/scripts/markdown-blocks.mjs`**, the block model the
  archivers already use, so fenced examples, heading levels and CRLF are solved once for the family;
  a document that model refuses is a loud refusal, never a silent empty read.

**Advisory by design:** nothing records that the checker ran, so a skipped or late call is
indistinguishable from a pre-edit declaration. A fingerprint-bound receipt a gate reads is the next
slice. Additive throughout — no existing command, flag, output field or exit code changed.

## 7.0.0 — `/agent-workflow-kit mcp` registers the typed channel in ONE project, and `uninstall` stops reporting an interrupted teardown as a success (AD-108)

> ### ⚠ BREAKING — an `uninstall` run that silently passed can now refuse, and its plan reports more
>
> **The outcome flips for the same filesystem situation.** When a mutable surface changed between the
> preflight and its own removal, `executePlan` passed over it and returned `{ applied: true }` — a
> teardown that had not finished, reported as one that had. It now raises a typed `UNINSTALL_STOP`
> whose message opens `the teardown is INCOMPLETE` and lists, separately, what was refused before it
> was touched, what may be **partially** removed, and what had already been applied. There is no
> alias and no deprecation window, which is the same shape this package twice sized as MAJOR —
> **4.0.0** (a receipt that used to be accepted is refused) and **5.0.0** (a `--check` that silently
> passed can now refuse); **5.6.0** stayed MINOR only because it kept the old field as an alias.
>
> **And it is not only the race.** `uninstall` previously read *through* a symlinked `.claude`, a
> symlinked `settings.json` and a non-regular pre-commit hook without saying so; each is now a
> REPORT_ONLY item read no further. For a project in any of those states the printed plan contains
> **more items than before** — and for a CLI the report is public surface.
>
> **Upgrading:** nothing to change in a normal teardown, which is unaffected. `uninstall` is complete
> **only on exit 0** — an `INCOMPLETE` stop is never a finished run. If you script it, branch on the
> two lists rather than on the word, because they call for opposite actions:
>
> - **left untouched** — the removal was refused *before* it touched anything (containment, or a
>   surface that stopped being provably ours). Nothing was changed by it: settle the tree and re-run.
> - **may be partially removed** — a removal failed *part way* and the tool cannot tell what survived.
>   **Do not re-run blind and do not proceed to a next step.** Inspect the named path by hand, decide
>   what is left, and only then continue. The run stops at the first of these on purpose, so anything
>   after it in the plan was not attempted either.

**The server shipped, and no project could see it.** 6.0.0 bundled a stdio MCP server exposing the
two promptless readers — `path_inventory` and `repo_search` — as typed tools whose arguments are
named JSON fields rather than a string handed to a shell. Shipping it did nothing on its own: a
client sees a project-scoped server only once the project **declares** it. This release is that
declaration.

`/agent-workflow-kit mcp` (guarded, preview-first) writes exactly two files: the `agent-workflow`
stdio entry in **`.mcp.json`** at the project root, and `enabledMcpjsonServers` plus the two allow
rules **derived from the server's own name and tool list** in `.claude/settings.json`. The exact
entry is printed **before** consent is asked — registering a server means your client will run that
command — and `--apply` writes `.mcp.json` first, then settings, merge-don't-clobber with each
file's EOL kept. Where an OS sandbox masks `.mcp.json` with a device node it writes nothing and
hands over **the entry alone** to merge, keeping every server it cannot see through the mask; a
whole-file body would have deleted them. An existing `agent-workflow` entry that **structurally
differs** (key order ignored) is refused unwritten.

**A veto check was built, worked, and then subtracted.** `disabledMcpjsonServers` rejects a server
in every mode, so a project can hold the entry, the enable and both rules and still have a dark
channel. Honouring it means reading that key from every settings scope the client merges, and each
scope has its own masked, symlinked, malformed and unreadable states in which a hidden deny still
yields a confident answer — three review rounds each closed one such hole and opened the next. **A
check that is wrong in states it cannot enumerate is worse than a stated limit**, so the limit is
now stated by name in the mode contract and pinned by tests: `registered` means *what this mode
writes is in place*, never *the client will load it*.

**`uninstall` learned that containment is the whole parent chain.** Guarding one named container is
a ladder with no top — close `.claude` and the next symlink moves to `.claude/hooks`, then to
`.git` — so the teardown now walks from the project root before every read and every removal, with
the project as the containment root. A surface that changes after the preflight is no longer passed
over under a successful report: a removal **refused before it touched anything** reads *left
untouched*, one that **failed part way** reads *possibly partially removed* and stops the run, and
both are fields of one typed INCOMPLETE outcome.

Also: the promptless-lane hint the gate hook delivers now names the typed tools before the CLI
lanes; `uninstall` reports both MCP seams as KEEP with the exact edit; and `doc-parity` binds the
registration's five public strings into both the mode contract and the uninstall KEEP list.

Source-size raises accepted for: the typed channel gains its registration half — the guarded mcp
writer, its read-only leaf, and the registry rows every drift guard keeps. The uninstall growth is a
15-round diff council: whole-chain containment, a typed incomplete-teardown lane, per-cause
classification.

## 6.0.0 — `grounding --plan` follows the canon it grounds: the three literal headings (AD-104; ships with engine 3.0.0 / memory 4.5.2)

**The tool required a heading the canon had deleted.** `grounding --plan` demanded `## Approach` —
the unbudgeted free-prose section engine **3.0.0** removes — so a plan written to the new capped
shape STOPped at the grounding step, and the author was pushed back into re-adding the very section
that grew the last plan to 690 lines.

> ### ⚠ BREAKING — an input that exited 0 now exits 1, and two exported symbols changed shape
>
> `grounding --plan <file>` on a plan written to any earlier canon now STOPs with exit 1, naming the
> missing heading. There is no deprecation window and no compatibility shim, which is the same shape
> this package twice sized as MAJOR — **4.0.0** (a receipt that used to be accepted is refused) and
> **5.0.0** (a `--check` that silently passed can now refuse). **5.6.0** is the converse precedent:
> it stayed a MINOR precisely because it kept the old result field as an alias, and nothing here
> keeps an alias. The incompatibility is created by the reader, so the kit takes a MAJOR for it in
> its own right — engine **3.0.0** takes one for the canon it rewrote, and the two are independent.
>
> For a programmatic consumer of `tools/grounding.mjs`: exported `PLAN_SECTIONS` changed from
> `[{ heading, optional }, …]` to a plain `string[]`, and `sliceSection(text, heading, { optional,
> label })` is now `sliceSection(text, heading, { label })`. Read the array as headings, and drop
> the `optional` option — there is no third state left for it to express.

- **`tools/grounding.mjs` — `PLAN_SECTIONS` is now the canon's three literal headings:**
  `## Goal and boundary`, `## Module ledger`, `## Verification`. Each is REQUIRED; a missing or
  duplicate heading is a loud STOP naming the heading it wanted. `## Approach` and
  `## Decisions (locked)` are gone with the canon that named them.
- **`sliceSection`'s `optional` arm went with the optional heading.** A section the canon does not
  name cannot be sliced, and one it does name cannot be absent — so the third state had no caller
  left. `sliceSection(text, heading, { label })` is the whole signature.
- Red-first: `tools/grounding.test.mjs` was rewritten to a canon-shaped fixture and a
  `core-evidence red-proof` was minted on the pre-fix tree for
  `grounding.test.mjs#extracts Goal and boundary` before the fix landed.
- The module header, `--help`, `references/modes/grounding.md` and the README row now say the same
  thing — one contract, four surfaces, each updated with the code.
- **`tools/procedures.mjs`** — the advisor's review-loop header pointed at `planning.md §9`, a
  numbered section that no longer exists; it now names the `procedures.md` *Fold + loop* anchor. The
  plan-time line "every Step that CREATES a file…" is DELETED with the Step vocabulary it belonged
  to — the declared source-size practice still rides every named-activity render, and the plan's
  *Module ledger* is where the layout is now cut to it.
- **`tools/inject-methodology.mjs`** — the outgoing methodology-slot text is appended to
  `KNOWN_PRIOR_METHODOLOGY_SLOT`, so an `AGENTS.md` still carrying the pre-rewrite pointer is
  recognised and refreshed instead of being flagged as a custom edit.
- **`references/templates/agent_rules.md`** — the lens fragment re-rendered through
  `reconcileLensText`: two per-Step clauses became per-row, and the session-continuity sentence went
  with the heuristic the canon dropped.

## 5.11.2 — the record vocabulary becomes a facade over five leaves (AD-103)

**Nothing you import changes, and that is the whole claim — now certified rather than asserted.**
`tools/flow-record.mjs` was the kit's most expensive module to read: 795 lines holding eleven of its
own section seams, from the closed record vocabulary through the field shapes, the identity family,
the two legality walks and the wrapper finding manifest. It keeps its path and its 29 export names,
and becomes a 55-line re-export facade over five single-responsibility leaves.

- **`tools/flow-record.mjs` keeps its path, its 29 export names and every one of its 30 import
  sites.** The internals moved verbatim into `flow-vocabulary.mjs` (96, the lowest leaf: the closed
  kinds, purposes, terminal lanes, seed assignment and transition table) · `flow-record-shape.mjs`
  (283, the closed per-kind field shapes and `validateFlowRecord`) · `flow-record-identity.mjs` (115,
  every answer to "what identifies this record or this set" — keys, tree identity, the canonical
  digest, the owner-scoped projection) · `flow-legality.mjs` (248, the two raw-order legality walks)
  · `flow-finding-manifest.mjs` (70). The edges run one way and the family is acyclic by
  construction; no leaf reaches a write module, so "pure form" is a structural pin instead of a
  header sentence.
- **Conservation is certified by ORDER as well as by count.** A comparator normalises both sides
  identically and then checks, per leaf, that its line SEQUENCE equals the concatenation of its
  declared source ranges in declared order — a reordering is caught, not just a loss — AND that the
  multiset over the union of the five leaves equals the pre-split module. Over the 587 executable
  lines: five sequences EQUAL, the difference EMPTY in both directions. It was then run three more
  times — against the unsplit module alone, with one leaf omitted, and with one line duplicated — so
  a comparator that could not report loss or excess could not pass for one that does.
- **The five shared grammars live in exactly one place.** `HEX64_RE`, `isHex64`, `isPlainObject`,
  `isNonEmptyString` and `refuse` are exported off-surface from the lowest leaf and used by the
  others. They are the record family's named grammars — `isHex64` IS the 64-hex digest grammar every
  consumer takes by reference — and a per-leaf copy is the only way two of them could ever drift.
  The layout suite pins that off-surface set exactly, so it cannot widen unnoticed.
- **The owning suite never moved.** `tools/flow-record.test.mjs` is BYTE-IDENTICAL and reproduces
  58/58; the fifteen named suites show an empty `git diff --name-only` in the worktree and under
  `--cached`. `test/flow-record-layout.test.mjs` (not shipped) pins the frozen 29-name surface, the
  facade-declared owner of every name, that the facade carries no logic at all, the size caps, and
  the one-way edges including their negative direction — four of its five cases were red-proofed on
  the unsplit tree before the split existed.
- **`test/package-content.test.mjs` moves its payload pin from 227 to 232 files** and names the five
  leaves: the facade imports all five, so a leaf falling out of the published tarball would break
  every flow read at load, and the pin is what says so.
- **One consumer line changed in the whole tree, and it is a comment.** `dispatch-record.mjs:15`
  cited the fail-closed rule by line number (`flow-record.mjs:22-24`); the split falsified that
  reference, so it now names the rule semantically.

Recorded size effect, reason `tranche 3: flow-record split`:

```text
  agent-workflow-kit/test/package-content.test.mjs: lines 600 → 617 (raise)
  agent-workflow-kit/tools/flow-record.mjs: lines 795 → none
  agent-workflow-kit: aggregate lines 125315 → 125669 (raise)
```

## 5.11.1 — the flow store becomes a facade over five leaves, and the direct-run guard fix finally reaches you (AD-102)

**A tool invoked through a symlink used to run nothing and exit 0 — and five of the affected tools are
declared gates.** That fix has been sitting in-repo, unpublished, since the delegation series' own
measurement came back FAIL and the fix was correctly held back from a release it should not have
justified (AD-101). It ships here, on a regular train, because it is a bug fix and every deployed
host still carries the broken form. Beside it, the flow store stops being one 827-line module.

- **The direct-run entry guard now decides by REAL PATH, in all 66 frozen sites.** The lexical form
  compared `import.meta.url` against `process.argv[1]`, which is false when the entry point is a
  symlink — so the tool did nothing, said nothing, and exited 0. Every one of the 39 kit tools now
  decides through the shipped `isDirectRun(import.meta.url)` leaf; the seven mirrored
  `references/scripts` and `references/hooks` payload scripts, which cannot reach that leaf from a
  deployed project, inline the same fail-closed realpath guard. `velocity-profile.mjs` also drops a
  vestigial exported predicate with no importer. The new `test/direct-run-sweep.test.mjs` pins list
  completeness, in-process import of all 39, the absence of the lexical byte pattern, and a SPAWNED
  symlink smoke — observed red 3/3 before the fix, so the silent-pass class is proven dead rather than
  believed dead.
- **`tools/flow-store.mjs` keeps its path, its 29 export names and every one of its 22 import sites,
  and becomes a 50-line re-export facade.** The flow-specific write side moved verbatim into five
  leaves with one responsibility each and one-way edges: `flow-chain-state.mjs` (91, the pure chain
  walk and the prior-terminal reference validator) · `flow-subset-budget.mjs` (81, the pure
  Decision-7/8 counting-context budget) · `flow-append.mjs` (309, the store's ONE write door, keeping
  the locked subset-attempt factory beside the lane it is the only sanctioned entrance to) ·
  `flow-adoption-mint.mjs` (70) · `flow-delta-proof.mjs` (307, the bookkeeping-delta custody proof).
  Nothing about the public surface moved, so nothing you import changes.
- **The split is proven, not asserted.** A line-multiset conservation check compares the pre-split
  module against the union of the five leaves and expects exactly two lines to have gained a copy —
  and it was run twice more, against the unsplit module alone and with one leaf omitted, so that a
  comparator which cannot report loss could not pass for one that does. The three owning suites are
  BYTE-IDENTICAL and reproduce 169/169; the ten named suites show an empty `git diff --name-only`.
  `test/flow-store-layout.test.mjs` (not shipped) now pins the frozen surface, that every facade name
  is the SAME binding as its leaf's export, that the facade carries no logic at all, the size caps,
  and the one-way import direction including its negative edges.
- **`test/package-content.test.mjs` moves its payload pin from 222 to 227 files** and names the five
  leaves: the facade imports all five, so a leaf falling out of the published tarball would break
  every append at load, and the pin is what says so.

Recorded size effect, reason `tranche 2: flow-store split`:

```text
  agent-workflow-kit/test/package-content.test.mjs: lines 583 -> 600 (raise)
  agent-workflow-kit/tools/flow-store.mjs: lines 827 -> none
  agent-workflow-kit: aggregate lines 124960 -> 125315 (raise)
```

Recorded size effect of the direct-run sweep that ships with it (reason: a standalone deployed script
cannot reach the kit's shared direct-run leaf, so it inlines the realpath guard — one lexical line
becomes an 11-line fail-closed IIFE; the 39 converted tool guards themselves only shrink):

```text
  agent-workflow-kit/references/hooks/gate-approve.mjs: lines 559 -> 570 (raise)
  agent-workflow-kit/references/hooks/state-block-guard.mjs: lines 425 -> 437 (raise)
  agent-workflow-kit/references/scripts/archive-changelog.mjs: lines 546 -> 557 (raise)
  agent-workflow-kit/references/scripts/archive-decisions.mjs: lines 1199 -> 1210 (raise)
  agent-workflow-kit/references/scripts/archive-issues.mjs: lines 415 -> 426 (raise)
  agent-workflow-kit/references/scripts/check-docs-size.mjs: lines 580 -> 591 (raise)
  agent-workflow-kit/references/scripts/migrate-gates.mjs: lines 722 -> 733 (raise)
  agent-workflow-kit: aggregate lines 124726 -> 124805 -> 124925 -> 124960 (raise)
```

## 5.11.0 — the fan-out half: which vehicle carries a sub-task, what a satellite is told, and what its handoff brings back (AD-100)

**A delegated thread could be recorded end to end, and the two questions around it still had no
mechanism.** The one BEFORE it — which vehicle carries this step class, and is that vehicle even
present on this host — lived in canon prose and in a remembered rule. The one AFTER it, one level up:
`worktrees` provisions a satellite and lands its diff, but nothing composed the prompt that satellite
starts from, and the handoff coming back was a convention with no rung — nothing read it, nothing
delivered what it held, nothing counted what it bought. Three new verbs close both.

- **`dispatch advise --step-class <c>`, and the same block as a footer on a form-VALID `dispatch
  check`.** It answers which vehicle carries the class on THIS host, what the ledger has recorded for
  it, and what the fallback is — and it can never decide. `check`'s exit code and FIRST line are
  byte-identical whether the advised vehicle is present or absent, and a form-INVALID contract still
  prints only its first violated field, so the advice cannot mask a refusal. Where the advice differs
  from `vehicle.selected`, it NOTEs the divergence — never a refusal — and names `vehicle.requested`
  only where that in turn differs from what the contract selected.
- **Capability is read off the filesystem, and each kind of ignorance keeps its own name.** The
  advisor module itself writes nothing and spawns nothing, and the verb's only subprocesses are
  read-only git probes: the repository top-level the cheap vehicles are anchored at, and the
  delegation store path unless `AW_DELEGATION_STORE` names it outright. It never runs a vehicle, a
  subscription CLI, or anything that writes. The execute backend resolves through the existing
  detector, the cheap vehicles through the
  presence of `.claude/agents/<name>.md` anchored at the repository top-level. That lane is
  FOUR-valued — present, missing, `unanchored` (no root resolved, so a file found here proves nothing
  against a nested shadow copy) and `probe-error` — because folding either ignorance into "missing"
  asserts something about a root the module does not know. The bundled vehicles are portable rows;
  `doc-research` renders HOST-LOCAL and names the solo fallback when absent; the harness-subagent lane
  renders ASSUMED/manual with no availability verdict at all.
- **Recorded history arrives through the ledger's existing single door**, over the closed state set
  `folded · failure-terminal · degrade-closed · open` — `open` counted separately, never as a closed
  thread. An absent store prints `history: no recorded history`; an unreadable one answers
  `history: unavailable — ` and then the store's own reason, verbatim. The advice prints either way,
  and the advisor never refuses.
- **`worktrees prompt <slug>` — the satellite's cold-start prompt, read-only — and `provision` now
  ends its report with the same composed text.** It carries the worktree path and branch, the seeded
  plan, where the shared series index lives, that landing runs from MAIN, and that the handoff is the
  one channel back. Every value is derived LIVE: the record freezes MAIN's paths at provision time, so
  a moved MAIN or a hand-edited field would otherwise put a stale RUNNABLE command in front of a
  satellite — a recorded value that no longer matches is NAMED beside the live one, and the install
  posture, which is probed on the satellite itself, carries its own cause rather than borrowing
  "MAIN moved". Commands are attributed: `MAIN $ …` / `HERE $ …`, because the landing command mutates
  MAIN — exactly what the satellite is forbidden to do — while a dependency-bearing checkout's install
  command runs HERE, and an unattributed `$` line read as an instruction to whoever held the prompt.
- **`dispatch handoff-return` — deliver, prove, then count.** It prints every user-owned fragment of
  the handoff — the content before and after `## Provision record` alike — BYTE VERBATIM, each
  fragment's OPENING boundary carrying its byte length so a fragment that itself imitates a boundary
  line cannot hide where it really ends, and names the MAIN-owned destinations that content folds into;
  the fold stays yours and the rung claims nothing about it. Its window is bound by TWO facts, because
  one cannot close it: a clean post-commit index reproduces the committed tree, so `prepared-tree`
  equality survives the commit. `land --prepare` now also records `prepared-head`, and the rung
  requires an UNCHANGED HEAD and a staged write-tree equal to `prepared-tree`, re-attesting both
  immediately before either answer. A record written by an earlier kit, carrying no `prepared-head`,
  refuses by name and names `land --prepare` as the fix. It is MAIN-side by construction and refuses
  from inside a satellite, where the shared common dir would let it measure the wrong tree.
- **One observation, recorded WHOLLY or not at all.** The `worktree-stream` numerator is the ATTESTED
  tree's blob bytes read through a fail-closed `cat-file` — never off disk, so an edit made after the
  prepare cannot move it — with component identity the canonical path. Anything unrepresentable is a
  NAMED `observation: NOT RECORDED` at exit 0: a deletion, a rename's absent old side, a symlink, a
  submodule, a non-UTF-8 path name, and a mode-only change, which has no measurable byte change at all
  and gets its own rule. A regular BINARY file is inside the domain. A number that silently omitted
  the deletions half of a landing would be worse than a named non-record.
- **A fold that lands AFTER the gates leaves those gates stale, and the fix is a printed command.**
  The rung and `references/modes/worktrees.md` carry one order: fold, re-stage, the configured review,
  `run-gates --final` over the current staged tree, `commit-guard --check`, the commit ask. "One
  writer per worktree" is stated at both points of use and in the composed prompt as the BAR it is —
  nothing new refuses a second writer.
- **Content-free evidence stops deciding commits on the two rungs where it had failed closed.**
  A degrade minted on a CLEAN tree binds the empty-payload fingerprint — the one value every clean
  moment of every repository shares — and the `#65` correlation resolved it to 19 distinct bases and
  failed closed forever, blocking every commit through `commit-guard`. The `#65` red rung and the `#64` ordering rung now
  step over content-free records and RECORD the step in the advisory channel, and `commit-guard`
  splits its content-free lanes by the INDEX rather than the payload: a dirty index means staged bytes
  the fingerprint cannot see and REFUSES, naming the `submodule.<name>.ignore` /
  `diff.ignoreSubmodules` configuration as the recovery; a clean one SKIPS the RECEIPT arms and PASSES
  while stating it attests NOTHING. That pass is scoped, never blanket — store HEALTH is deliberately
  not waived, and a flow refusal still refuses there, because neither is a correlation. The tree-bound
  correlations drop out only where the CALLER declares `treeCarriesBytes: false` — a routine
  clean-tree check still wants those rungs.
- **Seven new modules, and every moved name is re-exported.** `dispatch-advisor.mjs`,
  `advisor-matrix.mjs`, `worktrees-record.mjs`, `satellite-locator.mjs`, `worktree-prompt.mjs`,
  `worktree-handoff-return.mjs`, and `observation-builder.mjs` — the last extracted so `observe` and
  the rung build the identical record by ONE path. `worktrees.mjs` re-exports everything the leaves
  took, and the characterization claim held literally: no existing assertion or fixture in the
  worktrees suites changed.

**Known limitation, accepted.** The `observation` record's key set is closed and carries no artifact
digest, so the handoff digest and the two attested OIDs are the rung's PRINTED proof rather than
ledger fields — a later reader of the ledger alone cannot re-derive which handoff bytes and which tree
an observation was taken over. Widening a closed key set is a schema change with its own migration; it
is tracked as a follow-up.

## 5.10.0 — the bundled agy review reads the CLI's own envelope, and refuses a host that cannot honour the dispatch (AD-098; antigravity-cli-bridge 5.3.0, memory 4.5.0)

**A review used to read whatever the CLI happened to print, and recover the conversation id by
scraping the CLI's run log with a pattern its own comment called "agy's own to change".** Antigravity
CLI 1.1.13 answers `--output-format json` with one object whose `response` carries the model's
Markdown verbatim and whose `conversation_id` is a first-class field. The bundled bridge now drives
every dispatch that way and reads named fields instead of guessing — and the guess that used to rot
silently is deleted rather than hardened.

- **The envelope is transport, not contract.** The model is asked for exactly what it was asked for
  before, so the delivery-proof echo, the mandated section shape, the receipt and the "no recognized
  verdict" arm keep their semantics. On a successful run the wrapper still PRINTS the review text,
  never JSON. An unreadable envelope on a zero exit is its own loud failure (exit 5) with no receipt,
  distinct from a verdict-less answer (exit 4); a non-zero CLI exit keeps the CLI's own code and
  message, because the envelope is parsed only on a zero exit.
- **A pre-spend capability door, not a version floor.** Before anything is spent, the wrapper probes
  the DECLARED option tokens of `agy --help` for `--output-format` and `--disable-slash-commands`,
  requires `node >= 22` and the reader module, and refuses with the cost of the capability that is
  actually missing, the installed `agy` version and the recovery command. A `--help` that itself
  fails is never read as "capability present". A guessed version floor would have refused working
  installs — the release that introduced the flag is not measurable from one build.
- **The conversation id comes from the envelope; the log scrape and its `--continue` fallback are
  gone.** The UUID grammar the scrape validated is kept and applied to the field, so a missing,
  wrong-typed or malformed id stops the run before the next turn is spent instead of routing it at
  an arbitrary conversation.
- **`--disable-slash-commands` on every review dispatch.** A change-set line that begins with a slash
  command stays BODY, so the model reviews the delivered bytes rather than an expansion of them.
- **`--json-schema` was measured and REFUSED, and the reference says why.** It is not a constrained
  decode: the model answers in prose and the CLI spends a second turn asking it to restate that
  answer in schema shape. Matched control, same prompt and model, schema off vs on: 16,585 vs 33,446
  total tokens, with the structured reason coming back reworded rather than quoted. `stream-json`
  stays unadopted, with its two real gains named in the reference.
- **Node is now required for the review role** (the JSON parse lives in `bin/agy-envelope.mjs`, not
  in bash); `SKILL.md`, `setup/README.md` and the flags reference carry the requirement and the
  probed capability list in place of the old "1.1.13 or newer" line.
- **The state-block guard ships ONE vocabulary — English — and enumerates no other language.** A
  shipped phrase list is a guess about somebody else's dialogue and it never stops growing; a
  deployment whose dialogue language differs extends its own placed copy, and the mode contract
  states that silence instead of hiding it. The always-loaded rules template gains the closing
  state-block rule in the same shape: the slot LABELS stay English, because they are what lets a
  checker find the block at all, while everything written into a slot is in the project's dialogue
  language.
- **Two smaller consequences of that sweep, both load-bearing.** `lens-region.mjs` now recognizes the
  intermediate communications canon that shipped between the plain-language bullet and the
  state-block rule, so a deployment sitting on it is still detected as canon rather than flagged as a
  custom edit; and the index reader behind the size practice carries each entry's object id, so a
  consumer can read what the index HOLDS instead of what the worktree happens to show.

## 5.9.0 — the always-loaded navigator becomes something every deploy path actually creates (AD-096; memory 4.4.0)

**A fresh deployment's entry point declared `docs/ai/index.md` always-loaded, and no step ever
created it.** The navigator is generated, not templated, and nothing in bootstrap ran the generator:
a Node project was born with a pre-commit hook failing its own index check, and a project without
Node stayed quietly broken, its entry point pointing at a file nothing writes. The kit's own
fallback bootstrap carried the same gap. Every deploy and upgrade path now finishes by materializing
it, and two new guards make "the entry point only points at things a deploy creates" a test rather
than an intention.

- **A fifth project-configuration ensure: `index`.** `ensure-configs --reconcile` now regenerates
  `docs/ai/index.md` whenever it is missing or stale, so an existing deployment gains its navigator
  without a lineage bump — exactly like the config seeds. It never skips a No-Node project: the
  generator runs from the kit's own bundled copy, not the project's `scripts/`.
- **The LATE run is the authoritative one.** The agent-rules lens rewrites `docs/ai` after the
  step-3 reconcile, so `upgrade.md` documents a late `--reconcile --only index` rung at BOTH exits —
  after the lens block on the equal-head exit, at the end of step 7 on the migrated one — and the
  step-4/8 reports relay THAT line.
- **`ensure-configs --only <op>`** runs exactly one ensure. An unknown, missing or repeated
  selector is a usage error with zero writes: a run that cannot be narrowed as asked must not widen.
- **Failure says how far it got.** The index op classifies `generator-unlaunchable` (nothing ran),
  `generator-failed`, `index-probe-failed` and `index-stale-after-write`; every cause that can only
  arise after the generator ran DISCLOSES that a write may already have landed, and a claimed
  regeneration is verified by a re-probe that must ANSWER — an exit code alone would turn a failed
  probe into a false "still stale". Every relayed cause is now doc-parity-bound into `upgrade.md`.
- **The bundled generator's write is contained and atomic** (mirrored from the memory canon): a
  symlink at any level of `docs/ai/index.md` refuses, the body is published through an
  exclusive-create temp + rename, and the containment guard runs BEFORE the freshness read — a
  symlink whose target happens to hold current bytes is refused, not reported "already current".
- **Hidden mode now names the whole payload.** `KIT_OWN_PATHS` listed 14 of the 21 files a deploy
  copies into `scripts/`, so six shipped scripts besides the new one stayed visible in a hidden
  project's `git status`. The registry is complete (20 → 27) and a new test derives the expected set
  from the deploy payload itself. Known residual: a registered path is still hidden by NAME rather
  than proven ownership (`known_issues.md` → Issue-018).

## 5.8.0 — the bundled agy bridge speaks the catalog the installed CLI actually serves, and grounding stops needing a shell (AD-095; antigravity-cli-bridge 5.2.0)

**A model display string is matched exactly, so a stale one is not a documentation bug — it is a
dispatch that refuses before it spends anything.** The bundled agy bridge had been describing a
catalog captured from CLI v1.0.13 while the installed binary moved to 1.1.13. The user-facing
catalog tables and every default pin now carry bytes read off `agy models` on the live binary,
which also turned up a `Gemini 3.6 Flash` family no announcement mentioned and confirmed 3.5 Flash
is still served. (The wrapper's own header comment stays a compact family inventory and now says
so, pointing at `agy models` for the exact strings.)

- **`Gemini 3.7 Flash (High)` is the bridge default for both roles** — `agy-run` and `agy-review` —
  and joins the review wrapper's frontier set, so a council review on it stays advisory-silent.
  Asserting frontier grade for the review lane was the maintainer's explicit call, not a default
  that drifted. Any model remains selectable through `AGY_MODEL`; the sub-frontier advisory is
  still a silenceable warning, never a gate.
- **The catalog lists every served row individually.** The 3.6 and 3.5 families are enumerated
  Low/Medium/High rather than collapsed into `(Low|Medium|High)` — a collapsed row reads as a
  display string that does not exist, and pasting it fails.
- **The reference stops overclaiming the wrapper's output.** v1.1.13 adds `--output-format
  json|stream-json`, `--json-schema`, `--effort` and `--mode`; the wrapper adopts none of them, so
  the docs now say text-DEFAULT and state plainly that raw passthrough JSON rides without
  first-class parsing or validation. The old "no JSON output mode" sentence had become false.
- **`grounding.mjs --extra <text|@file>` merges orchestrator facts inside the tool.** Appending
  live judgment facts to an emitted facts payload had been a shell append four times running; it is
  now an input. Extras append byte-verbatim after the mechanical sections. The read surface is
  deliberately narrow and fail-closed: an `@file` must sit inside a PROVEN work tree (a successful
  `git rev-parse --show-toplevel` — never a `cwd` fallback) or the system temp surface, never
  inside the git dir including a linked worktree's `.git` gitfile, and it is read through the kit's
  no-follow descriptor door so a FIFO cannot block the assembler and a symlink leaf cannot
  substitute its target. Git location variables (`GIT_DIR`, `GIT_WORK_TREE`, …) are scrubbed before
  every git query, so an ambient override cannot prove a foreign tree.

## 5.7.0 — what the kit emits is now something a test executed, a claim it proved, or a command you can run (AD-092 + AD-093 + AD-094; memory 4.3.0)

**Three feedback plans, one release.** A live upgrade session on a deployed TypeScript project
returned seven items of feedback, and every one reduced to the same shape: the kit EMITTED something
— a shell command, a status line, a prescribed operation, an optimality verdict — and nothing
downstream ever ran it, proved it, or performed it. This release closes that shape end to end, and
closes it for future releases too: a kit candidate can no longer be dispatched without proving
itself against the kit that is ALREADY published.

- **The canonical coverage destination refuses by name instead of writing to the filesystem root.**
  The emitted cmd becomes `"${AW_GIT_DIR:?exported by run-gates}/agent-workflow-lcov.info"`:
  byte-identical where the runner injects the variable, a loud named bash refusal where a human
  pastes it into a bare shell — the old form expanded an unset variable to empty and sent the lcov
  to `/`. Recognition is APPEND-ONLY (`KNOWN_COVERAGE_FLAG_SETS`): every form the kit ever emitted
  stays recognized, so a deployed old-form gate is never reclassified as customized. `run-gates` now
  reads `${VAR:?…}` as a producer reference and refuses before spawning uninjected. And the emitted
  command is a FIXTURE the tests execute — asserted by running it, unset and injected both, never by
  admiring the string.
- **"Flow optimal" became a claim the advisor must be able to SEE.** Binary probes gained NAMED third
  outcomes: `gates-inert.producer-unrecognized` (a checker no recognized producer feeds, where the
  tracked-tree census says most of the tree is outside the assessable domain — the remedy is "mark
  the real producer or drop the checker", never a `node --test` prescription) and
  `gates-inert.coverage-domain-narrow` (a live producer certifying an assessable minority — converges
  only on an explicit acknowledgment, fingerprint-bound to the fact, re-firing when the fact
  changes). The census is the predicate: no census, no optimality claim. A vendored copy of a core
  check no longer stops the upgrade: the migration preserves it as a zero-diff `keep` row plus
  separately rendered verify metadata, and only a genuine id collision on the canonical checker id
  still hard-stops. On the advisor side, the source-size tool copy gets its own named outcomes —
  `source-size.adopted-elsewhere` (optional) and `source-size.id-squatter` (attention, a
  hand-apply recovery, never a stop).
- **The `lcovProducer` marker lets a declaration CLAIM a producer the closed cmd-world cannot name**
  — vitest, pnpm wrappers, any runner that writes the lcov. Only the literal `true` counts, the
  marker never widens what a RUN may certify (a marker-claimed producer that writes no lcov still
  ends `skipped-no-lcov`), and it is FORWARD-ONLY by decision: the published 5.6.0 kit rejects a
  marker-carrying `gates.json` loudly at validation (exit 5, naming the key) rather than degrading
  to a false advisory — upgrade the kit before marking (Issue-016 states all three cross-version
  axes).
- **Upgrade step 3 is a registry-owned run-list, not prose.** `tools/upgrade-runlist.mjs` owns the
  seven operation identities (`pointers` · `footprint` · `configs` · `gates-migration` · `bridges` ·
  `lens` · `bridge-settings`); the checklist in `references/modes/upgrade.md` renders from it, a
  structure test holds doc ↔ registry in both directions, and the four config "ensures" the doc used
  to prescribe as prose are ONE runnable command — `ensure-configs.mjs --reconcile [--dry-run]`,
  fixed order, create-only seeds, a CLOSED outcome vocabulary that throws on any token outside it.
  A library module invoked as a command now says so and exits 2 instead of silently exiting 0.
- **A tool-composed line a user reads is user-grade language.** Machine tokens and tool self-labels
  ride machine lines (`[tool] key=value`); alarm words render only under a DETECTED abnormal
  condition; the read-only refresh degrade states only what its re-scan PROVED (one scanner, closed
  `clean-parity`/`drifted`/`unverifiable` vocabulary) instead of an unconditional "may be PARTIALLY
  updated". The guard test enumerates every composer's outcome variants from its own closed branch
  set — a new line joins the contract by construction. The hide-footprint report now states its
  delta against the current managed block: `+N added` / `−N removed`, sets listed, `+0/−0` explicit.
- **A kit-carrying dispatch needs TWO receipts, dry-run included.** Beside the candidate smoke
  (packs the candidate, installs it into a foreign fixture, asserts the advisor sees what this
  repo's own suite cannot show), `scripts/release/cross-version-gate.mjs` installs the PUBLISHED kit
  `@latest` and asserts the three Issue-016 axes BY NAME — `schema-accept` / `execution` /
  `producer-recognition` — with the conditional arms decided by comparing the probed published
  version against `MARKER_AWARE_SINCE` (fixed forever at `5.7.0`, the first marker-aware kit), never
  inferred from the accept itself. Both receipts are HEAD-bound and field-validated; an unreachable
  registry refuses loudly with no receipt.

## 5.6.0 — a source-size practice your project declares, and a record that is debt rather than permission (AD-091; engine 2.1.0)

**A big module is expensive long before anyone calls it a problem, and nothing in the kit ever said
so.** This release adds the practice: a checker your project declares scope for, a cap it can hold
you to, and — for the files already over it — a recorded baseline that can shrink for free but can
only GROW with a written reason. Nothing is guessed and nothing is imposed: a project that declares
no scope gets no cap, and the one manual step is a scope file you author from the starter template
the tool prints — its `roots`/`extensions` placeholders are deliberately invalid until you replace
them. With a valid, judgeable scope, `--adopt` then mints the record and attempts the gate
declaration, reporting the second half if it is refused.

- **`source-size-check.mjs` — the checker, and it refuses rather than assumes.** In scope is what
  you DECLARE: git-tracked files under your roots, with your extensions, minus your excluded
  prefixes. New files stay within 400 lines and 1000 bytes per line by default (both overridable,
  because a multi-KB single line hides from a line count). There is no default root list, no default
  file-type list and no content sniffing — a fixed extension list would silently exempt every
  language it forgot. Run it with no config and it tells you the exact path to author and prints a
  template; the placeholders in that template are rejected by the validator until you replace them,
  so the printed starting point can never be pasted into a scope that matches nothing and passes.
- **A recorded size is DEBT, not permission — that is the whole design.** Adopting on a real
  codebase records today's oversized files instead of demanding a refactor you did not plan. From
  then on the record only moves under rules: it may not grow without `--reason "<text>"` (recorded
  verbatim in the entry it raised), it may not sit ABOVE what the tree now measures (a stale record
  is headroom nobody earned, so the checker tells you to tighten it), and it disappears when the
  file finally comes back under the cap. A record whose file is GONE is an error — that is what
  makes a split or a rename visible rather than silent. **Every declared root also carries a line
  budget**, so splitting 3000 lines across six modules buys exactly zero headroom.
- **Every ratchet refusal hands you the next step, and it is a step this build can actually
  perform.** Shrunk below the record? You get the regenerator command exactly as it should be pasted
  — no reason asked, because shrinking is progress. Growing something? You get the same command as a
  template with the reason placeholder and a plain statement that it is required: the tool cannot
  invent your reason. On a project path that would not survive quoting, no command is printed at all
  — the file, the actual, the allowed and the manual lane are stated instead, because a rendered
  command that runs somewhere else is worse than none. Inputs the checker cannot judge at all — a
  malformed config, a failed git enumeration — exit 2 stating what is unusable, with no rendered
  recovery command.
- **Adoption is one consented line.** `--adopt --reason "<text>"` records the baseline AND declares
  the gate in `docs/ai/gates.json` in a single step, is idempotent once adopted, and reports exactly
  what it did and did not do if the declaration is refused. Existing projects meet it as a
  Recommendations item at upgrade; new ones meet it the same way on their first upgrade.
- **The practice arrives BEFORE the code, not after it.** The plan-authoring and plan-execution
  procedure renders now print your declared caps, your recorded debt and the reason the caps exist;
  the checker's green line says the same thing on the enforced path. The point is a layout decided
  while a plan is written, with the gate as the backstop rather than the teacher. A project that
  declares no practice gets no invented
  limits: the render's declared-practice block stays empty and `gates-init` offers no candidate. The
  canon rule it does still print is conditional by construction — no declared cap, no limit to
  state.
- **The gate declaration learned where to place things.** A consented non-checker gate is now written
  BEFORE a trailing canonical coverage checker instead of after it, which is what makes adopting on a
  final-capable declaration work at all; entries you already declared are never reordered. The fill's
  result gained `placed`, and `appended` remains as an alias of it.
- **Scope, counting and the gate matcher live in a pure read core**, so the surfaces that ask about
  the practice — the advisor, the gate declaration, the procedure render — never reach the writer.
  Counting is pinned by literal fixtures: LF, and the CR of a CRLF, never count; a file with no final
  newline still counts its last line.
- **Housekeeping shipped alongside:** `flow-check.mjs` became a 254-line facade over two pure
  decision modules and a git-I/O leaf (the decision cores and the evidence rungs, plus the git lane
  that is deliberately the only one allowed to spawn `git`) with its behaviour and its declared gate
  command unchanged — the first tranche of the cleanup this practice arms, and the first proof that
  the record notices a split.

## 5.5.0 — delegating a sub-task stops being a feeling and becomes a record (AD-090; codex-cli-bridge 3.5.0, antigravity-cli-bridge 5.1.1)

**"How much does handing this off actually buy?" had no answer, because nothing wrote the answer
down.** A delegated task went out, something came back, and whether it was worth it was a judgement
made from memory. This release gives the handoff an **identity**, a **deadline** and an
**accounting**: the wrapper that runs your delegate now claims its work before spending anything and
publishes what it can prove afterwards, and the kit absorbs that into an append-only ledger you can
read back per task class. The number it reports is deliberately hard to fake and easy to refuse — it
would rather print INELIGIBLE with a reason than a figure it cannot stand behind.

- **A delegated run now has a name, and the name is claimed BEFORE the money is spent.** Pass
  `codex-exec --nonce <n>` and the wrapper writes a reservation beside the ledger, atomically and
  no-clobber, before it starts the CLI: a second dispatch on the same nonce refuses while it is still
  free to refuse. At exit the reservation is replaced by the finished receipt, and the ORDER is the
  guarantee — the delegate's report is published first, the receipt last, so anything that has
  arrived always has a complete report behind it. A run that cannot publish exits nonzero and says
  the tree is dirtied rather than leaving you to discover it. **An invocation without `--nonce` is
  byte-for-byte what it always was**: no reservation, no receipt, no extra process.
- **Five new verbs on `dispatch` — four that write, one that only reads — and none of them invents a
  second rulebook.** `open` puts a thread on the record with every mint-time field copied from the
  task brief's own header; `await` waits for that one dispatch to answer; `return` absorbs the
  wrapper's receipt; `fold` is the acceptance; and `degrade` closes a thread that never earned one,
  on the record and with its reason. The ledger's
  existing preflight stays the single legality door — the four WRITER verbs assemble a record and
  pass its refusals through in the store's own words, while `await` writes nothing at all.
- **`dispatch await` waits, and a wait that ends without an answer authorizes nothing.** Only the
  FINISHED receipt satisfies it; a run still holding the nonce means keep waiting; an artifact from a
  neighbouring feature refuses rather than pretending to answer. The bound is the deadline recorded
  when the thread opened — not a fresh timer per wait — so `--timeout` defaults to the time actually
  remaining, and while nothing has arrived and the dispatch is still inside its deadline one reaching
  past it is refused rather than quietly shortened; a terminal receipt already on disk returns
  ARRIVED before any bound is evaluated at all. An unanswered wait exits **3**, names which bound
  ended it, and says in as many words that **no writer slot was released**: it is a question for you,
  never permission to dispatch again.
- **The metric refuses rather than flatters.** The two sides of the ratio are different byte
  quantities — the numerator sums the image bytes of the returned objects, the denominator is the
  framed bundle of the change set's payload and its report — bound not by one buffer but by one
  observable change set: the same HEAD→index→worktree domain, bracketed by digests that refuse when
  drift is detectable. Start from a dirty tree and the result is recorded INELIGIBLE by name instead
  of counted. A change set carrying a binary, a non-regular path or a submodule is REFUSED outright
  at `return` and `fold`, because the shared fingerprint carries no content for those and their bytes
  could move underneath it. Gate output is not accounted at all in this version, and the mode doc
  says so rather than leaving you to find out.
- **`aggregate` reports one wave and refuses to guess.** Per task class it prints the finished
  threads with the inclusion rules applied — a folded success with provable bytes contributes its
  ratio, one whose bytes are unprovable is excluded from the mean but still counts in the first-pass
  rate, and a failed, degrade-closed or acceptance-failed thread counts as a real zero. Below the
  registered minimum nothing is computed. No pre-registration, an unfinished thread, a recorded
  refusal-to-delegate, an ambiguous wave: each stops the computation by name.
- **Both bridges ship a version anchor.** Every wrapper that stamps a version into a receipt now
  declares it on one marked line — at least one per bridge, at most one per file, and the requirement
  follows the USE, so a wrapper that never reads the constant is not asked for one. Every scalar
  `AW_BRIDGE_VERSION=` or `+=` occurrence anywhere else in a shipped `bin/*.sh` — comment, help text
  and heredoc body included — is refused by the version-sync verifier. What it deliberately does not
  model is stated rather than implied: a form that sets the name WITHOUT that literal token — an
  array-element setter, a spaced arithmetic assignment, an eval-constructed name — is out of scope,
  so this closes the drift that actually happened, not every drift imaginable.
  agy's wrapper gains only that marker — hence the PATCH — while codex's gains the whole dispatch
  identity.

Honest limits, all stated where they bite rather than in a footnote: a receipt is forgeable exactly
like every record in this family, so what these doors defend against is a buggy or interrupted
producer and never a hostile one; the fold's binding is equality of the visible canonical payload,
which is a change detector rather than a cryptographic identity of the tree; and at most ONE in-tree
delegated dispatch at a time remains a bar you keep, not a mechanism that enforces it.



## 5.4.0 — a check that certifies nothing now says so, and the render stops promising what no host guarantees (AD-089)

**A gate matrix could run green while verifying nothing, and the kit itself was what built it.**
Neither declaration path ever wired a coverage PRODUCER, yet both declared the coverage CHECKER — so
a fresh `gates-init` and an `upgrade` alike could leave a project whose `--final` prints three
`PASS`, `lcov-sha256=none`, `attested=yes`, `status=ok`, exit 0. The honest signal existed on one
inner line; it reached no surface a consumer keys on. This release stops both kit-owned declaration
paths from ever ADDING a producer-less pair, makes the withheld verdict travel, and stops the
autonomy render asserting host behaviour it cannot know. A pair you declare by hand stays yours —
the kit reports it, it never removes it. Nothing gains a state: exit codes, the `status=` token, the
receipt `status` enum, `--final` acceptance and the commit-guard disposition are untouched, so an
optional-coverage project never goes red by surprise.

- **Neither kit-owned declaration path ADDS a coverage checker without a producer.**
  `gates-init` wires the lcov reporter flags onto the one allowlist suite body that produces
  coverage unaided, WITHHOLDS the `coverage-check` candidate with a named reason when neither the
  offer nor the existing declaration carries a producer, and — this path alone — refuses at WRITE
  time on the MERGED declaration, because an offer-level check alone still let
  `--apply --only coverage-check` write a dead checker, still let a producer land after an
  already-last checker, and still let a SECOND canonical checker in under a different id. The
  ordering refusal names ORDERING as the cause, so you reorder instead of concluding the producer
  was rejected. `migrate-gates` stops adding the checker into a producer-less declaration and
  reports an already-declared one as INERT — loudly, with the remedy, and removing nothing for you.
- **One CLOSED producer predicate, shared by every consumer.** Recognition covers the full command
  forms the kit itself emits — never a substring probe, because `echo "$AW_GIT_DIR/…"` or a
  half-written flag set would pass one. The tail rule is a POSITIVE path-shaped grammar rather than
  an operator blocklist: `node --test <flags> && rm -f <lcov>` runs the suite and then deletes the
  file, so an open-ended tail would certify a producer that leaves nothing behind. The residual is
  stated in the source rather than papered over — the screen judges source bytes, so the claim is
  "configured with the reporters", never "the lcov survives the command"; a run that produces none
  is still caught honestly at runtime as `skipped-no-lcov`.
- **`attested=` binds to the BYTES consumed.** A run that read nothing never certifies, however
  valid its final-run context. The field states that a VERDICT WAS ISSUED — not that coverage
  passed — so a valid handshake over uncovered lines still reads `yes` and still exits 1.
- **The summary line carries a new closed `coverage=` field** — `certified` / `not-run` / `none` /
  `unknown`, one value defined for every run outcome — and the checker's table row names a withheld
  verdict. Both are derived by CROSS-READING the checker's two anchored machine lines, so a missing,
  duplicated or self-contradicting pair reads `unknown` rather than `certified`. The `--final`
  receipt records the run's own token, and `core-evidence summary` renders the qualifier FROM it
  instead of guessing from `lcovSha256` — which says what a receipt binds and never whether a
  verdict was issued. A pre-5.4.0 receipt is named as a legacy receipt, never as a claim about what
  it read.
- **New advisor item `gates-inert`** for a declaration that runs green and certifies nothing, with
  two causes and two remedies. Cause A — a canonical `coverage-check` with no producer BEFORE it
  (none at all, or one declared after it, which writes the lcov too late and lets stale bytes be
  certified) — is HAND-APPLY, since the append-only fill cannot reorder an existing declaration.
  Cause B — every declared gate is one of the kit's own checkers — renders the consent-gated
  `gates-init` preview. Cause A decides through the SAME producer-before-checker rule the
  `gates-init` fill enforces at write time — one home, so the advisor and the fill cannot drift
  apart; cause B is advisor-only, with no refusal path behind it. `--final` acceptance is unchanged
  either way: it still requires exactly one canonical coverage checker declared last, and it never
  inspects producers. `flow optimal` no longer renders over either cause.
- **Every settings-derived RUNTIME claim in the autonomy render is now host-conditional.** Not only
  the bridge-tier exclusion: `allowWrite`, `allowedDomains` and foreign exclusions are all claims
  about what a HOST does with a settings key, and whether a host honours them is runtime-unknowable
  — this kit's own mode doc has recorded that from live observation, while three separate surfaces
  went on promising the bridge wrappers run outside the sandbox. The classification is unchanged;
  only the unconditional promise is gone. The `allowWrite` degrade now resolves its entries
  (tilde, relative and absolute, on segment-aware containment shared with the advisor's
  worktrees-dir lane) and NAMES the external survivors instead of counting them — an entry pointing
  inside the repo or `$TMPDIR` is no longer reported as an `fs_outside_repo` weakening at all, and
  an unreadable value is its own UNVERIFIABLE class that asserts no weakening.
- **The bundled `codex-cli-bridge` mirror moves to 3.4.1** — a bundled-only bump carrying the fixed
  `codex exec resume` argv, so the changed bridge bytes in this tarball ship under a version of
  their own instead of silently.

Upgrading an existing project: nothing breaks and nothing is rewritten for you. If your declaration
already carries the dead pair, `status`/`upgrade` now say so and hand you the remedy — the reorder
case deliberately stays hand-applied.

## 5.3.0 — a check must speak where it is built to speak: the two silent checks (AD-088)

**Two shipped checks stayed SILENT at exactly the point they exist to speak** — one a GATE that
refuses and blocks a commit, one a WARNING that never changes an exit status. They were one
sentence, so they ship together.

- **The `flow-check` gate stops demanding the receipt its own run will write.** A red final on the
  current base was cleared only through a LATER completed retry — but `run-gates` appends the final
  receipt only after every gate has run, so the in-matrix `flow-check` could never see the receipt
  its OWN run would write, and each `--final` on an unchanged base minted red N+1. No number of
  rerun-causes converged; the only exit was a hook bypass. The `#65` rung is now consumer-aware, the
  same lane split the flow→final comparison already applies one arm away: on the `gate` lane a
  current-base red is
  ALSO answered by a provable IN-PROGRESS retry — an authoritative `rerun-cause` naming its attempt
  and binding the current fingerprint, a `final-start` at that fingerprint ordered strictly after
  that red whose attempt has no completed final, and that fingerprint correlating to exactly ONE
  base, the current one. Inside a real final run the conjunction holds by construction; a standalone
  check on a quiet tree still refuses.
- **`commit-guard` is unchanged.** It keeps the strict completed-retry demand, so the commit
  boundary still sees a real receipt. The rung's own `consumer` defaults to that strict lane, the
  relaxation is opt-in by EXACT match, and a tree whose fingerprint is unresolvable or ambiguously
  base-correlated never relaxes.
- **The interrupted-run residual is stated, not papered over.** An interrupted final run leaves the
  same record shape with no live run behind it, so a standalone `flow-check --check` reads PASS in
  that window. It authorizes nothing — the commit boundary refuses that shape twice over,
  independently — and the rung's own source comment says so, pinned end to end by a fixture that
  asserts all three conditions at once.
- **The bundled `codex-cli-bridge` mirror moves to 3.4.0** — its nested-sandbox scan now runs on
  every completed run, not only a failed one, so a delegated run that SURVIVES the failure and exits
  0 is no longer silent about it. On a successful run, it warns when the scanner recognises the
  expected per-item shape — both tokens inside the `aggregated_output` of ONE `command_execution`
  with a proven failure. The heuristic is biased toward under-firing on ambiguous or schema-drifted
  input: field reordering stops the match, and only the first matching item on a line is judged.
  The exit status deliberately stays 0 there — a heuristic scan must never gain the power to refuse
  real work.

Evidence: an end-to-end fixture drives a caused red at the current tree through ONE real
`run-gates --final` to a GREEN newest-authoritative final and a passing `commit-guard --check` — the
test that would have failed before this release.

## 5.2.0 — the flow machinery ships whole: recorded subset budgets, flow-bound finals, the round arms, and the dogfooded pipeline (AD-086)

**Everything the flow series built after 5.1.0 lands as one wave** — the record vocabulary, the
fd-custody store, chain identity + `flow-check`, `set-flow` arming, receipt-deadline + wrapper
finding manifests, and this release's closing moves. The flow schema (`schema: 1`) was never
published before, so every schema move below arrives migration-free.

- **`--pre-review` under an ARMED flow is a RECORDED lane** (new store-global `subset-attempt`
  kind): every subset run appends an attempt with a lock-computed monotone `attemptIndex`; the
  run producing the SECOND red at a key completes, records it, and exits red printing the
  diagnosis rule; a third attempt requires a recorded, byte-distinct `--diagnosis`; past three
  reds only a recorded fresh-eyes consult verdict reopens the key — every stop is self-servable,
  never a wait-for-a-human. A `pregateExclude` change opens a fresh counting context (new
  `subsetDigest`), and an unarmed repo's `--pre-review` stays byte-unchanged (characterized).
- **`run-gates --final` binds the flow store to the release decision** (AD-086 D10): the minted
  `final` carries an OPTIONAL `evidenceHashes.flow` — the owner-scoped projection hash — with
  projection movement under the run an `integrityFailure`; `commit-guard` (its lane only, after
  the dead-green selection) refuses a green final whose hash no longer matches the live
  projection, and an armed flow whose latest green final LACKS the field fails closed naming the
  fresh-`--final` remedy. No `EVIDENCE_SCHEMA_VERSION` bump: the released 5.1.0 validator accepts
  a flow-bearing final (proven against `4b08ace` worktree bytes, both directions).
- **The writer's round machinery arms**: `round-open` (pre-dispatch mint — per-backend watermark
  + fresh nonce, printed as pasteable dispatch + `receipt-deadline` lines) · `round-land`
  (arrival binding — receipt/manifest digests computed FROM the files; foreign, ambiguous,
  malformed, or symlinked artifacts refuse) · per-finding `--dispose` ledgers ·
  `freeze`/`unfreeze`/`converged` with completeness walks · `internal-attestation` refusing
  while any in-flight plan lacks an adopted chain. Caps (3 rounds/cycle, 1 unfreeze, the
  redesign valve) enforce at the arms; an over-cap mint requires a recorded `--justification`.
- **SECURITY — the receipts reader no longer follows symlinks** (new `fs-read-nofollow` leaf): a
  symlinked/FIFO/directory receipts path surfaces as a loud `readError` through all four
  consumers (review-state, core-evidence summary, flow-writer, flow-check) instead of content or
  an empty success. The 5.1.0 reader read straight through a symlink — this is the one behavior
  change for a conceivable existing loop, and the documented `AW_REVIEW_RECEIPTS` regular-file
  lane is untouched (suite-pinned), so it ships as a flagged security fix, not an API break.
- **Bridge bumps ride the kit mirrors** — codex-cli-bridge **3.3.0** · antigravity-cli-bridge
  **5.1.0**: an additive `--nonce <n>` flag on both review wrappers — the plain-argument lane
  onto the existing `AW_REVIEW_NONCE` seam for hosts whose dispatch policy has no env-prefix
  form (one seam: a disagreeing flag+env pair refuses pre-spend; a nonce-less run adds no nonce
  field and mints no manifest — the `wrapperVersion` field every receipt carries moves with the
  release), and a manifest-mint FAILURE whose temp unlink also fails now names the orphan
  path on both failure codes.
- **Smaller rungs**: the veteran-store arming fence (#65 — arming a store with pre-flow red
  finals no longer bricks `flow-check`; reds scope to the earliest own adoption instant,
  fail-closed edges) · mixed declared+undeclared delta forks name the undeclared path ·
  `velocity-profile` credential floor null-guards an unparseable harness version (was
  fail-open via `null >= 0`) · `set-flow` empty/duplicate `pregateExclude` ids exit 2 at parse
  time · the read surface's import closure is pinned write-free and acyclic
  (`read-graph-purity.test.mjs`) · suite file-URL paths go through `fileURLToPath` (Windows).
- **The pipeline is dogfooded end to end**: a permanent hermetic tracked-`docs/ai` fixture
  (`flow-dogfood.integration.test.mjs`) drives the REAL CLIs through set-flow → adoption →
  subset-attempts → two nonce-bound rounds → bookkeeping-delta + refresh → freeze/converged →
  `--final` with `evidenceHashes.flow` → guard PASS → the D10 post-final-append refusal — plus
  the second-red stop pair and the foreign-worktree advisory lane. This repo itself released
  under the armed flow.

## 5.1.0 — the config learns to carry the flow block before anything writes one (AD-085)

**Upgrade this before any flow feature arrives — that ordering IS the release.** The shared
`docs/ai/orchestration.json` is read by every collaborator's kit, and the validator is strict on
purpose: an unknown top-level key fails the config load loudly (exit 1) and reddens that machine's
entire gate matrix. The upcoming orchestration-flow feature adds a `flow` block to that file, so
the fleet has to tolerate the block before the first writer exists. This release is that
tolerance, shipped first and alone.

- `orchestration.json` may now carry a reserved `"flow"` object whose `"schema"` is the NUMBER
  `1` (`FLOW_SCHEMA_VERSION`, exported — flow-aware releases import it, never re-type it). Every
  other byte of the block is deliberately uninterpreted: nothing in 5.1.0 reads it, nothing
  writes it (the writer arrives with the flow store).
- Refusals stay loud and name the contract: a non-object `flow`, an absent or unknown `schema`,
  and the STRING form `"1"` each fail with the accepted version in the message; every other
  unknown top-level key refuses exactly as before.
- No writer change was needed for safety: `set-recipe` runs and the `_README` refresh carry a
  present `flow` block through JSON-value-equal (content-preserving, not byte-preserving —
  characterized for no-op runs, real slot changes, and all three README-refresh arms).
- Honest limit, doc-parity-bound so it cannot be reworded away: this release enforces NO version
  floor against a pre-flow reader — a kit older than 5.1.0 that meets a `flow` block still fails
  loudly. Tolerate-first ordering is the only mitigation until enforcement arms on the
  `set-flow` path (a later release); `references/modes/procedures.md` states both halves on its
  contract lines, pinned by two new doc-parity bindings.
- Groundwork pinned green: the floor mechanics the rollout leans on (the four `semver-lite`
  consumers, including the installer's never-downgrade lane) are characterized, and the
  characterization surfaced a `null >= 0` coercion trap the future arming floor must guard —
  queued, with the guarded comparison shape documented in the test.

## 5.0.0 — the deployed rotation gates fail closed (memory 4.0.0 mirrored; AD-084)

> ### ⚠ BREAKING — inherited from memory 4.0.0
>
> The kit deploys the archive scripts into every consumer's `scripts/`, so it inherits the memory
> 4.0.0 findings-contract change: a rotation `--check` that silently passed over unparseable
> content now refuses with `file:line` and a remedy. The refusal IS the fix arriving — see the
> memory 4.0.0 changelog for the full contract and the upgrade notes.

- `references/scripts/` mirrors memory 4.0.0 byte-for-byte: the shared `markdown-blocks.mjs`
  tokenizer (NEW file — deployed alongside the archivers on every path, including
  `migrate-adr-store --apply` companion-seeding), the fail-closed `archive-changelog.mjs` /
  `archive-issues.mjs` / `archive-decisions.mjs`, and their suites.
- `references/templates/changelog.md` + `known_issues.md` fallback copies follow: ISO taught on
  both `{{DATE}}` consumers; the known-issues resolved shape taught inside a fenced sample in the
  file preamble with a line-leading ISO-dated `**Resolved:**` field; the exact pre-5.0.0 template
  example section is recognised as an inert blank, so a legacy deployment's gate stays green.
- The deployed pre-commit template-seed test gains a stated deployed-context skip (a consumer has
  no `../templates`), so a consumer's pre-commit can never ENOENT on it.

## 4.5.0 — the ADR-store migration finds the projects that could never hear about it (AD-083)

**If your project still keeps its decisions in one big archive file, this release is the first thing
that will actually tell you so.** A whole class of projects was invisible to the old check.

**1. The old-layout check stopped guessing from filenames.** It used to say «old layout» only when a
retired `docs/ai/history/decisions-archive*.md` file was on disk. A project that never grew one — its
decisions file simply never got big enough to roll over — was reported as having nothing to do, and
running the migration greeted it with *«a fresh new-scheme tree»*. Its own pre-commit check agreed:
«OK — every tier is within its cap», forever, describing a retired layout as healthy. The check now
looks at the rotation script your project actually deploys: the current one knows about the
`docs/ai/adr/` store, the older one does not. That is a fact about your tree, not a guess — and it
deliberately stays quiet for two groups it would otherwise nag wrongly: projects whose current script
already reds their own commit with an actionable fix, and projects that run no Node at all.

**2. `migrate-adr-store` now finishes the job on those projects, and can always be re-run.** On a
project with no archive file to explode it snapshots, refreshes the enforcement scripts and seeds the
store (`docs/ai/adr/` + the navigator + a regenerated `docs/ai/index.md`). Interrupt it anywhere and
run it again: a store folder alone is no longer mistaken for «done» (the navigator has to exist and
your own check has to pass), the script refresh is re-planned in full each run, and a failed index
regeneration now fails the run loudly instead of reporting success. The preview earns its go-ahead —
it runs exactly the checks the real run does and writes nothing.

**3. `upgrade` stops saying «flow optimal» to a project on the old layout.** The recommendations
section now carries the migration as an item. It is HAND-APPLY on purpose: the one-liner it hands you
is the **preview**, and the real migration is a separate step you run only after seeing the plan and
agreeing to it — this crossing overwrites and deletes files your project already has, which is why it
never rides the ordinary one-click apply. Honest limit, stated plainly:
this reaches you through `status` and `upgrade`, so it mechanizes a door you already walk through — it
is not a new one for someone who never runs either.

## 4.4.0 — the guard stops charging you for answers it already has (AD-082)

**If the agent keeps asking you to approve its own `2>/dev/null`, this release is for you.** Two
things changed, and neither refuses anything.

**1. The ask now talks to the AGENT, not only to you.** Until now, when the guard stopped a read
carrying a redirection it explained itself in *your* approval dialog — and the agent that composed the
command never heard a word of it. So it composed the same shape again next turn. The ask now also
carries a hint back to the agent naming the lane that would not have prompted at all. Nothing is
blocked and nothing new is approved; if your host does not support the field, you get exactly the old
behaviour. **You should see the same class of prompt arrive less often, not disappear** — the honest
claim, stated because the alternative would be a promise this cannot keep.

**2. Two lanes so several small questions stop becoming one composed shell.**

- **`path-inventory.mjs` (new).** Does this exist, what type is it, how big, how many lines, what is
  in that directory, what does this small config say — for any number of paths, in ONE call, promptless.
  **A path that does not exist is a normal answer** (`exists:false`, exit 0), because "does either of
  these exist" is a question whose interesting answer is "no", and a tool that errors on it sends you
  straight back to writing a shell one-liner. Symlinks are reported by type and never followed;
  binaries are reported, never decoded; every bound names itself instead of truncating quietly.
- **`repo-search.mjs --paths-file`.** The pattern half already had a file lane; the TARGET half did
  not, so a path containing a backtick or `$(` could not be searched without a prompt however you
  quoted it. Now it can. Both lane files are excluded from the search itself, by real path.

**What you must do to get the promptless part.** Re-run `/agent-workflow-kit velocity --kit-tools`.
Allow rules are values already written into your `.claude/settings.json`; upgrading the kit does not
edit them. The advisor now tells you exactly which entries you are missing.

**Also in this release:** the guard's recovery advice is derived per tool (it used to name
`--pattern-file` for every scanned tool, which was wrong the moment a second one existed), and it now
names both possibilities — pass the byte out of band if it is part of an argument, drop it if it is a
real redirect — because the guard cannot tell those apart and should not pretend to.

**One consequence worth knowing about.** The guard's scanned-tool match is a substring match across
the command, deliberately inclusive because an over-match only ever costs an extra prompt. Adding a
second tool extends that: a command that merely MENTIONS `path-inventory.mjs` and carries a
redirection now asks where it previously got no decision at all. That is the same licensed over-ask
`repo-search.mjs` has had since 4.2.0, on one more path string.

**One rule worth knowing if you pass paths to either tool.** A target must name exactly one thing:
no empty value, no NUL byte, no `..` component (Node collapses it before the filesystem is consulted,
so the tool could answer about a different file than you named). A trailing `/` or `/.` is not
rejected — it asserts "this is a directory", exactly as it does to your shell. Awkward names are
still supported: spaces at the edges, backticks, control bytes — that is what the file lanes carry.

**Not in this release, deliberately.** A guard rung that DENIES instead of asking was designed,
reviewed and withdrawn — for the second time — because a command substitution can embed an arbitrary
command with no separator byte, so any such refusal can destroy work it never meant to touch. The
guard still never denies.

## 4.3.0 — the coverage gate no longer certifies evidence it cannot bind to your tree (AD-081)

**Read this if you have ever run `coverage-check --check` on its own.** Until now it read whatever
LCOV happened to be at the fixed path and gave you a verdict. Nothing tied that file to the tree it
was judging.

**The harmless direction is the one you may have already seen:** you add tests, re-run the checker
alone, and it prints the *identical* failure list and the *identical* `lcov-sha256` — because the
LCOV is produced by your `unit-tests` gate and nothing regenerated it.

**The same mechanism prints a false PASS,** and that is why this shipped as a fix. Run the suite,
append one executable line, re-run the checker alone: it certifies "every changed Node line is
covered". LCOV carries no executability signal, so a line that did not exist when the suite ran has
no `DA` entry and reads as *non-executable* — nothing to cover.

**What changed: a coverage VERDICT is now an outcome of `run-gates --final`, and nowhere else.**
That run already owns the artifact end to end — it deletes the LCOV before any gate starts, so inside
it "this came from this tree" is a fact rather than a hope. It now mints a random nonce and records
the attempt as a one-way commitment over `{nonce, tree fingerprint, base}`; the checker recomputes
that commitment and refuses to certify without it. The commitment is also the only place your base
commit is bound, so an identical dirty diff at a moved `HEAD` no longer looks like the same tree.

**Three outcomes replace two — and your findings are unchanged:**

- inside `--final` → the verdict, exactly as before;
- anywhere else → `attested=no` and `NO VERDICT`, exit 0, with **every finding still printed**;
- a context describing a different tree, or matching no recorded attempt → `REFUSED`, exit 1.

**Uncovered lines still exit 1 and are still listed `file:line`.** Nothing that was red turns green;
only the *attestation* is withdrawn where it was never earned — which is why this is a minor release.

**Two more places where a green could hide:** the runner no longer trusts the checker's exit status,
because that code is 0 both when it certifies and when it withholds — it now reads one anchored
`attested=` line, so **a run that actually consumed an LCOV can no longer mint a green receipt
without certifying it**. (A run that produced no LCOV at all still records a green receipt with
`lcovSha256: null` and a loud `skipped-no-lcov`, exactly as before — that path is unchanged.) And a
plain run prints the withheld verdict aloud instead of leaving a PASS row standing over a claim
nobody made.

**Stated honestly — what this does NOT cover.** Whoever runs both processes can still forge the store
or the code; that is the same self-discipline posture the receipts have always had. And "the run owns
the artifact" is exclusive **by convention over a fixed path, not enforced**: if something else writes
that path while a `--final` is in flight — a second `run-gates`, a hand-run `--only unit-tests`, an
orphaned test process — the verdict can still land on evidence this run did not produce. Closing that
needs a per-attempt artifact path, which means your `unit-tests` gate cmd would have to reference
`$AW_LCOV_FILE`; it is queued rather than rushed into a release, because this is verification code.
Your base commit is likewise persisted nowhere, so `commit-guard` stays fingerprint-only.

**What IS gone** is the false green that needs no second process and nobody trying: evidence that
simply predates your edit. That is the case that was observed live, and it is pinned by a regression
test that was watched failing before the fix existed.


## 4.2.0 — a search whose pattern contains `>` no longer has to ask (AD-080)

**Read this if you have ever approved `grep -rn "=>" src`.** 4.1.0 explained why that prompt cannot
be fixed inside the guard. This release stops routing the search through the guard at all.

**New: `tools/repo-search.mjs`** — a literal, read-only repository search with two lanes.

- `--pattern <literal>` for an ordinary pattern.
- `--pattern-file <path>` for a pattern containing `>`, `` ` `` or `$(` — the bytes the residual scan
  actually matches (`|` and `&&` do not trip it; they only take a command off the compound
  read-lane). Write the pattern with your host's file-write tool and pass the plain path. **The
  pattern's bytes never enter the command string**, so the scan has nothing to scan. That is not
  obfuscation — encoding would disguise bytes that stay on the shell surface where bash can still run
  them; these leave the surface entirely.

**You do not have to remember which lane to use.** The tool's invocation prefix is now in the hook's
scanned list, so picking the inline lane for a byte-carrying pattern earns a refusal that NAMES
`--pattern-file`. A wrong choice costs one guiding prompt; it never costs silence.

**The hook gained coverage, it did not lose any.** A non-core command never reached the residual scan
before — that is exactly why a plain kit tool is promptless — so a real redirection or command
substitution on this tool's own invocation would have gone unexamined. It is examined now, by the
UNCHANGED scan run over one more prefix. Nothing about what a byte MEANS was re-litigated; AD-079
stands, and the tool is deliberately NOT in the seeded read-only core, so it inherits no compound
read-lane allow.

**Scope, kept narrow on purpose.** Literal search only — an arbitrary synchronous regex cannot be
bounded by checks between work units, so the class is removed rather than mitigated. Only regular
files are read (a FIFO or device read hangs and defeats every bound). Any bound that fires returns a
structured `incomplete` naming which bound it was — never a silent empty result. The pattern file is
excluded from its own search by resolved path, so an exotic query cannot match itself.

**Honest residual.** Nothing forces the lane on a caller who ignores it: a bare `grep` prompts exactly
as before. A literal inline `$(` is still indistinguishable from an active one, permanently — use the
file lane. Bytes in search PATHS still over-ask. And promptlessness rests on your settings honouring
the tool's allow rule: the hook returning "no decision" is not the same as an allow.

## 4.1.0 — why the gate hook still over-asks, established rather than assumed (AD-079)

**Read this if the hook has ever made you approve `grep -rn "=>" src` or a plain read wearing
`2>/dev/null`.** Those prompts are still here. What changed is that the reason is now measured,
written down at the point of use, and pinned by tests — and one whole fix direction is formally
retired instead of being re-proposed every few releases.

**One behaviour change.** `>(…)` is now named in the command-substitution class, where it belongs: it
RUNS a nested command, and it must not depend on the redirection scan to catch it as a side effect.

**Three mechanisms were BUILT to stop the over-asking, and all three were REMOVED in review** — each
on a stop rule declared before the round that met it, each counterexample verified live and kept as
a regression test. (A fourth direction, deleting the redirection class outright, was rejected at plan
review before it was built: the velocity profile seeds the allowlist that turns a redirect on `cat`
into a silent write, so the guard closes a hole the kit itself opens.)

1. **A quote/escape-aware reading of the command**, so a `>` inside a search pattern would stop being
   read as an operator. Defeated by HEREDOCS: their bodies are not shell code, and one quote in each
   of two bodies opens a spurious quoted span and later closes it, with a genuinely active `$(…)`
   sitting between them — the walker ends balanced, so it never falls back, and the guard goes
   silent on a nested command.
2. **An fd-duplication exemption** (`2>&1` creates no file). Defeated by a missing token boundary:
   `>&word` duplicates only when the word is a bare number, and `grep x f >&12file` writes the FILE
   `12file`.
3. **A null-device exemption** (a redirect into `/dev/null` writes nothing), boundary included.
   Defeated by U+00A0: JavaScript's `\s` counts a no-break space as a word boundary and bash does
   not, so `grep x f >/dev/null` + U+00A0 + `sink` names a FILE and the span was deleted anyway.

**The transferable finding, now in the hook's own header:** this hook cannot decide what a
redirection byte MEANS — not by parsing it, and not even by deleting it — because JavaScript's notion
of a token boundary and bash's do not agree. On an ASK rung that costs an extra prompt, which is
safe. It is also exactly why the same scan must never become a DENY.

**What did NOT change.** Every guard the hook had, it still has, and every pre-existing acceptance
test is green and unmodified.

**The deny direction is retired.** A rung that REFUSED decorated reads was built for 4.0.0 and
withdrawn after five shell constructs defeated its proof. It is not coming back.

**Re-place the hook to get this.** The placed copy at `.claude/hooks/agent-workflow-gates.mjs` is a
self-contained snapshot: `rm .claude/hooks/agent-workflow-gates.mjs` then re-run
`/agent-workflow-kit hook --apply`.

## 4.0.0 — a review receipt must say HOW the code reached the reviewer (AD-078)

**BREAKING, and it costs money to ignore — read the two callouts before upgrading.**

An `agy` code review that never received your change set can no longer produce a receipt that says it
did — and a read-only review finally has a subagent vehicle that cannot flood you with approval
prompts.

> **Breaking 1 — existing `agy` code receipts stop attesting.** The reader now requires an `agy` code
> receipt to declare `delivery`. Receipts written by any earlier bridge lack the field, so
> `review-state --check` turns RED on a tree that was green before. **Migration: re-run the review.**
> Nothing else is needed and no receipt file needs editing. This is the same call as kit 2.0.0
> ([[AD-057]]): the incompatibility is created by the kit READER, so the kit takes the MAJOR alone.
>
> **Breaking 2 — the bundled `agy` bridge (5.0.0) changes what an OVERSIZED review COSTS.** It used
> to refuse and spend nothing. It now DELIVERS the change set as a chunked feed, which spends **N+1
> subscription turns** instead of one. The count is printed on stderr before the first turn is spent,
> and `AGY_REVIEW_MAX_TOTAL_BYTES` (default **240000**) refuses the whole run pre-spend when the feed
> would be larger than that. `AGY_REVIEW_ALLOW_ADDDIR` is retired: still recognized so an existing
> settings line never warns as unknown, but it arms nothing — clear it with
> `bridge-settings --unset AGY_REVIEW_ALLOW_ADDDIR --apply`. The bundled `codex` bridge (3.2.0) bounds
> its repo file map by the same shared helper; for any realistic repo its assembled payload is
> byte-unchanged.

**Why this exists.** An oversized `agy` review once returned two BLOCKING findings citing lines
612–767 of a 322-line file, naming functions that exist nowhere in the repository. It arrived as an
ordinary REWORK verdict with `file:line` citations — indistinguishable, to anyone trusting the
receipt, from a real review. The old escape hatch pointed the model at a staging file and asked it to
read it; on a headless host that read is auto-denied, so the lane could return a confident invention
or an empty SHIP with **no way to tell which**.

**What changed here.** The receipt classifier now requires an `agy` code receipt to declare `delivery`
— how the change set reached the model. Any well-formed declaration is accepted (the bridge emits
`inline` or `fed`); what is rejected is SILENCE, because the classifier deliberately ignores wrapper
versions and an old receipt at an unchanged fingerprint would otherwise still read as attesting.
**Existing agy code receipts therefore stop satisfying the review-state gate — re-run the review.**
The check runs after the verdict and grounding arms, so every other receipt class is byte-identical
to before and a later bad receipt still vetoes an earlier ship.

**The settings surface learned the word "retired".** A manifest entry can now carry a stated
`retired` reason (validated, never a bare flag): the key stays RECOGNIZED so an existing line never
starts warning as unknown, the writer refuses a new `--set` while `--unset` still clears it, and the
reader, `--json`, the status line and the init/upgrade survival check all render it as retired rather
than current. `AGY_REVIEW_ALLOW_ADDDIR` is the first such key, and the advisor item that used to
recommend arming it is gone — a checked deletion, since the coverage guard fails on an offer no
capability claims.

**A read-only review now has a vehicle.** `/agent-workflow-kit agents` places a fourth subagent,
`review-lens`: an additional independent read-only opinion on code your configured backends have
already seen. The point is what it lacks — **no `Bash`**. A read-only fan-out sent to a full-tool
subagent shells out for facts it could have read, and every shelled command is an approval prompt you
never needed to see; three such floods in one session traced to this one gap, because the cheap
vehicles are scoped away from review and a review-capable vehicle was always full-tool. The no-shell
property is now asserted across the whole vehicle set, not one member. The lens never replaces your
configured review recipe, and it is advisory like every review.

**A deny rung was built for the gate hook, and REMOVED before release — the removal is the result.**
The hook has exactly one answer for an agent's reflexive `grep … 2>/dev/null`: ask you. So the
maintainer pays an approval for a decoration, over and over. The fix looked clean: refuse only a
seeded read-only command that PROVABLY discards its output, since the shell was throwing that output
away and a refusal could not destroy anything anyone wanted. The argument holds. The byte-level PROOF
of "this command discards" does not, and could not be made to here — three council rounds produced
five shell constructs that defeated it: `1<&2` (an fd dup routes stdout back out of `/dev/null` AFTER
the approved `>`), a quoted literal `>/dev/null ` sitting in an argument, leading-token-only segment
matching (`… && npm test` runs real work behind the read), a bare `&` (backgrounds the read, runs the
rest), and a `#` comment (bash never executes that redirect at all). Every one was a FALSE REFUSAL.

The general lesson, now written into the hook's own header: **on an ASK rung an incomplete scan merely
over-asks, which is safe; on a DENY rung the identical incompleteness refuses real work.** Deciding
whether a `>` is an operator or text requires lexing the shell, which this dependency-free hook
deliberately does not do — so a deny rung needs a justification that does not rest on parsing command
bytes. The hook therefore still **never emits `deny`**, the five counterexamples are kept as the
specification for whoever attempts it next, and the prompt this was meant to remove is still there.
Stating that plainly is worth more than shipping a refusal we cannot certify.

## 3.15.0 — a shipped opt-in now advertises itself (AD-076)

`/agent-workflow-kit upgrade` and `recommendations` now offer the closing-block detector that 3.14.0
shipped.

They did not, and that was the whole defect. 3.14.0 added the detector with a mode doc, a catalog row
and a README row — every surface an agent reads — and no advisor entry. So a user who installed the
update and ran `upgrade` was told «nothing is broken — process is optimal» while the capability from
that very version sat unwired and unmentioned. Both statements were true alone. Together they meant
the only route to the new feature was reading this file or interrogating the agent.

The new item fires when no `Stop` hook runs the detector's runtime, states what goes unseen without
it, and carries a hand-apply pointer to the mode doc — there is still no writer for this hook, and the
doc carries the exact block plus the three merge cases. It matches on the runtime FILE NAME rather
than an exact command, because with no writer every user pastes their own path: an exact comparison
would keep nagging someone who already wired it.

The reason this was structurally invited is worth stating. Every other surface of a new mode is
drift-guarded — omit the `SKILL.md` row, the catalog entry or the mode doc and a test fails. The
advisor is the one surface with no such guard, and the only one a user receives without asking. The
guard that would close it needs an accurate claim for all 28 modes, so it is the next slice's first
item rather than a hurried addition here.

## 3.14.0 — the closing state block gets a checker (AD-075)

A new opt-in `Stop` hook reads the turn's final assistant message and warns when the closing state
block lies about the turn that just ended.

Every mechanised bar in this kit gates FILES — the gate declaration, release-scan, doc-parity, the
commit guard. The closing state block is CHAT output, which no file gate can see, and that is the
structural reason one contract recurred five times across three sessions while every file-level bar
held. This closes the gap for two shapes that a `Stop` hook can judge as FALSE rather than merely
discouraged, because at Stop time the turn is over: a «what I need from you» slot answering
*nothing* — a turn that has ended always needs a resume, so the slot must name it — and a
first-person promise of imminent work in a turn that produced none.

**It is detection, never prevention, and that is stated on every surface.** A `Stop` hook cannot
un-send the message it judges. What it buys is that a recurrence stops being silent.

The warning rides `systemMessage` on stdout at exit 0, because a `Stop` hook's stderr at exit 0
reaches the debug log and nobody else — a guard warning down an invisible channel would be
decoration. It never emits a blocking field and exits 0 on every path, including every failure path;
a guard that cannot see the turn says so rather than going quiet.

The absent-block report is opt-in behind `--require-block`: this kit does not mandate the
three-part block, and a hook that runs on every turn must not warn on every turn. An unrecognised
argument refuses the turn loudly instead of silently running weaker than you asked for.

`references/modes/state-block-guard.md` carries the check-first wiring block, the trust posture, and
every residual by name — including the ones that are limits of a lexical layer rather than bugs
awaiting a fix. There is **no writer** yet, and the reason is in that doc rather than hidden: a
second placement path through the gate-approval writer would grow a flag matrix on the component
that wires command auto-approval, and a dedicated second writer would duplicate hardened placement
logic.

## 3.13.0 — the commit guard proves the INDEX carries the verified tree (AD-074)

`commit-guard --check` now refuses an index that lags the working tree, so «verified» and «about to
be committed» are the same bytes by construction instead of by operator discipline.

The gap it closes was real and it fired: the gates and the tree fingerprint both describe the
WORKING tree, while `git commit` builds the commit from the INDEX alone — and against an
otherwise-empty index the fingerprint is byte-identical whether a hunk sits staged or unstaged. A
lagging index therefore passed every gate and every guard arm, and the commit shipped a strict
subset of what was verified. The kit's own 3.12.0 release commit did exactly that: a fix landed
without its regression arm, and only the publish dispatcher's dirty-tree refusal caught it, one step
later.

- **A new FIRST refusal, ahead of the fingerprint.** It fires on tracked paths differing
  index↔worktree or on reviewable untracked-not-ignored paths — the same never-committable stat
  filter the fingerprint applies, so ignored paths and device/FIFO/socket nodes never refuse. It runs
  first because its recovery re-stages the tree and re-mints the receipt, which re-decides every arm
  below it.
- **The message is actionable and bounded.** Offending paths are named up to a cap with the
  remainder stated, each rendered through the same escaper the review-state report uses, so no
  filename can break or forge an output line. The recovery is the complete whole-tree sequence
  (`git add -A`, re-run `--final`, commit the whole tree) — a truncated list cannot serve as a
  complete `git add -- <paths>` argument.
- **The probes cannot be blinded by configuration or index bits.** Entries carrying `skip-worktree`
  or `assume-unchanged` are invisible to `git diff`, so they are compared directly against the
  worktree — type, symlink target, executable bit where `core.fileMode` applies, and the blob oid
  through git's own clean filters. A de-materialised skip-worktree path is an ordinary sparse
  checkout and never refuses; a missing assume-unchanged path does. The submodule probe forces
  `--ignore-submodules=none`, so `diff.ignoreSubmodules` cannot erase a dirty submodule either.
- **A tracked submodule the index cannot prove current is named separately, with its own recovery**
  (commit or clean inside the submodule, then stage the gitlink): a root-level `git add -A` cannot
  reach a submodule's own worktree, so offering it there would be a recovery known in advance to
  fail. A submodule whose gitlink itself carries one of those index bits is not probed at all — it
  lags by construction. That reduction is deliberate: three review rounds each found a new way for a
  nested probe to answer "clean" wrongly, so the guard stops asking rather than accumulate patches.
  It stays a converging refusal (clear the bit and the guard falls silent), and an unflagged
  submodule is judged exactly as before.
- **Fail-closed.** An undecidable git probe refuses with its own named cause. The guard's claim is
  that the committed bytes ARE the verified bytes; it cannot make that claim about a tree it failed
  to read.

**Behaviour change worth knowing:** a deliberate partial commit is now blocked. `git commit --only
<path>` hands the hook a temporary index carrying less than the verified tree — precisely the
blindness this closes — so it refuses. No opt-out flag exists, deliberately: a flag that suspends
the arm would suspend the guard's whole claim. `git commit --no-verify` remains the stated residual.
`git commit -a` is unaffected when it captures the whole verified tree, and refuses when a reviewable
untracked path would be left behind.

The tree fingerprint itself is unchanged — making it stage-sensitive would have closed the same gap
at the cost of the lockstep with the wrappers' bash twin. Internally, one new computation of the
index↔worktree split now serves both this arm and `isTreeClean`, so the two can never disagree.

**Stated residual.** This makes the COMMIT capture the whole current working tree; it does not make
the RECEIPT unforgeable. The fingerprint payload still runs its diffs without
`--ignore-submodules=none`, so under `diff.ignoreSubmodules=all` a submodule can be changed and its
gitlink staged after a green final run while the fingerprint stays put, and the stale receipt is
reused. That is a receipt collision, not an under-capture — no commit ships less than the working
tree because of it — and closing it means moving the node payload and both bash twins in one
release. Tracked as its own class, deliberately not folded here.

## 3.12.0 — `--resume` tolerates the session's work: the verify proves per placed path (AD-073)

`provision --resume` no longer refuses a satellite you have worked in. The closing slice of the
resume-verify design: the post-provision verify stopped asking "is the whole tree clean?" and now
asks "is every path THIS run placed or kept in a git lane provision can prove?" — so your
uncommitted edits, untracked scratch and hook-created files are out of scope BY CONSTRUCTION.

- **What `--resume` now tolerates:** uncommitted tracked edits (including a dirty `package.json`,
  whose live state steers the refreshed install posture in both directions), untracked scratch at
  any depth, renamed tracked files, hook-created content, and every ignored file. A worktree wedged
  by a `post-checkout` hook that dirtied its fresh checkout now completes on `--resume` — previously
  both lanes refused at the same verify and the only way out was deleting the hook's files by hand.
- **What still refuses, fail-closed:** a path provision itself placed or kept whose lane is
  UNTRACKED, and any lane probe that errors. The STOP names the exact leaf — never a directory, never
  a session path — and carries the convergent fix first: restore the ignore rule (the only
  convergent fix for copy-set leaves, the `node_modules` link and `.vscode/settings.json`, since the
  next resume simply re-places a removed node). A droppable `--include` gets ONE instruction for its
  whole destination ROOT — dropping the flag orphans every copy under it — namely: move the root OUT
  of the worktree and drop `--include <root>` together. No removal command is ever derived (the tool
  cannot see what else you put inside that directory), and leaving it in place is not offered either
  (an orphan is what blocks landing). A node this attempt did not create is never advised away, and
  an unprovable probe carries no recovery command at all.
- **The proof set is a closed, frozen registry** (`PLACEMENT_REGISTRY`): the handoff stub, the seeded
  plan, copy-set leaves, include leaves, the `node_modules` link, `.vscode/settings.json`,
  pin-rebase targets, and the record refresh. It is leaf-only and kind-gated — a real `node_modules`
  directory where the tool places a symlink is YOUR content, never probed and never touched — and it
  freezes at the verify, so the record refresh can only write at the path the stub already
  journaled. The kind gate applies to KEPT nodes only: anything the run itself created stays proven.
- **The lane probes are literal.** Live-probed on git 2.43: `check-ignore` refuses pathspec magic and
  otherwise answers for a name that glob-matches a TRACKED sibling, so a file literally named
  `feature-[a].md` would read "not ignored" merely because `feature-a.md` is tracked. Tracked-ness is
  decided first by an explicit literal pathspec whose output must match the path BYTE-EXACTLY (a
  pathspec naming a directory lists its descendants, which prove nothing about the path itself), and
  the ignore probe runs `--no-index`. A `.vscode/settings.json` an earlier run placed is proven even
  when the current run's door skips writing it, so a lost ignore rule can no longer hide behind a
  successful resume.
- **The FIRST provision stays deliberately strict** — any dirt still refuses — and its untracked
  visibility is now explicit, so a repo's `status.showUntrackedFiles=no` can no longer turn that
  check into a silent no-op. Default behavior is unchanged.
- **The contract ships as a live constant** (`RESUME_VERIFY_RULE`) printed on every resume-verify
  STOP and pinned into the mode doc by the doc-parity gate. The record's fields are documented for
  what they are: `slug`, `branch` and the seeded plan name authorize a resume; `include`,
  `node_modules` and `vscode-settings` are recorded facts that never do.

## 3.11.0 — the record attests only a verified provision; tracked plans-chain paths refuse (AD-072)

Two provision honesty fixes from the converged resume-verify design (its slice R1; the
tolerance flip is the next slice):

- **The provision record is refreshed LAST — after the in-flight-plan check and the
  post-provision verify, in BOTH lanes.** The record now attests only a VERIFIED provision: a
  first provision that fails after the stub write leaves the STUB (identity still binds from
  it — `--resume` completes as before; a pre-write refusal leaves no handoff at all), and a
  failed resume leaves the PRIOR record bytes byte-exact. Previously a failed
  run left a freshly refreshed record (the 3.6.0 stated residual — now eliminated). A refresh
  failure after a clean verify keeps the worktree and names the exact re-run command in both
  lanes, preserving the original failure cause.
- **A TRACKED plans-chain path — the handoff or the seeded plan — refuses fail-closed in both
  lanes.** Its drift is undeliverable (`land` categorically refuses docs/plans, and satellite
  commits are not a lane), so tolerating it would strand the worktree. A fresh provision proves
  both paths untracked at ONE captured commit — the same OID the branch is cut from — and
  re-probes the new worktree's index and branch HEAD before the first write, catching
  post-checkout hooks that force-add or even COMMIT them (a committed add leaves the tree
  clean, invisible to the clean-tree verify). `--resume` probes the branch HEAD tree
  unconditionally plus the live index before reading the handoff identity.
- **Recovery is surgical and honest.** An index-only entry names a pathspec-LITERAL
  `git rm --cached` (a magic-shaped seed name never touches a pattern-matching neighbor) plus
  the ignore-rule restoration when the worktree's live rules lost it; a branch-HEAD-tracked
  file names salvage FIRST, then consented `cleanup --abandon` ONLY where the record identity
  binds (a malformed record routes to the shipped record recovery — abandon is never promised);
  the post-add hook STOP names inspect/salvage, then plain-git removal (never `cleanup
  --abandon` — no handoff identity exists yet); an irregular entry (tree / symlink / gitlink)
  fails closed with NO recovery command. Recovery never routes through the landing lane.
- **The mode doc states both contracts verbatim** (the corrected verify-then-refresh order and
  the plans-chain refusal), each pinned by a named test. Resume tolerance is UNCHANGED this
  release: the blanket clean-tree verify stays, pinned byte-exact.

## 3.10.0 — install advice reads the worktree checkout (AD-071)

The install advice picked its package manager from MAIN's `package.json` and lockfiles while the
printed command targets the SATELLITE (`cd <worktree> && <manager> install`) — so a dirty MAIN
lockfile could push the advice into ambiguity or the wrong manager, and a MAIN advanced past the
satellite's base steered a satellite it no longer describes. The evidence and the target are now
the SAME tree:

- **Manager evidence reads the worktree's own live checkout** — the `packageManager` field and
  the lockfile scan, the same live lane the dependency-free proof (3.6.0) and the node_modules
  symlink probe already use. MAIN's mutable working tree never steers manager selection; MAIN
  state legitimately steers only the symlink lane (the shared-cache link and its unlink-first
  posture), unchanged.
- **The contract ships in the worktrees mode doc as one pinned sentence** (a named test locks
  the exact wording): all manifest/lockfile install evidence is read from the worktree's own
  live files at the moment the posture is resolved — deliberately NOT «exactly HEAD», because a
  `post-checkout` hook can shape the checkout before the posture is resolved.
- **No resume-tolerance smuggling.** The shipped clean-tree `--resume` STOP is pinned byte-exact
  by a real-git test; dirty-resume semantics remain a queued redesign. A satellite behind an
  advanced MAIN now gets advice about ITSELF — pinned by a real-git test where MAIN's lockfile
  advance does not re-steer the satellite's refreshed record.
- **Honest residual:** a manager signal living only in MAIN's working tree (e.g. an ignored,
  uncommitted lockfile) no longer steers the advice — the checkout is the truth even when that
  yields the npm default.

## 3.9.0 — the --include copy door proves what it copies (AD-070)

Nothing bound the node the provision copy walk read to the node `--include` preflight approved:
a source swapped after preflight was copied as approved, and the path-based queue guard could not
see a node that had become the shared series index by identity (a hardlink, a swap) rather than
by path. The gap is closed at the existing descriptor door:

- **Preflight identity, captured before any git mutation.** The `--include` loop records each
  root's {device, inode, kind} from the canonical node. A root that is neither a regular file
  nor a directory — or an erroring identity probe — refuses BEFORE `git worktree add`: no
  worktree is left behind.
- **Three tiers at the walk.** A file root is verified at the door — the open descriptor must
  match the preflight identity, and a node that is no longer a plain regular file stops before
  any destination exists. A directory root gets a walk-start recheck (a recheck, not a binding).
  Child files keep the existing same-call lstat↔open identity; the child walk stays path-based
  (a stated residual).
- **The door-time queue refusal, every copied include file.** With the source descriptor open,
  the door opens the lexical queue path (following links, non-blocking — a FIFO-shaped queue
  classifies non-regular and stops), fstats the open descriptor, and compares identities with
  both descriptors open: never copy the node that IS the door-time queue. Queue identity is
  established at descriptor-open time and never cached across crossings — absence is not cached
  either. A truly absent queue keeps the lexical guard alone; a dangling, unreadable, or
  non-regular queue is unprovable and stops the copy. The queue descriptor closes exactly once;
  a close error surfaces without destination residue.
- **The contract ships as an exported constant** (`INCLUDE_IDENTITY_RULE`), emitted on every
  include-identity STOP and doc-parity-pinned into the worktrees mode doc.

## 3.8.0 — cleanup never deletes a node_modules it cannot prove ephemeral (AD-069)

Routine non-abandon `worktrees cleanup` used to delete an ignored user-built `node_modules`
silently: `node_modules` sat unconditionally in the provision-owned root lists, so real user data
was treated as removable provision footprint. The ownership call is now made LIVE, at cleanup
time, from what deleting the node would destroy — never from who created it, and never from the
handoff record:

- **The gate** classifies the worktree's `node_modules` with one no-follow lstat and (for a
  symlink) one buffer-form readlink: EPHEMERAL exactly when the raw target bytes equal MAIN's
  `node_modules` path — a relative or re-encoded target that merely resolves to main stays
  foreign, and the target's kind is irrelevant. Everything else — directory, file, special node —
  is FOREIGN. The lane is tracked-first: a tracked path or tracked descendant wins over any
  ignore rule, and an absent node with a live index entry is never clean-absent.
- **The one exemption** is the ignored-lane matching symlink (exactly what provision creates):
  the ignored inventory skips it and plain `git worktree remove` unlinks it — after the gate's
  verdict is RE-PROVEN, same class same lane, immediately before the irreversible remove. Any
  change in between, or any probe error at any point, is a fail-closed STOP with no remove call.
- **Every other state stops surgically**, and the recovery matches the lane and kind: ignored/
  untracked symlink, file, or special node → the exact single-node `rm`; directory → the
  recursive form; then re-run cleanup (`--abandon` always named second). A TRACKED
  `node_modules` never gets an `rm` — land its removal from MAIN instead. Probe errors offer no
  removal command at all. A clean-absent verdict follows the legacy path unchanged, so landing a
  tracked `node_modules` removal still converges. `--abandon` behavior is unchanged.
- The contract sentence ships as the exported `CLEANUP_OWNERSHIP_RULE`, emitted on every
  ownership STOP and pinned into `references/modes/worktrees.md` by a new doc-parity binding.

Honest residuals, stated in the mode doc: the revalidation→remove window stays open (git performs
the removal; no door there, deliberately); content that git cannot see — or that appears after
the inventories inside a reset-restored tracked directory — remains a pre-existing generic
worktree-removal residual, no longer masked by a false ownership claim; on Windows a strict-bytes
mismatch degrades to the surgical STOP, never a deletion.

## 3.7.0 — the worktrees-dir advisor item can finally converge (AD-068)

The `recommendations` advisor's `worktrees-dir` item used to fire forever: its only convergence
signal was a host write-capability callback that production never supplies, so the item kept
re-rendering even once its own advice had been applied. It now converges on either of two
maintainer-visible signals — while a genuinely supplied host signal still overrides both, in
EITHER direction (a trusted no keeps the item firing however the project is configured):

- **a declared grant** — a `sandbox.filesystem.allowWrite` entry covering the probed worktrees
  parent dir, in either settings scope; `~`/`~/…` resolve against home, and coverage is
  path-segment-aware (a grant on a sibling or on a child never counts). A declaration is proof the
  maintainer applied THIS item's advice, never proof of write capability — the provision
  preflight's real create+delete probe stays the runtime truth. The declaration read is guarded
  no-follow end to end (an out-of-project store reached through a symlink cannot silence the item;
  a non-regular target is a stated skip), and the `allowWrite` shape check is fail-closed over the
  whole array — one malformed entry invalidates the list rather than hiding beside a good one;
- **a dir-bound ack** — for hosts that ignore the settings key: the item's consent-gated APPLY
  one-liner is `ack-write --lane worktrees-dir` (a dry-run preview that prints the exact `--apply`
  which records the ack), while the HAND-APPLY grant advice rides the `recipe:` line as the
  labeled first step — the consent flow waits for the grant (or the terminal-fallback choice)
  before the ack runs, because the ack RECORDS a choice, never makes one. The recorded
  `worktreesDirAck` fingerprint lives in the family-owned `docs/ai/acks.json` and binds to the
  RESOLVED probe dir, so the item re-fires exactly when that resolved dir changes — never on a
  no-op re-render, never a security key. Against a trusted host NO the apply stays the HAND-APPLY
  grant advice and no ack surfaces (it could never converge there).

`ack-write` accordingly grows a closed-world `--lane` registry (`sandbox-lane` — the default, so
every existing invocation and rendered one-liner stays byte-identical — and `worktrees-dir`); one
lane sets exactly ONE store key, merge-preserving the rest, and an unknown lane is a usage refusal
at the writer, never an invented key in the shared store.

Honesty fix riding along: every shipped surface that promised the read-lane hook «fires on
subagent Bash too» now says «where the host fires hooks on subagent Bash» — that behavior has
never been measured, and a shipped surface must not assert it.

## 3.6.0 — the dependency-free install posture: a proof, never a default (AD-067)

On a provably dependency-free project the provision record and the default-lane report no longer
print a generic `npm install` hint: both state
`no install needed — the project declares no dependencies` (recorded node_modules mode
`no-dependencies`), and a `doc-parity` binding pins the posture string to the mode doc.

The verdict is a PROOF granted only on evidence the tool actually read — the WORKTREE'S OWN LIVE
checkout: what an install run there would actually read, exactly HEAD at provision time and the
satellite's own committed state on `--resume`, never MAIN's mutable working tree (which can
diverge from what the satellite actually holds; real-git tests pin both refresh directions).
Everything the tool cannot vouch for leaves the posture UNKNOWN with the honest advice kept — the
fail-safe direction, because a false "nothing to install" is worse than a redundant hint:

- a `workspaces` field of ANY shape, outright — a workspace install materializes member links and
  `.bin` shims even with zero dependencies, so a workspace tree is never provably install-free;
- an external workspace manifest beside the root (`pnpm-workspace.yaml`/`.yml`, `lerna.json`);
- a malformed manifest, dependency field, or `scripts` shape (three-valued verdicts — malformed is
  `unknown`, never "none"; a lifecycle key with a non-string value fails closed);
- an install-lifecycle script — dependency-free is NOT install-free: the CLOSED, test-pinned set
  is the npm lifecycle (including the deprecated `prepublish`) plus `pnpm:devPreinstall`; a
  native-addon manifest (`binding.gyp`) is a mandatory install too.

The posture COMPOSES with the shipped record contract, and LIVE STATE WINS: a node already at the
worktree — a directory, or a symlink left by an earlier provision, even dangling — records
`present` before any MAIN-state early return, the AD-065 symlink unlink-first arm is untouched for
every non-proven project, and `--install` remains an explicit request that is always answered with
the isolated-install command while the record still states the posture.

Third safe slice of the deferred parallel-track work (AD-063); node_modules ownership and
resume-verify semantics stay separate redesigns.

## 3.5.0 — the provision record orients a fresh satellite session (AD-065)

The worktrees provision record was an identity stub; a fresh satellite session could not derive
three facts from its own checkout. The record now carries all three, and the mode doc is pinned to
the live constants:

- **`shared-queue`** — the ABSOLUTE path to MAIN's `docs/plans/queue.md`, followed by the verbatim
  rule: the series index is SHARED, read it at that path, never copy it (a machine-local copy
  silently diverges); findings ride the handoff record and main appends them to the index. The rule
  ships only WITH the path — a record from an earlier kit carries neither. `--include` refuses to
  copy the index (or any directory containing it) into the worktree, at preflight AND re-asserted
  at the point of copy on the canonical pair resolved before `git worktree add`.
- **`landing`** — landing runs FROM MAIN, never from the worktree, with the runnable
  `… land <slug> --prepare` command already `cd`-ing back to main.
- **`install`** — the resolved install posture: the runnable isolated-install command, or the
  honest by-hand advice when the package manager is ambiguous.

The record is line-oriented and parsed back for IDENTITY, so it now REFUSES any value it cannot
round-trip: control bytes (an injected newline forges a field line or an `## …` heading that
truncates the section) and U+2028/U+2029 (which write fine and are then silently DROPPED on read —
a lost field with no error) are a typed STOP, never sanitized. Every value the record will carry is
validated BEFORE any git mutation — a refusal at compose time would strand a created worktree with
no handoff, which neither `--resume` nor `cleanup --abandon` can recover. Optional fields are
omitted when absent, never rendered as `null`, so a record written by an earlier kit survives a
refresh. Two new `doc-parity` bindings (`queue-shared-rule`, `landing-from-main`) pin
`references/modes/worktrees.md` to the emitted strings.

Second safe slice extracted from the deferred parallel-track work (AD-063) — no
node_modules-ownership coupling; the provable dependency-free install posture is the next slice.

## 3.4.0 — review-state names a latent arm on a clean-tree PASS (AD-064)

`review-state --check` under a configured `reviewed` or `council` recipe on a clean tree no longer
returns a bare `PASS — nothing to review`. It now NAMES every plan still in flight and states the
forward consequence — `this gate arms as soon as the
tree is dirty` — so a spent boot prompt left under a bare name is discoverable BEFORE it blocks,
instead of surfacing at the worst moment: a pending commit, or the landing of a feature worktree when
main first turns dirty. The quiet case (configured `solo`, no plan in flight, a non-git cwd) still
passes silently through the earlier-returning arms; dirty-tree behaviour is unchanged. A `doc-parity`
binding pins the
live notice to `references/modes/review-state.md`, so a reworded doc that drops it fails the gate — a
prose-only bar becomes a mechanism. This is the safe, converged slice of the deferred parallel-track
work (AD-063), shipped on its own with no worktrees-ownership coupling.

## 3.3.0 — version-pin honesty: the profile stops claiming a limit it never observed (AD-062)

`velocity --autonomy` told every user their credentials could not be protected by the sandbox —
naming a harness version frozen in the source, 30 patch releases stale, while a comment three lines
above recorded when the capability had actually arrived. A user who declared
`redlines.credentials=deny` got no `sandbox.credentials` key and a confident explanation that the
platform could not enforce it. Not a silent failure: a false claim about a security control.

Nobody can guarantee a vendor's version format, install layout or settings schema stays put, so the
fix is not "read the right version" — it is **never state what was not observed**. A pin goes stale
silently; a probe goes stale loudly, and that direction is the whole point.

- **`probeHarnessVersion`** reads the INSTALLED harness at render time — read-only PATH resolution,
  never a spawn. Two layouts matched exactly (`claude/versions/<version>` and the
  `@anthropic-ai/claude-code` package.json); a third-party wrapper whose name merely contains
  "claude", a decorated version segment, and a prerelease all resolve to a STATED unknown. A
  programming defect inside the walk is rethrown rather than reported as an unknown layout, and an
  unreadable package.json surfaces instead of folding into "not found".
- **A supported build now gets the protection.** `sandbox.credentials` is rendered, MERGED into
  settings (hand-declared `files` and foreign entries survive), drift-checked and local-mask-checked
  — the render owns exactly the entries for the env vars it protects, nothing more.
- **Every honest limit is a loud degrade, not a note.** Coverage is stated PARTIAL (env vars only;
  file credentials are NOT rendered, because that entry shape was never verified against an
  installed build). `credentials=ask` degrades rather than being quietly upgraded to deny. An older
  build, or one whose version cannot be RESOLVED from a recognised layout, degrades naming what was
  observed or stating that nothing was — and a PASSING `--check` prints the degrades too, because
  "in sync" only means the file matches the render. An UNREADABLE install (a permission error) or a
  defect inside the probe is a different outcome on purpose: it throws loudly rather than degrading,
  because "cannot confirm" must never read as "confirmed absent".
- **New `version-pin` rung in `release-scan`.** A harness version literal under `tools/` fails the
  scan unless a runtime probe sits beside it in the same JS file. The harness series is discovered
  from the scanned file, never hardcoded — a scanner carrying its own pin would be an instance of the
  class it catches, and a self-refusal test enforces that. Prose earns nothing: a `#` comment in a
  shell script or a line of Markdown cannot hold a probe, so those files stay scanned.
  **Stated residual, argued in the source:** the rung proves a probe is PRESENT, not that its result
  is compared with the literal. Proving the latter is JS lexing; an approximation of it was tried and
  withdrawn, because each increment of precision opened a new hole without buying the guarantee.

## 3.2.0 — plain language + posture as code: the friction cluster shipped as mechanism (AD-061)

Carrier release for a friction cluster (kit 3.2.0 bundles the bridges + shared contract + the
plain-language template bar; memory 3.1.0, codex bridge 3.1.0, agy bridge 4.1.0 ride alongside;
engine unbumped). The packaged fixes below ship as MECHANISM for every user, never as one agent's
discipline.

- **Shared command-shapes contract.** New `references/shared/command-shapes.md` states the
  promptless bar for improvised reads/probes — the host's file-read tool, else ONE plain
  undecorated command (no compounds, redirects, pipes, or command substitution; improvised writes
  use the host's file-edit tools). Bound via the existing `Requires:` mechanism to exactly the
  probe-instructing modes (bootstrap / upgrade / velocity, closed-world pinned) + a one-sentence
  inline bar on the SKILL.md router stamp-read. The honest residual is stated (a decorated form is
  host/config-dependent) — never a false "always prompts" guarantee.
- **Plain-language §2.5 Communication bar** in BOTH deployed `agent_rules` templates,
  byte-identically (the kit fallback deploy path communicates under the same bar): user-facing
  narration is short and plain in the dialogue language; transliterated jargon is banned; an
  English term survives only as the NAME of a thing (flag / command / file / test), glossed.
  `lens-region.mjs` now reconciles a SECOND region (Communication, canon from the kit's own bundled
  template) so EXISTING deployments gain the bar on `upgrade`, not only new bootstraps.
- **Posture as code on the codex-exec dispatch and both review dispatches** (bridges bundled here).
  `codex-exec` gains a D5 exec posture banner (fresh + resume, resolved post-validation values, a
  validated resume-id grammar + raw-byte NUL screen, control-byte refusal on every field). Both
  review banners gain a BANNER-ONLY `timeout` field — outside D5 banner↔receipt parity — from ONE
  shared effective-timeout resolver (byte-identical across the four wrappers) that closes the old
  env-validation bypass and resolves the timeout binary shadow-proof; no capping binary →
  `timeout=uncapped`, never fabricated. (The raw `agy-run` probe lane keeps no posture banner —
  out of scope this release.) The quote-the-banner-verbatim duty rides both bridges' driving
  contracts, capability notes, and the kit registry mirror, drift-guarded by a new cross-file test;
  the threat-model boundary is stated on the codex execute capability and `codex-exec --help`
  surfaces, including the kit mirror. The read-only `bridge-settings` advisor mirrors the wrapper's
  control-byte refusal semantics with escaped output.

## 3.1.0 — parallel feature worktrees v1: provision · list · land --prepare · cleanup (AD-060)

New `worktrees` mode + `tools/worktrees.mjs` — several features implemented simultaneously in
DIFFERENT agent sessions on one machine/repo, zero interference on working-tree files (the ONE
exception: the default `node_modules` symlink is a shared MUTABLE dependency cache — the printed
isolated install is the isolation lane), unambiguous ownership. One thin dependency-free tool over git: every verification datum is recomputed live
from git, never read from stored metadata (the ONE exception: the PREPARED OID in the handoff,
read back only for recovery). Git >= 2.36 floor (NUL-terminated worktree porcelain); typed STOPs,
never a guess.

- **`provision <slug> --plan <path>`** — visible sibling worktree + `aw/<slug>` branch: the
  registry-derived footprint copy-if-missing (a tracked file is NEVER overwritten), exactly one
  seeded feature plan, the `handoff-<slug>.md` stub, shared-`node_modules` symlink advice
  (`--install` only PRINTS the isolated install — zero spawn, zero write). `--resume` completes a
  half-done provision behind fail-closed identity: at most one handoff, slug AND branch must
  match, the section-required `## Provision record` (a duplicated field or section is a STOP,
  never last-wins), user content preserved byte-exact.
- **`list`** — read-only and honest: any read failure renders `handoff: (unreadable)` — a silent
  "no" never appears.
- **`land <slug> --prepare`** — the transient common-git-dir lock (shared with cleanup),
  dirty-main / graph-divergence / docs-ai-drift / red-review-state refusals, the satellite
  working-tree diff versus its base — staged AND unstaged inspected, every unstaged or
  untracked-not-ignored leftover listed and refused (ignored content is outside observation by
  design) — binary-safe transfer excluding exactly `docs/ai` + `docs/plans`, the optional
  porcelain-visible sync adapter, the main gate matrix, and an OID report (main HEAD · TRANSFER ·
  PREPARED · sync delta). The commit is NEVER run by the tool — it stays a dialogue ask. A second
  prepare is reset-only against the recorded PREPARED OID; transfer-apply and post-launch sync
  failures attempt a byte-clean rollback (rollback failures are composed without losing the
  primary error), while a red gate matrix that leaves the snapshot unchanged intentionally KEEPS
  the prepared tree and names both recovery lanes.
- **`cleanup <slug>`** — live landed-verification against main HEAD (exact land-exclusion
  parity), typed-EXACT ownership of ignored content (file/glob roots own only files), literal
  pathspecs, branch `-d`, prune; foreign content stops it. `--abandon` is the ONE destructive arm
  (`-D`; requires the handoff identity).
- **Hardening:** every content read and regular-file copy goes through two no-follow descriptor
  doors (identity-bound source · exclusive destination · descriptor-mode update), pinned as the
  only paths by tripwire tests; strict parsers everywhere (NUL porcelain fields,
  scan-before-parse JSON with any-depth duplicate-key refusal, atomic section surgery).
- **release-scan:** the reviewer-round-identity rung — an `agy` or `codex` name followed by an
  R-number reference is refused; shippable finding IDs use neutral
  `review-<scope>-rNN-<severity>-NN` IDs; the clean line now reads "no AI attribution or
  reviewer-round identity found".
- **recommendations:** the worktrees parent-dir item — write access stays "not confirmed" without
  a trusted host-capability signal; HAND-APPLY lines are host-qualified.
- **Docs:** the `references/modes/worktrees.md` contract — the MAIN/SATELLITE ownership matrix
  (shared git state included), satellite forbidden verbs (the v1 docs-only bar), the
  host-specific consent lane, and the other-harnesses PROVEN/ASSUMED split.

## 3.0.0 — strip-the-kit: the hardened computed core replaces the ledger machinery (AD-059)

> ### ⚠ BREAKING — the review loop is now computed, not recorded
>
> The review-ledger / fold-completeness / verification-profile / sarif / seed-gates tools, their
> modes, and their git-dir stores are **DELETED**. `run-gates --record` is a usage error (exit 7
> retired); the loop's ONE receipt is `run-gates --final`. Review receipts without a self-declared
> `posture` field (any pre-3.0.0/4.0.0 bridge wrapper) **stop satisfying** the review-state gate —
> fail-closed, like the AD-057 probe marker; re-run the reviews on the refreshed bridges. Node
> floor is **>= 22** family-wide. The consented `migrate-gates.mjs` migrates an existing
> `docs/ai/gates.json` (see `migrations/3.0.0-hardened-core-loop.md`).

The MAJOR family release (memory **3.0.0** / engine **2.0.0** / bundled codex bridge **3.0.0** /
bundled antigravity bridge **4.0.0**; deployment-lineage head **3.0.0**). One theme: **every claim
the loop makes is computed from artifacts it can re-verify — never remembered, never recorded prose.**

- **The hardened core (new tools):** `core-evidence.mjs` — the ONE git-dir evidence writer
  (red-proof records with observed-red 3/3 custody + content hashes; explicit per-backend degrade
  records; the stateless end-of-loop `summary`) · `coverage-check.mjs` — the D3(d) final-run
  checker (changed-line lcov coverage + red-proof verification + the `lcov-sha256=` machine line) ·
  `commit-guard.mjs` — the D10 read-only pre-commit (binds a commit to the LATEST green final
  receipt at the exact tree fingerprint; `--no-verify` stays a stated residual).
- **`run-gates --final`:** canonical realpath-anchored core checks (review-state + coverage-check,
  checker LAST), evidence-store drift teeth, the checker-bound lcov sha with an end re-hash, ONE
  attempt-linked receipt (green/red DERIVED, integrity failures explicit), `AW_GIT_DIR` exported
  to every gate child on every run.
- **`review-state` D3(b):** ship-class-only on the latest NORMAL receipt; a recognized negative is
  an authoritative VETO; unknown verdicts, probe-marked, unmarked, posture-less, and malformed
  receipts never attest (each with its stated recovery); explicit degrade records are the only
  escape and never all backends.
- **Consumer lanes:** `gates-init.mjs` (D9 consented fill preview; the coverage-check candidate
  appended LAST so a whole-offer apply is final-run-ready) · `migrate-gates.mjs` (D8, mirrored
  from the memory canon: canonical-anchor matching with move/collision semantics, the full lcov
  reporter flag set, retired-store cleanup, symlink/control-byte STOPs) · the hook installer's
  explicit `--commit-guard <path>` arm with strict carry-forward · a `commit-guard`
  Recommendations item gated on final-run-capability.
- **Bundled bridges (D4/D5):** a verdict-less review run exits 4 with NO receipt (exact agy
  `### Verdict` parse; structural top-level JSON verdict in codex schema mode); one stderr banner
  states the ACTUAL dispatch posture and every receipt records the same `posture{}`; control-byte
  and unknowable-model postures refuse pre-spend. Manifests carry VALIDATED posture pins; the kit
  renders the CONFIGURED posture (pins + the bridge-settings tier overlay) in the backend status
  line, drift-guarded end to end (`posture-parity`).
- **Suite economics note:** the supervisory ledger cluster (~98% of the old 94s unit matrix) is
  gone with its machinery; the dedicated speedup pass (plan Phase 5) follows post-release.

## 2.1.0 — agy code review fails CLOSED pre-spend: grounded facts required, `--ungrounded` is the explicit escape (AD-058)

> ### ⚠ the bundled antigravity bridge takes MAJOR 3.0.0 — bare `agy-review code` now refuses
>
> `agy-review code` without a NON-EMPTY `--facts` payload exits 2 **before spending a run**
> (previously: a loud warning, then a spent subscription run whose `grounded:false` receipt the
> review-state gate rejects — a guaranteed-wasted spend). Scripts calling bare `agy-review code`
> must add `--facts @f` (grounded) or `--ungrounded` (explicit throwaway opinion; the receipt still
> records `grounded:false` and never attests). `plan`/`diff` modes and continuations are unchanged.
> The kit itself is a MINOR carrier — the incompatibility is created, and declared, by the bridge.

A **fail-closed** release (kit MINOR carrying antigravity-cli-bridge **3.0.0** MAJOR in-tarball;
codex-cli-bridge 2.8.0 / engine 1.17.0 / memory 2.3.0 unchanged; the deployment-lineage head stays
`2.0.0`). One theme: **a run that cannot produce a usable result refuses before the money is spent.**

- **The refusal prints the recovery:** the exact `grounding.mjs` invocation — resolved from the
  wrapper's own location across the three real install layouts (monorepo canon / deployed skills
  sibling / kit-bundled mirror), quoted so an install path with spaces still yields a runnable
  command — plus the `agy-review code --facts @<file>` re-run line.
- **Escapes, both honest:** `--ungrounded` (code-mode only, contradicts `--facts`, refused on a
  continuation) and `AGY_PROBE=1` (a probe may now run ungrounded — a probe receipt never attests
  either way; its `modeCatalog` descriptor now marks the facts file optional).
- **Every declaration home moves in lockstep** (drift-guarded both directions): `--help` ⟷
  `capability.json` (`roles.review.contract` + `modeCatalog` `review.code` operands/guardrails +
  the `AGY_PROBE` env-hook) ⟷ the kit registry mirror (`tools/detect-backends.mjs`) ⟷ `SKILL.md` ⟷
  `references/driving-agy.md` / `models-and-flags.md` / `review-prompt.md`.
- **Receipt contract scoped honestly:** in `code` mode an absent/empty facts payload now means NO
  run and NO receipt; the `grounded:false`-on-empty clause applies to `plan`/`diff` only.

## 2.0.0 — Bridge mode catalog (manifest-as-source) + a review receipt that SELF-DECLARES (AD-057)

> ### ⚠ BREAKING — a review receipt written before this release no longer attests a tree
>
> The kit now **rejects an unmarked receipt**: a receipt must self-declare whether it came from a
> probe run, and silence is not a declaration. Every receipt on disk from kit ≤ 1.49.0 / bridges
> ≤ 2.7.1 / ≤ 2.6.1 predates the marker, so `review-state --check` and `review-ledger --check` will
> RED on it where they previously passed.
>
> **How to upgrade:** `npx @sabaiway/agent-workflow-kit@latest init`, which also ATTEMPTS to refresh
> the placed bridges — then read its per-bridge outcome. **`skipped-readonly` or `could not refresh`
> means a compatible writer is not guaranteed:** the new reader may still be paired with an old bridge
> writer, whose reviews write unmarked receipts this gate now rejects. Re-run the refresh from a
> writable environment (using the recovery command if one was printed), then **re-run the review** on
> the tree you are working on. That is the whole migration; no project file changes, and the
> deployment-lineage stamp is untouched.
>
> **Why it is not opt-in.** The pre-marker wrappers already honoured `CODEX_PROBE` / `AGY_PROBE` and
> wrote no marker, so an unmarked receipt is **indistinguishable from a probe receipt** — a review
> that ran with the frontier-model/max-effort guard switched off. Any transitional window that keeps
> accepting unmarked receipts keeps the hole fully open for exactly the receipts it targets: it would
> not defer the break, it would cancel the fix.

A **breaking + feature** release (kit MAJOR carrying codex-cli-bridge **2.8.0** + antigravity-cli-bridge
**2.7.0** MINOR in-tarball — the bridges only ADD a field to the receipt they write, which is additive;
the incompatibility is created by the kit READER that now refuses the old form. engine 1.17.0 / memory
2.3.0 unchanged; the deployment-lineage head is a separate axis and stays `2.0.0`). Two independent
contracts, one theme: **what a bridge offers, and what a receipt claims, must both be readable off the
artifact itself — never inferred from source or from silence.**

- **`modeCatalog` — a machine-readable answer to "what modes does this bridge offer, and why?"** A new
  **top-level, additive-optional** manifest block (schema stays 1), typed-validated exactly like
  `settings`: absent → valid (a bridge predating it keeps validating), present-but-malformed → invalid.
  Both bridges now declare their real mode set — codex `{exec, exec.resume-last, exec.resume,
  review.plan, review.code, CODEX_PROBE}`, agy `{review.code/plan/diff, review.continue,
  review.conversation, run, AGY_PROBE}` — each entry carrying a closed taxonomy (`primary` /
  `continuation` / `env-hook`, where an env-hook names `parents[]` rather than faking a role), a
  required one-line `purpose` + `whenToUse`, and — declared only where they apply — `whenNotTo`, typed
  `operands[]`, structured `guardrails` `{value, enforcement, condition?, source}` and `customHooks[]`
  (`exec.resume` carries no `guardrails`; a mode with no operands omits `operands`). Invocation forms compose **by
  reference** (`invocationRefs[]`) into the existing AD-033 driving contract — the catalog is the
  user-facing DISCOVERY layer and never shadows it.
- **Descriptor honesty is enforced, not promised.** Declared operand slots must set-EQUAL the
  placeholders the referenced forms really carry, in **both** directions over the deduplicated union of
  an entry's forms: an undeclared placeholder is as dishonest as an invented slot, since either way the
  render shows a form as ready-to-run that the reader cannot actually fill. `enforced` is claimable only
  for an OS- or code-enforced fact; a runtime bound rides in `condition`; anything a prompt merely asks
  for renders `advisory`. Catalog `submode` values are drift-guarded against the wrappers' real parser
  arms, and every declared env-hook must be a real EXECUTABLE condition (heredocs and comments excluded,
  so a name-grep cannot stay green after the logic is deleted).
- **A probe review can no longer attest a tree — and the receipt says so itself.** Both wrappers wrote
  receipts unconditionally, so a `CODEX_PROBE=1` / `AGY_PROBE=1` review — which runs with the
  frontier-model/max-effort guard **off** — minted a `fresh:true`/`grounded:true` receipt the
  review-state gate accepted. Both wrappers now write `probe` on **every** successful review, `true` or
  `false`, through the shared byte-identical `write_review_receipt` block: a receipt self-declares and
  nothing has to infer it. The kit rejects a probe-marked receipt (a probe never attests) and **equally
  rejects an unmarked one — silence is not a declaration**.
- **One attesting predicate, three consumers.** `classifyReviewReceiptForTree` /
  `summarizeReviewReceiptsForTree` / `describeMissingReviewAttestation` live in the neutral
  `review-ledger-core.mjs` and are read by `review-state.mjs`, `receiptCrossCheck` and the round writer
  — two gates disagreeing about what attests is precisely the class AD-050 closed, and a second copy
  would re-open it. The shared summary also fixes a latent hole: the ledger took `own[own.length - 1]`,
  so a probe landing **after** a real review became the authoritative verdict — a probe SHIP could bury
  a real REWORK and let both gates report convergence. The summary now returns the latest **attesting**
  receipt, never the last line.
- **Scope of the claim.** What a marker carries is UNTRUSTWORTHINESS, never provenance — receipts are
  not authenticated, and a forger could write `probe:false` as easily as any other field. This is
  self-discipline made legible, not a security boundary. See the BREAKING callout above for the upgrade
  path; full record, including the mid-execution amendment of the original design and the stated
  residuals, in AD-057.

## 1.49.0 — Honesty/robustness bundle: refresh EROFS stated skip · settings integer-overflow parity (AD-056)

A small **honesty/robustness** release (kit MINOR carrying the two bridge PATCH bumps in-tarball —
codex-cli-bridge 2.7.1 + antigravity-cli-bridge 2.6.1; engine 1.17.0 / memory 2.3.0 unchanged; lineage
head stays `2.0.0`). Two kit/bridge fixes ship here (a third, repo-only dispatcher fix, rides the same
commit); all three share one theme: **a blocked environment must produce a STATED degrade, never a
false red — and a real failure stays loud.**

- **Refresh under a read-only skills dir is a stated skip, not a false failure.** Under the harness
  session sandbox `~/.claude/skills` is read-only, yet `--refresh-placed` re-syncs even at the current
  version (repair-on-rerun). That write EROFSed into the generic catch → *"could not refresh — … recover
  with setup"*, though both versions were already current AND `setup` hits the same read-only dir. It now
  reports a new **`skipped-readonly`** outcome (exit 0): it names the current version, states the re-sync
  was skipped/incomplete, and names the read-only cause — never claiming a re-sync ran or file integrity
  (a partial copy may precede the block). Only a read-only-class write failure at the copy-write boundary,
  at an equal version, degrades; a read-side / source-side / `linkWrappers` / real-I/O failure, or a
  version-**behind** refresh, stays a loud *could not refresh* (its recovery pointing at a writable
  rerun). The opt-in `setup` placement lane keeps its loud failure. Any drift persists until a writable
  rerun (converge-on-re-run).
- **Settings integer validation is now shell↔JS exact (Issue-012, Resolved).** The four bridge wrappers'
  shared `aw_settings_valid` integer arms did `(( 10#$v … ))`, which wraps modulo 2^64 on a 19+ digit
  string — the shell **accepted** `18446744073709551916` (2^64+300) while the kit's `settingValueValid`
  (safe-integer) **rejected** it. A shared overflow-safe `aw_int_in_range` helper (byte-identical across
  all four wrappers) strips leading zeros then rejects on a digit count exceeding the max's — never
  running arithmetic on a huge string. A leading-zero **in-range** value (e.g. `000…086400`) still
  passes on both sides; the value is pinned by a new behavioral shell↔JS parity test.

## 1.48.0 — Family-owned neutral ack store + read-prompt-economy hook lane (AD-055, the CLAUDE-CODE-HARNESS-FRICTION cluster)

A **feature** release (kit-only — engine 1.17.0, memory 2.3.0, bridges 2.7.0/2.6.0 unchanged) that
closes two sibling defects where the kit fought the Claude Code host surface. Both land under one ADR
(AD-055), two commit-anchored segments.

- **Part I — the neutral ack store relocates off the host settings schema.** The `sandbox-lane`
  upgrade recommendation converged by writing `agentWorkflow.sandboxLaneAck` into
  `.claude/settings.local.json` — an advertised apply path a Claude Code host blocks twice (the
  Edit-tool settings validator rejects the unknown key; the command sandbox EROFS-denies
  `.claude/settings*.json`). The neutral fingerprint acknowledgement now lives in a **family-owned
  `docs/ai/acks.json`** no host validator guards, written by a new consent-gated **ack writer**
  (`tools/ack-write.mjs`); the legacy settings-scope key is still read for one deprecation window
  (until the next MAJOR). The recommendation's apply line is an executable writer one-liner again.
- **Part II — the read-prompt-economy hook lane kills read-side Bash prompts at the source.** A
  Claude Code prefix allow rule can never match a `;`/`&&`/`|` COMPOUND even when every segment is
  seeded, so routine read compounds still prompt. The placed gate hook gains an **opt-in read-lane**
  (rung c): with `docs/ai/lanes.json` set to `{ "readLane": true }` (read live per call, fail-closed),
  a command whose every separator-split segment is a plain frozen read-only core command with **zero
  shell metaprogramming** is auto-approved — a conservative closed-world allow bounded by the audited
  core (a standalone opt-in grant). `lanes.json` is a **separate** kit-owned file — `gates.json`, its
  validators and the byte-mirrored template stay untouched. The residual guard (settings-allowed
  singles) additionally trips the bash-5.3 funsub openers, a backslash-newline line-continuation
  splice, and a de-spliced re-scan of quote/backslash/bracket/brace `--output` reconstruction.
- **`gate-hook --read-lane`** is the consent-gated writer for `lanes.json`: it verifies the placed
  hook is byte-current **and** wired **and** at the deployment stamp head before enabling (a pre-1.48
  hook never reads `lanes.json`), refusing a stale/unwired hook with the delete-to-reseed recovery
  (absolute paths). The upgrade Recommendations advisor surfaces the **read-lane offer** once the hook
  is placed+wired (RISK_NOTED, with a consent-moment posture note; ATTENTION variants when the placed
  hook is stale or missing — no silent dark lane).
- **Canon honesty.** `velocity.md` (read-side invocation shape), `hook.md` (rung c + `lanes.json` +
  the currency check + delete-to-reseed), the three cheap-agent templates (they grant no Bash — a
  missing `Grep`/`Glob` falls back to the Read tool; a harness-forced Bash read stays plain-single),
  and the README hook row.

Reviewed by the codex + agy council across three rounds (agy SHIP ×3; every fixable fold red-first);
the surviving word-construction-on-a-single major is a documented inherent-layer-residual — a
string-based residual guard cannot close every shell reconstruction of a write flag without a full
shell parser or an over-ASK that would defeat the read-prompt-economy goal (rung b is a trust-posture
convenience, not a sandbox). Dogfooded live on this host: the read-lane currency guard passed on the
re-placed 1.48 hook and wrote `lanes.json`.

## 1.47.0 — REPORT-FACTS train: live-fact report contract · batched ledger writer · version-sync wrapper lane · sandbox-lanes canon & bridge contract twins (AD-054)

A **feature** release (ships with engine 1.17.0, memory 2.3.0, bridges 2.7.0/2.6.0 bundled) that
bundles three top-of-queue items plus this session's own live prompt-defects into one train, all
fixed kit-level:

- **Report-facts contract (D1/D2).** A binding clause at the point of use: any claim a report makes
  about the CURRENT host or session state — prompts fired, sandbox scope, whether a bypass was
  needed, network reachability, approval counts — must trace to **live tool output** run **this
  session**; with no live signal the claim is **omitted or explicitly marked unverified**; a
  memory/handover snapshot is **context, never report facts**. The full clause lives in
  `references/shared/report-footer.md` (single home); upgrade steps 4/8 and the recommendations
  advisor carry one binding line each, pinned by the new `report-facts-contract.test.mjs`.
- **Batched ledger writer (D3).** `review-ledger-write.mjs` gains a `batch` verb — one invocation
  applies an ordered record/classify/override list through the SAME single-verb code paths (no forked
  validator). Two passes: the whole envelope is validated structurally first with ZERO writes, then
  ops apply sequentially and fail-fast on the first typed STOP with an honest partial-success report
  (prior ops stay recorded, append-only). One writer call for a records stage instead of ~13.
- **version-sync wrapper lane (D4).** `version-sync --bump` for a bridge now also rewrites the
  line-anchored `AW_BRIDGE_VERSION` in `bin/*.sh` (closed one-anchor rule — every assignment counted,
  so a shadowing malformed line is caught; a non-canonical anchor or a downgrade is refused with zero
  writes), and the no-flag verify checks it across all four constants (each wrapper + its kit mirror).
  The bridge version constant is now first-class — dogfooded on this very release.
- **Bridge contract twins + codex-exec detection (D7).** Both bridge manifests gain a typed `notes[]`
  (codex execute = the nested-sandbox limit; agy review = the pre-dispatch host-diff), rendered in the
  `procedures` advisor and each wrapper `--help`, bidirectionally drift-guarded. `codex-exec.sh` now
  detects the nested-sandbox failure class (a sandbox-mechanism token AND a permission/read-only
  failure token together) and emits a stated recovery hint — route codex-exec OUTSIDE the harness
  sandbox ON the observed failure, never a preemptive blanket. The velocity bridge-tier stays
  REVIEW-wrappers-only; delegated execution keeps its human prompt.
- **Canon twins (D5/D6).** The prompt-economy canon gains a writer-batch clause and two sandbox-lane
  sentences (pre-dispatch host-diff + nested-sandbox honesty), rendered across `orchestration.md` §5,
  the agent-rules lens (re-rendered into both templates), and the kit cost-lanes advisor.

## 1.46.0 — Recommendations UX rework: verdict-first, shape-capped, user-language; sandbox-lane discoverability (REC-UX-REWORK, AD-053)

A **feature** release (ships with engine 1.16.0, memory 2.2.0, bridges 2.6.0/2.5.0 bundled)
reworking the upgrade Recommendations section after its first consumer-side report drew a
readability verdict — the section now LEADS with an answer instead of a wall of caveats:

- **Verdict-first render (D1).** Items carry a frozen two-class severity (`attention` — a
  configured declaration broken/drifted/degrading/invalid; `optional` — an offer to enable an
  unconfigured capability). The optimal state stays the byte-identical empty-state line; every
  other state opens with ONE composed verdict line from frozen, doc-parity-bound templates
  (`{K} item(s) need attention` / `nothing is broken` ONLY when nothing needs attention AND no
  probe was skipped / `{N} optional recommendation(s), apply any you want` / `optimality NOT
  attested — {M} probe check(s) skipped`). Items render attention-first with severity tags.
- **Shape is contract (D2).** Every benefit string and static WHAT template lives in frozen
  exported registries (`BENEFITS` + the new `WHATS`, per-site variants included) pinned by a
  static gate: one line, 140-char cap, banned tokens (RISK/CAVEAT/IF-hedges/dates); dynamic parts
  cap by truncation-with-count; `add()` backstops composed items into the stated-skip lane; skip
  reasons normalize to one capped line. The first RED run enumerated 9 violators — all rewritten.
- **Risk moved to the consent moment (D3).** Posture/risk prose lives in the mode doc's per-item
  notes (closed, bidirectionally test-pinned via `RISK_NOTED_KEYS`); the apply lane is an explicit
  informed-consent checkpoint: select → posture note inline → explicit confirm → run EXACTLY the
  rendered one-liner.
- **`network-allowlist` → `sandbox-lane` (D4, merges REC-SANDBOX-LANE).** The unknowable-condition
  hedge is gone: the item surfaces the manifest-declared observed session-sandbox recipe
  (`networkHosts` ∪ resolved `writableDirs`) for wired review wrappers and converges on a NEUTRAL
  fingerprint acknowledgement (`agentWorkflow.sandboxLaneAck`, either settings scope; home-symbolic
  normalization keeps a committed ack machine-portable; a changed recipe re-fires). Security keys
  are never read as an ack and never recommended as a fix; the mode doc's sandbox-lanes section
  routes per host class by a narrowest-scope ladder.
- **User-language presentation (D5).** The paste-verbatim contract is retired: the agent PRESENTS
  the section in the user's conversational language — every fact and count, nothing added or
  dropped; commands/paths/hosts byte-exact; raw tool block on request (the AD-032 lane). A static
  language-contract test pins the new tokens present and the retired phrases absent on every live
  surface (both mode docs, README row, tool header, doc-parity comments).
- **Manifests: `writableDirs` (D6).** New optional validated field — `{env, default}` entries
  (validate `--strict`, fixtures ×4, kit mirrors re-synced); the advisor resolves at run time
  mirroring the wrapper byte-semantics (non-empty env wins; empty ≡ unset; only `~`, `~/…`,
  absolute forms ride as-given — anything else anchors to the project root).
- **Prompt-economy render (D7).** The cost-lanes advisory (`procedures.mjs`) gains the
  prompt-economy clause rendered from the engine canon (read-only fan-out on restricted-tool
  vehicles only; one plain pipeline per call; capability-gated launcher guidance; the
  quality/speed guard + honest limit), drift-guarded by one distinctive token per invariant on all
  three surfaces.

Bundled bridges: **codex-cli-bridge 2.6.0**, **antigravity-cli-bridge 2.5.0** — each manifest
gains its `writableDirs` declaration (codex `{CODEX_HOME, ~/.codex}`; agy
`{null, ~/.gemini/antigravity-cli}`); wrapper behavior unchanged.

## 1.45.1 — codex frontier pin moves to gpt-5.6-sol (bridge 2.5.0)

A **patch** release (the kit surface is unchanged; the bundled `codex-cli-bridge` moves 2.4.0 →
**2.5.0**). Both codex wrappers' quality-first pin advances to the new frontier:
`DEFAULT_CODEX_MODEL` `gpt-5.5` → **`gpt-5.6-sol`** ("Latest frontier agentic coding model",
catalog priority 1 — verified against the codex CLI's own server model catalog on 2026-07-12),
effort stays **`xhigh`** (supported: low…ultra), the service tier stays STANDARD by default (the
`priority`/Fast spend knob is unchanged: consented, never a default). The refuse-on-non-default
guard, `CODEX_PROBE=1` escape, and every other wrapper contract are untouched; SKILL/references
prose follows the pin in lockstep.

## 1.45.0 — Autonomy series close: upgrade Recommendations, bridge-wrappers tier, review-domain mask fix (AD-044 Plan 4)

A **feature** release closing the AD-044 autonomy series (ships with memory 2.1.0, engine 1.15.0,
bridges 2.4.0; the deployment lineage head stays `2.0.0` — the new seed is ensure-if-missing,
stamp-independent). Three maintainer pains land as product:

- **`recommendations` mode — the mandatory upgrade advisor.** Read-only `recommendations.mjs`
  renders a frozen 12-item registry as `{what is sub-optimal · one-line fact-true benefit · exact
  apply one-liner}`; every `upgrade` run now ENDS with the section on BOTH exits (equal-head and
  re-stamp), present-even-when-empty ("no recommendations — flow optimal."), BEFORE the report
  footer and commit ask. Apply lines are PURE executable commands, cwd-independent (absolute
  paths, pinned `--cwd`); probes degrade to stated skipped-item lines — an unreadable bundled
  manifest or an uncaveated unknown-freshness row is a stated skip, a stray bundle-root file is
  ignored, a duplicate-carrying bridge-settings file renders fix-duplicates-first instead of the
  writer command it would refuse. The network-allowlist item is HAND-APPLY by design: the kit
  never seeds `sandbox.network.allowedDomains`; hosts derive from the bridges' manifest
  `networkHosts`, the paste value is PROJECT scope ∪ missing (a local-scope allowance counts
  toward coverage but never widens into the committed file), and the item renders only on the
  full two-surface tier proof. Autonomy facts resolve from the PROJECT ROOT on every paste
  surface, and seed detection is STRUCTURAL via the shared `isSparseSeedConfig` predicate on all
  four surfaces (an explicit declared-defaults policy still gets its render nudge — the red-line
  ask rules ride the render).
- **`--bridge-tier` velocity lane — unattended council runs.** A frozen tier seeds, behind its own
  consent flag, BOTH surfaces a promptless review run needs: `permissions.allow` prefix rules for
  the review wrappers' CODE mode only (`Bash(codex-review code:*)`, `Bash(agy-review code:*)` —
  plan/diff modes and the exec wrappers keep their human prompt) + the wrapper names in
  `sandbox.excludedCommands` (the harness runs those outside the sandbox — no agent-side bypass).
  The grounding pre-step rule seeds in the EXACT double-quoted byte-form the procedures advisor
  renders (byte-parity pinned; derives only when agy-review is placed). Tier entries register at
  all three audit points — the audit never flags an entry the tier itself seeded. The
  exfiltration posture is INFORMED CONSENT, notice-pinned; the invocation-shape contract is
  stated (prefix rules match only PLAIN invocations). `grounding --out` hardened: temp-only
  outside-repo, create-only + exclusive fresh in-repo, non-regular-leaf refusals.
- **Review-domain device-mask fix + `sandbox-masks` lane.** Never-committable stat classes
  (char/block devices, FIFOs, sockets) are filtered out of the ENTIRE review domain in lockstep —
  node fingerprint/isTreeClean + both bash twins' payload, diff assembly and preflights + parity —
  so the sandbox-mask fingerprint-divergence class is dead BY CONSTRUCTION (untracked symlinks and
  directories/gitlinks stay in the domain). The GUARDED `sandbox-masks` tool adds the cosmetic
  lane: read-only probe (derives/revalidates, refuses every non-mask class) + consent-gated
  `--apply` full-block REPLACE of its own fence in `git-path info/exclude` (a stale-real-only
  fence renders the `--clear` form on every surface); `review-state --check` advises the exact
  one-liner when masks are visible. Honest residual: REGULAR fake-file injection is a new design
  round if it appears.
- **Autonomy surfacing + seeds.** `recipes --status-line/--json/--active-line`, `set-recipe` echo
  and `procedures` gained autonomy segments rendered from `resolveAutonomy` (MALFORMED-loud on
  every paste surface; `procedures` also exits 1 on a malformed policy while still rendering);
  the sparse defaults-equivalent `autonomy.json` template mirrors from memory; bootstrap seeds it,
  upgrade ensures-if-missing. Manifests gained consult-locked `networkHosts` (observed-minimal
  INCLUDING apex domains; validated by `--strict`; synthetic examples in schema.md); velocity.md
  carries the honesty matrix (settings sandbox keys are INERT under harness-managed sandboxes —
  live-observed). Tarball sentinel 145→150; router/read-set budgets took documented KB bumps.

## 1.44.0 — Autonomy provisioner: the consent-gated sandbox doctor (AD-044 Plan 2)

A **feature** release (kit-only; memory/engine/bridges unchanged, lineage head stays `2.0.0`). New
routable GUARDED mode **`autonomy-doctor`** — the cross-platform system provisioner that makes the
AD-044 sandbox actually initializable on consumer hosts: detect → consent-gated install → verify →
loud degrade, over the locked matrix (macOS Seatbelt built-in / Linux + WSL2 `bwrap` + `socat` /
native Windows → WSL2 redirect). This is the kit's FIRST tool that can run a privileged command,
so the consent and honesty contract is the release:

- **Three explicit lanes.** Flagless = FS-only preview (ZERO subprocesses): the diagnosis, the
  exact install command it WOULD run, and the exact `--apply <pm>:<pkg,…>` consent line — never a
  "ready" claim (Linux flagless is `present-unverified`, exit 3, by design). `--verify` = the only
  source of a Linux "ready (verified)": a pinned bwrap user-namespace smoke + `socat -V`.
  `--apply <pm>:<pkgs>` = consent bound to the previewed tuple — any mismatch vs the re-derived
  plan refuses (exit 2) and runs nothing; a successful install auto-verifies.
- **Privileged execution, closed world.** Every executed token — package manager, sudo, and the
  binary `env` execs — resolves to an ABSOLUTE path inside the fixed trusted-dir allowlist
  `/usr/bin:/bin:/usr/sbin:/sbin` (`/usr/local/bin` deliberately excluded); a PATH-shadowed
  binary triggers a loud advisory, never execution. Frozen 4-family map: apt via the
  env-trampoline (`sudo /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get install -y …`, so
  non-interactivity survives sudo `env_reset`) · dnf `-y` · pacman `--needed --noconfirm` ·
  apk `add`; package names come from a frozen internal map — no repo/config/user input ever
  enters the command line. Unknown PM or untrusted location → stated degrade (exit 6), never a
  guess.
- **Sudo boundary, honestly.** `sudo -n true` preflight; passwordless success is LOUDLY stated;
  no TTY + a password required → the ENFORCED print-handoff (the doctor runs NOTHING and prints
  the exact command to run in your own terminal) — the designed primary path under an agent
  harness. Root callers (`euid 0` / `SUDO_UID`) are refused a "verified" claim — a green smoke
  under root can't prove unprivileged user namespaces (`root-unproven`, exit 5).
- **Frozen output contract.** Exported EXIT/status table (Linux exit 0 ONLY via the verify
  oracle) + a machine-parseable summary LAST line in every diagnosis outcome (`--help` prints the
  help text alone); `doc-parity` bindings pin the contract to the mode doc. The docs/ai
  deployment gate runs after arg parsing, ahead of every diagnosis/verify/apply lane; the
  `.workflow-version` stamp gate is a stated EXEMPTION (the doctor mutates the OS, never
  lineage-bound repo content).
- **Host-proven where it counts.** The bwrap smoke fixture and the apt env-trampoline descriptor
  are host-proven (the trampoline crossed a real sudo boundary with zero apt prompts); a fresh
  Claude Code session on the newly-ready host shows the series payoff — an ad-hoc command's
  prompt-delta drops 1 → 0 while the commit/push/publish red-lines still ask (content-scoped
  `ask` rules pierce `autoAllowBashIfSandboxed`; the render's exact rule form is load-bearing)
  and network egress still prompts. The sandbox is picked up at session START — the doctor's
  success output always states the restart step.
- **Registration + guards.** The GUARDED legend now reads "consent-gated destructive/privileged
  actions (dry-run-first)"; the velocity render's sandbox-unavailable degrade message points at
  the doctor; the doctor stays OUTSIDE every velocity auto-approve tier; tarball sentinel
  143 → 145 (`tools/autonomy-doctor.mjs` + `references/modes/autonomy-doctor.md`).

## 1.43.0 — Closed-world gate seeding: lifecycle hooks die by construction (AD-052)

A **feature** release (kit-only; memory/engine/bridges unchanged, lineage head stays `2.0.0`). The
consent-gated `gates.json` seeder (`seed-gates.mjs`) moves from BLOCKLIST screening to a
**closed-world** offer derivation — the structural fix for **Issue-011**, whose three residuals the
AD-042 council could only ever push one gap further, never close. Since a declared gate is
hook-auto-approvable, the offer is now conservative BY CONSTRUCTION: the worst case is a legit
command not offered (add it by hand), never a dangerous one offered.

- **Uniform hook-free exec form.** The seeded cmd is
  `COREPACK_ENABLE_NETWORK=0 <pm> exec -- <allowlisted-body>` for the detected package manager.
  `exec` runs a command, not a named script, so no `pre<name>`/`post<name>` lifecycle hook can fire
  — structurally and uniformly across **npm, pnpm, and yarn** (classic + berry). Never `<pm> run
  <name>`, which would re-expose hooks and let a later `package.json` edit change what a byte-exact
  approved gate runs.
- **Body allowlist, not blocklist.** The script body must be a string member of a 9-entry literal
  runner allowlist (`node --test`, `vitest run`, `jest`, `jest --ci`, `eslint .`,
  `prettier --check .`, `tsc --noEmit`, `tsc -p . --noEmit`, `vite build`) after a pinned
  ASCII-only normalization; anything else — an injected `curl … | sh`, a `release:npm` alias, an
  env/path body — is not offered, by non-membership. Editing the allowlist ADDS a test-guarded
  entry; it can never weaken a filter.
- **Per-PM fail-closed floor.** npm is pinned `--offline --script-shell /bin/sh` (no registry fetch
  of a missing runner; a hostile `.npmrc script-shell` loses); the `COREPACK_ENABLE_NETWORK=0`
  prefix blocks a Corepack-shimmed PM from fetching a hostile `packageManager` pin before exec;
  pnpm/yarn fail closed natively; a package manager whose exec contract cannot be verified is
  WITHHELD with a loud note. Screened-out gate-class scripts are counted and named — never silently
  absent (preview and apply alike).
- **Preflight parent-chain guard.** `assertDocsAiDeployment` (`atomic-write.mjs`) now walks the
  `docs` parent chain (refusing a symlinked `docs` parent or cwd root) before any read, closing the
  seeder's preview-path escape; all four write consumers inherit it. ENOENT-safe — a brand-new
  project still gets the normal no-deployment stop.
- **Scoped safety claim.** Safe-by-construction is the OFFER DERIVATION, not a runtime sandbox: a
  script gate runs the project's own tooling (project-controlled code), the disclosed residual
  bounded by the two-consent trust chain. The preview discloses it.

## 1.42.0 — Opt-in ADR-store migration mode + old-layout detection (AD-051)

A **feature** release, co-released with **memory 2.0.0 (MAJOR)** — the one-file-per-ADR store that
retires the 3-tier decisions cascade (engine 1.14.1 rides along as a housekeeping patch; bridges
unchanged). The deployment-lineage head bumps
`1.3.0` → `2.0.0` in lockstep (`EXPECTED_WORKFLOW_VERSION`; the cross-package drift guard pins it
to memory's `LINEAGE_HEAD`) — the first structural `docs/ai` migration in the lineage.

- **`/agent-workflow-kit migrate-adr-store`** — the consent-gated, opt-in crossing for an EXISTING
  deployment (`references/modes/migrate-adr-store.md` + `tools/migrate-adr-store.mjs`; dry-run →
  apply, the velocity/seed-gates writer pattern). It force-refreshes the WHOLE consumer
  `references/scripts` enforcement set atomically (`archive-decisions.mjs` + `check-docs-size.mjs`
  + their tests — so the consumer's own docs-cap gate can't red against the new scheme), writes
  the durable git-dir snapshot FIRST (docs + the pre-refresh scripts; a PROVEN out-of-tree
  fallback; a locally-EDITED enforcement script is snapshotted or the apply refuses — never
  silently clobbered), then runs the idempotent, conservation-checked `--migrate --apply` and
  regenerates navigator + index. Previews first; never commits.
- **Old-layout detection lives in the kit's project-aware surfaces** (the memory installer is a
  global, knows-nobody skill installer): `status` gains the `adrLayout` axis (old / migrated /
  none) via `family-registry`'s project survey; `upgrade` DETECTS a legacy
  `docs/ai/history/decisions-archive*.md` monolith and LOUDLY instructs the migration mode
  INSTEAD of seeding the new-scheme rotator into an un-migrated tree — an un-migrated consumer
  keeps their old rotator + old layout fully working until they opt in.
- **Kit fallback templates retargeted in lockstep** with memory's (`references/templates/` —
  `decisions.md` HOT-window seed + the seed `adr/log.md`): a kit-fallback fresh bootstrap seeds
  the new scheme, never the old; pinned by `template-parity` + the scripts-mirror guard.

## 1.41.0 — review-state degraded lane: align the presence gate with the review-ledger (AD-050)

A **feature** release (kit-only; deployment-lineage head stays `1.3.0` — no migration;
memory/engine/bridges untouched). Closes the AD-049 residual: the family shipped two read-only review
gates that **disagreed** on the same tree when a ready backend genuinely can't review a diff.
`review-ledger --check` (convergence) already excused a recorded-degraded backend; `review-state
--check` (presence) had no degraded model, so a legitimately degraded agy (stalled → no receipt) read
missing/stale → exit 1. On BUGFREE-3 S2 the loop converged codex-only with agy recorded degraded, yet
review-ledger PASSED and review-state FAILED — a consumer wiring review-state into a pre-commit hook
was blocked on an honest degrade.

- **A neutral read-core (`review-ledger-core.mjs`)** — the validated review-ledger read/schema core
  (path/base resolvers, `validateRecord` + its validators + schema constants, loop/segment filters)
  moves VERBATIM into a new node-built-ins-only module both read-only checkers import; `review-ledger.mjs`
  re-exports every symbol for back-compat. It breaks the `review-ledger ↔ review-state` import cycle so
  review-state can read the ledger without a back-import (the `changed-surface.mjs` precedent). Pure
  mechanical move — no behaviour change, pinned by the full pre-existing suite + import-split pins.
- **The `review-state` degraded exemption** — `review-state --check` now exempts a recipe-named backend
  without a current grounded receipt when the in-flight segment's LATEST review-ledger round records it
  `degraded:true` at the current tree fingerprint, with ≥1 non-degraded recipe-named backend present in
  that round and grounded, the loop unambiguous, and the ledger clean. It **mirrors review-ledger's
  `decideStop`** exactly (allPresent + presence) and stays **verdict-blind** — presence, not unanimity:
  the two gates now AGREE on an honestly-converged-with-degrade tree, and still intentionally differ on
  a non-converged one. `--await` inherits the exemption.
- **Fail-closed, exemption-scoped** — an unreadable/malformed ledger DENIES the exemption but never
  fails a tree whose receipts independently satisfy the gate (all-current stays exit 0, the ledger issue
  surfaced). More than one plan in flight suppresses the exemption without adding a fail-closed arm.

Contract surfaces moved in lockstep (the tool header, `--help`, the human render, the `--await`
comment, `references/modes/review-state.md`). Council-converged both segments (S1 codex + agy SHIP; S2
review round 1 revise → allPresent fold → review round 2 both SHIP), the two-gate agreement dogfooded live + pinned by a
detector-independent `two-gate-agreement.test.mjs` + a 17-case matrix; full suite 2969 green.

## 1.40.0 — Universal verification profile + session-loop economics (a)–(h) (BUGFREE-3, AD-049)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration; co-released with
`@sabaiway/agent-workflow-memory` 1.12.0; engine/bridges untouched). BUGFREE-3 closes two residuals
AD-046/AD-048 left open: the fold-completeness signal was **JS/V8-only** (coverage read solely from
`NODE_V8_COVERAGE`, the single-test probe parsed node:test TAP from stdout — no consumer on another
runner could use the gate), and the BUGFREE-2 retro left **eight kit-level cycle-costs** unfolded.

- **Verification profile** — a new, optional, versionable `docs/ai/verification-profile.json`
  (`schema:1`; memory-canon template + kit mirror + a read-core kit tool) declares WHERE the suite
  leaves coverage (`coverage.kind ∈ {v8,lcov}` + `lcovPath`), the single-test template
  (`resultFormat ∈ {tap-stdout,tap-file,junit-xml}`), and an optional SARIF findings path. Env knobs
  override; an **absent profile reproduces today's V8 + node:test behaviour exactly**. Every declared
  path is realpath-guarded gitignored/out-of-tree. LCOV branches at `readCoverage` (the V8 path is
  byte-unchanged); the single-test strategy preserves `resolvable = matched>0` across every format;
  SARIF is advisory, never on the blocking path.
- **(a) one suite run per fingerprint** — fold RESULT schema v3→v4 carries suite-execution evidence,
  and `run-gates --record` **credits** the `unit-tests` gate from it instead of re-spawning (strictly
  fingerprint-bound + tree-unchanged + command-identity + exit-0; no "recent enough" cache).
  **(c) same-segment re-attest** — a recorded `reattest` receipt anchors custody at a new file hash
  for a green-only test append without fabricating a red-observe (the honest replacement for
  mis-using `red-proof`). **(f) `--preflight`** — the cheap set only, actions routed by kind, no
  suite/probe spawn.
- **(b) doc-parity** — a new read-only lint + mode: a closed, live-imported registry pins each
  mode-contract doc token to its code constant (caps, schema versions, the ledger vocabulary),
  fail-closed on drift. **(d) `review-state --await`** — block until every recipe-named backend has
  a fresh grounded receipt for the current tree (receipts-not-pgrep; deadline-first, bounded sleep).
  **(e) `grounding --ledger-summary`** — a loop/base-scoped review-ledger digest for `--facts`,
  fail-closed on an unreadable/malformed ledger. **(g) `record --from-receipts`** — draft
  `backends[]` from the current-fingerprint receipts; an explicit non-degraded row is a loud STOP.
- **(h) a rotation regenerates `docs/ai/index.md`** (the memory-canon `archive-decisions.mjs` reuses
  the root-parameterized `check-docs-size.mjs --write-index --report`) — so an ADR rotation never
  leaves the index stale mid-release-matrix; **dogfooded on this very release** (AD-049's rotation
  regenerated the index automatically, zero mid-matrix trip).

**Stated residual (Option A).** The (a) credit rides `NODE_V8_COVERAGE`, an observable env var — a
test reading it could flip; bounded (fails-under-coverage caught by exit-0), documented, and tested.
The clean closure (Node ≥22 + `--test-reporter=lcov`) is queued.

## 1.39.0 — Fold boundaries: commit-anchored segments, the diff-size cap, the green-baseline receipt, no-repro-no-fold, and gate telemetry (AD-048)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration; engine/memory/bridges
untouched). AD-047's own execution loop field-proved the next gap twice: the ledger hard-capped a
MULTIPHASE plan at 3 rounds total (11 real council rounds across 4 commit boundaries went
unrecordable, their late fixable-bugs unbindable), and custody obligations spanning the whole plan
forced a waiver for every later-phase edit of an earlier-bound test file (4 of 5 recorded
overrides). One structural move fixes both and gives the three most-replicated fold-boundary
effects in the review literature real teeth: **the SEGMENT**.

- **The segment (review-ledger schema v4)** — every new record carries `base` = the commit the
  dirty tree sits on (`git rev-parse HEAD`; null on an unborn branch); a segment = (activity,
  loop, base). Round numbering, `REVIEW_CAP`/`HARD_MAX` (values unchanged — scope corrected),
  every writer tooth, and `--check` operate **per segment**; a segment closes ONLY through a
  gated commit, so a round-counter reset is **earned, never declared**. The field-proven
  11-round/4-base shape records completely while round 4 within one segment stays refused.
  `--status` groups rounds by segment; v1..v3 records stay readable and never enter one (the
  failure reason names the schema upgrade).
- **The diff-size cap (D4, writer tooth)** — `recordRound` refuses a round whose changed source
  surface exceeds `AW_REVIEW_DIFF_CAP` (default 400 new-side lines; fail-closed parser) without a
  recorded segment-scoped **`size-cap`** override carrying the EXACT sanctioned magnitude (it dies
  at the next commit — a grown surface needs a fresh recorded sanction). Counted classes are
  pinned: assessable + unsupported SOURCE lines count (excluding TS would gift a bypass); tests
  and out-of-domain never count; pure deletions are free. The computation lives in the NEW
  NEUTRAL `tools/changed-surface.mjs` — one home shared with the coverage domain (the runner and
  the writer can never drift; the writer never imports the runner).
- **The green-baseline receipt (D5)** — `run-gates --record` mints a v4 **`gate-run`** record via
  the ledger's NEW sole-writer API `recordGateRun` (the runner never opens the ledger itself):
  the FULL declaration + exactly what ran + the tree fingerprint BEFORE and AFTER the run; a red
  run records honestly; a failed record is its own loud **exit 7**. `recordRound` then refuses
  without a **quality-green** gate-run at the current fingerprint — gates-before-review is
  computed, not remembered: a subset that omits a declared non-process gate (any `--only` run
  short of the full quality set) or a tree-changed run never satisfies, while the kit's own
  `--check` loop gates (a CLOSED whole-command classification — compound lines and suffix-named
  tools never match) legitimately fail mid-loop and never block — omitting THOSE is fine. The velocity tier
  auto-approves only the exact no-`--record` form. Revert-first beyond this ships as protocol +
  telemetry visibility — stated plainly, never pretend-teeth.
- **No-repro-no-fold (D6)** — a blocking finding of the previous segment round may not VANISH
  unclassified: present means present-AS-BLOCKING (a severity downgrade does not survive), a
  pending `escalate` never clears, and the new v4 triage class **`refuted`** is the honest
  phantom lane (mandatory grounds in `note`; it also resolves in `decideStop` — additive rows
  beside the untouched truth table — so an honestly refuted phantom minted at the hard-max round
  can never wedge a segment). Minors stay exempt. Every fixable-bug fold therefore binds its
  red→green `testId` at the round it folded — late binding on multiphase plans restored.
- **Segment-scoped fold custody (D7, fold ledger v3)** — run and observed-red records carry
  `base`; bound testIds, receipts, custody chains, and tamper all filter to the current segment.
  A committed phase's custody obligations **close with its commit** — the cross-phase churn class
  that forced 4 of BUGFREE-1's 5 overrides is dead (regression-pinned); a receipt never crosses a
  commit boundary, so a cross-segment fold still takes the recorded `red-proof` lane (the stated
  residual).
- **Gate telemetry (D8)** — `review-ledger --telemetry`: read-only COUNTS across all loops and
  both ledgers (rounds/segments, finding origins, classification distribution incl. `refuted`,
  per-backend verdicts + divergence rounds, override usage by scope, gate-run quality-green and
  red-results-by-gate, fold runs, observed-red receipts, quarantined probes). Counts only —
  which gates earn their keep stays the maintainer's judgment. Never combined with `--check`.
- Bound-test probes now pass the pattern in the `=`-joined `--test-name-pattern=` form — a test
  name beginning with `-`/`--` no longer parses as a node option and silently selects no test
  (the pattern-half sibling of AD-047's dash-spawn fix; found live by this release's own `--red`
  loop).
- Surfaces in lockstep: both mode-refs + `gates.md` (the runner's claim is now "writes nothing
  BY DEFAULT"), README rows, catalog one-liners, the procedures advisor (run `run-gates --record`
  BEFORE recording a round; per-segment wording), velocity notes; the surface test pins every
  token.

Dogfooded live on its own three-segment execution loop (the AD-047 precedent): the cap fired on
the plan's own 792-line Phase-1 surface (exact re-sanctions recorded), the quality-green receipt
was consumed by the D5 tooth at the very first segment-2 round, and segment closure was exercised
end-to-end at each phase commit.

## 1.38.0 — Honest red→green: observed-red receipts, flaky quarantine, content custody, and the oracle-tamper guard (AD-047)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration; engine/memory/bridges
untouched). AD-046 verified each bound test is resolvable and green — this release closes its one
named hole: **nothing proved the test ever FAILED before the fix**. A test written green beside the
fix ("fix theater") passed every shipped check. Now the gate demands the whole honest sequence:
red observed → fix folded → green observed — with the test's bytes in custody in between.

- **Observed-red receipts** — a new runner verb, `fold-completeness-run.mjs --red "<testId>"`,
  observes a test on the REAL pre-fold tree: failing on N/N runs → it mints a receipt (testId,
  rerun counts, the test file's sha-256, fingerprint) into the fold ledger (schema **v2**: records
  carry a kind, `run` | `red-probe`; v1 records stay readable, a v1 record as the loop's latest run
  fails with a re-run reason). Observed-green / unresolvable / mixed / timed-out are DISTINGUISHED
  refusals — nothing is written. A test that cannot even load pre-fold is authored with a dynamic
  `import()` (the refusal says so).
- **The N/N verdict algebra** — every probe side runs `AW_FOLD_RERUNS` times (default 3): RED and
  GREEN are strict N/N verdicts; anything mixed or timed out is **QUARANTINE** — never converted,
  no override lane (a flaky pin proves nothing — replace the test). Probes gain the kit's first
  spawn timeout (`AW_FOLD_PROBE_TIMEOUT_S`, default 120, per RUN, probes only); both knobs go
  through one fail-closed positive-integer parser.
- **Content custody** — the gate requires, per bound testId: the receipt, the receipt PRECEDING the
  loop's latest run (a post-hoc red proves nothing), N/N-green probes, and that the green test is
  **byte-identical** to the test seen failing (per-FILE hash custody; appending the next fold's red
  test re-attests its file without ceremony). Probes always spawn the safe resolver's canonical
  absolute path — a leading-dash or OS-resolved traversal path would execute a different file than
  the hashed one.
- **Oracle-tamper guard + recorded overrides** — the runner records a tamper surface over the
  tracked diff (test-classified paths + bound-testId file halves at HEAD, by hunk old-side
  polarity; pure additions and new test files never trip it; parsing is config/platform-proof).
  The gate fails closed on a tampered file without a recorded **`oracle-change`** override; a
  **`red-proof`** override waives receipt+custody for exactly one testId (the loud escape for a
  genuinely unestablishable red). Overrides are a new review-ledger record kind (schema **v3**;
  v1/v2 records stay valid; exact per-scope payloads), written only by
  `review-ledger-write override --json '…'` with the standard teeth plus a strict
  single-in-flight-loop rule. **Never silent: every waiver is a durable, auditable ledger entry.**
- **Honest limits (stated):** insertion-only weakening inside a pre-existing test body,
  expectation artifacts outside test files (snapshots/goldens), and weakening an already-green
  test behind a newer same-file receipt (characterization-pinned) remain in the stated
  self-discipline residual class; ledgers stay forgeable — this is a self-discipline mechanism,
  not a security boundary. No mutation testing ships (still shelved); the checker still fails
  closed on any mutation data.
- Dogfooded live on its own development loop: ten pre-fix receipts, six council-found bugs folded
  strictly red→fix→green, and the guard's own refusals caught one premature fix mid-plan.

## 1.37.1 — Fix: the fold-completeness probe on Node 18/20 counted pattern-filtered SKIP lines as executed tests

A **patch** release (one product fix + its pinned fixtures; no other change). On Node 18/20 —
versions the kit supports — `node --test --test-name-pattern` EMITS every non-matching test as
`ok N - <name> # SKIP test name does not match pattern` (newer Node omits them), so the 1.37.0
probe parser counted those lines as real matches: a `testId` whose pattern matches NOTHING was
reported **resolvable with a green baseline**, and the fold-completeness gate green-vouched a test
that never ran — defeating the gate's purpose on exactly those Node versions. Caught by the CI
matrix (node 18 + 20) on the 1.37.0 release commit; invisible on newer local Node.

- `parseProbeOutput` now ignores any TAP result line carrying a **SKIP/TODO directive** — a skipped
  test was not executed, on any Node version; a real test name containing a literal `# skip` would
  only fail CLOSED (unresolvable), never open.
- New pinned fixtures: the node-18/20 pattern-filter TAP shape (nomatch → unresolvable; a matched
  test among skipped ones still resolves), a lowercase `# skip`, and a `# TODO` directive.

## 1.37.0 — Fold-safety completion: a fixable-bug requires its test, and a coverage gate attests the fold against the changed code (AD-046)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration). AD-045's ledger computed
WHEN a review loop stops; this release mechanizes **"a fold carries no new bug"** — M2 + M3a of
DEBT-TEST-COMPLETENESS. **No mutation testing ships** — that half was researched and shelved (see the
honest-limits note below).

- **M2 — testId enforcement (ledger schema v2).** A `fixable-bug` triage classification now REQUIRES a
  `testId` (`<test-file>#<test-name-pattern>` — a `#` separator with both halves non-empty; the writer
  validates FORMAT only, staying hermetic). Schema 1→2 with per-version validation: historical v1
  records stay valid on read, a mixed ledger reads back clean; the writer emits v2 only; `decideStop`
  is untouched.
- **M3a — the fold-completeness tool pair (read/run split, mirroring the ledger's read/write split).**
  `tools/fold-completeness-run.mjs` — the SOLE tree-toucher + result writer: ONE suite run under
  `NODE_V8_COVERAGE` (the coverage dir lives OUTSIDE the work tree), the changed surface classified by
  a CLOSED extension rule (assessable JS · unsupported TS/JSX fails the gate closed · out-of-domain
  docs/config listed loudly, never blocking), every bound testId probed shell-free for resolvability +
  a GREEN baseline, and ONE machine-only record bound to BOTH the tree fingerprint AND the sorted
  fixable-bug testId set — either moving makes the record stale. `tools/fold-completeness.mjs` — the
  read-only `--status` / `--check` gate (fail-closed; the normative exit contract lives in its header,
  the single home) that never imports the runner (import-split test).
- **Command surface** — the `fold-completeness` catalog entry (a writer) + `### Mode: fold-completeness`
  + its mode reference. **Consumer seeding is deliberately ON HOLD**: the signal is JS/V8-only in v1,
  so the consent-gated seeder does not offer this gate yet — wire it by hand (the mode-ref carries the
  candidate `gates.json` line and the hold's reason).
- **Activity-aware canon pointer (with engine 1.14.0)** — the procedures advisor renders the ledger
  pointer (record / `--status` / `--check`) for `plan-execution` ONLY, plus an unconditional
  triage-classification bullet (`fixable-bug / inherent-layer-residual / escalate`) for every
  review-backed activity.
- **Honest limits (stated in the tool headers, like `review-state`'s):** coverage proves EXECUTION, not
  assertion — the per-fold proof remains the red→green test discipline (M2), the coverage run is the
  whole-surface prefilter; records/testIds are forgeable (a self-discipline mechanism, not a security
  boundary); line-entry granularity (same-line branch gaps are out of scope without an AST).
  **Mutation (M3b) was researched and SHELVED** — bounded local-boundary mutation did not catch the
  motivating interaction bug and is not language-independent; records carry a reserved EMPTY `mutation`
  shape and the checker fails CLOSED on any record carrying mutation data.

## 1.36.0 — Review-round ledger: the prose crossover-stop becomes a computed signal (AD-045)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration). The review-loop
crossover-stop that `planning.md` §9 and `procedures.md` describe in prose — "cap ≤2 rounds", the
crossover, "fold-at-altitude vs residual", "{round N · finding-origin tally · per-backend verdict} …
a computed signal, not a remembered rule" — was **prose with no checker** and broke under load. This
ships it as a **mechanism** (DEBT-REVIEW-CAP): a review-round **LEDGER** that protects every consumer
project's `plan-execution` review loop.

- **`tools/review-ledger.mjs`** (read-only) — the record schema (two kinds, `round` / `triage`,
  internally-consistency-validated), a tolerant reader (malformed lines counted + surfaced, never
  dropped), the pure **`decideStop`** truth table returning exactly one state under a fixed precedence
  (**converged > resolved-residual > triage-required > continue**) from machine fields only, the
  integrity receipt cross-check, and the **`--check`** gate — **fail-CLOSED** on every unknown state
  (detector failure · unreadable / malformed ledger · a corrupt round sequence · more than one plan
  in flight).
- **`tools/review-ledger-write.mjs`** (the sole writer, over the hardened `atomic-write` core) —
  `record` / `classify` with **the teeth**: it refuses a round while triage is required, beyond the
  hard-max ceiling of 3, or without a grounded review receipt, and enforces round-sequence integrity +
  round-bound classifications. The read/write split is pinned by an import-split test; the ledger lives
  in the git dir (uncommittable by construction, mirroring the receipts precedent).
- **Command surface** — a `review-ledger` catalog entry + `### Mode: review-ledger` + its mode
  reference; the conditional `seed-gates` candidate (offered only when `plan-execution.review` is
  reviewed / council).
- **Honest residual** (stated in the tool header, like `review-state`'s): the ledger attests a review
  occurred and its ship-class is consistent; it does not prove the recorded counts are truthful nor
  that a self-reported `degraded` is real — a self-discipline mechanism, not a security boundary.

Self-arming dogfood: this release's own review loop was recorded through the ledger it builds
(`--check` exits 0 via the `converged` branch). The optional per-fold `testId` slot exists but stays
**unenforced** — enforcement + a fold-completeness signal + the canon-pointer mechanization are the
next plan (DEBT-TEST-COMPLETENESS).

## 1.35.0 — Host-level bridge settings surface + Codex Fast tier as configuration (AD-043)

A **feature** release (deployment-lineage head stays `1.3.0` — no migration). Bridge knobs like the
Codex Fast tier are now enabled **through a host-level settings file that survives kit upgrades**,
never re-researched and re-patched per host; the kit surfaces, reconciles, and honestly refreshes
that surface. Ships **bridges 2.3.0** inside the tarball (the four wrappers' shared settings-reader
block + the `CODEX_SERVICE_TIER` knob + the typed `settings` manifest schema, Phase 1). The kit
machinery (Phase 2):

- **`bridge-settings` mode — the host-config reader + consent-gated writer.** New
  `tools/bridge-settings.mjs` (+ its read-only core `tools/bridge-settings-read.mjs`) reads/writes
  `${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf` — a `KEY=VALUE` file **outside
  every kit tree** (D2, upgrade-survival is structural). Previews by default, `--apply` writes via a
  hardened out-of-tree atomic core (`writeContainedFileAtomic` / `writeHostConfigFileAtomic`,
  factored from `atomic-write.mjs` — symlink/parent/TOCTOU-safe, dir created on first use). The
  allowlist + typed validation come from the **bundled bridge manifests** (`settings` blocks,
  manifest-as-source / D6 — `settingValueValid` is now the single shared predicate). It refuses an
  unknown key, an out-of-range/invalid value, and — loudly, naming the key — a duplicate-carrying
  file; model/effort stay unsettable (the quality guard is untouched, D4). Routed in `SKILL.md`
  (`guarded` kind) with `references/modes/bridge-settings.md`.
- **Refresh overwrite honesty (D5).** `tools/setup-backends.mjs` now byte-compares a placed bridge
  against the bundle on an equal-version re-sync and **states** the local edits it overwrote (file
  list + the settings-file pointer), instead of the old silent wipe; a version upgrade never cries
  wolf about the version delta, an unreadable placed file degrades honestly, a placed-only extra is
  preserved and never claimed as loss.
- **Init/upgrade reconcile.** `init` (`bin/install.mjs`) and `Mode: upgrade` run
  `bridge-settings --reconcile` after the bridge refresh: every settings-file key is validated
  against the new bundled manifests; an unknown/retired key is a loud flag, **preserved verbatim**
  (never edited — the lens-region posture).
- **Status + advisor surfaces (fact-only).** `status`' execution-backends block, the `procedures`
  driving-contract render, and `recipes --status-line` now surface the active knobs (env>file>default)
  and each wrapper's settable knobs — fact-only, no model claim, localized-on-error; the status line
  stays byte-identical unless a knob is active.
- Parity + budgets: new `test/settings-reader-parity.test.mjs` pins the reader block byte-identical
  across the four wrappers; the always-loaded router stays `≤ 10240 B` (the equal-head reconcile
  enumeration was trimmed to its step-3 pointer to make room for the new mode); the `routerPlusMode`
  budget is re-pinned `29696 → 30720` (documented) for the reconcile paragraph added to `upgrade.md`.

## 1.34.0 — Onboarding UX: one batched setup prompt, honest installer messaging, the visible accelerator funnel, and the consent-gated gates seeder (AD-042)

A **feature** release (first-contact flow + a new consent-gated writer; deployment-lineage head
stays `1.3.0` — no migration). First contact now interrupts once instead of three times, the
opt-in accelerators are discoverable from every happy path, and a project's own verification
commands can be seeded into `docs/ai/gates.json` behind an explicit per-entry yes:

- **F11 — ONE batched setup prompt.** Bootstrap asks the three setup questions (visibility /
  conversational language / attribution) as one structured multi-question prompt where supported
  (`AskUserQuestion`, up to 4 questions per call), records each answer individually, and writes
  nothing until all are answered (`references/modes/bootstrap.md` preamble + steps 2–4;
  `references/contracts.md` · `references/shared/deploy-tail.md` · `launchers/windsurf-workflow.md`
  reworded to match). Upgrade batches its two migration asks the same way ONLY when BOTH
  `AGENTS.md` blocks are missing (a pre-1.1.0 deployment), collects them in step 6 BEFORE the
  migrations apply, and never re-asks a collected answer (`references/modes/upgrade.md`); the four
  migration files are untouched — their own ask stays the standalone fallback. New
  `test/ask-contract.test.mjs` pins the wording across all 7 files and holds the kit↔memory
  `references/contracts.md` ask paragraph byte-identical (a hand-lockstep pair, deliberately not a
  sync-mirrors family).
- **F12 — the installer says what a returning user needs.** `bin/install.mjs` prints a restart
  hint on every run over a PRE-existing install ("restart the session so the agent reloads the
  refreshed kit files") — on the verb path, at most once per run, surviving even the fatal
  engine-install abort — and replaces the false "Claude Code / Codex / Devin Desktop all use the
  same /agent-workflow-kit" claim with the real per-agent matrix (Codex invokes via its `/skills`
  menu and may auto-trigger) in `--help` AND the final next-steps block. Pinned in
  `bin/install.test.mjs`.
- **F10a — the opt-in funnel is visible.** The welcome-mat ladder
  (`references/shared/report-footer.md`) gains caveat-aware rungs: velocity when the allowlist is
  unseeded, `agents` when no cheap-lane vehicle is placed, `hook` when gates are DECLARED
  (non-empty — file presence alone would misfire on the empty seed) but the hook is unwired; the
  two new signals ride the existing status envelope (`tools/family-registry.mjs`
  `surveyCheapAgents` + `surveyGateHook.declaredGates` → `tools/view-model.mjs` →
  `tools/renderers.mjs` — no new helper call). Bootstrap step 11 ends on a compact
  optional-accelerators block (velocity · agents · gates seeding + hook · set-recipe;
  preview-first, nothing runs without a yes), and `help` output gains a matching "Tune" tail
  (`tools/commands.mjs` — no new mode, no new kind; the router SKILL.md is untouched). New
  `test/report-footer-rotation.test.mjs` pins one shorthand ladder literal across
  upgrade + bootstrap; the `gate-approve-hook` fixture is refreshed to the live 10-gate shape.
- **F10b — the consent-gated `gates.json` seeder (the seeding↔hook trust chain).** New
  `tools/seed-gates.mjs`: dry-run by default (prints the derived `{ id, title, cmd }` entries,
  writes NOTHING; declining leaves the file byte-identical); `--apply [--only <id>]…` appends
  exactly the consented entries — append-only (never modifies or removes an existing entry),
  id-collision refusal, validator-checked (it imports the runner's `validateDeclaration`; the
  runner never imports it), stamp-gated apply, and OUTSIDE every velocity tier (a consent-per-run
  writer is never pre-approved). Offered candidates are terminating verification classes only
  (test / lint / type-check / build) — release/publish/deploy scripts, watch/serve modes, and
  MUTATING variants (`lint:fix`, `test:update`, bodies carrying `--fix`/`--write`/`-w`/`-u`)
  never enter the offer; commands are package-manager-aware (npm/pnpm/yarn). The review-state
  candidate is included ONLY when `docs/ai/orchestration.json` declares reviewed/council on
  `plan-execution.review` (the slot the checker enforces), with the resolved QUOTED path. Every
  preview prints the trust-chain disclosure: the hook auto-approves byte-exact declared commands —
  seeding and hook wiring are two separate consents. The hardened atomic write core is extracted
  into `tools/atomic-write.mjs` (exclusive-create tmp+rename, TOCTOU re-check, symlink STOPs) and
  shared with `tools/orchestration-write.mjs` (public API unchanged). The consent-seed protocol
  lives in `references/modes/gates.md`; `references/modes/review-state.md` step 3 now names the
  seeder path ("by hand OR the explicit-consent seeder — never without consent").
- **AD-039 amendment (documented, AD-042):** `test/router-contract.test.mjs` `routerPlusMode`
  28672 → 29696 — the F11 upgrade batching caveat is +422 B of new contract text against 154 B of
  headroom; the router itself is byte-identical. Tarball 116 → 118 files
  (`test/package-content.test.mjs` count + payload pins for the seeder pair).

## 1.33.0 — The agent-rules lens region: render + reconcile from the engine canon (AD-041)

A **feature** release (new shipped tool + wiring; deployment-lineage head stays `1.3.0` — the
refresh is stamp-independent, no migration). The deployed `docs/ai/agent_rules.md` lens section
is now a RENDER of the engine's canonical fragment, kept current by the kit:

- **New `tools/lens-region.mjs`** — the lens reconcile: heading-anchored region (no markers; a
  renamed heading is a natural preserve+advise), render with the file's OWN section number,
  refresh IFF the body matches the engine fragment or a known-prior body (fragment + prior store
  read LIVE from the installed engine — no kit-side prior constants), a customized region
  preserved verbatim + a one-line advisory, cap-guard from the target's frontmatter `maxLines`
  (loud non-fatal refusal; no frontmatter → stated skip), atomic write, document EOL preserved.
  Lazy + fail-loud: an absent/invalid engine is a STOP with the install command; a valid engine
  older than 1.13.0 (no lens pair) is a stated soft skip. CLI:
  `node tools/lens-region.mjs reconcile <path/to/agent_rules.md>`; invariants pinned by
  `tools/lens-region.test.mjs` incl. the canon-change simulation (v1 deploy + v2 engine →
  refreshed; re-run → zero-diff).
- **`Mode: upgrade` step 3** gains the SEVENTH stamp-independent reconcile (plain-language
  outcomes: refreshed / already current / custom preserved + note / file absent / engine too
  old / over the line cap), reported in both the step-4 and step-8 exit reports; the stale
  "other three reconciles" phrasing went count-free.
  `references/shared/composition-handoff.md` runs the same reconcile in BOTH bootstrap paths
  (its own precondition: after `docs/ai/agent_rules.md` exists) — this is what converges a
  stale-memory seed; `handoffPlan` names the lens region in `kitWrites` for both paths.
- **`family-registry`**: a distinct plain-language `status` caveat for an engine that does not
  ship the lens canon — keyed on the PAIR (fragment + prior store), so a half-shipped engine
  never reports healthy.
- **`test/lens-mirror.test.mjs` REWRITTEN** to render-parity vs the known-canonical set (each
  template lens block byte-equals a render of the engine fragment or a prior-store entry; the
  checkout additionally pins the CURRENT render; injected non-vacuity). The 22-token × 4-file
  vocabulary mesh is deleted — token presence now lives in the ENGINE's own lens-fragment guard,
  so a future lens wording change is an engine-only release (no forced kit/memory diffs).
- `references/templates/agent_rules.md` §2.5 intro carries the provenance clause (the render);
  `tools/engine-source.mjs` exports `LENS_FRAGMENT_REL` + `LENS_PRIORS_REL`. Tarball 115 → 116
  (`lens-region.mjs` reverse-pinned). AD-039 byte budgets hold unchanged.
- Repo-local release harness: `scripts/release/smoke-init.mjs` gains repeatable
  `--expect-file <sandbox-HOME-relative path>=<substring>` (installed-file content assertions;
  both path dialects fenced to the sandbox HOME).

## 1.32.0 — Approval-idle reduction: the opt-in `velocity --kit-tools` tier, an audited core extension, and the standing-consent advisory (AD-040)

A **feature** release; packaging-only for deployments (lineage stays `1.3.0` — no `docs/ai`
structure change, no migration). Routine read-only kit-tool invocations — the session-start
discovery line, the procedures advisor, the status/backends/gates checks — stop idling on
approval prompts, opt-in and honestly labeled; nothing that writes, commits, or publishes gets
any quieter.

- **`velocity --kit-tools` (opt-in tier).** On top of the read-only core, seeds 12 entries derived
  from the RUNNING skill's own location at seed time: 8 read-only kit tools as resolved-absolute
  script path + args wildcard (`recipes` / `procedures` / `family-registry` / `detect-backends` /
  `commands` / `review-state` / `manifest/validate` / `release-scan`), `run-gates.mjs` as ONE
  exact byte-string pinned `--cwd <resolved project root>` and advertised **project-exec, never
  "read-only"** (a wildcard would be broader than the AD-037 hook boundary), and the three
  default-dry-run writers' exact arg-free preview byte-strings (`velocity-profile`,
  `cheap-agents`, `gate-hook`) — every `--apply`/`--write`/`--yes` still prompts. Fail-safe by
  construction: a moved skill or stale path simply prompts again; non-POSIX / space- /
  quote-carrying paths are refused up front with a typed error (hand-add fallback). Flagless
  `velocity` behavior is unchanged (validates core-only, never depends on skill paths).
- **Dead-rule prevention as a test.** The `velocity.md` tier subsection lists the covered dispatch
  line per tool (the documented-invocation source); the new `test/kit-readonly-tools.test.mjs`
  substitutes the resolved skill dir (+ project root for run-gates) into each line and asserts the
  seeded byte-form matches (prefix for wildcard, equality for exact) — plus the tier ↔
  `commands.mjs` catalog-partition guard (run-gates the only project-exec member; the two
  non-mode-backed validators get a writes-nothing source assertion).
- **Audited read-only core 18 → 31 (the AD-021 empirical method, probe record in AD-040).**
  Survivors: `diff`, `stat`, `du`, `basename`, `dirname`, `realpath`, `git rev-parse`,
  `git blame`, `git shortlog`, `git describe`, and the FIXED forms `git tag --list`,
  `git stash list`, `git worktree list` (their bare forms mutate — probe-proven). FAILED and
  excluded: `file` (`-C -m` compiles a magic FILE WRITE) and `git cat-file` (`--textconv`/
  `--filters` run configured filters; `git show` already covers the reads). The PreToolUse hook's
  `SEEDED_READONLY_CORE` extends in LOCKSTEP (order-sensitive parity guard); an already-placed
  hook keeps the OLD core (a strict subset) until a delete-to-reseed refresh.
- **Sharper pre-existing advisory.** `node …`-shaped allow entries OUTSIDE the derived tier
  (foreign script path, foreign `run-gates --cwd` root) are now flagged for hand review — the
  tier's shape can never hide arbitrary local JS.
- **`set-recipe` standing-consent advisory (wording-only).** After a `--write` that names a
  reviewed/council recipe, the mode file now advises the one-time HAND-adds
  (`codex-review` / `agy-review` / `grounding.mjs`) to `settings.local.json` — stating plainly
  that auto-approval spends subscription quota without a per-run prompt, the kit never writes
  that file, and the entry must match the invocation byte-form including quoting. Solo recipes
  get no advisory; the tool echo is untouched.
- **Honesty floor, twinned.** The velocity residual notice + its `velocity.md` prose mirror now
  both carry the approval floor: every writer apply-class flag still prompts, clobber-protection
  STOPs still stop, the three release asks (commit/push/publish) stay maintainer-owned.

## 1.31.0 — Progressive disclosure: SKILL.md becomes a thin router over references/modes + references/shared (AD-039)

A **feature** release; packaging-only for deployments (the deployment lineage stays `1.3.0` — no
`docs/ai` structure change, no migration). The 112,106 B / 680-line SKILL.md monolith — loaded
whole on EVERY invocation — becomes a **10,139 B router** plus per-mode files, so an invocation
reads only what it needs:

- **The router** keeps the always-needed core: the composition-root decision (detect → delegate /
  fallback + the init refresh-cascade), the safe-routing rule + version-status routing note, and
  16 bare `### Mode:` headers each carrying ONE line — the catalog `kind` EXACTLY + ``read
  `${CLAUDE_SKILL_DIR}/references/modes/<mode>.md` before acting.``
- **`references/modes/<key>.md` ×16** — the mode bodies, moved verbatim (set-equality-guarded
  against the `tools/commands.mjs` catalog). **`references/shared/`** — the point-of-use
  contracts: `report-footer.md` (backend-status line · version block + welcome mat · version
  disclosure), `composition-handoff.md` (hand-off + bounded pointer reconciliation),
  `deploy-tail.md` (Gotchas · Setup contracts · System principles · Hard-Constraints template).
  Each mode file opens with one `Requires:` line naming its shared reads (bootstrap/upgrade → all
  three; status → the report footer; the daily modes none).
- **Byte budgets are acceptance, not vibes** — the new `test/router-contract.test.mjs` asserts,
  over the real files: router ≤ 10,240 B · router + any mode ≤ 28,672 · every full read set
  ≤ 53,248 · the daily no-shared modes ≤ 16,384 (realized: 10,139 · 27,392 · 48,419 · 14,121 —
  a daily `help` run is ~10× lighter, the worst path (`upgrade`) ~2.3×). It also pins the D4
  pointer audits permanently: every `Requires:` resolves, zero italic/plain cross-mode refs, zero
  bare kit-relative links, moved shared-section references carry their pointer.
- **Nothing else moved:** runtime routing untouched (`routeInvocation` never reads SKILL.md);
  packaging additive (`references/` already rides `files[]`; the npx installer copies it
  recursively; tarball 96 → 115 files, exact-count-pinned); frontmatter byte-compatible with the
  twin version readers; 7 content-coupled guards re-anchored to the new files.

## 1.30.0 — Review-recipe enforcement: the configured recipe is impossible to miss, "reviewed ≠ shipped" is detectable, grounding is a command (AD-038)

A **feature** release (ships the bundled bridges refreshed to **2.2.0**). Origin: a real
council-substitution incident + independent kit-user feedback — the configured review recipe could
be silently skipped, downgraded, or run before later edits, and nothing could detect it. Three
mechanisms, each self-firing at its point of use:

- **The discovery line** — `tools/recipes.mjs --active-line` (`Mode: recipes`): exactly ONE
  machine-composed line rendering the **CONFIGURED** recipe of every activity/slot from
  `docs/ai/orchestration.json` + live readiness — source labeled, degradation stated, wrapper set
  named, explicitly contrasted with the readiness recommendation (which is informational). Wired
  where a session already reads: the deployed `agent_rules.md` §1.1 gains step 2 (read the
  orchestration config BEFORE picking a task; a silent recipe downgrade is a forbidden
  substitution) and `handover.md` gains a standing **"Active recipes:"** slot — both template
  regions byte-identical with the memory copies (new `test/template-region-parity.test.mjs`,
  injected-divergence non-vacuous); `set-recipe` now ECHOES the freshly composed line + a
  handover-slot reminder after every successful `--write` (additive `activeLine` field in
  `--json`). New `test/active-recipe-line.test.mjs` proves the line derives from the CONFIG, not
  the recommendation.
- **`/agent-workflow-kit review-state`** (`tools/review-state.mjs`, read-only + colocated tests) —
  makes "reviewed ≠ shipped" mechanically detectable. The 2.2.0 review wrappers append one JSONL
  receipt per SUCCESSFUL review to `<git dir>/agent-workflow-review-receipts.jsonl`
  (`AW_REVIEW_RECEIPTS` overrides; never committable by construction); the checker resolves the
  effective `plan-execution.review` recipe, recomputes the canonical **uncommitted-state
  fingerprint** (sha256 over staged diff + unstaged diff + untracked-not-ignored contents — exactly
  the review-payload domain), and `--check` exits 0 only when every recipe-named backend holds a
  **fresh, grounded, current-fingerprint** receipt (**presence, not unanimity** — verdicts stay
  orchestrator judgment). Any later edit stales the receipts; plan/diff receipts and continuations
  (`fresh:false`) are informational-only — after a fold, only a fresh grounded re-run restores
  green. Normative exit contract in the tool header; plan-in-flight detector keyed on the
  documented `docs/plans` naming convention; honest residual stated (`--no-verify`, receipt-file
  deletion — discipline, not a sandbox). The gate line is **never auto-seeded** ([[AD-021]]): the
  template `gates.json` stays empty; the candidate line lives in `Mode: review-state`/`Mode: gates`
  prose. New `test/review-fingerprint-parity.test.mjs` proves bash (both wrappers, byte-identical
  block) ↔ node fingerprint parity — hash, serialization, AND behavioral domain equality.
- **`/agent-workflow-kit grounding`** (`tools/grounding.mjs` + colocated tests) — the
  grounded-review facts assembler, catalogued honestly as a **WRITER**: `--constraints` slices the
  root `AGENTS.md` Hard-Constraints section verbatim (exactly-one-match, else a loud STOP);
  `--plan <path>` extracts the decision-bearing sections (`## Approach` + `## Verification`
  required, `## Decisions (locked)` when present; duplicates STOP); output honors the wrapper's
  `AGY_MAX_PROMPT_BYTES` budget minus `--reserve-bytes` with a loud tail-trim; `--out` accepts only
  gitignored / out-of-repo scratch (a tracked or in-repo not-ignored path is refused — a new
  untracked file would move the fingerprint it grounds). `procedures.mjs` renders the invocation as
  a POPULATED pre-step whenever agy is dispatched (exactly one plan in flight → its path; else a
  placeholder + discovery caveat; additive `groundingPreStep` in `--json`).
- **Bundled bridges 2.2.0** (mirrors byte-refreshed): `codex-review.sh` mandates + parses ONE
  literal `Verdict: ship|revise|rethink` line (schema mode reads the JSON field); `agy-review.sh`
  records the `### Verdict` token verbatim (SHIP / SHIP WITH NITS / REWORK), `grounded` +
  `factsHash` from `--facts` (an empty payload is visible), and marks continuations `fresh:false`
  with a one-line fresh-run notice; a receipt write failure warns and never fails the review. The
  review-role `capability.json` contracts gain the `receipt` block (the fingerprint definition
  home) — three-way lockstep wrapper `--help` ↔ manifest ↔ `detect-backends.mjs` registry,
  drift-guarded.
- **Catalog/report wiring:** two new SKILL modes + README rows; the bootstrap/upgrade report
  footers paste the active-recipe line beside the backend-status line; `package-content` pin
  94 → 96 (the two new tools).

## 1.29.0 — Velocity scope C: an opt-in PreToolUse gate-approval hook

A **feature** release (ships the bundled bridges unchanged at **2.1.0**). The shipped, probe-proven
closure of the velocity trust-posture residual ([[AD-021]] scope C, recorded in **AD-037**) — a
new opt-in `.claude/` writer, the family's third:

- **`/agent-workflow-kit hook`** (`tools/gate-hook.mjs`) — places a **self-contained** hook runtime
  (`references/hooks/gate-approve.mjs` → `.claude/hooks/agent-workflow-gates.mjs`; no kit imports, so
  the placed copy survives an uninstall) and wires ONE `PreToolUse` "Bash" entry into
  `.claude/settings.json`. Velocity writer discipline verbatim: `--dry-run` default, deployment-gated
  `--apply`, symlink-safe, refuses unsafe modes in either settings file, merge-don't-clobber,
  idempotent, never `settings.local.json`, never commits. Place-file-FIRST-then-wire; a malformed
  existing `hooks` shape or a diverged-and-unwired target file is a STOP with zero writes (it refuses
  to wire an unknown script as a hook); the target is re-verified no-follow immediately before wiring.
- **The hook's decision ladder**, read against `docs/ai/gates.json` LIVE per call (one declaration,
  two consumers with *Mode: gates* — editing gates.json never needs re-wiring): **(a)** a command
  BYTE-EXACT to a declared gate `cmd` (trim-only; never a pattern — the rejected AD-021 shape),
  invoked from the project root, under `default`/`acceptEdits` → `allow`; **(b)** a seeded-read-only
  command carrying a documented runtime residual (output redirection, command/process substitution
  `$(…)`/`` ` ``/`<(…)`, or the `--output` write-flag family — matched as a whole-command substring so
  a quoted/escaped form can't hide it) → `ask`, overriding a settings allow rule (**proven live** on
  Claude Code 2.1.185); **(c)** else no decision. Never `deny`. Fail-safe is **decoupled**: a broken
  `gates.json` disables only (a), the guard keeps running; every anomaly exits 0, never 2. Validation
  parity with the runner (`_README` included) — an invalid declaration approves nothing.
- **Integration.** `/.claude/hooks/` joins the hidden-mode footprint registry; `uninstall` gains the
  hook seam (reports the settings edit + preserves a still-wired or non-bundle file, removes only a
  byte-identical unwired one and cleans an emptied `.claude/hooks/`, all lstat-no-follow + AD-011
  preflight; the wired-probe reads DECODED settings JSON so an escaped `\/` path still counts as
  wired); `status` gains one row (wired / file placed / declaration present) through the full
  surface→view-model→renderers pipeline; the velocity residual notice + SKILL/README point at the
  shipped hook instead of a "deferred" one. Kit-only; the runtime + writer ride the tarball.
- **Review.** Council at the diff converged over four rounds (codex + agy, grounded): every finding
  closed one obfuscation-of-a-string-scan class (process substitution, quoted/escaped `--output`,
  JSON-escaped `\/`) or a TOCTOU/symlink window — folded by code with red→green regressions; the
  final round was codex **ship** + agy **SHIP**, 0 blockers / 0 majors.

## 1.28.0 — Lens-mirror guards the checked-vs-unchecked plan boundary

A **feature** release (template + test only; ships the bundled bridges unchanged at **2.1.0**).
The kit's half of the §9 sharpening:

- **`references/templates/agent_rules.md` (B5)** — the byte-identical sharpened lens bullet (see
  the memory 1.9.0 entry): a plan carries only checked syntax plus literal fixture/schema
  fragments a named test validates; un-run, logic-bearing syntax never lives in plan prose.
- **`test/lens-mirror.test.mjs`** — Set-1 gains the two new tokens `checked syntax` +
  `logic-bearing`, pinned inside the lens region of all four files (engine planning §9, engine
  procedures, both templates); template byte-identity unchanged; non-vacuity proven by an
  injected red→green (a broken token fails the guard twice over — region + byte-identity).

## 1.27.0 — Cost-tiered execution: the `gates` runner + the `agents` cheap-lane writer

A **feature** release (ships the bundled bridges unchanged at **2.1.0**). Two new modes move
mechanical work off the frontier lane — one batches every project gate into a single exit code,
the other places cheap-model subagents for extraction work:

- **`/agent-workflow-kit gates`** (`tools/run-gates.mjs`) — the **generic project gate runner**:
  reads the project-declared `docs/ai/gates.json` (`{ id, title, cmd }`, strict schema, unknown
  keys rejected — the declaration names WHAT to check, never who executes it), runs each `cmd`
  as ONE bash line from the project root, prints a per-gate PASS/FAIL table + one
  machine-readable summary line, exits 0 iff all green. A failing gate's own output is preserved
  verbatim; `--only <id>` re-runs a subset; **honest distinct outcomes** for a missing (exit 3,
  recovery named), empty (4), or malformed (5) declaration and a bash-less host (6) — never a
  silent green. Trust posture stated: it executes the project's OWN declared commands — a
  batching convenience, not a sandbox. 33 hermetic tests + one real-spawn brace-glob fixture.
- **`/agent-workflow-kit agents`** (`tools/cheap-agents.mjs`) — the opt-in **cheap-lane subagent
  writer** (the second `.claude/` writer, on the velocity discipline: dry-run default,
  deployment-gated `--apply`, symlink STOPs, never `settings*.json`, never commits). Places
  three bundled vehicles (`references/agents/`): `mechanical-sweep`, `changelog-skeleton`,
  `gate-triage` — each pinned `model: haiku` + `effort: low` + read-only tools (content-tested).
  A diverged existing file is **preserved and reported, never overwritten**. Claude-Code-specific,
  like velocity.
- **`gates.json` seeded everywhere** — `references/templates/gates.json` ships byte-identical in
  kit + memory (template-parity guard); bootstrap seeds it; upgrade **ensures-if-missing from
  the kit's OWN twin** (a stale memory never silently loses the feature) and preserves an
  existing declaration byte-for-byte. Also new on upgrade: a stamp-independent
  **enforcement-script ensure** seeds a missing `archive-decisions.mjs` pair into deployed
  projects (the kit's byte-identical fallback mirror of the memory canon — pinned by the new
  `test/scripts-mirror.test.mjs` across ALL shared reference scripts).
- **The advisor now routes by cost** — `procedures.mjs` renders an unconditional **cost-lanes**
  block (L0 script · L1 cheap subagent · L2 bridge · L3 frontier; cheapest adequate executor; no
  guardrail → no down-move; the red lines) + an additive `costLanes` field in `--json`,
  drift-guarded against the engine canon on both sides. One byte-identical cost-lane bullet
  joined both `agent_rules.md` templates (lens-mirror tokens + an injected red→green non-vacuity
  proof).
- **Footprint registries** — `KNOWN_FOOTPRINT` += `/.claude/agents/` (the vehicles stay
  invisible in a hidden deployment); `KIT_OWN_PATHS` += the two deployed decisions copies;
  snapshots + the `contracts.md` mirror row updated in lockstep. Tarball re-pinned (92 files,
  reverse pins for every new asset).

## 1.26.0 — Deterministic bridge freshness & delivery; machine-composed status line; honest installer messaging

A **feature** release (ships the bundled bridges unchanged at **2.1.0**). One architecture across
four fixes: **the registry computes, the tools speak, the agent pastes** — no factual line on these
surfaces is agent-composed anymore.

- **Bridge freshness is now visible.** `family-registry` compares each placed bridge against the
  kit-bundled mirror (both local files — nothing checks npm): behind → a plain caveat + the runnable
  `/agent-workflow-kit setup` recommend + `refresh.behind:true` in `--json`, reaching the
  bootstrap/upgrade footers and the welcome mat (priority 1 is now caveat-generic and quotes the
  firing note's OWN recovery verbatim); uncheckable → an explicit unknown note (never "current",
  never "behind"); zero-behind → the TOOL prints a checked-scope verdict (`all N checked members are
  current` — any unknown blocks the all-current claim). New dependency-free `tools/semver-lite.mjs`.
- **Placed bridges refresh on `init` and `upgrade`.** A refresh-only driver in `setup-backends.mjs`
  (`--refresh-placed`) refreshes proven-managed placed bridges and NEVER places an absent one —
  placement stays opt-in via `/agent-workflow-kit setup`. `npx … init` calls it best-effort (a miss
  is a loud warning + a recovery command composed from the resolved install target + exit 0;
  `--no-bridges` opts out; win32 is a stated skip); `Mode: upgrade` runs it as a fourth
  stamp-independent reconcile and pastes the output verbatim. **Never-downgrade:** a placed bridge
  NEWER than the bundle is a stated keep + "update the kit", enforced at both the plan and the write
  boundary (TOCTOU re-inspect at apply); an unparseable version is treated as legacy repair, stated.
- **The one-line backend status is machine-composed.** `tools/recipes.mjs --status-line` emits the
  exact line (deterministic order, one alias table; additive `statusLine` in `--json`; strict argv —
  an unknown flag exits loudly instead of masquerading as the human render); SKILL.md now says run
  the tool and paste its line verbatim — the realistic example that once got echoed as fact is
  replaced by an explicitly-placeholder template.
- **The installer speaks facts.** The final verb is keyed on the OBSERVED version comparison
  (installed / updated / refreshed-the-already-current / downgraded-under-`--allow-downgrade`); the
  same-version note states that the copy ran (a re-run repairs locally modified files) + a
  CONDITIONAL `@latest` hint; the false "npx likely served a cached build" accusation is gone. One
  message contract with the engine installer (engine `1.9.0`).
- Lens sync everywhere — "placed by `setup` (opt-in), refreshed by `init`/`upgrade` once placed" —
  across SKILL.md, both READMEs and `family-members.mjs`, guarded by the new region+token
  `test/init-refresh-lens.test.mjs` (non-vacuous, injected red→green proven).

## 1.25.0 — The bridge driving contract at the point of use (advisor render + wrapper `--help`)

A **feature** release (additive; ships the bundled bridges at **2.1.0**). An agent told to run a
bridge no longer re-derives the invocation from wrapper source — where it missed documented levers
(agy's `--facts`/`--decided` grounding, the `agy-review --continue` round-2 delta) and wasted
subscription runs on ungrounded reviews. The contract is now **delivered at the moment a recipe
dispatches a backend**, from ONE machine-readable source:

- **Manifest as source.** Each bridge `capability.json` dispatchable role (`review`, `execute`) now
  carries a structured `contract`: exact copy-pasteable **invocation descriptors** (operands and
  alternatives included), the **grounding** note, the closed **flag** set (agy-review), the
  **round-2 / resume** descriptors, and codex-exec's **tiered guarded passthrough**
  (always-blocked vs `CODEX_PROBE=1`-relaxable). Documented in `tools/manifest/schema.md`.
- **Advisor renders it at the point of use.** `/agent-workflow-kit procedures <activity>` prints,
  under every dispatched backend of every slot (review recipes AND `execute=delegated`), the full
  driving contract VERBATIM — e.g. council shows `agy-review code [--facts @f] [--decided @f] …`
  plus the `agy-review --continue` delta beside `codex-review plan|code`. `--json` carries the same
  in an **additive** `slots[*].contracts` field (`backends: string[]` unchanged).
- **Every wrapper answers `--help`/`-h`** — pre-preflight (no CLI, no login, no git tree, no
  AGENTS.md needed); keyed on the FIRST argument only, so an open wrapper's passthrough payload
  (`codex-exec - -- --help`) is never intercepted. The three **dispatchable** wrappers
  (`codex-review`, `agy-review`, `codex-exec`) print the manifest contract; `agy-run` (probe role —
  never dispatched by a recipe slot) ships a lightweight wrapper-authored help, pinned for
  pre-preflight reachability only, with no manifest pivot by design.
- **Drift-guarded in both directions (test-as-spec), for the dispatchable wrappers.** The kit
  registry mirror (`wrapperContractFor`) deep-equals each manifest; the advisor's rendered
  descriptor set set-EQUALS the manifest (a missing AND a stale-extra descriptor both fail); each
  dispatchable wrapper's `--help` set-EQUALS the manifest; and a **source-level reverse guard**
  extracts each dispatchable wrapper's real parser arms (mode/flag/resume/passthrough-tier `case`
  arms, heredocs excluded) and pins them to the manifest — adding a wrapper mode or flag without
  updating the surfaced contract fails a test.
- Stale "unguarded codex flags" wording in the codex bridge docs corrected to the real **guarded**
  passthrough contract.

## 1.24.0 — Humanize the deploy/version report: hide the internal structure number in the happy path

A **feature** release (report-contract only — no logic, migration, or lineage change; the
deployment-lineage head stays `1.3.0`).

The bootstrap/upgrade report no longer leads with the internal `docs/ai` **structure version**
(`deploymentHead`) — an un-actionable number that leaked into **every** successful report, including
zero-diff no-ops, and read as "smaller than the version on npm/GitHub".

- **Happy path is now plain.** A zero-diff no-op `upgrade` says **settings already current — no update
  needed** (rendered in the user's language); a fresh `bootstrap` keeps its "deployed and ready"
  framing. Neither surfaces the structure semver, the stamp filename, or any head/lineage vocabulary.
- **The number survives only where it is actionable** — the never-downgrade STOP gate and the explicit
  `Mode: status` view — now **named "the `docs/ai` structure version"** (never "lineage head"), paired
  with a plain, on-demand two-axes note (*Version disclosure*). A migration that ran is described in
  **human terms**; the raw number is omitted, never recited on a successful report.
- **The version-status check is framed as internal routing**, not a line printed on every invocation.
- Pinned by a new static contract test (`test/report-contract.test.mjs`, invariants A1–A6).

## 1.23.0 — Surface the review-loop economics + the resolved backend set in the procedures advisor

A **feature** release. `/agent-workflow-kit procedures <activity>` now makes the review-loop discipline
mechanical at the point of use:

- **Backend-set aid.** Each resolved recipe prints its EXPLICIT wrapper set beside the recipe name
  (`review: council → run every backend every round: codex-review + agy-review`; `reviewed → codex-review`;
  `delegated → codex-exec`; solo prints none), so recipe fidelity is visible where the advisor is invoked.
  Sourced from `planRecipe().dispatch` + a new role-keyed `detect-backends.wrapperCmdFor` (reading
  `KNOWN_BACKENDS[].roleCmds`, now the source of truth; the deduped `wrapperCmds` readiness list is derived
  from it). Drift-guarded against each bridge manifest `roles[role].cmd`.
- **Review-loop economics block.** For a review slot resolving reviewed|council (omitted for solo) the
  advisor prints the ≤2-round cap, the bar-met-by-raising-a-major rule, the backend-divergence crossover
  stop, the thin-plan/diff-review carve-out, a self-consistency read, and the required per-round emission
  {round N · finding-origin · per-backend verdict}. `--json` carries per-slot `backends` + a top-level
  `reviewLoop`.
- The fallback `agent_rules.md` **§2.5** lens mirror gains the same review-loop disciplines (byte-identical
  to memory's §2.6).

Read-only; no resolution behaviour change. Deployment-lineage head stays `1.3.0`.

## 1.22.0 — Harden the planning canon: two-set lens drift guard + the deployed lens

A **feature** release. The kit's fallback `agent_rules.md` **§2.5** lens is generalized from *Right-altitude
& code-grounded* to **Planning, review & process-fidelity invariants** (byte-identical to memory's §2.6
block) and now carries all **seven** methodology invariants. The cross-package **`test/lens-mirror.test.mjs`**
is extended to **two scoped, non-vacuous token sets**:

- **Set 1 (cross-all-four)** pins the §9-native review/fold + convergence disciplines in EVERY region —
  planning §9, procedures (`## plan-authoring` onward), and both template lens blocks: `0 blockers + 0
  majors`, `test-as-spec`, `no code-mechanics`, `at the diff`, `characterize-first` (alongside the
  existing `fold by code` / `file:line` / `altitude`).
- **Set 2 (template-scoped)** pins the process-fidelity invariants A1/A2 (`ExitPlanMode`, recipe-fidelity
  `every round`) PRESENT in both template lens blocks — closing the gap the byte-identical check alone
  cannot (it only proves the two templates AGREE, so both could drop A1/A2 and stay green).

The lens heading + the `extractLensBlock` regex move in lockstep. The deployment-lineage head stays
**`1.3.0`** (no `docs/ai` structural change, no migration); the kit **package** version is a separate axis.

## 1.21.0 — Ships the antigravity-cli-bridge 2.0.0 mirror (grounded agy-review)

A **feature** release. The kit now bundles the **antigravity-cli-bridge 2.0.0** byte-identical mirror —
a grounded `agy-review` review wrapper beside the `agy-run` probe. The two-wrapper ripple lands kit-side:
`detect-backends` readiness probes the **EXPECTED** bundled wrapper set (a stale install missing
`agy-review` now reports **DEGRADED**, not a false "ready 1/1"), `setup` / `uninstall` manage **both**
wrappers, `release-scan` allowlists `agy-review`, and the recipes ↔ engine Issue-001 caveat is loosened
(grounded review is a sound second opinion; the service-stall risk is kept). The deployment-lineage head
stays **`1.3.0`** (no `docs/ai` structural change, no migration); the kit **package** version is a
separate axis.

## 1.20.0 — Fallback-template lens + the cross-package lens-mirror guard (kit)

A **feature** release. The kit's fallback `agent_rules.md` template gains the **§2.5 Right-altitude &
code-grounded** lens (byte-identical to memory's §2.6 block), and a new cross-package
`test/lens-mirror.test.mjs` ties the shared lens vocabulary across the engine canon (`planning.md` +
`procedures.md`) AND both `agent_rules.md` templates: each distinctive token must survive in every file's
lens **region**, and the two template blocks must stay byte-identical apart from the heading number. The
deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change, no migration); the kit
**package** version is a separate axis.

- **Non-vacuous guard.** Deleting the lens block (or a distinctive token) from any of the four files
  fails the guard — proven by an injected-divergence dry-run.

## 1.19.0 — One-command freshness: `init` refreshes memory too, and a capability-adaptive `status` (kit)

A **feature** release that closes the returning-user gap and modernizes the status surface. The
deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change, no migration); the kit
**package** version is a separate axis.

- **`init` now leaves no stale core member.** After installing/refreshing the kit, `npx
  @sabaiway/agent-workflow-kit@latest init` also refreshes the **memory substrate** and the
  **methodology engine** over npm — so a returning user is no longer left with silently stale memory.
  The memory refresh is **best-effort: a miss is a loud DEGRADED success** — a warning with the exact
  recovery command (and the on-disk version) plus **exit 0**, never a silent skip and never the engine's
  hard STOP. New **`--no-memory`** flag skips it for air-gapped/scripted installs. The cascade
  membership is derived from the one family registry and drift-guarded; bridges are still placed by
  `setup`, never by `init`.
- **Capability-adaptive `status` output.** The direct-CLI status view (`node tools/family-registry.mjs`)
  is rebuilt as a `surface → view-model → renderers` pipeline: it auto-detects the terminal (plain vs
  ANSI, color via `NO_COLOR`/`FORCE_COLOR`, width with a 40-col floor, ASCII-glyph fallback) and renders
  all four blocks (members · bridges · project deploy/visibility · settings). `--format=<auto|plain|ansi|json>`
  (with `--json` as sugar) selects the surface; unknown flags and a missing `--dir` value now **reject
  loudly** instead of being silently ignored.
- **Additive `--json` freshness signal.** Each `installed[]` entry gains a structural `refresh`
  `{ behind, recommend }` object (derived from the registry, never parsed from a caveat). The
  agent-mediated `/agent-workflow-kit status` reads it to show a localized "needs refresh" label + the
  exact command **once**; every existing envelope field is unchanged.
- **Docs.** Install help + READMEs document the memory/engine refresh, `--no-memory`, the degraded-success
  recovery, and that bridges are not installed by `init`. Tarball **75 → 81** (the pure member-table leaf
  + five status-presenter modules).

## 1.18.0 — Agent-writable orchestration config (`set-recipe`), version-aware setup, durable session contracts (kit)

A **feature** release. The per-project recipe config (`docs/ai/orchestration.json`) is no longer
hand-edit-only: a new **`set-recipe`** writer turns plain-language intent into a validated, previewed,
atomic write — and `setup` now surfaces bridge versions and proactively offers to set the review recipe
when a backend becomes ready. The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural
change, no migration); the kit **package** version is a separate axis.

- **`/agent-workflow-kit set-recipe` (new WRITER).** The agent maps plain language → explicit
  `--set <activity>.<slot>=<recipe>` / `--unset <activity>.<slot>` ops; the kit validates → merges →
  **previews by default** → writes only on `--write`. Split modules: `tools/orchestration-config.mjs`
  (schema/read/pure — the shared slot-recipe validity table + `parseOp` / `applySetOps` /
  `serializeConfig` / the canonical-refresh helpers) and `tools/orchestration-write.mjs` (the **only**
  fs-writer — deployment gate, exclusive-create temp + rename, symlink/TOCTOU-safe, last-writer-wins).
  `procedures` never imports the writer → the read-only invariant is **structural**. Renamed from the
  planned `orchestrate` (it never *runs* a recipe). Hand-editing the file stays fully supported.
- **Setup surfaces versions + closes the loop.** Each skill line shows the bridge version (`(vX)` for a
  fresh place / equal refresh, `(vOld → vNew)` on a bump, never `vnull → …`); a closing pointer at
  `/agent-workflow-kit status`; and — re-detecting AFTER apply — a proactive `set-recipe` offer for
  **both** `plan-authoring.review` and `plan-execution.review` when a review backend just became ready.
- **Canonical-refresh reaches the filled base.** `inject-methodology` refreshes a filled pointer slot to
  the current engine canon when its content matches a known-prior fragment (a customization is preserved
  + advised); the `_README` refresh reuses the same `refreshIfCanonical` helper, and the upgrade
  config-ensure is now seed-**or-refresh**.
- **Docs.** New `### Mode: set-recipe`; `procedures` / `velocity` / README no longer say the config is
  "never written for you". Tarball **72 → 75** (three new `tools/*.mjs`).

## 1.17.0 — Hardened Codex bridge: quality-first delegation, clean capture, enforced git-write boundary (kit)

A **feature** release. The bundled `codex-cli-bridge` (`bridges/codex-cli-bridge/`) is overhauled to
make delegating to the OpenAI Codex CLI faster, quieter, and safer **without lowering output quality** —
economy comes only from quality-neutral waste removal, never a model/effort downgrade. The bridge's own
contract bumps to **`2.0.0`** (MAJOR — the hardened wrappers now *refuse* inputs the `1.0.0` wrappers
silently accepted). The kit's own modes/CLI are unchanged, so the kit is a MINOR bump; the
deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change, no migration). The kit
**package** version is a separate axis.

- **Quality-first — no silent downgrade.** Both wrappers pin frontier `gpt-5.5` @ `xhigh` and **refuse
  with a loud error** if `CODEX_MODEL`/`CODEX_EFFORT` resolves to a non-default, unless the explicit
  throwaway `CODEX_PROBE=1` mode is set (echoed loudly). Outside that probe mode the passthrough guard
  now blocks **every** model/context/policy-affecting flag (`-m/--model`, `--add-dir`, `-C/--cd`,
  `-p/--profile`, `--oss`, …), not just the previous subset.
- **Clean output capture.** `-o` (final message only) + a `--json` event trace +
  `hide_agent_reasoning=true` + `--color never` replace the streamed reasoning transcript; the session id
  is persisted to a sidecar (`CODEX_SESSION_FILE`) for resume; on failure the trace tail is surfaced to
  stderr (no silent failure). Reasoning still runs at `xhigh` — quality unchanged.
- **Hard timeout.** New `CODEX_HARD_TIMEOUT` (`timeout`/`gtimeout`, generous `xhigh`-sized defaults —
  exec `3600` s / review `1800` s, `--kill-after=15s`); a hard kill (124/137) reports `codex exceeded
  hard timeout`. A host with no `timeout` warns and runs uncapped (no silent skip).
- **Precomputed-diff review.** `review code` now assembles the diff itself (`git status` + cached/unstaged
  `git diff` + untracked file **contents**, binary-skipped; a payload above the
  `CODEX_REVIEW_MAX_TOTAL_BYTES` threshold goes via a repo-local temp file, never truncated) and feeds it
  to `codex exec` — killing the agentic
  discovery roaming that read unrelated files (incl. `~/.claude`). Reads stay `read-only` for
  surrounding-file context; a no-change preflight exits before spending a run. Optional structured
  findings via `CODEX_REVIEW_SCHEMA=1` (default off, raw-text fallback).
- **Invariant-preserving resume + enforced git-write boundary.** A dedicated `--resume-last` /
  `--resume <id>` entrypoint re-establishes every wrapper invariant (`--ignore-user-config`, the pin,
  posture restated via `-c`). A **physical `git` shim** (a real executable on a temp `PATH`, since
  `execve` bypasses bash functions) enforces a strict read allowlist and blocks every write verb by
  default — defence beyond the prompt contract.
- **First hermetic bridge tests + tarball `70 → 72` files.** `bridges/codex-cli-bridge/bin/{codex-exec,codex-review}.test.mjs`
  ship as byte-identical mirror payload (matching `agy.test.mjs`); `npm pack --dry-run --json`
  re-verified. The byte-identical bridge mirror + `capability.json` stay valid.

## 1.16.0 — Onboarding & discoverability: `help`, honest versioning, an enriched `status` (kit)

A **feature** release (additive, backward-compatible). Makes the kit self-explanatory: a discoverable
command surface, honest installed-on-this-machine version legibility, and one `status` that answers
"versions + deployment + settings + bridges". The deployment-lineage head stays **`1.3.0`** — nothing
in the deployed `docs/ai` *structure* changed, so there is **no migration**. The kit **package**
version is a separate axis from that head.

- **`/agent-workflow-kit help` + safe unknown-invocation routing.** New `tools/commands.mjs`: a frozen
  command catalog (grouped Inspect / Configure / Orchestrate / Lifecycle, each tagged read-only /
  writer / guarded) + a pure `routeInvocation` router. A discoverable index, and the read-only landing
  spot for any unrecognized invocation — **no unrecognized/garbage token ever reaches a writer/guarded
  mode** (only an explicit known token, or the acknowledged bare-bootstrap exception). Drift-guarded
  against the `### Mode:` headers.
- **Honest version legibility.** `tools/family-registry.mjs` gains a no-leak `--json` envelope (user-safe
  field names only — never the internal manifest/stamp terms) feeding a shared **version block**:
  deployment-structure head · installed package versions per member · the two-axes disambiguation when
  the numbers coincide. An **offline caveat** flags a memory install too old to ship the current
  orchestration template ("installed on this machine", no network). Bootstrap/upgrade now print a
  **welcome mat** (success → version block → backend line → one caveat-aware next step) and bootstrap
  opens with a one-line first-contact orientation.
- **Settings & bridges in `status`.** `status` is now the single answer to "versions + deployment +
  settings + bridges": orchestration recipes (effective per slot), attribution
  (`includeCoAuthoredBy`), velocity (`defaultMode`), the bridges (readiness + wrapper PATH presence,
  no model claim), and **visibility** (visible / hidden / unclear, via `inferVisibility`) — each in
  plain language, **localized-on-error** (a malformed file surfaces its own error, the rest still
  renders). `loadConfig` is shared with the procedures advisor (one strict-JSON reader).
- **Surgical delegation gate.** `references/templates/orchestration.json` joins the memory
  required-asset set: a memory too old to seed `docs/ai/orchestration.json` now **falls back** to the
  kit's bundled substrate (which seeds it) instead of being delegate-classified — closing the
  stale-memory trap the read-only note only informs about.
- **Tarball guard `69 → 70`** (the new shipped `tools/commands.mjs`); `npm pack --dry-run --json`
  re-verified. No `docs/ai` structural change → lineage head unchanged.

## 1.15.2 — Strip the package's own tests + fixtures from the npm tarball (kit)

Packaging only — no API/behaviour change; removed the package's own colocated tests + fixtures from
the published tarball, deploy/mirror payload tests retained. The deployment-lineage head stays
**`1.3.0`** (no `docs/ai` structural change, no migration file). The kit **package** version is a
separate axis.

- **`files[]` scoped negation.** Appended `!bin/*.test.mjs`, `!tools/**/*.test.mjs`, and
  `!tools/manifest/fixtures/**` to the package allowlist (npm ignores a root `.npmignore` when
  `files[]` is present, so negation entries in `files[]` are the mechanism). Tarball **115 → 69
  files**: 18 of the package's own colocated tests + 28 manifest fixtures no longer ship.
- **Deploy/mirror payload tests retained.** `references/scripts/*.test.mjs` (deployed into a
  consumer repo's `scripts/` with the docs-rotation scripts) and
  `bridges/antigravity-cli-bridge/bin/agy.test.mjs` (part of the byte-identical bridge mirror the
  installed kit links from) still ship — a blanket `!**/*.test.mjs` would have silently broken
  installs, so the negation is deliberately scoped. **Never broaden it to `!references/**` or
  `!bridges/**`.**
- **New tarball guard.** `test/package-content.test.mjs` (dev-only; outside `files[]`, never ships)
  pins the exact shape via `npm pack --dry-run --json`: no own-test/fixture leak, payload tests +
  runtime files present, exact file count `=== 69`.
- Test files stay on disk; the gate + publish CI run them from the checkout, unchanged. This is a
  tarball-only exclusion.

## 1.15.1 — Version-axis clarity, hidden-mode invariant, lineage-head drift guard (kit)

Patch: documentation + a regression guard; no behaviour change to shipped tooling, and the
deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change, no migration file). The
kit **package** version is a separate axis.

- **`upgrade` now names the version axis in its report.** Steps 4 (equal-head exit) and 8 (re-stamp)
  state that a project's stamp tracks the **deployment-lineage head** (`1.3.0`) — a separate axis from
  the kit **package** version on npm/GitHub — so an equal-head report is no longer mistaken for a
  stale deployment when GitHub shows a higher package number. A packaging-only release bumps the
  package without moving the head; the head advances only when the deployed `docs/ai` structure does.
- **Hidden-mode maintenance invariant made explicit (Visibility contract).** Hidden mode changes only
  what *git* sees — never the agent's duty to read/maintain `docs/ai`. "Git-ignored / `git status`
  clean" is **not** "optional to update"; those updates simply live on disk and never enter a commit.
- **New cross-package drift guard** (`test/lineage-head-drift.test.mjs`): asserts the kit's
  `EXPECTED_WORKFLOW_VERSION` equals the canonical `LINEAGE_HEAD` in `agent-workflow-memory`, so a
  future head bump can't silently desync the two duplicated literals (which would make
  `velocity --apply` reject a correctly-upgraded project). Runs in the gate; lives outside the package
  `files` whitelist, so it is never shipped.

## 1.15.0 — Velocity-profile onboarding (kit)

An opt-in **`/agent-workflow-kit velocity`** mode seeds a fixed, audited **read-only** Claude Code
allowlist into `.claude/settings.json` so an agent stops idling on approval prompts for routine
read-only commands while the maintainer is away. It never allowlists `commit`/`push`/`publish`, so a
direct invocation still ASKs — the only caveat is the trust-posture residual (below), closed by a
deferred hook.

### Added
- **`tools/velocity-profile.mjs`** — the pure core (a frozen 18-entry `UNIVERSAL_READONLY_ALLOWLIST`,
  the `screenAllowlistEntry` read-only screen, the read-only `discoverGateCandidates` gate advisor, and
  the `validateProfile` drift guard) **plus** the programmatic settings writer + CLI
  (`[--dry-run | --apply] [--accept-edits] [--cwd <dir>]`). Strict **preflight-then-mutate**:
  merge-don't-clobber, opt-in `acceptEdits`; refuses an unsafe `permissions.defaultMode`
  (`bypassPermissions` / any non-`{default,acceptEdits,plan}` mode present in **either** settings file),
  a symlinked `.claude`, malformed settings JSON, or a non-current deployment stamp on `--apply`. Writes
  **only** `.claude/settings.json`, never `settings.local.json`.
- A **`### Mode: velocity`** section + a `## Modes` dispatch entry + a one-line opt-in bootstrap offer
  in `SKILL.md`.
- The guarded **`uninstall`** now also reports `permissions.defaultMode`/`permissions.allow` in
  `.claude/settings.json` **non-committally** (REPORT_ONLY, never auto-removed — the writer stores no
  ownership marker).

### Honesty
- This is the family's **first programmatic `.claude/settings.json` writer** — a new writer subsystem
  with its own tests and teardown reporting, **not** merely an extension of the attribution prose seam.
- The audited core is **read-only by intent — verified, not assumed** (no mutating command, no inline
  code execution): build-time probes proved `git grep` (`--open-files-in-pager`) and `sort`
  (`--compress-program`) give inline code execution; both were dropped (the core is 18, not 20).
  `git diff`/`log`/`show` are kept with a documented bounded-write (`--output`) residual.
- A seeded read-only allow entry is a **trust posture, not a sandbox**: Claude Code's settings-level
  allow rules do not inspect output redirection (`cmd > file`) nor command substitution (`cmd $(…)`),
  so that residual is surfaced honestly in the consent copy, bounded by `acceptEdits` staying opt-in,
  and **fully closed only by a deferred PreToolUse hook** (a recorded follow-up). `commit`/`push`/
  `publish` are never added as allow rules.

Lineage head stays **1.3.0** (no `docs/ai` structural change; no migration). See AD-021.

## 1.14.0 — Activity procedures: recipe-aware, configurable playbooks

A new read-only **`/agent-workflow-kit procedures <activity>`** advisor turns a bare command like
"write a plan" into a codified, recipe-aware procedure. It reads the named activity's ordered steps
**live** from the installed engine (`references/procedures.md`) and prints them verbatim, then resolves
the **effective recipe per slot** from a new per-project, hand-edited config and the read-only backend
detector. Two v1 activities: **`plan-authoring`** (slot: `review`) and **`plan-execution`** (slots:
`execute`, `review`). It composes with the AD-018 recipes; **`recipes` stays read-only** (the config is
hand-edited, never written by the kit).

### Added
- **`tools/procedures.mjs`** — the read-only CLI: live engine read + per-activity section extraction,
  config IO + validation, and the resolved recipe per slot (default = Reviewed when a backend is ready,
  Council on request, slot-aware incl. Delegated). A repeatable `--override <slot>=<recipe>` adjusts one
  slot per run. Exit codes: `0` success (an unsatisfiable override degrades **loudly** but still `0`),
  `2` usage (unknown activity / bad `--override`), `1` config or engine error (loud `path: reason`).
- **`docs/ai/orchestration.json`** — the per-project, strict-JSON config (`{ activity: { slot: recipe } }`;
  all slots optional; an optional `"_README"` is allowed + ignored). Hand-edited; kit-validated.
- **`resolveActivityRecipe` / `ACTIVITIES` / `SLOT_RECIPES`** in `tools/recipes.mjs` — the pure resolver
  (graceful default vs loud override degradation), drift-guarded against the engine canon's `Slots:`
  lines. `planRecipe` / `recommendRecipe` are unchanged.
- A **`workflow:methodology`** pointer clause routing to `/agent-workflow-kit procedures <activity>`
  (the feature's only auto-discovery route — both engine + kit are `disable-model-invocation`).

The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file). See
**AD-019**.

## 1.13.0 — Orchestration recipes: a named way to compose the bridges

The kit now knows **how to put the optional execution-backends to work**, not just whether they're set
up. A new read-only **`/agent-workflow-kit recipes`** advisor presents four named recipes — **Solo**
(no backend), **Reviewed** (one backend reviews), **Council** (both review, you synthesize), and
**Delegated** (a backend executes a bounded sub-task) — plans the right one for your environment, and
**degrades gracefully with a stated reason** when a backend isn't ready (Council → Reviewed → Solo;
Delegated → Solo). It offers the choice (a multiple-choice prompt where your agent supports it) and
prints exactly what running it entails, including advisory quota/health notes. It is **read-only**:
the orchestrator runs the chosen recipe through the bridge skills and always makes the single commit —
the kit never executes a recipe and never runs a subscription CLI.

Every deployed `AGENTS.md` now also carries a one-line **orchestration-recipes pointer** (right under
the methodology pointer), reconciled live from the engine on bootstrap + upgrade. And the read-only
backend-status line that bootstrap/upgrade already print gains an **actionable tail** — e.g.
*"recipes: Reviewed available (via codex) — see /agent-workflow-kit recipes"* — so you're nudged
toward the recipe that fits, never left guessing.

Both entry-point templates were trimmed for headroom so both pointers fit inside the 100-line cap; if
an entry point is already at the limit, the orchestration pointer is **skipped and reported** (never
silently) while the methodology pointer still lands. The deployment-lineage head stays **`1.3.0`** (no
`docs/ai` structural change; no migration file). See **AD-018**.

## 1.12.0 — See the whole family, and uninstall it cleanly

Two new in-agent modes, built on a single **unified family registry**. `/agent-workflow-kit status`
shows — read-only — which family members (kit / memory / engine / the two bridges) are installed, at
what version, and (in a project) what is deployed (`docs/ai`, the version stamps, the hidden-mode
fence). `/agent-workflow-kit uninstall` is the **guarded teardown**: it reverses what `init` and
`setup` placed — installed skill dirs + bridge wrappers, and in a project the hidden-mode fence + the
marker pre-commit hook — but it **never deletes user-authored content** (`docs/ai`, `AGENTS.md`, your
`.claude/settings.json`); for those it prints the exact commands and lets you run them. It removes only
what is **provably ours** (a valid manifest, name + kind match; a wrapper symlink that points at our
source) — anything else is left untouched — and it **previews with `--dry-run` and preflights before
it touches anything**, so a conflict makes zero changes. The deployment-lineage head stays **`1.3.0`**
(no `docs/ai` structural change; no migration file). See **AD-017**.

### Added
- `tools/family-registry.mjs` — the unified, kit-owned registry over every family member (the
  `KNOWN_BACKENDS` precedent, generalized to all five). Resolves each member's `detect.installed`,
  manifest health, and installed version; powers `/agent-workflow-kit status`. A drift-guard test pins
  it to the five in-repo `capability.json` files.
- `tools/uninstall.mjs` — the guarded uninstaller behind `/agent-workflow-kit uninstall`: a pure
  classifier (`buildPlan`) + a preflight-then-mutate executor (`executePlan`). Four surface classes —
  safe-remove (provably-ours skill dirs), managed-marker (wrapper symlinks / the hidden-mode fence /
  the marker hook), report-only (never-deleted user content), and stop (present-but-not-ours).
- `tools/fs-safe.mjs` gains `removeTreeManaged` + `unlinkManaged` — the symlink-safe inverses of
  `copyTreeRefresh` / `linkManaged` (refuse to delete through a symlink or outside the root; remove
  only a symlink whose target is ours).

### Changed
- `tools/manifest/validate.mjs` exports `readAuthoritativeVersion` so the registry reports an installed
  member's version from the same authoritative source the validator checks.
- The kit's own `capability.json` now declares `uninstall.removeResolved` (uniform with memory +
  engine); the guarded uninstaller's behavior matches it — it removes exactly the resolved
  `detect.installed` dir, so the long-declared teardown is now realized, not just documented.

The bounded methodology fragment the kit writes into a project's `AGENTS.md` is now read **live from
the installed `@sabaiway/agent-workflow-engine`** — the family's single source of truth. The kit's old
bundled mirror of that text (and its drift-guard) is **retired**: there is exactly one copy now, in the
engine. `npx @sabaiway/agent-workflow-kit@latest init` installs the engine as a **core** part of the
kit (it is core methodology, not an optional backend — deliberately diverging from AD-011 §5), so the
slot can always be filled. The read is **lazy + fail-loud**: the engine is consulted only when a slot
actually needs filling — a deployment whose slot is already filled upgrades to a **zero-diff no-op even
without the engine** — and when a fill *is* needed but the engine is absent/invalid the reconcile
**STOPs** with the exact install command, never a silent fallback. The deployment-lineage head stays
**`1.3.0`** (no `docs/ai` structural change; no migration file). See **AD-016**.

### Added
- `tools/engine-source.mjs` — resolves the installed engine via the family `detect.installed` pattern
  (env `AGENT_WORKFLOW_ENGINE_DIR` → `~/.claude/skills/agent-workflow-engine`, **not** an npm
  dependency), validates it with the kit's own manifest validator, and reads the live fragment —
  throwing a loud, actionable error (with the install command) when the engine is needed but absent.
- `npx … init` now installs the engine after placing the kit. `--no-engine` opts out (the live read
  then STOPs until the engine is installed by hand). An install failure **retries once**, then fails
  loudly with concrete recovery steps and a non-zero exit (the kit itself is already on disk).

### Changed
- `tools/inject-methodology.mjs` sources the fragment live from the engine (a lazy `slotNeedsFill`
  guard), not a bundled file. `SKILL.md` / `README.md` rewired to the live-read reality; the
  `init-command-uses-latest` drift-guard now also covers the engine's `init` command.

### Removed
- The bundled mirror (`references/planning.md` + `tools/methodology-slot.md`) and its drift-guard
  `test/methodology-mirror.test.mjs` — retired in favor of the live read.

### Honesty
- `init` now contacts a server (it fetches the engine over npm) and the kit gains a **runtime
  dependency on the installed engine**; the "nothing contacts a server" / "no new dependency" notes
  were scoped accordingly. The stale-version gate stays no-network, and there is still no telemetry.

## 1.10.0 — Hidden mode covers the full AI/agent footprint, project-local

Hidden visibility now hides the **full AI/agent footprint** — the kit's own artifacts **and** every
known foreign tool's files (Claude skills, Cursor, Windsurf, Gemini, Copilot, Aider, Continue, …) — in
**one managed block in the project-local `.git/info/exclude`**, never the machine-global
`core.excludesFile` (which leaked the same rules to every repo on the host). **AD-014** amends
**AD-006** and generalizes the `.claude/skills/` one-off (AD-013). The deployment-lineage head stays
**`1.3.0`** — this is a stamp-independent reconcile wired into bootstrap + the upgrade flow (the AD-010
methodology-slot precedent), so there is **no migration file**.

### Added
- `tools/known-footprint.mjs` — the `KIT_OWN_PATHS` + `KNOWN_FOOTPRINT` registry (+ `patternToProbe` /
  `expandGlob` / `matchesKnownGlob`), guarded by a frozen-snapshot + count-sentinel drift test.
- `tools/hide-footprint.mjs` — the single hide-writer. Classifies each path (tracked → **ASK** with the
  printed `git rm --cached`; present generic-name → **ASK**; else **hide**), writes one re-derived
  managed fence (a clean re-run is byte-identical / zero-diff), `migrateFromGlobal` (detect + report the
  residual machine-global block by default; `--remove-global` removes it with a printed backup),
  `--reconcile` (upgrade-time visibility inference: visible → zero bytes, ambiguous → ASK),
  `--unhide`, `--include`. Unit + real-`git` integration tests (worktree, precedence, delegated memory).

### Changed
- `references/contracts.md` Visibility contract rewritten (project-local; full footprint table);
  `SKILL.md` bootstrap step 9 + the upgrade reconcile now drive the tool; root + kit READMEs corrected.
- A **tracked** file is never silently un-tracked — the tool prints the `git rm --cached` it will not run.

## 1.9.1 — Front-door value framing for the optional bridges; kit flow-pointer

The optional execution-backends (the `codex` / `agy` bridges) were **listed** but never **sold**: a
reader couldn't tell what they add to the workflow or why they'd want them. Promoted per **AD-009**
altitude — value lives at the **family front door**, the per-package page stays a manual.

- **Root README** — the existing `## 🔌 Optional delegated execution (the bridges)` section now
  frames what the bridges add to **plan → execute → review**: an *independent reviewer* (a second
  opinion in the **review** phase) and a *delegated executor / parallel hand* (a bounded task to
  `codex exec` in the **execute** phase), under your own subscription (no pay-as-you-go billing,
  subject to each provider's quotas). The honesty caveats are unchanged (`init` bundles but never
  places a bridge; link-only `setup`; third-party services; context-file priority).
- **Kit README** — one manual-altitude flow-pointer in the composition-root bridge bullet: the
  bridges plug into the **execute** and **review** phases, routing **up** to the front door for the
  *why*. No value pitch duplicated into the manual (AD-009 anti-drift).

Documentation change only — no code, detector, or `init`/npx behaviour change, no `docs/ai`
structural change, deployment-lineage head stays **`1.3.0`**, `agent-workflow-memory` untouched, no
migration. The **kit** README + metadata ship in the kit tarball (the root README is the GitHub
family front door, outside the package), so the kit README edit rides a patch bump — three version
sources in sync.

## 1.9.0 — `upgrade` surfaces the optional backends at every successful exit

`/agent-workflow-kit upgrade` said **nothing** about the optional execution-backends (the `codex` /
`agy` bridges). A maintainer running `upgrade` on a fresh machine got a full report with **zero**
bridge mention — because when a deployment is **already current** (by far the most common case),
`upgrade` stopped *before* its final report. Bootstrap already prints a read-only one-line backend
summary; `upgrade` never adopted it.

- **The already-current exit is now a real success report**, not a silent stop: it states in plain
  language whether the workflow-methodology pointer was added, was already present, or was skipped
  because the entry point is over its line limit; prints the **one-line backend-status line**; and
  asks before committing when anything changed (otherwise it says "already up to date" and still
  prints the read-only line).
- **The full-migration exit** prints the **same** one-line backend-status line before the commit
  gate — so **every** successful `upgrade` exit now surfaces what's set up vs missing, mirroring
  bootstrap's summary verbatim
  (`backends: codex ✓ ready · antigravity ✗ needs-credentials — run /agent-workflow-kit backends`).
  Both exits share one definition in `SKILL.md`, so the line stays identical everywhere.
- **Detection-only, honesty-safe.** The line is **read-only · never blocks the commit gate · never
  runs a subscription CLI · the pointer is the in-agent `backends` mode, never a network fetch ·
  `init`/npx is unaffected (it still never places a bridge).** If the **agent host** can't run the
  detector (no `node` on its PATH, or the detector errors), the line is skipped with a
  plain-language reason — never a silent skip (Hard Constraint).
- **README "Use" table:** the `upgrade` row notes the read-only backend-status line (never installs
  a bridge — set one up with `/agent-workflow-kit setup`).

Agent-procedure / documentation change only — no detector or `init`/npx behaviour change, no
`docs/ai` structural change, deployment-lineage head stays **`1.3.0`**, `agent-workflow-memory`
untouched, no migration.

## 1.8.2 — Upgrade DX: graceful, plain-language handling when the methodology slot can't fit the cap

On a real `upgrade`, a project whose `AGENTS.md` was already over its 100-line cap hit the
methodology-slot `reconcile`'s **cap refusal** (adding the bounded pointer would push the file to 109
lines). The tool behaved correctly — it refused and left the file byte-for-byte unchanged — but the
upgrade procedure had **no instruction for this exit path**, so the agent improvised: it surfaced a
confusing, kit-internal multiple-choice prompt to the user (ADR ids, tool / operation names, marker
terminology) that a third-party user has no vocabulary to answer.

- **Defined the cap-refusal path in `SKILL.md` (upgrade step 3):** a cap-exceeded `reconcile` refusal
  is now a **soft, explicitly-reported skip — not a STOP** (a malformed slot / missing-or-duplicate
  anchor still STOPs, unchanged). The upgrade continues without the slot; the skip is reported in
  plain language in the final report — the methodology is already documented in
  `docs/ai/agent_rules.md`, and trimming the entry point then re-running adds the pointer. It is
  **not** silent (Hard Constraint — no silent failures). The reported line count is the file's
  **current** size, not the tool's would-be post-injection number, and any remaining mandatory
  `AGENTS.md` edit must keep the file ≤100 lines rather than bust the cap to land a migration.
- **No-Node manual path:** count the lines before pasting the slot by hand — skip + report if it
  would take the file over the cap.
- **New Gotcha — communication firewall:** never surface ADR ids, tool / function / operation names,
  marker / slot / fragment / anchor terminology, or verbatim tool stderr to the user; translate every
  tool outcome into plain language.

Agent-procedure / documentation change only — no `inject-methodology.mjs` behavior change (the tool
was already correct), no `docs/ai` structural change, deployment-lineage head stays **`1.3.0`**, no
migration.

## 1.8.1 — Fix: `npx … init` ran nothing (the installer's own run-guard mis-fired under npx)

1.8.0 set out to fix "`npx <pkg> init` quietly did nothing" — and shipped a *second*, unrelated
silent no-op in the same spot. The reported symptom: `npx @sabaiway/agent-workflow-kit@latest init`
installs the package, prints the npx "Ok to proceed?" line, and then **prints nothing and does
nothing** — none of 1.8.0's new DX messaging, no install, exit 0.

Root cause: the bottom-of-file run-guard that gates `main()` so importing the module has no side
effects:

```js
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
```

npx never runs `bin/install.mjs` by its real path — it runs the `node_modules/.bin/agent-workflow-kit`
**symlink** to it. Node resolves `import.meta.url` to the real file but leaves `process.argv[1]` as the
symlink path, so the string compare is always false, `main()` never runs, and the process exits 0
without a word. (Running `node bin/install.mjs` directly — as the test suite did — has no symlink, which
is why every test passed while real `npx` was broken.)

- **Fix:** the guard now compares **real paths** (`realpathSync` on both sides), which collapses the
  `.bin` symlink so direct and npx invocations both register as a direct run; it also holds under
  `--preserve-symlinks`. Import-with-no-`argv[1]` and a missing file still fall through to `false`, so
  importing the module continues to run nothing.
- **Regression test:** a new case invokes the installer **through a symlink** (the exact `.bin` shim
  npx uses) and asserts it both prints and writes the payload — the previous suite never exercised a
  symlinked invocation, so the bug slipped through.

Installer bugfix only — no `docs/ai` structural change, deployment-lineage head stays **`1.3.0`**, no
migration.

## 1.8.0 — Stale-version DX: `@latest` everywhere + a no-network never-downgrade gate

A returning user ran the headline `npx @sabaiway/agent-workflow-kit init` and it quietly did nothing:
a bare `npx <pkg> init` (no `@latest`) reuses the npx cache and re-runs an **older cached build** of
the installer, which exits 0 and reports it "updated" — to the same stale version. This release makes
that mistake hard to miss while **the installer itself stays 100% network-free** — the only thing
that ever contacts npm is npx resolving `@latest`, exactly as it already does (the no-phone-home
principle is preserved; see AD-012):

- **`@latest` is the documented default everywhere.** Every prescribing surface (both READMEs, the
  bridge `SKILL.md` files + their bundled mirrors, the installer `--help` / header) now shows
  `npx @sabaiway/agent-workflow-kit@latest init`. A new drift guard
  (`test/init-command-uses-latest.test.mjs`) fails the build if a bare form sneaks back in (historical
  contexts — CHANGELOG / `releases/` / `migrations/` — are exempt).
- **Never-downgrade gate (no network).** `init` reads the installed skill's version from
  `SKILL.md` **before** writing; if the installed kit is **newer** than the version you ran (the exact
  stale-cache signature), it **refuses** (nonzero) and points at `@latest`, rather than silently
  overwriting a newer install with old code. `--force` overrides. A legacy install with no version
  stamp still upgrades cleanly.
- **No-op re-run hint.** When `init` refreshes the skill with the *same* version it already had, it
  says so and points at `@latest` — the no-network signal that catches the reported scenario.
- **In-agent skill** (`SKILL.md`): surfaces a one-line version status (project `docs/ai/.workflow-version`
  vs the lineage head) + routes (bootstrap / upgrade / current), spells out the **two independent
  version axes** (project deployment vs kit freshness — the latter is the npx installer's job, never
  this skill's), and tells you to **restart the session** after refreshing the kit so the new skill
  files load.

New users are unaffected (an empty npx cache already fetches `latest`); this targets the returning-user
trap. No `docs/ai` structural change → the deployment-lineage head stays **`1.3.0`**; no migration.

## 1.7.0 — Link-only backend auto-setup; bridges bundled in the tarball

The optional execution-backend bridges (`codex-cli-bridge` → `codex`, `antigravity-cli-bridge` →
`agy`) can now be set up from the kit itself, via a new **opt-in, in-agent** mode —
**`/agent-workflow-kit setup [backend]`** (`tools/setup-backends.mjs`). It owns only the two
**deterministic, secret-free** steps and **guides** the rest (AD-011):

- **Bridges are bundled in the kit's npm tarball** under `bridges/<name>/` — a **byte-identical
  mirror** of the repo-root bridges, pinned by `test/bridges-mirror.test.mjs` (the same drift-guard
  pattern as the methodology mirror). So `setup` places a bridge from local files, with **no network
  fetch**. `init` (npx) bundles them but still **does not place** them — that stays the opt-in
  `setup` job (preserving the honest `init` ≠ deploy claim).
- **`setup` places/refreshes the bundled bridge skill**, but only into a dir that is **absent /
  empty / proven-managed** (valid manifest, matching `name`+`kind`); a stub/foreign/invalid/
  unsupported manifest, a marker fs-error, a non-empty unknown dir, or a symlinked dir → **STOP**,
  never overwritten. Refresh re-runs on a managed dir so re-running `setup` delivers bundled fixes.
- **It links the wrappers** (`codex-exec` / `codex-review`; `agy-run`) onto `PATH` (`~/.local/bin`,
  override with `--bindir`) via **managed symlinks** — replacing only a symlink already pointing at
  our source. It **preflights every target first**, so a conflict on one wrapper makes **zero**
  changes; a non-symlink or a foreign symlink → STOP. Wrapper presence is judged **per-bindir**, not
  PATH-wide. `--dry-run` prints the plan and changes nothing.
- **The binary install + the interactive subscription login stay manual** — `setup` prints the exact
  commands (the detector's axis-aware `guideFor`), never runs a subscription CLI, never commits. On
  **Windows** it reports *unsupported — use WSL* and mutates nothing (the wrappers are POSIX `.sh`).
- Internal: the symlink-traversal-safe copy/link primitives are now shared in `tools/fs-safe.mjs`
  (the npx installer consumes them and gained an `isDirectRun` guard so importing it runs nothing).
  The per-package publish workflow now gates the kit on its **whole** test suite, not just the
  shipped enforcement scripts.

No `docs/ai` structural change → the deployment-lineage head stays **`1.3.0`**; no migration.

## 1.6.0 — Methodology slot reconciliation; engine becomes the canonical methodology home

The workflow methodology now has a **single canonical home** in `agent-workflow-engine`
(`available:false` — content only, not yet published or wired live), and the kit keeps
**byte-identical mirror copies** so the existing injection + fallback keep working with **no new
runtime dependency**. A drift-guard test (`tools/methodology-mirror.test.mjs`) pins the mirrors to
the engine canon: `references/planning.md` and `tools/methodology-slot.md` must equal their engine
counterparts byte-for-byte.

The user-facing win is **stamp-independent slot reconciliation**. A single atomic, idempotent kit
operation now **ensures the `workflow:methodology` slot exists and is filled** in a deployed
`AGENTS.md`, on **bootstrap** and on **every upgrade**:

- **`tools/inject-methodology.mjs`** gains `METHODOLOGY_ANCHOR`, `EMPTY_SLOT`, `ensureSlot`, and
  `reconcileSlot` (reusing the existing `findSlot` / `injectMethodology` / `extractSlot` marker
  parser — no second parser). `reconcileSlot` = **ensure the slot exists** (insert an empty marker
  pair right after the Session-Protocols anchor when a legacy entry point lacks one) → **inject the
  bounded fragment ONLY IF the slot is empty** (a filled / user-customized slot is preserved
  verbatim) → **cap-check** (`AGENTS.md` ≤ 100 lines). On a malformed slot or a missing / duplicate
  anchor it **STOPs with an error and never edits** — the file is left byte-for-byte unchanged.
- A new CLI mode — `inject-methodology.mjs reconcile <AGENTS.md>` — runs that policy as **one
  atomic write** (temp + rename); there is no partial state where markers exist but the fill failed.
- The kit **fallback** entry-point template (`references/templates/AGENTS.md`) now ships the **empty
  methodology slot** (matching memory's template) instead of an inline methodology line, so a fresh
  fallback bootstrap gets a slot the kit fills. A new test (`test/fallback-template-cap.test.mjs`)
  pins that template — empty and filled — under the 100-line cap.
- **Bootstrap** and **upgrade** (`SKILL.md`) now run `reconcile`. On upgrade it runs
  **before** the lineage short-circuit, so the slot is reconciled on every upgrade — reaching even
  legacy **`1.3.0`** deployments — **without bumping the deployment-lineage head**.

The deployment-lineage head **stays `1.3.0`** and `agent-workflow-memory` is **untouched** (no code,
version, or migration change): reconciliation is stamp-independent, so it needs no head bump (which
would have forced a memory republish, since the head is hard-coded in memory's stamp module).
Additive — no user-facing break. The engine's npm packaging, `available:true`, and the live
`kit → engine` read selector are deferred to the next plan.

## 1.5.2 — README uplift to front-door grade (docs)

Docs-only patch. The npm-facing `README.md` is uplifted to match the GitHub family front door's
pitch and voice while staying the kit's **manual**: a stronger hero, a compact "Part of the
agent-workflow family" callout, a new **composition-root** section (the kit delegates to the memory
substrate, injects the methodology, and detects the optional `codex` / `agy` bridges — all on the
in-repo deploy, never on `npx … init`), a two-tier cross-agent note, and links **up** to the family
front door instead of re-telling the whole-family story (AD-009). Accuracy passes hold: `init` ≠
project deploy, the scoped `dependency-free` / `no telemetry` claims, bridges-as-skills, the
`available:false` engine stub, and the bridge context-file priority. A new dev-only test
(`test/readme-structure.test.mjs`) enforces fenced-ASCII width ≤ 78, in-page anchor resolution, and
local-link existence across the published READMEs. No code, schema, or deployed-payload change; the
deployment-lineage head stays `1.3.0` (no migration).

## 1.5.1 — README hero fix (docs)

Docs-only patch. The hero showed a hardcoded `v1.4.0` chip while the kit was 1.5.0; the chip is
removed (the shields.io npm-version badge already shows the live version). A repo test
(`test/readme-no-stale-version.test.mjs`, dev-only — not shipped) now asserts no published README
hero carries a pinned `vX.Y.Z` chip, so the drift can't recur. No code, schema, or deployed-payload
change; the deployment-lineage head stays `1.3.0` (no migration).

## 1.5.0 — Backend detection (detect + guide)

The kit's onboarding can now **see the optional execution-backends** — the thin bridges to
subscription CLIs (`codex-cli-bridge` → `codex`, `antigravity-cli-bridge` → `agy`) — instead of
being blind to everything but the memory substrate. **Additive and read-only**: no `capability.json`
schema change, no validator change, no auto-install. Since nothing in the deployed `docs/ai/`
structure changes, **no migration is needed** and the deployment-lineage head stays `1.3.0`
(`upgrade` reconciles and re-stamps with nothing to apply).

- **`tools/detect-backends.mjs` — the read-only detector.** Pure, dependency-injectable,
  dependency-free (Node ≥ 18), and already shipped (it lives under `tools/`, which is in the
  package `files` + the installer `PAYLOAD`). It reports two **decoupled** axes so a healthy
  manifest is never confused with a usable backend: `manifestState` (health of the bridge *skill*:
  `not-installed | unsupported-schema | invalid-manifest | foreign | stub | ok`) and the readiness
  signals `cli` / `credentials` / `wrappers`, probed **independently** for every registry entry even
  when the skill is absent — so "the `codex` CLI is installed and signed in, but the bridge skill
  isn't" reads as `needs-skill`, with the setup pointer. Every fs probe is wrapped → an explicit
  `unknown` + reason, never a throw and never a nameless failure.
- **Detection is read-only — it never runs a subscription CLI.** "credentials present/missing" is
  the existence of the credential-marker **file**, never a live `codex login status` / `agy` check
  (which would spawn a paid, slow, networked CLI). The report deliberately never says
  "authenticated" (a unit test asserts the word's absence).
- **Kit-owned registry (`KNOWN_BACKENDS`), not a schema change.** A missing bridge has no manifest
  on disk and no `setup/README.md` in the kit tarball, so the per-backend facts (`bin`, credential
  marker, stable setup URL) must live in the detector. A **drift-guard** test keeps the registry in
  lockstep with the in-repo manifests (set equality with every `kind:execution-backend` dir, unique
  names, `detect.installed` match, `setup/README.md` exists).
- **Two surfaces.** A new **`/agent-workflow-kit backends`** mode presents the table and, for any
  backend that is not `ready`, points to its setup (local `setup/README.md` when installed, else the
  setup URL). Bootstrap **step 11** also prints a one-line backends summary — read-only, and it
  **never blocks the commit gate**. Honest about Windows: detection works, but the bridges' POSIX
  `.sh` wrappers are not promised to run there.

## 1.4.0 — Delegation-aware composition root (agent-workflow family, Plan 1)

The kit becomes the **composition root** of the new `agent-workflow` family. **Additive** — the
kit keeps its entire bundled substrate as a fallback, so the existing one-command install is
unchanged and **no migration is needed** (`upgrade` reconciles and re-stamps; the deployment
lineage head stays `1.3.0`). Published from the new `agent-workflow` monorepo.

- **Memory extracted to `@sabaiway/agent-workflow-memory`** — the memory substrate (`docs/ai/`,
  the entry point, caps / archive / index, the three setup contracts) now also ships as its own
  package. The kit **delegates** substrate deployment to it when a **kit-owned detector** finds it
  valid, and otherwise uses its own bundled copy. Detection runs the kit's **own shipped**
  `tools/manifest/validate.mjs` (never a validator shipped by the candidate) and requires
  `kind: memory-substrate` **valid** plus all required assets present; unsupported / invalid /
  unavailable / wrong-family / wrong-name → bundled fallback. The fallback decision is made
  **before** any project write.
- **Family manifest contract** — every member ships a `capability.json` (`schema 1`, JSON,
  dependency-free). The kit **owns and ships** the schema + validator at `tools/manifest/`
  (in the tarball + installer `PAYLOAD`, so an installed kit can run the detector; root CI invokes
  the same file). The kit's own manifest is `kind: composition-root`.
- **Methodology slot injection** — memory ships an **empty** delimited `workflow:methodology` slot
  in `AGENTS.md`; the kit is its **only** writer, injecting a **bounded** summary + pointer
  (`tools/inject-methodology.mjs` + `tools/methodology-slot.md`) that keeps `AGENTS.md` under its
  ≤100-line cap. Marker contract: exactly one ordered pair → replace between; absent → no-op;
  malformed → no-op with an error.
- **Two-stamp delegation hand-off** — delegated mode: memory writes `.memory-version`, the kit
  injects + writes the fallback `.workflow-version` (→ both stamps); fallback mode: `.workflow-version`
  only. Exactly **one** composition-level commit gate, owned by the kit, after injection. The
  decision + hand-off matrix is codified and unit-tested in `tools/delegation.mjs`
  (`detectMemory` + `handoffPlan`), so it does not depend on agent interpretation.
- **Release gate — attribution-off** — `tools/release-scan.mjs` fails on AI/reviewer attribution
  (co-author trailers, "Generated with <AI>" footers) anywhere in the release tree, so no agent
  attribution can ship by accident.
- **Hardened installer** — `copyRecursive` never writes *through* a destination symlink
  (root / intermediate / leaf). `capability.json` + `tools/` added to `files` and the installer
  `PAYLOAD`. `repository`/`homepage`/`bugs` repointed to the `agent-workflow` monorepo.

## 1.3.0 — Skill authoring aligned with Anthropic's Skills guidance

Internal refinements to how the kernel itself is written — no change to what gets deployed into a
project, so **no migration is needed** (`upgrade` reconciles and re-stamps to `1.3.0` with nothing
to apply). Drawn from [*Lessons from building Claude Code: how we use Skills*](https://claude.com/blog/lessons-from-building-claude-code-how-we-use-skills).

- **Consolidated Gotchas section in `SKILL.md`** — the blog calls the Gotchas section "the highest-signal content in any skill". The non-obvious traps that were scattered through the procedure (harness-added `Co-Authored-By` vs prose, hidden mode never touching `package.json`, `CLAUDE.md` as a symlink not a copy, source-vs-target dir, no-Node → skip enforcement, never overwrite an existing entry point/hook) are now also a single scannable list.
- **Setup contracts moved to `references/contracts.md`** — progressive disclosure: `SKILL.md` keeps a lean *Setup contracts* pointer (with one-line defaults), and the full Visibility / Communication / Attribution rules load only when needed. Trims the always-loaded `SKILL.md` by ~40 lines without losing any rule.
- **Setup questions use structured prompts where supported** — the three bootstrap questions (visibility, language, attribution) and the equivalent `upgrade` migration questions now call for a structured multiple-choice prompt (`AskUserQuestion` in Claude Code) where the agent supports it, falling back to prose elsewhere — keeping cross-agent portability (Codex / Cursor / Devin) intact.

## 1.2.0 — Agent attribution is opt-in

**Attribution question at setup**

- **Bootstrap now asks whether the agent may attribute work to itself / AI** — a new step 4 in `/agent-workflow-kit`, alongside the visibility and language questions. The answer is recorded in a new *Attribution* block in the project's `AGENTS.md`, so every agent that reads the entry point honours it.
- **Default is `off`** — people are routinely surprised to find an AI listed as a repo contributor (a single `Co-Authored-By` trailer is enough to do it, and GitHub keeps it via permanent PR refs). So attribution is **opt-in**, never opt-out.
- **`off` means nowhere** — no `Co-Authored-By` trailers, no "Generated with …" footers, and no AI/agent/model mentions in code, comments, commit messages, PR titles/bodies, branch names, or docs. The work reads as the human author's.
- **Two enforcement layers** — the *Attribution* block binds everything an agent writes by hand; the automatic `Co-Authored-By` trailer is added by the **harness**, so for **Claude Code** the kit also sets `"includeCoAuthoredBy": false` in the project's `.claude/settings.json` (a doc directive alone can't stop a harness-added trailer). See the *Attribution contract* in `SKILL.md`.
- **Existing deployments are covered** — `/agent-workflow-kit upgrade` backfills the block on a pre-1.2.0 project, asking (and defaulting to `off`). See `migrations/1.2.0-agent-attribution.md` (idempotent, additive).

**Devin Desktop rebrand (formerly Windsurf)**

- Cognition rebranded Windsurf → **Devin Desktop** (and Cascade → **Devin Local**) on 2026-06-02. Docs, install messages, and labels now say "Devin Desktop"; `windsurf`/`devin` are both kept as keywords. The launcher is unchanged functionally — the `~/.codeium/windsurf/global_workflows/` paths persist, and detection now also recognises a `devin` binary.

## 1.1.0 — Conversational language + unambiguous install guidance

**Conversational language (dialogue only)**

- **Bootstrap now asks the conversational language** — a new step 3 in `/agent-workflow-kit`, alongside the visibility question. The agent records the answer in a new *Communication language* block in the project's `AGENTS.md`, so every agent that reads the entry point talks to the user in that language and stops drifting between languages mid-session.
- **Dialogue-only scope, by design** — the choice governs what the agent writes *for the user to read* (questions, explanations, summaries, status). Code, identifiers, file paths, shell commands, log output, and abbreviations stay in their source language; the deployed `docs/ai/` files and `AGENTS.md` are not translated either (the conversational choice governs the chat, not the artifacts). See the *Communication contract* in `SKILL.md`.
- **Existing deployments are covered** — `/agent-workflow-kit upgrade` backfills the block on a pre-1.1.0 project, asking the user their language. See `migrations/1.1.0-communication-language.md` (idempotent, additive).

**Clearer install / upgrade guidance**

- **`init` now distinguishes a fresh kit install from a refresh** — prints `installed v…` the first time and `updated the kit to v…` on re-run, so it's obvious the command targets the *kit*, not a project.
- **The "Next" message is unambiguous about which path to take** — it spells out *first time in a project* (`/agent-workflow-kit`) vs *project already has the kit* (`/agent-workflow-kit upgrade`), and reminds that re-running `npx … init` updates the kit's own files. `--help` and the README install table say the same. Resolves the prior single-line hint that read the same for first-timers and upgraders.

## 1.0.0 — Initial public release

First public release of `@sabaiway/agent-workflow-kit`. The kernel — distilled from a battle-tested, multi-year-verified reference implementation — ships on npm + GitHub so it installs (and self-upgrades) in one command. Adoption is countable from the registry's public per-version download numbers — no telemetry, no phone-home.

**The kernel — a portable AI-agent memory & workflow system**

- **Entry point** — `AGENTS.md` (cross-agent open standard: Codex / Cursor / Windsurf / Copilot read it natively) + `CLAUDE.md` symlink for Claude Code; concise Memory Map, protocols delegated to `agent_rules.md`.
- **`docs/ai/` structure** — `handover`, `active_plan`, `current_state`, `technical_specification`, `architecture`, `known_issues`, `decisions`, `changelog`, `env_commands`, `tech_reference`, `agent_rules` + `pages/` (`index`, `shared-patterns`, `PAGE_TEMPLATE`). Layered lazy-loading: always-loaded / on-demand / hierarchical subdir `AGENTS.md` / archive.
- **Frontmatter caps** — every file declares `maxLines` + `staleAfter`; the validator errors over cap, warns when stale.
- **Index-freshness gate** — `check-docs-size.mjs --check-index` regenerates the navigator in memory and diffs it against the on-disk `index.md`, using the on-disk header date so a day-rollover is not a false positive.
- **3-tier rolling archive** — `archive-changelog.mjs` (HOT changelog → WARM `recent.md` → COLD `YYYY-MM.md`) + condensed-index META; `archive-issues.mjs` for resolved issues.
- **Pre-commit hook** — `install-git-hooks.mjs` wires caps + index freshness + archive checks + the `scripts/` test suite; package-manager-agnostic (`node` directly).
- **Tests** — rotation/cap pure functions covered by `*.test.mjs`, runnable under `node --test` via a zero-dependency `expect` shim.
- **Planning** — `references/planning.md`: Plan→Phase→Step→Substep, ephemeral plan lifecycle, `queue.md` series-index, mandatory Cleanup, plan-then-execute split + session-continuity heuristic.
- **Two modes** — `/agent-workflow-kit` (new) and `/agent-workflow-kit upgrade` (existing).
- **Cross-agent invocation** — `launchers/`: `SKILL.md` is a native Codex skill (same cross-agent standard); a Windsurf workflow launcher + `install-launchers.sh` let Codex/Windsurf users run the bootstrapper too, not just Claude Code.
- **Visibility** — `visible` (committed) and `hidden` (in-tree, hidden via `~/.gitignore_global`).

**Distribution & install**

- **`npx @sabaiway/agent-workflow-kit init`** — `bin/install.mjs` (dependency-free, Node ≥ 18) copies the kit into `~/.claude/skills/agent-workflow-kit/` and runs `launchers/install-launchers.sh` (auto-detects Codex / Windsurf). `--dir` / `AGENT_WORKFLOW_KIT_DIR` override the target; `--no-launchers` skips the wiring.
- **Self-upgrade** — `npx @sabaiway/agent-workflow-kit@latest init` refreshes the kit's own files; distinct from `/agent-workflow-kit upgrade`, which migrates a project's `docs/ai/` deployment.
- **Manual install still supported** — `git clone` + `install-launchers.sh`; only the npx path is reflected in install stats.
- **Additive & safe** — the installer writes only the kit's own namespaced slots and never deletes your settings. A pre-existing non-kit Codex link or Windsurf workflow is left untouched unless you pass `--force`, which backs it up to `*.bak.<timestamp>` and prints a restore command first. Windsurf launcher files carry an `agent-workflow-kit:managed` marker so the installer can tell its own file from yours.

**Known limitation** — condensed-index grows O(total archived entries); shard per-year on a multi-year horizon (noted in `archive-changelog.mjs`). Fully-external hidden mode is deferred to a later release.
