@sabaiway/agent-workflow-kit 12.4.0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,167 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
4
4
  is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
5
5
  every `migrations/<version>-<slug>.md` newer than it, in semver order.
6
6
 
7
+ ## 13.1.0 — the delegation ledger learns a base: a dispatch records `head` or `checkpoint`, every measurement of a checkpoint thread is base-relative over the snapshot scope, and a checkpoint thread holds the delegate's session (AD-140)
8
+
9
+ **MINOR: one optional key on the dispatch record, one new flag on `open`, one new IO leaf; every record minted
10
+ before the field reads as today, every head-base thread measures as today, byte for byte.** Until now every
11
+ ledger measurement was HEAD-relative, so the second task of a story — opened over the first task's uncommitted
12
+ diff — recorded a DIRTY baseline and a `dirty-baseline` return, and the metric could not attribute its bytes
13
+ (measured on this very story: fifteen of its seventeen returns). The epic's task tier keeps a checkpoint snapshot per
14
+ task; the ledger can now measure against one. The contract is `docs/ai/specs/kit/dispatch-baseline/index.md` with
15
+ its part `scope.md` (live, revision 1, S1–S6 bound) beside `held-session.md` revision 3 (S8–S9 bound).
16
+
17
+ - **The record carries its base; the schema stays 1.** A `dispatch` record carries `baseline: { kind, treeOid }` —
18
+ `head` with `null`, or `checkpoint` with a git object id of exactly 40 or 64 lowercase hex characters; an
19
+ unknown kind, a malformed id, a head with an id and a checkpoint without one each refuse by name over the nested
20
+ closed-key rule (`validateBaseline`). `baseline` is the ONE optional key of the dispatch kind (`OPTIONAL_FIELDS`):
21
+ a record without it reads as head through the one reader `readsBaseline(dispatch)`, an unknown extra key still
22
+ refuses, and the writer always mints the key. `BASELINE_KINDS`, `HEAD_BASELINE`, `isTreeOid` are exported.
23
+ - **`tools/dispatch-baseline.mjs`, the IO leaf (172 lines).** `snapshotScope` states the scope positively and
24
+ index-independently: every work-tree path not ignored by git's standard excludes, judged under an EMPTY temporary
25
+ index so the real index never decides membership, plus every present `KIT_OWN_PATHS` entry, plus every path HEAD's
26
+ tree or the base tree names, minus `docs/plans/`, the three settings files, every `KNOWN_FOOTPRINT` pattern and
27
+ `node_modules`; the comparison domain is the scope alone — an excluded base path is never touched and stays at
28
+ its base bytes, a scope path the base holds and the tree lost is measured as a deletion. The walk that materialises
29
+ the scope is a state table over the lstat class and base membership: absent → removed; a regular file or a
30
+ symlink → present; a never-committable class at a base path → refuse; elsewhere → skip; a directory at a base
31
+ path → removed (the file is gone, its own not-ignored children are scope paths of their own, its excluded
32
+ children never enter the index); elsewhere → skip; and a present path in an ancestor/descendant relation with an
33
+ excluded base path makes the measurement undecidable before the index is touched — git's own directory/file
34
+ conflict would otherwise drop the excluded entry into the payload. `computeBasePayload` delegates to
35
+ `core-evidence.mjs` byte for byte under `head` and, under `checkpoint`, reads the base tree into its OWN temporary
36
+ index, forces the live scope in and answers ONE `git diff --cached <treeOid>`, the index directory removed before
37
+ every return; `computeBaseFingerprint` is the sha256 of that payload; `isCleanAgainstBase` delegates to
38
+ `isTreeClean` under a head base and under a checkpoint base is "the payload is empty"; `runBaseDiff` serves the
39
+ producer's walks; `resolveTreeObject` is the type proof (a commit, a blob, a tag
40
+ and an unknown id refuse by name). A thrown runner, a failed git read, an unreadable path, an uncreatable index,
41
+ a non-tree base and the undecidable conflict each answer null — never clean. `core-evidence.mjs`, the review
42
+ wrappers and their bash twins are untouched (`review-fingerprint-parity` green unchanged).
43
+ - **The producer and the store read the base, never a flag.** `enumerateReturnedObjects` and `computeReturnedDiff`
44
+ take `io.base`: a head base walks exactly as today; a checkpoint base walks ONE layer over the base→live diff
45
+ (the base blob as pre-image, the forced-add blob as post-image, the binary markers from a numstat over the same
46
+ base→live diff, no untracked section), and both checkpoint walks force `--ignore-submodules=none` exactly as the
47
+ checkpoint payload does.
48
+ `uncommittedStateFingerprint(cwd, fsx, base)` routes by base. The preflight refuses a retry whose base differs in
49
+ either field from its origin's, both read through `readsBaseline` (a pre-field origin admits its head retry).
50
+ - **`open --checkpoint <oid>`; `return` and `fold` read the base from the record.** The grammar first, then the
51
+ tree-type proof, nothing written on a refusal; the clean probe, `preTreeDigest`, the return's bracketing
52
+ fingerprint and diff, the fold's fingerprint and enumeration are all base-relative, so no half-routed pair drifts.
53
+ The CLEAN line of a checkpoint open names the base; the DIRTY wording is unchanged. `baselineClean` keeps its
54
+ name and now means clean against the recorded base: two cycles on one tree with no commit are both CLEAN and both
55
+ metric-eligible, and a head-based open on the same dirty tree is still DIRTY.
56
+ - **A checkpoint base is never a fresh start for the held session (`held-session` revision 3).** A `code` dispatch
57
+ on a checkpoint base expects HELD exactly as `baselineClean:false` does; its substitution is resolved by ONE record,
58
+ the thread's own ledger `degrade`, which also withdraws the expectation (HELD is cleared, the next fold establishes
59
+ it — retryCap 0 with a dead session heals in one step; the report's "held session: none" reason names both
60
+ states); a terminal return records no substitution; no evidence-store degrade is consulted for a checkpoint thread
61
+ at either site (its fingerprint is HEAD-relative, the return's is base-relative). The judge's verdict is per thread
62
+ (`threadVerdict`, `baseKind`, `substituted`), so the `fold` VERB refuses a substituted checkpoint `code` thread by
63
+ its own verdict — judged over the SAME inputs `review-state` gives the judge, the execute-degrade records through
64
+ `review-state`'s exported reader — naming the held session the thread was opened against and the ledger degrade as
65
+ the recovery, and fails closed on an error epoch or an unreadable ledger; `review-state`'s recovery hint names the
66
+ ledger degrade, then a retry or a new thread.
67
+ - **Stated where they bite.** `DISPATCH_CONTRACT` carries the base clause and `references/modes/dispatch.md` carries it
68
+ verbatim (doc-parity); the header, HELP and the mode doc's Invariants name TWO git-write exceptions (handoff-return's
69
+ write-tree; the checkpoint measurement's own temporary index and forced-add blobs — never a ref, never the real
70
+ index); every head-only premise — "staging moves the tree", the frozen payload's residuals, the concealing-tree
71
+ guard's digest-blindness rationale in the fold's comment, HELP and mode doc, and the same guard's claim that a
72
+ deleted skip-worktree file is invisible to every probe (its comment, its refusal message, HELP and the mode doc) —
73
+ is qualified to a head base; `review-state`'s header (the single home of its exit contract), `review-state.md` and
74
+ `core-evidence.md` qualify the accepted-replacement escape to a head base and state that a checkpoint substitution
75
+ closes only by its ledger degrade (the HELP's restatement of that rule is subtracted); the producer's payload
76
+ comments and the return guard's rationale name the base they hold for, the producer's own prohibition on forcing
77
+ the submodule domain is SUBTRACTED where its walks now force it, and the non-UTF-8 symlink-target and binary
78
+ content-blind refusals name the head payload their reason is about and state that they hold under either base —
79
+ BOTH say outright that they refuse CONSERVATIVELY, each reason holding for an UNTRACKED object alone, since a
80
+ tracked binary's diff carries its blob ids and a tracked link's target rides the diff body raw; the content-blind
81
+ refusal's own envelope says the same rather than generalising one kind's blindness over the class, and the two
82
+ assertions that pinned its old opening were realigned to the new one.
83
+ Tarball pin 304 → 305; the publish workflow's kit test line and `ci.yml`'s unit-test step both gain
84
+ `tools/*.test/*.test.mjs` — **nineteen** suites live in the kit's `tools/*.test/` subdirectories, and neither workflow
85
+ reached ANY of them: the two this story adds and seventeen that four earlier stories left running only in the local
86
+ gate matrix, which has always used the recursive glob. Recorded sizes raised by six writes carrying the plan's and
87
+ the folds' reasons, then TIGHTENED by a seventh as the review rounds subtracted prose and raised again by an
88
+ eighth where a refusal had to say what it conservatively covers (`dispatch.mjs` 1883 lines,
89
+ its contract line 1597 bytes; `exec-producer.mjs` 503; `review-state.mjs` 918; `test/package-content.test.mjs` 799;
90
+ the record pair of the dispatch grammar; the kit aggregate); four spec-coverage debts paid (107 → 103).
91
+ - **Recorded debt.** Eight queue rows on this change, beside one PRACTICE row its later rounds motivated (one
92
+ rationale gets one home, so a sentence cannot drift on five surfaces at once). Five on the shipped code: the leaf hands the whole domain to `git add -f` on
93
+ argv (a Windows argument-length limit; `--pathspec-from-file` is the fix); the scope's set arithmetic is
94
+ exact-string, so a `core.ignorecase` index can fold a live path onto an excluded base entry; three guards — the
95
+ concealing-tree probe, the non-UTF-8 symlink-target refusal and the binary arm of the content-blind refusal —
96
+ refuse a checkpoint thread for a blindness its payload does not have (one row); the fold's error-epoch arm is
97
+ unreachable through real git; the binary and non-UTF-8-symlink refusals are wider than their own reasons, since a
98
+ TRACKED binary's diff carries both blob ids and a TRACKED link's target rides the diff raw, while the reasons hold
99
+ only for an untracked one (one row). Two from the execution itself: a delegate's brief must pre-authorise the fixture
100
+ edits its change implies, and a drift guard must hold the publish workflow's test globs to the tree. One on
101
+ ADJACENT modules the review reached but this change does not touch: `core-evidence.mjs` and `commit-guard.mjs`
102
+ still name a submodule-ignore setting as staged content the payload cannot see, which the payload's own flags
103
+ closed. Three STATED
104
+ LIMITS of the contract itself, not rows: the checkpoint enumeration inherits the unborn-branch refusal, the frozen
105
+ payload keeps its unframed/no-mode residuals, and the concealing-tree probe reads the real index by design under
106
+ both bases.
107
+
108
+ ## 13.0.0 — a plan proves its own ground: `plan-shape` judges a plan's rows against its epic story's claims in both arms and `--in-flight`, the claim relation lives in one leaf, and the containment rule is one positive predicate (AD-139)
109
+
110
+ **MAJOR: the containment rule refuses a row or anchor path spelling 12.4.0 accepted — a trailing or doubled slash, a
111
+ `.` segment — so a consumer's declared `--in-flight` gate can turn red with no edit of theirs (the refusal names the
112
+ row and the rule; no migration note, as for every kit MAJOR since 4.0.0 — the class AD-117 and AD-133 called MAJOR),
113
+ and the story relation joins the structural set, so a consumer already on the 12.4.0 epic tier can go red the same
114
+ way with no edit of theirs (each code's reach is stated in the bullets below); three new shipped leaves, one new
115
+ structural rule set, no deployed gate line's TEXT changes.** The epic
116
+ tier (12.4.0) gave a story a ledger of claims; the bar "a plan stays inside its story's claims" lived in prose, and
117
+ the last bar this epic wrote down drifted inside a day. The rung is now a tool, enforced where a dispatch is spent: `--in-flight` is already the gate
118
+ this family runs before every dispatch. The contract is `docs/ai/specs/kit/plan-shape-ownership.md` (live, revision 1,
119
+ S1–S11 bound) beside `plan-review-loop` revision 9.
120
+
121
+ - **The story line.** A plan carrying an epic story names it with exactly one line `Story: S<N> of <ID>` inside
122
+ `## Goal and boundary`, `<ID>` the epic's file stem under `docs/ai/epics/`. Two lines refuse `story-line`; an
123
+ unknown epic or story id refuses `story-unknown` by name; a landed epic refuses `story-epic-landed`. Zero lines is
124
+ a storyless plan and stays legal — judged as today plus the collision half. The plan's own story is looked up in
125
+ the full record list, landed rows included; its `state:` is never read.
126
+ - **ONE relation, in the STRUCTURAL set.** Over every concrete row path after glob expansion, against every
127
+ not-landed story of every open epic minus the plan's own: an `owns` overlap refuses `story-owns`; a `shared`
128
+ overlap inside the plan's own epic refuses `story-shared` unless a transitive `depends-on` edge orders the two
129
+ in either direction; a storyless plan against a `shared` claim refuses the same way (name the story); a
130
+ cross-epic `shared` overlap is not judged (the epic tier prints it). For a storied plan, an in-scope row path —
131
+ the declared source-size practice's roots, extensions and exclusions — that no own claim covers refuses
132
+ `story-scope`; the package-content pin path is covered by the shipped `create` or `delete` row it pins and is
133
+ never claimed by a story. `checkPlan`, `verifyPlan` and `checkPlanStructure` all carry it, so `--check`,
134
+ `--verify` and `--check --in-flight` agree.
135
+ - **Facts.** `openRepo` reads `docs/ai/epics/` once per run through the epic sweep's own no-follow door, and
136
+ `buildFacts` hands the judge `stories` (one record per valid story row of every open epic: `{ path, id, line, state, claims,
137
+ reachable }`), `landedEpics` (the file stems the sweep read as landed) and `storeRefusal`. Only ENOENT reads as
138
+ an absent store; a symlinked root, a regular file, ENOTDIR, any other error, a failed read, an unreadable `.md`
139
+ entry, a frontmatter the header reader refuses, an open epic the shape check refuses or a state outside the
140
+ closed set refuse EVERY judged plan `story-store`. A facts object with no `stories` field (the bare callers)
141
+ leaves the relation unjudged.
142
+ - **The pin exemption (`plan-review-loop` revision 9, S34).** Several `modify` rows whose OWN path is one
143
+ package-content pin path may own it — the ONE exemption to `duplicate-path`: a single pin row owes nothing; with
144
+ two or more, every one binds in ledger order the nearest untaken shipped `create` or `delete` row above it whose
145
+ package pin is that path, and a row that finds none refuses `pin-unbound`. A sweep expansion never rides it; the
146
+ `before`/`after` totals count each concrete path once; a judge with no `pathFacts` admits repeated `modify` pin
147
+ rows unjudged; a duplicate involving a `create` or `delete` still refuses.
148
+ - **Containment enumerated by proof.** A row or anchor path is admitted only when it has no backslash and every
149
+ `/`-separated segment is non-empty and neither `.` nor `..` — one predicate instead of an exclusion list, after
150
+ `./x` (plan review) and `x/` (diff review, confirmed live) each walked around a claim; a leading slash was
151
+ already refused by 12.4.0's own predicate.
152
+ - **Three leaves, no cycle, no claim primitive written twice.** `tools/claim-relation.mjs` (`expandClaim`,
153
+ `hasPathOverlap`, `findReachable`, `listStories`, `unique` — `plan-shape.mjs` re-exports `unique` so every import
154
+ site stands), `tools/epic-store.mjs` (`EPICS_REL`, `readEpicEntries` — the epic CLI's door, unchanged — and
155
+ `readEpicStore`, the facts half's no-follow probe), `tools/plan-shape-ownership.mjs` (`readStoryLine`,
156
+ `checkStoryOwnership`, `PIN_FILE`, `PIN_VERBS`). `epic-shape-ledger.mjs` and `epic-shape-cli.mjs` keep their
157
+ findings and counts byte for byte under their unchanged suites; the sweep result gains `landedEpics` beside
158
+ `counts`. Tarball pin 301 → 304. Every touched shipped module stays under the 400-line cap (`plan-shape.mjs` 384);
159
+ the kit's recorded aggregate rises by the three leaves and their suites, the reason recorded in the ratchet.
160
+ - **Recorded debt (queue).** `openRepo` walks the repository before the store probe, so an EACCES directory
161
+ anywhere exits 2 before `story-store` can be named (refuse-direction, pre-existing); the robustness coverage
162
+ table reads a filesystem errno as a process-launch outcome; a delegate read its own sandbox's spawn limit as a
163
+ reason to stop a slice; a near-miss story line (`- Story: …`, the bullet marker the canon's own sentence sits
164
+ under) reads as a storyless plan instead of refusing; the one-line read describer is written twice by the store
165
+ extraction (`describeRead` in the epic CLI and in the store leaf); a storied plan whose only pin-path row ships
166
+ no `create` or `delete` above it refuses `story-scope` with no repair inside the rule.
167
+
7
168
  ## 12.4.0 — the epic tier gets its checker: `epic-shape` judges a capped concept file by one altitude predicate and one claim relation, renders its review brief, folds the findings by the same predicate, and closes it by rewriting one byte range (AD-138)
8
169
 
9
170
  **MINOR: one new tool, four verbs, four shipped modules; no deployed gate line changes its verdict.** The epic this
package/README.md CHANGED
@@ -236,7 +236,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
236
236
  | `/agent-workflow-kit recipes` | any time | **read-only** orchestration advisor: presents five named recipes for composing the carriers of a step — the bridges and the full-tool executor subagent — into plan → execute → review — **Solo / Reviewed / Council / Delegated / Subagent** — plans + recommends one for your environment (degrading with a stated reason when a backend isn't ready, or when the executor vehicle is missing/unusable), and offers the choice. The activity/slot registry (four activities) is rendered by `procedures` and by `set-recipe --help`. The orchestrator runs it via the bridge skills or the executor vehicle and **always commits**; the kit never executes a recipe, never runs a subscription CLI, never commits. |
237
237
  | `/agent-workflow-kit procedures <activity>` | any time | **read-only** activity-procedures advisor: prints a named activity's ordered steps (`plan-authoring` / `plan-execution` / `routine` / `feedback-triage`) read **live** from the engine, plus the **resolved recipe per slot** from your `docs/ai/orchestration.json` (agent-writable via `set-recipe`, or hand-edited) + carrier readiness (`plan-authoring`: author, fold, review; default Reviewed when a backend is ready, Council on request; slot-aware incl. Delegated and Subagent) — and, for every dispatched backend, the **full driving contract at the point of use** (exact copy-pasteable invocation, grounding levers like agy's `--facts`/`--decided`, the round-2 `--continue` delta, guarded passthrough), verbatim from the bridge manifests (drift-guarded; each wrapper's `--help` prints the same) — except `feedback-triage`, whose record-bound block carries the check with `--excerpts`, the resolved member lines (ready bridge commands, the ready lens brief, or explicit skipped members), and either the record round-table command for a bridge obligation or the no-receipt fact otherwise: the record is the artifact. `--override <slot>=<value>` adjusts one slot per run. Composes with `recipes`; never writes, never commits, never runs a subscription CLI. |
238
238
  | `/agent-workflow-kit set-recipe` | any time | **config writer** for `docs/ai/orchestration.json`: tell the agent your preference in plain language and it maps it to explicit `--set <activity>.<slot>=<value>` / `--unset` ops; the kit validates, **previews by default**, and writes only on `--write` (deployment-gated, atomic, symlink/TOCTOU-safe), resolving the effective recipe vs live readiness. Writes **only** that file — **never runs a backend or a subagent, never commits**; hand-editing stays fully supported. |
239
- | `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand or via the consent-gated seeder** (preview → your explicit yes; never without consent). `--await [--timeout <s>]` (AD-049) BLOCKS until every recipe-named backend has receipted the current tree — the durable completion signal is the receipt, never a process event — so you wait for the bridges instead of hand-polling. Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
239
+ | `/agent-workflow-kit review-state` | any time | **read-only review-receipt checker** — makes "reviewed ≠ shipped" detectable: the bridge review wrappers append a receipt per successful review (into a file inside the git dir — never committable); this checks that every backend your configured `plan-execution.review` recipe names holds a **fresh, grounded receipt for the current uncommitted tree** (any later edit the payload can see moves the fingerprint and stales the receipt; a review continuation never re-attests a folded tree). `--check` gives a gate exit code to declare in `docs/ai/gates.json` **by hand or via the consent-gated seeder** (preview → your explicit yes; never without consent). `--await [--timeout <s>]` (AD-049) BLOCKS until every recipe-named backend has receipted the current tree — the durable completion signal is the receipt, never a process event — so you wait for the bridges instead of hand-polling. Never writes, never commits, never runs a subscription CLI; it spawns read-only `git` queries to fingerprint the tree — and `git commit --no-verify` stays possible (discipline, not a sandbox). |
240
240
  | `/agent-workflow-kit sandbox-masks` | any time | **cosmetic exclude lane for sandbox device masks** — an OS sandbox (Claude Code) injects character-device masks into the work tree as untracked `git status` noise; the review domain already ignores them **by construction** (never-committable untracked classes — char/block devices, FIFOs, sockets — are excluded from the fingerprint, the assembled review payload, and the clean checks). This mode hides them from `git status` too: flagless = read-only probe (derives the CURRENT mask set from the unfiltered walk + lstat — never a frozen list — and revalidates fenced entries, loudly flagging one that became a real path); `--apply` = consent-gated FULL-BLOCK replace of its own fenced block in `git rev-parse --git-path info/exclude` (stale masks drop by construction; `--clear` always means REMOVE the block — it takes precedence over the derivation). Writes ONLY its fence — never `.gitignore`, never global config; symlinked/non-regular exclude paths and malformed fences fail closed. Watch note: a real file at an excluded path is silently skipped by bulk staging (`git add -A`/`git add .`) — delete the stale line first; the probe flags exactly this case. |
241
241
  | `/agent-workflow-kit mcp` | Claude Code · opt-in | **typed-channel registration** — the kit ships a read-only stdio **MCP server** exposing its two promptless readers as TYPED tools (`path_inventory`: exists / type / size / line count / listing / a small file's text, many paths in ONE call; `repo_search`: literal search, the pattern a JSON string). Their arguments are named JSON fields rather than a string handed to a shell, so a pipe, a redirect or a quote inside a pattern or a path stays DATA and is never interpreted — legal bytes to search for, with no shell to read them as operators. Shipping the server does nothing on its own — a client sees it only once the project declares it, and this mode is that declaration: the `agent-workflow` entry in **`.mcp.json`** (command `node`, args = the absolute path of the RUNNING kit's server) plus `enabledMcpjsonServers` and the two derived tool allow rules in **`.claude/settings.json`**. Preview by default and the **exact entry is printed before consent** (registering a server means your client will run that command); `--apply` writes `.mcp.json` FIRST, then settings, merge-don't-clobber with each file's EOL kept, and a re-apply adds nothing twice. An existing `agent-workflow` entry that **structurally differs** from what this kit copy would write is refused unwritten (the comparison ignores key order, so re-serialized identical bytes are the same registration) — silently changing what an MCP server launches is what consent must not slide past. Where an OS sandbox masks `.mcp.json` with a device node, it writes nothing, hands you both paste-ready texts and exits 0. The server is a read-only child of your client (no write, no exec API) running outside the Bash sandbox as the client does. Never writes `settings.local.json`; never commits. |
242
242
  | `/agent-workflow-kit grounding` | any time | **grounded-review facts assembler** — mechanizes populating `agy-review --facts @f`: slices your entry-point's **Hard Constraints** section verbatim (exactly one match, else a loud stop) and/or a plan's three canon sections (`## Goal and boundary` + `## Module ledger` + `## Verification`, each required; duplicates stop), under the same byte budget the agy wrapper enforces (minus `--reserve-bytes` for the artifact share), with a loud tail-trim on overflow. `--autonomy` (AD-044) appends the COMPUTED effective autonomy policy from the git-top `docs/ai/autonomy.json` (every red-line + per-activity level, stated source line; absent file → the computed defaults ARE the policy, exit 0; a malformed policy fails CLOSED, exit 1). `--extra <text|@file>` (repeatable) appends your own facts **byte-verbatim** after the mechanical sections, so the merge is a tool input rather than a shell append — an `@file` must sit inside the proven git work tree (never the git dir) or the system temp surface, and is read through a no-follow descriptor (a FIFO cannot block it, a symlink leaf cannot substitute its target). Prints to stdout; `--out` writes **one scratch file only** — system-temp outside the repo ($TMPDIR / /tmp, rewritable) or a **fresh** gitignored in-repo path (create-only, exclusive write; an existing in-repo file, even gitignored, is refused — the `.env` clobber class); tracked, not-ignored-in-repo, other outside-repo, and symlink/non-regular destinations are all refused. Never commits, never runs a subscription CLI. |
@@ -245,7 +245,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
245
245
  | `/agent-workflow-kit commit-guard` | any time | **the read-only pre-commit guard** (D10) — makes the commit capture the whole current working tree, so «verified» and «about to be committed» are the same bytes (the receipt itself has a stated residual — see the mode doc). FIRST it refuses an **INDEX that lags the verified working tree** (the gates and the fingerprint describe the WORKING tree while `git commit` takes the INDEX alone, and the fingerprint cannot tell them apart — so a lagging index used to ship a strict subset of what was verified): unstaged tracked paths or reviewable untracked-not-ignored paths, named up to a bounded cap with the remainder stated, a dirty tracked **submodule** named separately with its own recovery, and fail-closed on an undecidable probe. This deliberately blocks a partial commit. Then it binds the LATEST completed `run-gates --final` receipt to the EXACT current tree: refuses on a missing/red/stale receipt, fingerprint drift under the run, a dangling later attempt, declaration content drift, evidence-hash or lcov drift, or unsatisfied review obligations (the same review-state decision, recomputed over a sanitized env — forged out-of-repo stores never satisfy). Re-runs NO gate or test. Wire it into `.git/hooks/pre-commit` (the installer writes the RESOLVED invocation). `git commit --no-verify` stays the stated residual. |
246
246
  | `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe, a feature-spec layer never adopted) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (the sandbox-lane live recipe, the worktrees-dir hand-apply-first grant advice, the agents hidden-mode reconcile follow-up, or the spec-adoption decline preview) · the exact consent-gated apply one-liner}**. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners only on your yes, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); the kit never seeds `sandbox.network.allowedDomains` / `filesystem.allowWrite` (**HAND-APPLY** territory), and the sandbox-lane item's convergence is a neutral fingerprint acknowledgement recorded by a consent-gated ack writer into `docs/ai/acks.json` — never a security key (the recipe is documented per bridge in `capability.json` `networkHosts` + `writableDirs`). `--cwd` is required (the target project is explicit); never writes, never commits, never runs a subscription CLI. |
247
247
  | `/agent-workflow-kit doc-parity` | any time | **read-only doc-parity lint** (AD-049) — kills the doc-drift class where a mode-contract doc silently lags a code constant (a `--check` doc still reading `300` after the diff cap moved to `400`): a **closed, exported registry** binds each live constant (review caps, schema versions, the ledger's own class/scope vocabulary, and the autonomy-doctor EXIT/status/trusted-dir contract) to the exact token its `references/modes/*.md` contract must carry, and asserts the CURRENT value renders into every bound file — a drifted doc, an unreadable file, or an absent token **fails closed**. The values are sourced from the live imports (never re-typed), so the lint can't itself go stale; adding a binding is adding a checked entry (closed-world, edit-safe). `--check` is a gate exit code for `docs/ai/gates.json`. Never writes, never commits, never runs a subscription CLI. |
248
- | `/agent-workflow-kit dispatch` | any time | **the delegation engine** — makes "how much does delegating a sub-task actually buy?" a measured number instead of a feeling. `check <file>` validates a sub-task brief's contract block and exits 0/1 naming the first violated field — **form only**: whether the task is genuinely bounded, its design decided and its acceptance adequate stays your judgment, and a well-formed absurdity passes here by construction. `advise --step-class <c>` answers the question that comes BEFORE that one — which vehicle should carry this kind of sub-task, whether it is even present on this machine, and what the ledger has already recorded for threads of that class (finished, failed, closed without a fold, and still open — counted separately) — and it decides nothing: it refuses no dispatch, gates no verb, and a choice that diverges from the advice is noted rather than blocked. It reads the filesystem only: nothing is launched to find out what is installed, an absent ledger prints "no recorded history", an unreadable one prints the store's own words, and either way the advice still prints. The same block appears as a footer under a form-valid `check` — never under a refusal, so it can never hide one. `register` pre-registers an acceptance wave (step classes, pairing key, minimum observations per class, the mean and first-pass thresholds) so the bar can never be chosen after the results it judges — immutable per wave. `observe` records ONE hand-written observation (`solo-construction`, the baseline whose ratio is 1 by construction — except over a scope measuring zero bytes, which has no ratio at all and is recorded ineligible by name — or `self-reported`) — delegated numbers are never hand-written, they are derived. The writer verbs are where that derivation happens: `open` puts a delegated thread on the record with every mint-time field copied from the brief's own header, refusing a deadline the wrapper's cap plus its kill grace would not fit inside, and recording whether the tree it started from was clean (a dirty one makes the result honestly unmeasurable rather than quietly counted); `await` waits for that one dispatch to answer and writes nothing — only the finished receipt satisfies it, a run still holding the nonce means keep waiting, and a wait that runs out says so with its own exit code, names whether the deadline or your own timeout ended it, and authorizes nothing: a wait that ended without an answer is a question for you, never permission to dispatch again; `return` absorbs the receipt the wrapper minted — only a finished one, checked against the contract it actually ran, its deadline and its own report, and refused outright when the tree hides changes from git or moves while the return is being computed — and derives the bytes from git rather than from anyone's claim; `fold` is the acceptance, and it refuses if the tree moved since the return — precisely, it re-confirms **equality of the visible canonical payload**, which is a change detector rather than a cryptographic identity of the tree: that payload is unframed and carries no file mode, so a content or symlink target that imitates the marker opening the next entry can alias two trees, and an executable-bit flip moves nothing (both named as residuals in the mode doc, with the fix queued). Where the payload cannot follow an object's bytes at all — a binary, a non-regular path, a submodule, a symlink whose target is not valid UTF-8 — `return` and `fold` **refuse** rather than promise what they cannot check. `degrade` closes a thread that never earned a fold, on the record and with its reason. `aggregate` prints one wave: the registered thresholds, every observation as context, and per step class the finished threads — a folded success with git-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 thread, a degrade-closed one, or one folded after failing acceptance each count as a real zero. Below the registered minimum nothing is computed (insufficient); at or above it is computed and labeled PILOT evidence. It REFUSES rather than guessing: no pre-registration, an unfinished thread in scope, a recorded refusal-to-delegate that never opened a thread, or an ambiguous wave each stop the computation by name. `handoff-return --slug <s>` closes the worktree loop after `land --prepare`: it re-attests that main still holds exactly the prepared tree under the same HEAD (both recorded in the handoff at prepare time), prints the satellite handoff's user-owned content byte for byte with its boundaries, byte lengths and the main-owned destinations each part folds into, prints the handoff digest and both OIDs as its proof, states the after-the-fold order (a fold landed after the gates leaves them stale), and records ONE self-reported worktree-stream observation only when the whole prepared change set is measurable — a deletion, a rename, a symlink, a submodule or a mode-only change ends with a named NOT RECORDED instead, never a partial number. Writes only its own append-only ledger inside the git dir (never committable); never commits, never runs a subscription CLI. |
248
+ | `/agent-workflow-kit dispatch` | any time | **the delegation engine** — makes "how much does delegating a sub-task actually buy?" a measured number instead of a feeling. `check <file>` validates a sub-task brief's contract block and exits 0/1 naming the first violated field — **form only**: whether the task is genuinely bounded, its design decided and its acceptance adequate stays your judgment, and a well-formed absurdity passes here by construction. `advise --step-class <c>` answers the question that comes BEFORE that one — which vehicle should carry this kind of sub-task, whether it is even present on this machine, and what the ledger has already recorded for threads of that class (finished, failed, closed without a fold, and still open — counted separately) — and it decides nothing: it refuses no dispatch, gates no verb, and a choice that diverges from the advice is noted rather than blocked. It reads the filesystem only: nothing is launched to find out what is installed, an absent ledger prints "no recorded history", an unreadable one prints the store's own words, and either way the advice still prints. The same block appears as a footer under a form-valid `check` — never under a refusal, so it can never hide one. `register` pre-registers an acceptance wave (step classes, pairing key, minimum observations per class, the mean and first-pass thresholds) so the bar can never be chosen after the results it judges — immutable per wave. `observe` records ONE hand-written observation (`solo-construction`, the baseline whose ratio is 1 by construction — except over a scope measuring zero bytes, which has no ratio at all and is recorded ineligible by name — or `self-reported`) — delegated numbers are never hand-written, they are derived. The writer verbs are where that derivation happens: `open` puts a delegated thread on the record with every mint-time field copied from the brief's own header, refusing a deadline the wrapper's cap plus its kill grace would not fit inside, and recording whether the tree it started from was clean against the thread's base — HEAD by default, or the tree `--checkpoint <oid>` names, so the second task of a story is measured over the first one's uncommitted work instead of opening dirty (a dirty one makes the result honestly unmeasurable rather than quietly counted); `await` waits for that one dispatch to answer and writes nothing — only the finished receipt satisfies it, a run still holding the nonce means keep waiting, and a wait that runs out says so with its own exit code, names whether the deadline or your own timeout ended it, and authorizes nothing: a wait that ended without an answer is a question for you, never permission to dispatch again; `return` absorbs the receipt the wrapper minted — only a finished one, checked against the contract it actually ran, its deadline and its own report, and refused outright when the tree hides changes from git or moves while the return is being computed — and derives the bytes from git rather than from anyone's claim; `fold` is the acceptance, and it refuses if the tree moved since the return — precisely, it re-confirms **equality of the visible canonical payload**, which is a change detector rather than a cryptographic identity of the tree: under the default head base that payload's untracked section is unframed and carries no file mode, so a content or symlink target that imitates the marker opening the next entry can alias two trees, and making an untracked file executable moves nothing (both named as residuals in the mode doc, with the fix queued); under a checkpoint base it is one indexed diff that carries modes. A change set carrying a binary, a non-regular path, a submodule, or a symlink whose target is not valid UTF-8 is **refused** at `return` and `fold` — CONSERVATIVELY, rather than promise what the default head payload may not be able to follow — and the same refusals hold for a checkpoint thread. `degrade` closes a thread that never earned a fold, on the record and with its reason. `aggregate` prints one wave: the registered thresholds, every observation as context, and per step class the finished threads — a folded success with git-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 thread, a degrade-closed one, or one folded after failing acceptance each count as a real zero. Below the registered minimum nothing is computed (insufficient); at or above it is computed and labeled PILOT evidence. It REFUSES rather than guessing: no pre-registration, an unfinished thread in scope, a recorded refusal-to-delegate that never opened a thread, or an ambiguous wave each stop the computation by name. `handoff-return --slug <s>` closes the worktree loop after `land --prepare`: it re-attests that main still holds exactly the prepared tree under the same HEAD (both recorded in the handoff at prepare time), prints the satellite handoff's user-owned content byte for byte with its boundaries, byte lengths and the main-owned destinations each part folds into, prints the handoff digest and both OIDs as its proof, states the after-the-fold order (a fold landed after the gates leaves them stale), and records ONE self-reported worktree-stream observation only when the whole prepared change set is measurable — a deletion, a rename, a symlink, a submodule or a mode-only change ends with a named NOT RECORDED instead, never a partial number. Appends only to its own ledger inside the git dir (never committable); it also writes `handoff-return`'s `write-tree` tree object and, for a checkpoint measurement, the leaf's OWN temporary index file and the blobs its forced adds store — never a ref, never the real index; never commits, never runs a subscription CLI. |
249
249
  | `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first — printed only for a link whose RAW TARGET is MAIN's `node_modules`, since a foreign or unreadable link is reported as such with no removal advised); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `prompt <slug>` is read-only too and re-prints the satellite's cold-start prompt — the same text `provision` ends its report with: where the worktree is, its ONE seeded plan, the handoff as the one channel back, MAIN's orientation (shared series index, landing) and, under its own heading because it is probed on the satellite, that checkout's install posture — every value derived LIVE rather than replayed from the frozen provision record, a recorded value that no longer matches NAMED as a divergence (with the cause its source makes likely) instead of printed as the runnable one, every offered command marked with WHO runs it (`MAIN $ …` for the landing, `HERE $ …` for this checkout's own install, a posture with nothing to run staying prose), and a control character in any rendered value a typed STOP rather than a forged prompt line; `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
250
250
  | `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
251
251
  | `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: agent-workflow-kit
3
3
  description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow".
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '12.4.0'
6
+ version: '13.1.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-kit",
5
5
  "kind": "composition-root",
6
- "version": "12.4.0",
6
+ "version": "13.1.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-kit",
3
- "version": "12.4.0",
3
+ "version": "13.1.0",
4
4
  "description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -7,7 +7,7 @@ The ONE **loop-evidence writer** (strip-the-kit D6a/D7) — every core evidence
7
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/core-evidence.mjs <verb> …`:
8
8
 
9
9
  1. **`red-proof "<test-file>#<test-name-pattern>"`** — the D3(c) observed-red DECLARATION, minted **BEFORE a bugfix is applied**: it runs the named test N times (default 3, `AW_CORE_EVIDENCE_RERUNS`; per-run timeout `AW_CORE_EVIDENCE_TIMEOUT_S`) on the CURRENT (pre-fix) tree, requires red N/N (observed green / unresolvable / mixed / timed-out are DISTINGUISHED refusals — nothing is written; mixed/timeout is QUARANTINE, no override lane), and records { testId · repo-relative file · content sha256 (custody) · N/N red · base = HEAD sha · the pre-fix tree fingerprint }. The final run (`coverage-check --check`) then requires every current-base record's test green N/N with the hash unchanged; a record whose pre-fix fingerprint EQUALS the current tree never satisfies (nothing changed — reuse/forgery); a commit expires records (base moves); editing a bound test file means re-observing red (the new record supersedes the old — same {base, testId} key).
10
- 2. **`degrade --backend <name> --reason "<why>"`** — the ONLY escape from a required review backend (D3(b)): an explicit per-backend record at the CURRENT fingerprint. The backend name also has a second reading: the execute wrapper name `codex-exec` is the accepted-replacement escape honoured by the held-session arm; for the held-session arm the record binds the substituted return's `postTreeDigest` and keeps standing after later folds move the tree; the record form and writer are unchanged, and the review receipt id `codex` never grants that escape. Never all review backends: the review gate still requires ≥1 non-degraded ship-class receipt whenever ≥1 backend is configured. Any tree edit that MOVES the fingerprint re-stales a review-backend degrade record — which is every edit the payload can see, and no more: a change the payload cannot see (a gitlink hidden from `git diff` by an ignore configuration) leaves the record standing. **The per-tree claim has one honest exception, and it is not a corner case:** a degrade minted while the work tree is CLEAN binds the CONTENT-FREE fingerprint — the one value every clean moment of every repository shares — so it identifies no working state and, since the record carries no base, no moment either. Such a record is therefore OUTSIDE the `#64` degrade-before-final ordering rung (`flow-check` records the skip as an advisory naming how many were skipped), because the ORDER of two records that cannot be shown to belong to the same moment states nothing about any tree. The writer still accepts it — refusing would strand the one lane that honestly records an unavailable backend during a plan round, which attests no tree at all. Closing the exception needs a durable `base` on the record, which is the queued store migration.
10
+ 2. **`degrade --backend <name> --reason "<why>"`** — the ONLY escape from a required review backend (D3(b)): an explicit per-backend record at the CURRENT fingerprint. The backend name also has a second reading: the execute wrapper name `codex-exec` is the accepted-replacement escape honoured by the held-session arm for a head-base thread; for that thread the record binds the substituted return's `postTreeDigest` and keeps standing after later folds move the tree; a checkpoint-based thread's substitution is not lifted by any core-evidence degrade — its own ledger degrade closes it; the record form and writer are unchanged, and the review receipt id `codex` never grants that escape. Never all review backends: the review gate still requires ≥1 non-degraded ship-class receipt whenever ≥1 backend is configured. Any tree edit that MOVES the fingerprint re-stales a review-backend degrade record — which is every edit the payload can see, and no more: a change the payload cannot see leaves the record standing. **The per-tree claim has one honest exception, and it is not a corner case:** a degrade minted while the work tree is CLEAN binds the CONTENT-FREE fingerprint — the one value every clean moment of every repository shares — so it identifies no working state and, since the record carries no base, no moment either. Such a record is therefore OUTSIDE the `#64` degrade-before-final ordering rung (`flow-check` records the skip as an advisory naming how many were skipped), because the ORDER of two records that cannot be shown to belong to the same moment states nothing about any tree. The writer still accepts it — refusing would strand the one lane that honestly records an unavailable backend during a plan round, which attests no tree at all. Closing the exception needs a durable `base` on the record, which is the queued store migration.
11
11
  3. **`summary`** — the ONE stateless end-of-loop render (D6): gate result from the latest final attempt, per-backend verdicts from the review receipts, red-proof outcomes, degrade records. Computed from the stores at read time — no ledger, no rounds, nothing remembered. A malformed store exits non-zero and WITHHOLDS the affected section, never renders a partial as complete. The final-run line NAMES an absent coverage verdict from the `coverage` token the run RECORDED (an ADDITIVE optional field on the `final` record — the closed `certified | not-run | unknown` set, never `none`, and `certified` requires a bound `lcovSha256`): GREEN never renders unqualified over a run that issued no verdict. A LEGACY receipt carrying no token and no lcov digest is named as exactly that, never as a claim about what it read — the withheld verdict travels here too, as DETAIL beside the unchanged status word.
12
12
 
13
13
  **Promptless writer lane (D7):** both verbs ride plain `node ${CLAUDE_SKILL_DIR}/tools/core-evidence.mjs …` invocations — surface the paste-ready allow entries once (`.claude/settings.json`), and a full loop transcript runs at zero writer approval prompts.
@@ -6,7 +6,7 @@ The **delegation engine** (delegation Plans 1–2): the sub-task **contract chec
6
6
 
7
7
  Run **`node ${CLAUDE_SKILL_DIR}/tools/dispatch.mjs <check|advise|register|observe|open|await|return|fold|degrade|handoff-return|aggregate> …`**:
8
8
 
9
- 1. **`check <dispatch-file>`** — validates the ONE ```` ```aw-dispatch-contract ```` fenced block the dispatch file carries: exit `0` when its form is valid, `1` naming the FIRST violated field. The contract of the whole mode, in one sentence: the contract check is FORM-only — fields present, grammars respected, never boundedness, design-decidedness or acceptance adequacy — and `aggregate` REFUSES instead of computing acceptance for a wave with no pre-registration record, over an OPEN thread in scope, over a PRE-DISPATCH degrade that opens no thread, or across several waves with no `--wave`; the writer verbs add NO second legality door — the store's preflight is the only one, and its refusals travel verbatim — while `open` copies every mint-time field from the contract header and refuses a deadline below the wrapper cap plus the kill grace, `return` absorbs only a TERMINAL exec receipt whose backend, nonce and independently computed contractDigest match the dispatch it answers, and `fold` binds the CURRENT tree to the folded return's postTreeDigest, so a tree that moved between the two never folds, and `await` is satisfied ONLY by the TERMINAL exec receipt of its own dispatch's {backend, nonce} — never by a review receipt, a ledger line or a finding manifest — while an expiry names a supervision question and releases NO writer slot. Whether the sub-task is genuinely bounded, whether its design is decided, and whether its acceptance criteria are adequate stay **explicit orchestrator judgment** — a well-formed absurdity passes here by construction.
9
+ 1. **`check <dispatch-file>`** — validates the ONE ```` ```aw-dispatch-contract ```` fenced block the dispatch file carries: exit `0` when its form is valid, `1` naming the FIRST violated field. The contract of the whole mode, in one sentence: the contract check is FORM-only — fields present, grammars respected, never boundedness, design-decidedness or acceptance adequacy — and `aggregate` REFUSES instead of computing acceptance for a wave with no pre-registration record, over an OPEN thread in scope, over a PRE-DISPATCH degrade that opens no thread, or across several waves with no `--wave`; the writer verbs add NO second legality door — the store's preflight is the only one, and its refusals travel verbatim — while `open` copies every mint-time field from the contract header and refuses a deadline below the wrapper cap plus the kill grace, `return` absorbs only a TERMINAL exec receipt whose backend, nonce and independently computed contractDigest match the dispatch it answers, and `fold` binds the CURRENT tree to the folded return's postTreeDigest, so a tree that moved between the two never folds, and `await` is satisfied ONLY by the TERMINAL exec receipt of its own dispatch's {backend, nonce} — never by a review receipt, a ledger line or a finding manifest — while an expiry names a supervision question and releases NO writer slot; `open` records the thread's BASE (`head` by default, `checkpoint` under `--checkpoint <oid>`), `return` and `fold` measure against the base the dispatch recorded, never a flag, and the `fold` VERB itself refuses a checkpoint `code` thread the held-session judge names SUBSTITUTED in that thread's own verdict — a precondition like its hidden-path and content-blind guards, the store's preflight staying the only door on the record. Whether the sub-task is genuinely bounded, whether its design is decided, and whether its acceptance criteria are adequate stay **explicit orchestrator judgment** — a well-formed absurdity passes here by construction.
10
10
  2. **`advise --step-class <c>`** — the **vehicle-routing advisory**, and the question that comes BEFORE a contract check: *which vehicle carries this step class on THIS host, and what has the ledger already recorded for it.* It prints one block — the advised vehicle with its availability, the solo fallback, the recorded history, and the note below — and **decides nothing**: the advice never gates a dispatch: which vehicle carries a sub-task stays orchestrator judgment, and a divergence from the advice is recorded as a note rather than refused. Exit `0` for every legal step class, `2` only on usage. Its posture, stated exactly: the advisor module itself writes nothing, spawns nothing and opens no second ledger door; the VERB may run read-only git probes — the delegation store path (unless AW_DELEGATION_STORE names it outright) and the repository top-level the cheap vehicles are anchored at — and it never runs a vehicle, a subscription CLI, or anything that writes. Host capability is read from the filesystem — the execute backend from the bridge install (the detector spawns nothing at all), a cheap vehicle from the presence of `.claude/agents/<name>.md` **at the repository top-level**; where that root is not resolved, the lane answers **`unknown`** rather than claiming a vehicle is unplaced, because an absent file under an unanchored directory is not evidence of absence and a present one there is not evidence it is this repository's vehicle. The recorded history is the ledger's OWN thread walk, over the closed state set `folded | failure-terminal | degrade-closed | open` with **`open` counted separately** (a live thread is not evidence about a finished one). An **absent** ledger prints `no recorded history`; an **unreadable** one prints the store's own words and the advice still prints; neither is a refusal. The same block prints as a **footer on a form-valid `check`** (never under a refusal, so it cannot mask one), where the advised vehicle is compared against the contract's `vehicle.selected` and a difference is printed as a divergence NOTE naming `vehicle.requested` when the pair itself differs.
11
11
 
12
12
  The vehicle-routing matrix — one row per step class, and this copy is held to the code's registry CELL for cell by a structure check (a reorder, a duplicate, a dropped row, a mis-bound vehicle and a drifted availability or returns cell all leave every token present, so a token check would pass every one of them). The check reads only the anchored surface between the two markers below, so a copy of this table anywhere else in the doc can neither stand in for it nor mask its drift:
@@ -30,22 +30,22 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/dispatch.mjs <check|advise|register|observ
30
30
 
31
31
  3. **`register`** — appends the wave's **pre-registration** record (`--wave --step-classes --pairing-key --min-per-class --mean-l-threshold --first-pass-num --first-pass-den`). Acceptance is pre-registered so thresholds can never be chosen after the observations that will be judged against them; a registration is **immutable per wave** and a second one refuses. `--pairing-key` accepts only a key the aggregator actually implements (today `stepClass`) — a wave registered under any other key would record a contract the computation never follows, and the aggregator re-checks the registration independently, since a store can be hand-written.
32
32
  4. **`observe`** — appends ONE **observation** (`--wave --step-class --scope --plan --phase --provenance [--denominator-bytes]`). Provenance is `solo-construction` or `self-reported` **only**: delegated accounting is DERIVED from nonce threads and is never hand-appended. The numerator is the post-image bytes of the objects the scope names; a **solo** observation takes that same number as its denominator — `L = 1` by construction, so `--denominator-bytes` is refused — while a self-reported one states its own. One stated exception: a solo scope that measures **zero** bytes has no ratio at all, and is recorded INELIGIBLE by name (`zero-denominator`) rather than as `L = 1` — `0/0` is undefined before it is one, and a named ineligibility is what this family records instead of a silent number. **`--scope` is repeatable**, one repo-relative path per occurrence: no separator is safe inside a POSIX path, so a split list could not express `docs/my file.md` and would silently measure the wrong set wherever its fragments happened to exist. Paths are anchored at the **git top-level**, never at the caller's cwd, and the record carries a **JSON-encoded array of canonical repo-relative paths, carried in the schema's string field** (the record's `scope` is a string by D3, so the array travels encoded). An object's identity IS its canonical path, so two files with identical bytes are two objects and count twice, while one path reached twice (a repeated listing, an in-repo ancestor symlink) is one object and counts once. The run refuses by name outside a git work tree, for an absent or non-regular path, and for a path whose **real** location leaves the repository (an ancestor symlink is not a way out).
33
- 5. **`open`** — appends the **dispatch** record that opens a thread (`--contract <dispatch-file> --wave --backend --rationale --wrapper-cap-s --kill-grace-s [--retry-of <nonce>]`). Every mint-time field — `nonce`, `stepClass`, `vehicle`, `deadlineS`, `retry.index`, `retry.cap` — is **COPIED from the contract header** and bound by `contractDigest`, so a dispatch can never disagree with the header it claims to carry. It fingerprints the tree (`preTreeDigest`) and probes it for a CLEAN baseline; a **dirty** one is recorded as such, and the eventual return is then metric-INELIGIBLE by the name `dirty-baseline` — the uncommitted-state fingerprint is blind to the index↔worktree split, so a dirty baseline cannot attribute bytes to a dispatch. It **refuses a `deadlineS` below `--wrapper-cap-s` + `--kill-grace-s`** (both explicit operands: the kit never reads another package's default), because a dispatch that cannot be capped inside its own deadline could never honour the terminal-exit rule. Every retry rule — one successor per thread, the cap read off the chain's ORIGIN, wave and class equality, a changed contract after a `contract-refusal` — is the **store's**, and `open` surfaces its refusals verbatim.
33
+ 5. **`open`** — appends the **dispatch** record that opens a thread (`--contract <dispatch-file> --wave --backend --rationale --wrapper-cap-s --kill-grace-s [--checkpoint <oid>] [--retry-of <nonce>]`). Every mint-time field — `nonce`, `stepClass`, `vehicle`, `deadlineS`, `retry.index`, `retry.cap` — is **COPIED from the contract header** and bound by `contractDigest`, so a dispatch can never disagree with the header it claims to carry. It fingerprints the tree (`preTreeDigest`) and probes it for a CLEAN baseline; a **dirty** one is recorded as such, and the eventual return is then metric-INELIGIBLE by the name `dirty-baseline` — the uncommitted-state fingerprint is blind to the index↔worktree split, so a dirty baseline cannot attribute bytes to a dispatch. It **refuses a `deadlineS` below `--wrapper-cap-s` + `--kill-grace-s`** (both explicit operands: the kit never reads another package's default), because a dispatch that cannot be capped inside its own deadline could never honour the terminal-exit rule. Every retry rule — one successor per thread, the cap read off the chain's ORIGIN, wave and class equality, a changed contract after a `contract-refusal` — is the **store's**, and `open` surfaces its refusals verbatim. `--checkpoint <oid>` records the checkpoint base: the operand must be a git object id of exactly 40 or 64 lowercase hex characters that resolves to a TREE (a commit, a blob, a tag and an unknown id each refuse by name, nothing written), and every measurement of that thread — the clean probe, `preTreeDigest`, the return's bracketing fingerprint and diff, the fold's fingerprint and enumeration — is computed against that tree over the snapshot scope — every work-tree path not ignored by git's standard excludes judged without the real index, plus every present kit-own path, plus every path HEAD's tree or the base tree names, minus `docs/plans/`, `.claude/settings.json`, `.claude/settings.local.json`, `.mcp.json`, every known foreign-tool footprint and `node_modules`; the base tree's non-excluded paths deleted since the snapshot are measured as deletions; without the flag the base is `head` and every byte of the MEASUREMENT is today's (the writer always mints the `baseline` key, so the record itself is not byte-identical to 13.0.0's). The CLEAN line of a checkpoint open names the base.
34
34
  6. **`await`** — watches for ONE dispatch to **answer**, and writes nothing at all (`--nonce [--timeout <s>]`). Satisfaction is the arrival of the **TERMINAL** exec receipt for the dispatch's own `{backend, nonce}`: a `reserved` artifact means the run holds the nonce and has published nothing about how it ended, so the wait continues — the same artifact that is a **supervision refusal** at `return`, because absorbing it would record an outcome nobody observed. Satisfaction is decided **positively**, by the exec-receipt reader itself, so an artifact of a neighbouring family standing at that path — a review receipt, a delegation ledger line, a finding manifest — **refuses** rather than satisfies: only an exec receipt answers an exec dispatch, which is the D10 rule the review waiter states from its own side. The bound is the **ABSOLUTE** deadline, measured from the DISPATCH record's timestamp rather than from whenever this wait began: `--timeout` defaults to the time **remaining**, and one reaching past the deadline is **refused** rather than silently clamped (a shortened wait would let a caller believe they waited longer than they did). An already-expired dispatch is **answered immediately**, never waited on. **Arrival is read first, before EVERY bound** — the two wait bounds and the `--timeout` admissibility check alike, so a receipt already on disk answers `ARRIVED` even when the supplied `--timeout` is inadmissible, and the check runs only once there genuinely IS a wait to bound. A receipt already on disk is a fact this verb reports; whether a *late* one may be absorbed is the absorb door's question, refused there by name — **lateness has exactly one decision site, and it is `return`**. The review waiter checks its deadline first for the opposite reason: a receipt *line* carries no dispatch-bound timestamp at all, so there the clock is the only evidence there is. Each pass consults the clock over the poll that has already happened, then sleeps and polls again, so a receipt landing during the last sleep is reported rather than lost to a cutoff a moment later. An unanswered wait exits **`3`** — its own status so a caller that **branches** on the code can tell it from a refusal, never a defence against one that discards failure wholesale — names whether the **deadline** or the `--timeout` ended it, and states that **no writer slot was released**: a wait that ended without an answer never authorizes the next dispatch. Every artifact read rides the store's own no-follow reader, so a symlinked, FIFO, unreadable or malformed artifact refuses **by class** instead of standing in for an answer.
35
- 7. **`return`** — absorbs the wrapper's exec receipt and appends the **return** record (`--nonce [--outcome <o>] [--no-receipt --exit-status <n>]`). The receipt and its report live **beside the ledger**, in the store's own directory. It reads the **TERMINAL** receipt — a `reserved` one is a **supervision question**, not a timeout, and refuses by saying so — then checks the receipt's independently computed `contractDigest` against the dispatch's (a run that executed a *different* contract would otherwise correlate cleanly), its `{backend, nonce}` body against the dispatch it answers (the filename alone is composed by this side), its `capS + killGraceS` against the recorded `deadlineS`, and its timestamp against the **ABSOLUTE** deadline (`dispatch.timestamp + deadlineS`) — a late return refuses by name. It re-verifies the report's digest and length against the receipt, refuses a change set **hidden from the plain `git diff`** the metric is computed over (assume-unchanged, skip-worktree, `diff.ignoreSubmodules` — the numerator and the denominator would both be blind to it), and then enumerates the returned objects and frames the canonical integration bundle, **bracketed by the tree fingerprint**: a tree that moves mid-computation refuses rather than mixing two change sets. `--outcome` records an orchestrator judgment under the **closed override form** — a wrapper outcome either stays itself or moves to `contract-refusal | partial-edit | acceptance-failure | stale-return | store-failure`, and `success` is recordable only from a receipt that already says success; the record vocabulary's own cross-field rules cut the remaining incompatible pairs. **`--no-receipt`** means "no TERMINAL receipt", never "no evidence": it absorbs a **reservation ONLY** — over a `terminal` artifact it refuses, because a hand-stated outcome there would discard an exit status, a session id and a report digest the run actually proved, and skip the report check that guards them. It builds the return from that reservation (`wrapperVersion` and `posture`, never hand-typed), requires `--exit-status` and an `--outcome` from `transport-failure | contract-refusal | store-failure | missing-identity` (so `partial-edit` and `stale-return` are inexpressible there by name), and reads the report **if present** — an absent one records length 0 and the metric is then ineligible **by name**: `empty-report` where there IS a diff, and `no-op-diff` where the tree is unchanged too, since the eligibility rule names the diff first. That keeps a failed REPORT write and a failed TERMINAL write two separately named lanes. With **no artifact at all** there is no honest return — `--no-receipt` reads the same path and would refuse the same way — so close the thread with `degrade`.
36
- 8. **`fold`** — appends the integration re-confirmation and CLOSES the thread (`--nonce --verdict <text>`). It runs the **same hidden-path guard** as `return`, and here for a sharper reason: `treeDigestAtFold` is computed from the very payload that is blind to those paths, so a change made behind an `assume-unchanged` / `skip-worktree` bit between the return and the fold leaves the digest **equal** and the fold would accept bytes nobody returned — the digest cannot catch what the payload cannot see. It then computes the **current** tree digest, which the store binds to the folded return's `postTreeDigest`: a tree that moved between the return and the fold refuses in the store's own words, and the recovery it names is a fresh dispatch, never a fold. Staging usually moves the tree too — every change set carrying an untracked path does — so the order is `return` → review (own eyes + the configured review recipe) → a plain gate run → `fold` → `git add`. **The one shape staging does not move** is a tracked-only change passing from the worktree into a **clean** index: the payload concatenates the staged and unstaged diffs, so those bytes are identical either side of `git add` (the uncommitted-state fingerprint's stated blindness to the index↔worktree split). The fold is still honest there — identical payload bytes mean the folded content IS the returned content — so the rule is "**the fold precedes staging**", never "staging refuses the fold by construction".
35
+ 7. **`return`** — absorbs the wrapper's exec receipt and appends the **return** record (`--nonce [--outcome <o>] [--no-receipt --exit-status <n>]`). The receipt and its report live **beside the ledger**, in the store's own directory. It reads the **TERMINAL** receipt — a `reserved` one is a **supervision question**, not a timeout, and refuses by saying so — then checks the receipt's independently computed `contractDigest` against the dispatch's (a run that executed a *different* contract would otherwise correlate cleanly), its `{backend, nonce}` body against the dispatch it answers (the filename alone is composed by this side), its `capS + killGraceS` against the recorded `deadlineS`, and its timestamp against the **ABSOLUTE** deadline (`dispatch.timestamp + deadlineS`) — a late return refuses by name. It re-verifies the report's digest and length against the receipt, refuses a change set **hidden from the plain `git diff`** the head-base metric is computed over (assume-unchanged, skip-worktree, `diff.ignoreSubmodules`), and then enumerates the returned objects and frames the canonical integration bundle, **bracketed by the tree fingerprint**: a tree that moves mid-computation refuses rather than mixing two change sets. `--outcome` records an orchestrator judgment under the **closed override form** — a wrapper outcome either stays itself or moves to `contract-refusal | partial-edit | acceptance-failure | stale-return | store-failure`, and `success` is recordable only from a receipt that already says success; the record vocabulary's own cross-field rules cut the remaining incompatible pairs. **`--no-receipt`** means "no TERMINAL receipt", never "no evidence": it absorbs a **reservation ONLY** — over a `terminal` artifact it refuses, because a hand-stated outcome there would discard an exit status, a session id and a report digest the run actually proved, and skip the report check that guards them. It builds the return from that reservation (`wrapperVersion` and `posture`, never hand-typed), requires `--exit-status` and an `--outcome` from `transport-failure | contract-refusal | store-failure | missing-identity` (so `partial-edit` and `stale-return` are inexpressible there by name), and reads the report **if present** — an absent one records length 0 and the metric is then ineligible **by name**: `empty-report` where there IS a diff, and `no-op-diff` where the tree is unchanged too, since the eligibility rule names the diff first. That keeps a failed REPORT write and a failed TERMINAL write two separately named lanes. With **no artifact at all** there is no honest return — `--no-receipt` reads the same path and would refuse the same way — so close the thread with `degrade`.
36
+ 8. **`fold`** — appends the integration re-confirmation and CLOSES the thread (`--nonce --verdict <text>`). It runs the **same hidden-path guard** as `return`, and here for a sharper reason: `treeDigestAtFold` is computed from the very payload that is blind to those paths, so under a HEAD base a change made behind an `assume-unchanged` / `skip-worktree` bit between the return and the fold leaves the digest **equal** and the fold would accept bytes nobody returned — the digest cannot catch what the payload cannot see. It then computes the **current** tree digest, which the store binds to the folded return's `postTreeDigest`: a tree that moved between the return and the fold refuses in the store's own words, and the recovery it names is a fresh dispatch, never a fold. Under a HEAD base, staging usually moves the tree too — every change set carrying an untracked path does — so the order is `return` → review (own eyes + the configured review recipe) → a plain gate run → `fold` → `git add`. **The one shape staging does not move** is a tracked-only change passing from the worktree into a **clean** index: the payload concatenates the staged and unstaged diffs, so those bytes are identical either side of `git add` (the uncommitted-state fingerprint's stated blindness to the index↔worktree split). The fold is still honest there — identical payload bytes mean the folded content IS the returned content — so the rule is "**the fold precedes staging**", never "staging refuses the fold by construction". A checkpoint measurement never reads the real index, so staging moves nothing there, while the fold still precedes `git add` by the same rule. `fold` refuses a checkpoint `code` thread the held-session judge names SUBSTITUTED in its own verdict, naming the ledger degrade and retry as recovery, and fails closed on an error epoch or an unreadable ledger.
37
37
  9. **`degrade`** — appends the recorded no-fold closure (`--wave --step-class --rationale [--nonce]`), **threaded** (with `--nonce`) or **pre-dispatch** (without). The pre-dispatch form opens no nonce thread, so once one is recorded `aggregate` **REFUSES the whole wave by name** until the wave's counting unit is decided — said here and on the line that writes it, because it is a live cost of the record, not a surprise to discover later.
38
38
  10. **`handoff-return --slug <s> --wave <id> --plan <id> --phase <n>`** — the **worktree-stream return rung**, run FROM MAIN after `land --prepare` (from inside a linked worktree it refuses: the git dir is not the git common dir, and the shared common dir would let it attest the wrong tree). It locates the satellite through the handoff identity (the same locator leaf the worktrees mode binds on), **DELIVERS** every user-owned fragment of the handoff **byte verbatim** — the content before and after `## Provision record` alike, each between boundary lines carrying its byte length, so the delivery is verifiable rather than asserted — and names the MAIN-owned destinations (findings → `docs/plans/queue.md`; decisions and session records → the `docs/ai` records); the fold itself stays orchestrator judgment and the rung claims nothing about whether it happened. It requires **BOTH** `prepared-tree` and `prepared-head` from the record and re-attests them against MAIN — the staged write-tree must equal the recorded tree and HEAD must equal the recorded head, because a clean post-commit index reproduces the committed tree, so a tree comparison alone cannot close the window; a record with no `prepared-head` was written by an earlier kit and refuses by name pointing at `land --prepare`. Then it classifies the prepared change set against the **observation domain** (present regular files only — a regular BINARY file is inside, its bytes read like any other) and appends ONE `self-reported` `worktree-stream` observation through the store's single door — numerator: the change set's **blob bytes, read from the attested tree itself** (`git cat-file` over the diff-tree entries' new OIDs, every answer validated fail-closed) — never from disk, which an unstaged edit after the prepare moves silently; denominator: the handoff byte count. The staged write-tree and HEAD are re-checked once more **immediately before either answer** (the pre-append idiom — it narrows the race window rather than closing it, this family's stated posture). A **deletion**, a **rename's absent old side**, a **symlink**, a **submodule**, a **mode-only change** (no measurable byte change at all), a **path whose name is not valid UTF-8** (the record's string domain cannot carry it; it renders as hex) and every other unrepresentable form end instead with `observation: NOT RECORDED — <form> at <path> is outside the observation domain` at exit `0`, delivery and proof still printed — **no partial scope is ever recorded**: a number that silently omitted the deletions half of a landing would be worse than a named non-record. **Honest limit (double-recorded):** the closed observation key set carries no artifact digest, so the handoff's sha256 and the two OIDs are the rung's **printed proof, never ledger fields** — an ACCEPTED limitation, recorded in the ADR with its follow-up row in the queue. The printed next-step order is the worktrees mode's after-the-fold order: a fold landed after the gates leaves them stale.
39
39
  11. **`aggregate [--wave <id>]`** — reports one wave: the registered thresholds, every observation (context — never part of the acceptance number), and per registered step class the delegated threads with the **D7 inclusion table** applied. A folded **success** with an eligible `wrapper-git` metric contributes its `L`; a folded success whose metric is ineligible or self-reported is EXCLUDED from the mean and from `n` while still counting in the first-pass rate; a failure-terminal thread, a degrade-closed one, and a folded **acceptance-failure** (the fold-fix judgment — the orchestrator paid for the same work twice, so its bytes are not leverage) are each in `n` at `L = 0`. The first-pass rate is per retry CHAIN: its denominator is the `retryIndex`-0 threads, so a retry never reports one piece of work twice. Below the registered minimum acceptance is **NOT computed** (insufficient); at or above it is computed and labeled **PILOT** evidence. The primary statistic is the per-class MEAN of `L`; the byte-weighted aggregate prints as SECONDARY only.
40
40
 
41
41
  **Exit codes:** `0` success (a computed report and an insufficient one both exit 0 — go/no-go is judgment, never an exit code); `1` a refusal (a form violation, an unreadable dispatch file, a supervision question, any store STOP verbatim); `2` usage; `3` an `await` that ended with no terminal receipt — its own status so a caller that **branches** on the exit code can tell an unanswered wait from a refusal; a caller that discards every failure discards this one too.
42
42
 
43
- **A concealing tree is refused at every door that measures or binds one** (`open`, `return`, `fold`) — the recorded baseline, the counted bytes and the folded identity are all claims about a tree that is telling the truth. Two arms, and the first refuses a **bit**, not its effect: any index entry whose `ls-files` tag is not `H` (assume-unchanged, skip-worktree, an unmerged index) stops the verb, because deleting a *materialized* skip-worktree file is invisible to `computeWorkingState`, to the plain diff, to the tree fingerprint **and** to the producer's enumeration — there is no pair of views whose difference could reveal it, and at `open` it is worse than invisible: the baseline records CLEAN and the delegate is later credited with a deletion it never made. The second arm compares the forced diff against the plain one **per side and on raw bytes** (a union lets a path visible on one side mask its hidden state on the other; decoded names let two distinct paths collapse into one), refusing what an `ignore-submodules` setting hides. Both name the path; the recovery is `git update-index --no-assume-unchanged / --no-skip-worktree`, or clearing the diff config.
43
+ **A concealing tree is refused at every door that measures or binds one** (`open`, `return`, `fold`) — the recorded baseline, the counted bytes and the folded identity are all claims about a tree that is telling the truth. Two arms, and the first refuses a **bit**, not its effect: any index entry whose `ls-files` tag is not `H` (assume-unchanged, skip-worktree, an unmerged index) stops the verb, because under a HEAD base deleting a *materialized* skip-worktree file is invisible to `computeWorkingState`, to the plain diff, to the tree fingerprint **and** to the producer's enumeration — there is no pair of views whose difference could reveal it, and at `open` it is worse than invisible: the baseline records CLEAN and the delegate is later credited with a deletion it never made. The second arm compares the forced diff against the plain one **per side and on raw bytes** (a union lets a path visible on one side mask its hidden state on the other; decoded names let two distinct paths collapse into one), refusing what an `ignore-submodules` setting hides. Both name the path; the recovery is `git update-index --no-assume-unchanged / --no-skip-worktree`, or clearing the diff config.
44
44
 
45
- **Honest limits of the exec lane, v1:** **gate output is never accounted** — the wrapper's EXIT trap removes its trace, so no `gate-output` component is emitted and the metric counts the returned change set only; a change set carrying a **binary, non-regular or submodule** object is **REFUSED** at `return` and `fold`. For the first two the payload carries no content at all — a binary by name alone (`untracked-binary:<path>`, and a tracked binary's diff is the line "Binary files … differ"), a non-regular path by name alone. The **submodule** arm is *conservative* rather than a claim about every submodule: a clean staged gitlink replacement does carry exact OIDs in the payload, but once a submodule is **dirty** nothing further about its nested state is recorded (probed: a second, different nested edit and a whole new nested file both leave the digest unchanged), and no cheap probe separates the two at this door. A **symlink whose target is not valid UTF-8** is refused for a narrower reason on the same principle: the payload reads a target as a *string*, so those bytes are folded to `U+FFFD` before the digest is taken (probed: targets `0xff` and `0xfe` produce one payload and one enumeration), and a later change of target would move nothing. Reading that target fails closed on every errno but `ENOENT` — a deleted link has nothing to lose, its bytes riding the diff, while `EINVAL` means the object is **no longer a symlink at all**: the producer labels an object `symlink` when *any* layer carries mode `120000`, so a committed symlink replaced by a binary regular file is a `symlink` entry that the content-blind refusal never sees either, and the type change would fall between the two guards.
45
+ **Honest limits of the exec lane, v1:** **gate output is never accounted** — the wrapper's EXIT trap removes its trace, so no `gate-output` component is emitted and the metric counts the returned change set only; a change set carrying a **binary, non-regular or submodule** object is **REFUSED** at `return` and `fold`. For the first two the head payload cannot be trusted to follow the bytes — an UNTRACKED binary by name alone (`untracked-binary:<path>`), a non-regular path by name alone; the binary arm is CONSERVATIVE over its class, since a tracked binary's diff does carry both blob ids on its `index` line. The **submodule** arm is *conservative* rather than a claim about every submodule: a clean staged gitlink replacement does carry exact OIDs in the payload, but once a submodule is **dirty** nothing further about its nested state is recorded (probed: a second, different nested edit and a whole new nested file both leave the digest unchanged), and no cheap probe separates the two at this door. A **symlink whose target is not valid UTF-8** is refused for a narrower reason on the same principle, and CONSERVATIVELY over its class: the head payload reads an UNTRACKED link's target as a *string*, so those bytes are folded to `U+FFFD` before the digest is taken (probed: untracked targets `0xff` and `0xfe` produce one payload and one enumeration) and a later change of target would move nothing, while a TRACKED link's target rides `git diff` raw; both refusals hold for a checkpoint thread too. Reading that target fails closed on every errno but `ENOENT` — a deleted link has nothing to lose, its bytes riding the diff, while `EINVAL` means the object is **no longer a symlink at all**: the producer labels an object `symlink` when *any* layer carries mode `120000`, so a committed symlink replaced by a binary regular file is a `symlink` entry that the content-blind refusal never sees either, and the type change would fall between the two guards.
46
46
 
47
- **Which ledger the tree-binding verbs use.** For `open`, `return` and `fold` the resolved store must be the **canonical** one, exactly — `<git common dir>/agent-workflow-delegation.jsonl` — and every other store refuses. Three failures collapse into that one equality: a store placed inside the work tree is carried by the payload *and enumerated as an object*, so the metric would count its own bookkeeping as delegated work while the append that follows `postTreeDigest` moves the tree every later fold binds; a store belonging to another repository would measure this tree against a foreign thread, the frozen record schema binding no worktree identity; and a **second** ledger in the same git dir would share artifact names with the first, since those are a function of `{backend, nonce}` alone — so a return could absorb the neighbouring ledger's receipt. One repository, one delegation ledger, which is what resolving to the git *common* dir already meant. `register`, `observe`, `await` and `aggregate` keep the unrestricted override — they bind no tree. Path comparison canonicalizes through symlinks and **fails closed** on any error but `ENOENT` (a path this kit cannot resolve is not one it may approve); the check is repeated immediately before every append, which *narrows* the replacement window rather than closing it — a check-then-write pair is not atomic, and this module defends against a buggy or interrupted producer, never a racing adversary. `open` additionally refuses **pre-spend** when either exec artifact name for its `{backend, nonce}` is already taken: one nonce, one artifact pair. **Stated residual:** two *linked worktrees* of one repository share a common dir by design (the ledger is worktree-shared), so this rule cannot separate them — opening in one linked worktree and returning or folding in another measures the wrong tree, is **forbidden** under the D10 one-writer bar, and is named here rather than mechanized, because the frozen record schema carries no worktree identity to bind it with. Their bytes can therefore move under a digest that cannot follow them, while the numerator counts a size re-read at return time; documenting that would leave a promise knowingly false, so the capability is **subtracted** until the frozen shared payload (which the review lane binds too) can carry their content — queued as its own item.
47
+ **Which ledger the tree-binding verbs use.** For `open`, `return` and `fold` the resolved store must be the **canonical** one, exactly — `<git common dir>/agent-workflow-delegation.jsonl` — and every other store refuses. Three failures collapse into that one equality: a store placed inside the work tree is carried by the payload *and enumerated as an object*, so the metric would count its own bookkeeping as delegated work while the append that follows `postTreeDigest` moves the tree every later fold binds; a store belonging to another repository would measure this tree against a foreign thread, the frozen record schema binding no worktree identity; and a **second** ledger in the same git dir would share artifact names with the first, since those are a function of `{backend, nonce}` alone — so a return could absorb the neighbouring ledger's receipt. One repository, one delegation ledger, which is what resolving to the git *common* dir already meant. `register`, `observe`, `await` and `aggregate` keep the unrestricted override — they bind no tree. Path comparison canonicalizes through symlinks and **fails closed** on any error but `ENOENT` (a path this kit cannot resolve is not one it may approve); the check is repeated immediately before every append, which *narrows* the replacement window rather than closing it — a check-then-write pair is not atomic, and this module defends against a buggy or interrupted producer, never a racing adversary. `open` additionally refuses **pre-spend** when either exec artifact name for its `{backend, nonce}` is already taken: one nonce, one artifact pair. **Stated residual:** two *linked worktrees* of one repository share a common dir by design (the ledger is worktree-shared), so this rule cannot separate them — opening in one linked worktree and returning or folding in another measures the wrong tree, is **forbidden** under the D10 one-writer bar, and is named here rather than mechanized, because the frozen record schema carries no worktree identity to bind it with.
48
48
 
49
- **What the fold's binding IS, and its stated residuals.** The binding is **equality of the visible unframed payload** between the return and the fold — never an identity of the tree, and the difference is not academic. The payload is an unframed concatenation, so **two different trees can produce the same bytes**: probed live, a single file containing the line `untracked:two.txt` yields the same fingerprint as a tree of two files, and a symlink's target can imitate the marker that opens the next untracked entry. It also carries **no git-relevant mode**, so `chmod 0644 → 0755` on an untracked file moves neither the fingerprint nor the enumeration — a blind attribute, not a fourth unsupported content class. Regular-file content and symlink targets are deliberately **not** subtracted: they are present in the payload, only ambiguously framed, and refusing them would refuse `new` and `symlink`, which is every delegated change set there is. The real fix — a framed payload carrying mode bits — is queued against that frozen surface, since it revalues every stored fingerprint in the family. A **receipt is forgeable**, exactly like every record in this family, so what the absorb door defends against is a buggy or interrupted producer, not a hostile one; the numerator **over-counts a rename** (a delegate's rename arrives as a deletion plus a creation, and no identity matching is done — that machinery was built and deliberately subtracted), so the metric reads optimistic, except in the rename-then-recreate case, which is indeterminate and named in the producer's own header; and **D10 is a bar, not a mechanism** — at most ONE in-tree exec dispatch runs at a time, and nothing here refuses a second one.
49
+ **What the fold's binding IS, and its stated residuals.** The binding is **equality of the visible payload** between the return and the fold — never an identity of the tree, and the difference is not academic. Under a HEAD base the payload is an unframed concatenation, so **two different trees can produce the same bytes**: probed live, a single file containing the line `untracked:two.txt` yields the same fingerprint as a tree of two files, and a symlink's target can imitate the marker that opens the next untracked entry. Its UNTRACKED section carries **no git-relevant mode**, so `chmod 0644 → 0755` on an untracked file moves neither the fingerprint nor the enumeration — a blind attribute, not a fourth unsupported content class (a TRACKED file's mode flip rides the diff as `old mode` / `new mode` and does move it). Regular-file content and symlink targets are deliberately **not** subtracted: they are present in the payload, only ambiguously framed, and refusing them would refuse `new` and `symlink`, which is every delegated change set there is. The real fix — a framed payload carrying mode bits — is queued against that frozen surface, since it revalues every stored head-base fingerprint in the family. Under a checkpoint base the payload is one indexed diff: a mode change moves it and no untracked marker line exists. A **receipt is forgeable**, exactly like every record in this family, so what the absorb door defends against is a buggy or interrupted producer, not a hostile one; under a head base the numerator **over-counts a rename** (a delegate's rename arrives as a deletion plus a creation, and no identity matching is done — that machinery was built and deliberately subtracted), so the metric reads optimistic, except in the rename-then-recreate case, which is indeterminate and named in the producer's own header; under a checkpoint base git's own rename detection applies inside the one base-relative layer and a detected rename is counted once as `renamed`; and **D10 is a bar, not a mechanism** — at most ONE in-tree exec dispatch runs at a time, and nothing here refuses a second one.
50
50
 
51
- **Invariants:** writer — appends only to the delegation ledger, `<git common dir>/agent-workflow-delegation.jsonl` (`AW_DELEGATION_STORE` overrides, absolute only), through the store's own lock-serialized append; the store's preflight is the single legality door and this mode adds no second validator · every verb that DERIVES anything from the ledger (`return`, `fold`, `degrade`, `aggregate`) replays the store's preflight over the whole file first and refuses at the first record the append path would have refused, naming its physical line — a verb never computes over a "legal prefix" · **never commits**, never runs a subscription CLI, spawns nothing but git READS — with ONE stated exception: `handoff-return` attests MAIN's index with `git write-tree`, which may write a tree OBJECT into the odb and moves no ref (the same probe `land --prepare` itself uses; the ledger stays the only thing this mode APPENDS to) · the ledger is separate from the review receipts and the flow store, and a line of one family never satisfies a reader of another.
51
+ **Invariants:** writer — appends only to the delegation ledger, `<git common dir>/agent-workflow-delegation.jsonl` (`AW_DELEGATION_STORE` overrides, absolute only), through the store's own lock-serialized append; the store's preflight is the single legality door and this mode adds no second validator · every verb that DERIVES anything from the ledger (`return`, `fold`, `degrade`, `aggregate`) replays the store's preflight over the whole file first and refuses at the first record the append path would have refused, naming its physical line — a verb never computes over a "legal prefix" · **never commits**, never runs a subscription CLI, spawns nothing but git READS — with TWO stated exceptions: `handoff-return` attests MAIN's index with `git write-tree`, which may write a tree OBJECT into the odb (the same probe `land --prepare` itself uses), and checkpoint measurement writes the leaf's OWN temporary index file and forced-add blob objects — never a ref, never the real index; the ledger stays the only thing this mode APPENDS to · the ledger is separate from the review receipts and the flow store, and a line of one family never satisfies a reader of another.
@@ -7,7 +7,7 @@ Read-only **review-receipt checker** (AD-038) — it makes *"reviewed ≠ shippe
7
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs [--check] [--json]`:
8
8
 
9
9
  1. Plain run → the human report: resolved recipe + source, plan-in-flight, tree fingerprint, the `held session: <id> — <n> fold(s) rode it` / `none` / substitution line when configured delegated execution arms it, and per-backend receipt state (current / stale / ungrounded / probe / rejected / missing) with verdict + grounding + timestamp.
10
- 2. **`--check`** → the gate exit code. Before the review-recipe early return, a configured `plan-execution.execute = delegated` with a plan in flight and a dirty, fingerprintable tree audits the delegation ledger and refuses a substituted held session; an absent ledger is inert, while an unreadable, malformed, foreign, audit-refused ledger or failed HEAD read fails closed with its own reason. The accepted-replacement escape is a `codex-exec` degrade at the substituted return's tree, standing after every later fold, never the review receipt id `codex`. The remaining **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (after the held-session arm above) (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a cwd where git ITSELF answers not-a-repository under both discoveries (the ONLY non-work-tree location that passes — `error`, `redirected`, `no-work-tree` and `env-only` refuse by their state name once a receipt could be required), or every recipe-named backend SATISFIED for the current tree. **A clean-tree PASS under a NON-SOLO review obligation is never silent about a latent arm:** when a `reviewed` or `council` recipe is configured, it NAMES every plan in flight and states that this gate arms as soon as the tree is dirty — the condition 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). Nothing is announced under a configured `solo` recipe or a not-a-repository cwd, where the gate can never arm. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY review-backend escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (ANY edit after a review moves the fingerprint), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
10
+ 2. **`--check`** → the gate exit code. Before the review-recipe early return, a configured `plan-execution.execute = delegated` with a plan in flight and a dirty, fingerprintable tree audits the delegation ledger and refuses a substituted held session; an absent ledger is inert, while an unreadable, malformed, foreign, audit-refused ledger or failed HEAD read fails closed with its own reason. For a head-base thread, the accepted-replacement escape is a `codex-exec` degrade at the substituted return's tree, standing after every later fold, never the review receipt id `codex`. A checkpoint-based thread's substitution closes only by its own ledger `degrade` (which also withdraws the expectation, so the next fold establishes HELD) — no core-evidence degrade lifts it, and the report's recovery hint says so. The remaining **normative exit contract lives in the tool header** (the single home — do not re-enumerate it elsewhere): exit 0 for a CONFIGURED solo recipe (after the held-session arm above) (a computed readiness-degrade NEVER silently becomes solo — it needs the explicit degrade record below), no plan in flight (the `docs/plans` naming convention: `queue.md` and `EXECUTE-`/`FEEDBACK-`-prefixed or `PROMPT`/`prompt`/`handoff`-carrying names are scratch), a clean tree, a cwd where git ITSELF answers not-a-repository under both discoveries (the ONLY non-work-tree location that passes — `error`, `redirected`, `no-work-tree` and `env-only` refuse by their state name once a receipt could be required), or every recipe-named backend SATISFIED for the current tree. **A clean-tree PASS under a NON-SOLO review obligation is never silent about a latent arm:** when a `reviewed` or `council` recipe is configured, it NAMES every plan in flight and states that this gate arms as soon as the tree is dirty — the condition 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). Nothing is announced under a configured `solo` recipe or a not-a-repository cwd, where the gate can never arm. **Satisfaction is ship-class-only on the LATEST NORMAL receipt (D3(b)):** per backend, the latest probe-free current-fingerprint receipt is selected FIRST and THEN verdict-checked — only the recognized ship-class vocabulary (`ship` / `ship with nits`) satisfies; a recognized NEGATIVE (`revise` / `rethink` / `rework`) is an authoritative VETO (an earlier ship never survives a later revise); an UNRECOGNIZED verdict (e.g. `unknown` from a dead run) fails CLOSED unconditionally — a later `unknown` never lets an earlier SHIP stand, and a fresh normal re-run supersedes it. **The ONLY review-backend escape is an explicit degrade RECORD** (`core-evidence.mjs degrade --backend <name> --reason "…"`, fingerprint-bound to the current tree) — and never all backends: ≥1 non-degraded ship-class receipt is required whenever ≥1 backend is configured; a malformed evidence store denies the escape fail-closed but never fails an independently-satisfied tree. Exit 1 otherwise — missing, **stale** (every edit the payload can see moves the fingerprint, and no more), ungrounded, vetoed, or unrecognized. Plan/diff receipts and continuations (`agy-review --continue`) are **informational-only**: after a fold, only a **fresh grounded re-run** (`codex-review code`; `agy-review code --facts @f`) restores green. **Probe receipts never attest either:** a `CODEX_PROBE=1` / `AGY_PROBE=1` review runs with the frontier-model/max-effort guard OFF, so the wrapper stamps `probe:true` and this checker excludes it — per receipt, so a real review at the same fingerprint still satisfies. **Silence is not a declaration:** a malformed *or* absent probe marker is rejected fail-closed and stated in the check line. Honest bound: receipts are **not authenticated** (a forger could write `probe:false` as easily as any other field) — like the rest of the receipt this is a self-discipline mechanism, not a security boundary.
11
11
  3. **Wire it as a gate by hand OR via the explicit-consent init preview — never without consent (AD-021/D9).** The candidate line for your own `docs/ai/gates.json`: `{ "id": "review-state", "title": "Review receipts current for the uncommitted tree", "cmd": "node <path-to-this-skill>/tools/review-state.mjs --check" }` — with the path your project actually reaches the kit by, QUOTED so a path with spaces survives, executable from the project root. The consent-gated gates-init preview — `node ${CLAUDE_SKILL_DIR}/tools/gates-init.mjs --cwd <project>` (dry-run: it prints the derived entries and writes NOTHING; `--apply --only review-state` writes exactly this one on your explicit yes) — offers exactly this entry, path resolved and quoted, ONLY when your `docs/ai/orchestration.json` declares `reviewed`/`council` on `plan-execution.review` (the slot this checker enforces). The full consent-fill contract is in `${CLAUDE_SKILL_DIR}/references/modes/gates.md`. Once declared, the opt-in `${CLAUDE_SKILL_DIR}/references/modes/hook.md` auto-approves it like any other declared gate — and `run-gates --final` requires this check among its canonical core gates.
12
12
  4. **`--await [--timeout <s>]`** (BUGFREE-3 / AD-049) → BLOCK until every recipe-named backend is SATISFIED for the current tree (i.e. until `--check` would PASS), or the bounded timeout elapses (a loud exit 1; default 900s). Run it after dispatching the review bridges to WAIT for their receipts to land instead of hand-polling a pid: the durable completion signal is the **receipt**, never a process event (a harness "completed" notification fires early; a bridge's output late-flushes). It inherits the `--check` decision whole — a backend with a current-tree degrade RECORD stops being waited on (the shared decideCheck), so you never hand-`--await` around a known degrade. A held-session refusal and an **AUTHORITATIVE veto terminate the wait loudly BEFORE the deadline**: neither can be moved by waiting; only the held-session retry/degrade lane or a fresh review can move its corresponding refusal. Still read-only (it re-reads the receipts + the evidence store); solo / no-plan / clean-tree resolve instantly. For waiting on **one dispatch's arrival** (never obligation satisfaction) use the receipt-arrival deadline runner, `${CLAUDE_SKILL_DIR}/references/modes/receipt-deadline.md`.
13
13
 
@@ -0,0 +1,33 @@
1
+ const OPEN = 'open';
2
+ const CLAIM_FIELDS = Object.freeze(['owns', 'shared']);
3
+ const TRAILING_SLASH = /\/$/;
4
+ const SOURCE_EXTENSION = /^(.*[^/])(\.[^./]+)$/;
5
+ const TEST_FILE = /\.test\.[^/]+$/;
6
+ const TEST_SUFFIX = '.test';
7
+
8
+ export const unique = (items) => [...new Set(items)];
9
+
10
+ export const expandClaim = (path) => {
11
+ const source = SOURCE_EXTENSION.exec(path);
12
+ return source && !TEST_FILE.test(path)
13
+ ? [path, `${source[1]}${TEST_SUFFIX}${source[2]}`, `${source[1]}${TEST_SUFFIX}/`] : [path];
14
+ };
15
+ export const hasPathOverlap = (left, right) => {
16
+ const a = left.replace(TRAILING_SLASH, '');
17
+ const b = right.replace(TRAILING_SLASH, '');
18
+ return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
19
+ };
20
+ export const findReachable = (graph, pending, visited = new Set()) => {
21
+ const unseen = unique(pending.filter((id) => !visited.has(id)));
22
+ if (unseen.length === 0) return visited;
23
+ return findReachable(graph, unseen.flatMap((id) => graph.get(id) ?? []), new Set([...visited, ...unseen]));
24
+ };
25
+ const getClaims = (row) => CLAIM_FIELDS.flatMap((field) => row[field].map((path) => ({ field, path, ground: expandClaim(path) })));
26
+
27
+ export const listStories = (epics) => epics.filter((epic) => epic.state === OPEN).flatMap((epic) => {
28
+ const graph = new Map(epic.rows.map((row) => [row.id, row.dependsOn]));
29
+ return epic.rows.filter((row) => row.valid).map((row) => ({
30
+ path: epic.path, id: row.id, line: row.line, state: row.state,
31
+ claims: getClaims(row), reachable: findReachable(graph, row.dependsOn),
32
+ }));
33
+ });