@sabaiway/agent-workflow-kit 10.6.0 → 11.0.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/SKILL.md +9 -1
  3. package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +6 -6
  5. package/bridges/antigravity-cli-bridge/capability.json +1 -1
  6. package/bridges/codex-cli-bridge/SKILL.md +1 -1
  7. package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
  8. package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
  9. package/bridges/codex-cli-bridge/capability.json +1 -1
  10. package/capability.json +1 -1
  11. package/package.json +1 -1
  12. package/references/modes/commit-guard.md +2 -2
  13. package/references/modes/control-bytes.md +91 -0
  14. package/references/modes/flow-writer.md +16 -4
  15. package/references/modes/procedures.md +2 -0
  16. package/references/modes/review-state.md +1 -1
  17. package/references/modes/robustness-brief.md +61 -0
  18. package/references/modes/velocity.md +2 -0
  19. package/references/robustness-literals.json +107 -0
  20. package/tools/changed-surface.mjs +1 -1
  21. package/tools/commands.mjs +14 -0
  22. package/tools/commit-guard.mjs +20 -9
  23. package/tools/control-bytes.mjs +264 -0
  24. package/tools/core-evidence.mjs +33 -20
  25. package/tools/coverage-check.mjs +1 -1
  26. package/tools/dispatch-store-read.mjs +2 -2
  27. package/tools/flow-check-git-lane.mjs +2 -1
  28. package/tools/flow-delta-proof.mjs +4 -4
  29. package/tools/flow-record-shape.mjs +44 -2
  30. package/tools/flow-round-gates.mjs +151 -0
  31. package/tools/flow-store-read.mjs +1 -1
  32. package/tools/flow-writer.mjs +235 -82
  33. package/tools/fold-scope.mjs +7 -3
  34. package/tools/fs-read-nofollow.mjs +56 -4
  35. package/tools/git-env.mjs +120 -0
  36. package/tools/grounding.mjs +4 -3
  37. package/tools/plan-shape-facts.mjs +23 -10
  38. package/tools/plan-shape.mjs +8 -0
  39. package/tools/procedures.mjs +18 -4
  40. package/tools/repo-lex.mjs +29 -0
  41. package/tools/review-state.mjs +43 -34
  42. package/tools/robustness-brief.mjs +209 -0
  43. package/tools/robustness-literals.mjs +101 -0
  44. package/tools/sandbox-masks.mjs +2 -1
  45. package/tools/set-flow.mjs +2 -1
  46. package/tools/source-size-scope.mjs +19 -25
  47. package/tools/tracked-tree-census.mjs +3 -3
  48. package/tools/velocity-profile.mjs +5 -2
  49. package/tools/worktree-handoff-return.mjs +2 -2
  50. package/tools/worktrees.mjs +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,91 @@ 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
+ ## 11.0.0 — the per-fold walk is a record, the cap is a refusal at the round table's own signal, and the fingerprint argv has one home (AD-130)
8
+
9
+ Plan 2's two attestation rounds on CONVERGED bytes each found one member of the kit's own robustness
10
+ list that the executor never received: the tagged sweep row got its brief, the untagged suite and
11
+ generator rows did not. Nothing asked "does the tag cover the row" before the dispatch, nothing
12
+ recorded the adversarial walk a fold owes, and past the round table's `cap reached` / `crossover`
13
+ signal nothing refused the next round — on the release-run feature the table printed CROSSOVER from
14
+ round 2 for eleven rounds and nothing acted on it. Rungs 4 and 5 of the first-pass-quality series
15
+ close that, and the two fingerprint classes plan 1 measured RED (Issue-023 / Issue-024) close with
16
+ them. Contracts: `robustness-literals.md` rev 2 (S10–S11 bound), `plan-review-loop/index.md` rev 6
17
+ (S30–S33 bound), `release-run/index.md` rev 2.
18
+
19
+ **Coverage is the generator's second mode.** `robustness-brief.mjs --plan <plan> --coverage` judges
20
+ every `create`/`modify` row except a document (`.md`, `.txt`) or the shipped list itself: a class is
21
+ PRESENT when the row file's BYTES carry a member literal of kind env/flag/ref/syscall/errno/argv as a
22
+ BOUNDED token (a comment counts, `state` members are never searched), `uncovered` = present minus
23
+ tagged, a `delete` row and an absent file are LISTED, a sweep row is judged over every path the facts
24
+ leaf expands (`expandSweepPaths`, one home in `plan-shape-facts.mjs`); exit 0 covered / 1 naming each
25
+ `row:class:literal` / 2 on a row file the 1 MiB no-follow read cannot deliver. Measured on itself: the
26
+ first run over this very plan named `R01:spawn-outcome:ENOENT` and the tag grew before the next
27
+ dispatch. `references/modes/robustness-brief.md` states the mode, its read surface and the bounds.
28
+
29
+ **Every fold owes a walk, recorded.** `flow-writer internal-attestation <planId> --walk <file>` reads a
30
+ closed JSON walk (`{ listVersion, rows: [{ id, class, checked }] }`) relative to the git COMMON dir,
31
+ derives the obligation from the ADOPTED plan (every judged row, every class in TAGGED ∪ PRESENT needs a
32
+ non-empty `checked`), records `uncovered` and never refuses on it; `round-open` for every round after
33
+ the first refuses without an authoritative walk-carrying attestation at round N's coordinates, and
34
+ `--justification` lifts that refusal as an echoed INPUT whose durable trail is the attestation's
35
+ absence. A plan with no judgeable row owes no walk; a plan that lost adoption coverage fails closed
36
+ by name (`resolveAdoptedPlan`, one home).
37
+
38
+ **The cap is the round table's own signal, and terminal is digest equality.** The new pure leaf
39
+ `tools/flow-round-gates.mjs` (`blockingItems`, `roundsForSignal`, `capIssue`, `walkIssue`,
40
+ `escalationIssue`, `validateWalk`; `signalFor` imported from `review-rounds.mjs`, never modified)
41
+ builds one round per authoritative head from its LANDED dispatches; on `cap reached` or `crossover`
42
+ only, `round-open` refuses unless every blocking ITEM of every landed receipt of the latest head is
43
+ covered by a disposition whose `findingDigest` equals `sha256(itemText)` — text mode the wrapper's own
44
+ line, schema mode the `issue` field verbatim — and prints every uncovered item beside its digest. An
45
+ enumeration failure on a readable manifest falls back to the `>= 1` floor with an ADVISORY; the
46
+ checks run cap → walk → floor in the lock-free pass and again on the locked snapshot.
47
+
48
+ **A finding with no proven narrow fix is never queued; a lost manifest is a record.** `round-land
49
+ --dispose queued --finding <quote> --claim <invariant> --proof-kind consult-attestation|red-proof
50
+ --proof-digest <d>` mints only on `decideFoldScope` ACCEPT `new-invariant`, with `debtId`/`debtDigest`
51
+ DERIVED from the ONE resolved queue row `fold-scope` now returns (`normalize` exported, one home); a
52
+ consult-attestation proof binds through `proposedFixDigest === sha256(itemDigest + "\n" + claim)`, a
53
+ red-proof through the head's `base`, the tree `round-land` runs on and the row's `proof` field. The
54
+ fourth arm `escalated --finding <quote> --override-digest <d>` resolves a `maintainer-override` of THIS round's vetoing
55
+ receipt; the FIFTH arm `custody-lost --receipt <receiptDigest>` mints only on a REAL loss (`absent |
56
+ foreign | error | malformed | foreign-identity | swapped`), covers every item of that receipt for the
57
+ cap and is recordable whenever the loss is real. `flow-record-shape.mjs` admits `walk`, the queued
58
+ trio (all-or-none), `escalated` and `custody-lost`; `FLOW_SCHEMA_VERSION` does not move.
59
+
60
+ **The fingerprint argv has one home.** `core-evidence.mjs` exports `FINGERPRINT_CACHED_DIFF_ARGV` /
61
+ `FINGERPRINT_UNSTAGED_DIFF_ARGV` carrying `--no-ext-diff --no-textconv --ignore-submodules=none`;
62
+ `flow-delta-proof.mjs` imports them; both wrappers' bash twins (`emit_fingerprint_payload`,
63
+ `assemble_code_diff`, the no-diff preflight) carry the flags, mirrored into `bridges/`, and
64
+ `test/review-fingerprint-parity.test.mjs` EXTRACTS the preflight line and proves it on real git with a
65
+ gitlink under `diff.ignoreSubmodules=all`. Issue-023 and Issue-024 close; `commit-guard`'s
66
+ content-free refusal 2 narrows to a fail-closed backstop with a generic message that names no flag
67
+ and no config key (`references/modes/commit-guard.md`).
68
+
69
+ **Tests and records.** New suite `flow-round-gates.test.mjs`; the touched suites (`robustness-brief`,
70
+ `flow-record`, `flow-writer`, `flow-dogfood.integration`, `core-evidence-hostile`,
71
+ `commit-guard-content-free`, `review-fingerprint-parity`, `package-content` — the tarball pin 284 →
72
+ 285 — and `fold-scope`) green with real git; thirteen red-proof cases (34 `core-evidence red-proof`
73
+ re-observations on the parked pre-fix trees); the source-size baseline re-recorded under one stated reason. Every fold of the three diff
74
+ rounds rode the delegate's held session (AD-127): fourteen accounted dispatches, one session.
75
+
76
+ ### BREAKING
77
+
78
+ - **Minimum bridge versions: codex-cli-bridge 3.7.0, antigravity-cli-bridge 5.6.0.** The fingerprint
79
+ domain moves on a repository configuring `textconv` (neutralised by `--no-textconv`) or
80
+ `diff.ignoreSubmodules` / `submodule.<name>.ignore` in `.git/config` or `.gitmodules` (both overridden
81
+ by `--ignore-submodules=none`): a kit-11 receipt and a pre-3.7.0 / pre-5.6.0 wrapper receipt hash the
82
+ same tree differently. `init` and `upgrade`
83
+ refresh a placed bridge (`bin/install.mjs`); a bridge never placed is never placed by them.
84
+ - **`round-land --dispose queued` changes its operands:** `--debt-id` / `--debt-digest` are gone;
85
+ `--claim`, `--proof-kind` and `--proof-digest` are required together with `--finding`.
86
+ - **`round-open` refuses where `--justification` used to lift:** past the round table's signal the
87
+ cap is never lifted by a justification; a missing walk is.
88
+ - **The one-way door:** a flow store carrying any post-rung record form — an `internal-attestation`
89
+ with `walk`, a `queued` disposition with `claim`/`proofKind`/`proofDigest`, an `escalated` or a
90
+ `custody-lost` disposition — reads under kit >= 11 only. A pre-rung store still reads.
91
+
7
92
  ## 10.6.0 — a fold on delegated code rides the delegate's held session, judged from the ledger the kit already mints (AD-127)
8
93
 
9
94
  The 2026-08-30 `release-run` execution took thirteen diff-review rounds, and every fold was made by
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: '10.6.0'
6
+ version: '11.0.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-kit
@@ -183,6 +183,14 @@ guarded — read `${CLAUDE_SKILL_DIR}/references/modes/bridge-settings.md` befor
183
183
 
184
184
  read-only — read `${CLAUDE_SKILL_DIR}/references/modes/review-state.md` before acting.
185
185
 
186
+ ### Mode: control-bytes
187
+
188
+ read-only — read `${CLAUDE_SKILL_DIR}/references/modes/control-bytes.md` before acting.
189
+
190
+ ### Mode: robustness-brief
191
+
192
+ read-only — read `${CLAUDE_SKILL_DIR}/references/modes/robustness-brief.md` before acting.
193
+
186
194
  ### Mode: receipt-deadline
187
195
 
188
196
  read-only — read `${CLAUDE_SKILL_DIR}/references/modes/receipt-deadline.md` before acting.
@@ -2,7 +2,7 @@
2
2
  name: antigravity-cli-bridge
3
3
  description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its headless behaviour.
4
4
  metadata:
5
- version: '5.5.0'
5
+ version: '5.6.0'
6
6
  ---
7
7
 
8
8
  # antigravity-cli-bridge
@@ -343,7 +343,7 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.7 Flash (High)"
343
343
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
344
344
  # version (drift-guarded by agy-review.test.mjs against capability.json).
345
345
  AW_RECEIPT_BACKEND="agy"
346
- AW_BRIDGE_VERSION="5.5.0" # aw-version-anchor
346
+ AW_BRIDGE_VERSION="5.6.0" # aw-version-anchor
347
347
  # `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
348
348
  AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
349
349
  # D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
@@ -867,8 +867,8 @@ warn_never_committable_untracked() {
867
867
  # implements the SAME serialization in node — cross-checked by the kit's
868
868
  # review-fingerprint-parity.test.mjs.
869
869
  emit_fingerprint_payload() {
870
- git diff --cached --no-ext-diff
871
- git diff --no-ext-diff
870
+ git diff --cached --no-ext-diff --no-textconv --ignore-submodules=none
871
+ git diff --no-ext-diff --no-textconv --ignore-submodules=none
872
872
  local path
873
873
  while IFS= read -r -d '' path; do
874
874
  if [[ -L "$path" ]]; then
@@ -1556,10 +1556,10 @@ assemble_code_diff() {
1556
1556
  emit_status_porcelain_filtered
1557
1557
  echo
1558
1558
  echo "=== staged diff (git diff --cached) ==="
1559
- git diff --cached --no-ext-diff
1559
+ git diff --cached --no-ext-diff --no-textconv --ignore-submodules=none
1560
1560
  echo
1561
1561
  echo "=== unstaged diff (git diff) ==="
1562
- git diff --no-ext-diff
1562
+ git diff --no-ext-diff --no-textconv --ignore-submodules=none
1563
1563
  echo
1564
1564
  echo "=== untracked file contents ==="
1565
1565
  local path
@@ -1834,7 +1834,7 @@ else
1834
1834
  cd "$(git rev-parse --show-toplevel)"
1835
1835
  # No-diff preflight — never spend a run on a clean tree. Never-committable untracked masks do
1836
1836
  # not count: the FILTERED domain is the review surface.
1837
- if git diff --quiet && git diff --cached --quiet && ! has_reviewable_untracked; then
1837
+ if git diff --quiet --no-ext-diff --no-textconv --ignore-submodules=none && git diff --cached --quiet --no-ext-diff --no-textconv --ignore-submodules=none && ! has_reviewable_untracked; then
1838
1838
  echo "agy-review: no uncommitted changes to review — the working tree is clean." >&2
1839
1839
  warn_never_committable_untracked
1840
1840
  exit 0
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "antigravity-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "5.5.0",
6
+ "version": "5.6.0",
7
7
  "provides": ["review", "probe"],
8
8
  "posture": { "model": "Gemini 3.7 Flash (High)" },
9
9
  "roles": {
@@ -2,7 +2,7 @@
2
2
  name: codex-cli-bridge
3
3
  description: Delegate work to the OpenAI Codex CLI (`codex`) under a ChatGPT subscription — run plan/instruction EXECUTION in a sandboxed workspace, or get a read-only ADVISORY review of a plan or working-tree diff — as a second delegated-execution backend beside Antigravity. Use when the user wants to hand a bounded coding task or plan to `codex exec`, get a second-opinion review from codex, install or authenticate Codex CLI, understand its sandbox/network/approval policy, drive codex efficiently from the main agent (exec vs review, resume, the commit boundary), bridge project context (`AGENTS.md`) into codex, or troubleshoot codex flags, models, auth, or its no-TTY headless behaviour.
4
4
  metadata:
5
- version: '3.6.0'
5
+ version: '3.7.0'
6
6
  ---
7
7
 
8
8
  # codex-cli-bridge
@@ -372,7 +372,7 @@ CHATGPT_LOGIN_GUARD="Logged in using ChatGPT"
372
372
  # receipt this wrapper mints; scripts/release/version-sync.mjs bumps it under the one-anchor-per-file
373
373
  # rule, so a release can never leave it behind (the AD-053 drift class).
374
374
  AW_RECEIPT_BACKEND="codex"
375
- AW_BRIDGE_VERSION="3.6.0" # aw-version-anchor
375
+ AW_BRIDGE_VERSION="3.7.0" # aw-version-anchor
376
376
  # The kill grace handed to timeout(1) as --kill-after, and recorded in the receipt as killGraceS:
377
377
  # ONE constant, so the number the ledger checks against the dispatch deadline is the number the run
378
378
  # actually applied.
@@ -284,7 +284,7 @@ DEFAULT_CODEX_EFFORT="xhigh"
284
284
  # Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
285
285
  # version (drift-guarded by codex-review.test.mjs against capability.json).
286
286
  AW_RECEIPT_BACKEND="codex"
287
- AW_BRIDGE_VERSION="3.6.0" # aw-version-anchor
287
+ AW_BRIDGE_VERSION="3.7.0" # aw-version-anchor
288
288
  CODEX_MODEL="${CODEX_MODEL:-$DEFAULT_CODEX_MODEL}"
289
289
  CODEX_EFFORT="${CODEX_EFFORT:-$DEFAULT_CODEX_EFFORT}"
290
290
  # Generous hard cap for a slow xhigh review (subscription latency varies).
@@ -527,8 +527,8 @@ warn_never_committable_untracked() {
527
527
  # implements the SAME serialization in node — cross-checked by the kit's
528
528
  # review-fingerprint-parity.test.mjs.
529
529
  emit_fingerprint_payload() {
530
- git diff --cached --no-ext-diff
531
- git diff --no-ext-diff
530
+ git diff --cached --no-ext-diff --no-textconv --ignore-submodules=none
531
+ git diff --no-ext-diff --no-textconv --ignore-submodules=none
532
532
  local path
533
533
  while IFS= read -r -d '' path; do
534
534
  if [[ -L "$path" ]]; then
@@ -811,10 +811,10 @@ assemble_code_diff() {
811
811
  emit_status_porcelain_filtered
812
812
  echo
813
813
  echo "=== staged diff (git diff --cached) ==="
814
- git diff --cached --no-ext-diff
814
+ git diff --cached --no-ext-diff --no-textconv --ignore-submodules=none
815
815
  echo
816
816
  echo "=== unstaged diff (git diff) ==="
817
- git diff --no-ext-diff
817
+ git diff --no-ext-diff --no-textconv --ignore-submodules=none
818
818
  echo
819
819
  echo "=== untracked file contents ==="
820
820
  local path
@@ -923,7 +923,7 @@ case "$mode" in
923
923
  code)
924
924
  # No-diff preflight — never spend a subscription run on a clean tree. Never-committable
925
925
  # untracked masks do not count: the FILTERED domain is the review surface.
926
- if git diff --quiet && git diff --cached --quiet && ! has_reviewable_untracked; then
926
+ if git diff --quiet --no-ext-diff --no-textconv --ignore-submodules=none && git diff --cached --quiet --no-ext-diff --no-textconv --ignore-submodules=none && ! has_reviewable_untracked; then
927
927
  echo "codex-review: no uncommitted changes to review — the working tree is clean." >&2
928
928
  warn_never_committable_untracked
929
929
  exit 0
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "codex-cli-bridge",
5
5
  "kind": "execution-backend",
6
- "version": "3.6.0",
6
+ "version": "3.7.0",
7
7
  "posture": { "model": "gpt-5.6-sol", "effort": "xhigh", "tier": null },
8
8
  "provides": ["execute", "review"],
9
9
  "roles": {
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": "10.6.0",
6
+ "version": "11.0.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": "10.6.0",
3
+ "version": "11.0.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 **read-only pre-commit guard** (strip-the-kit D10) — the last line of the
7
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/commit-guard.mjs --check [--cwd <dir>]` — it refuses, each with a named recovery, on:
8
8
 
9
9
  1. **an INDEX that lags the verified working tree** — the gates and the fingerprint both describe the WORKING tree, while `git commit` builds the commit from the INDEX alone, and the fingerprint domain is identical whether a hunk sits staged or unstaged (staging a lone tracked modification does not even move it). So this arm runs FIRST, before the fingerprint is computed: it refuses when the working tree holds anything the index does not — tracked paths differing between index and worktree, or reviewable untracked-not-ignored paths (the SAME never-committable stat filter the fingerprint applies; ignored paths never refuse). An index entry carrying **skip-worktree** or **assume-unchanged** is invisible to `git diff`, so those entries are compared DIRECTLY against the worktree (type, symlink target, executable bit where `core.fileMode` says so, and the blob oid through git's own clean filters) — a de-materialised skip-worktree path is an ordinary sparse checkout and never refuses, while a missing assume-unchanged path does. The submodule probe forces `--ignore-submodules=none`, so `diff.ignoreSubmodules` / `submodule.<name>.ignore` cannot erase a dirty submodule from the comparison. Offending paths are named in THREE categories — plain lagging paths, paths held back by an index bit, and submodules — because their recoveries differ; ONE shared cap spans all of them, each category reserves a slot so no clause is ever nameless, the remainder is stated once, and every path is rendered as one escaped line. Recovery is ONE ordered sequence, and `git add -A` alone is NOT it: a bit-carrying entry must have its `skip-worktree` / `assume-unchanged` bit cleared FIRST (scoped to the paths the refusal names — never to everything `git ls-files -v` reports, which includes de-materialised sparse paths whose deletions would then be staged), then `git add -A`, then re-run `run-gates --final` at its RESOLVED path beside this tool, then commit the WHOLE tree. The listed paths are capped, so the loop is the completion signal: re-run the guard until it names none. A tracked **submodule** the index cannot prove current is named separately with its own recovery — commit or clean inside the submodule and stage the gitlink — because a root-level `git add -A` cannot capture a submodule's internal worktree changes. A submodule whose gitlink itself carries one of those index bits is **not probed at all**: it lags by construction. That is a deliberate REDUCTION — three consecutive review rounds each found a new way for a nested probe to answer "clean" wrongly (inherited superproject `GIT_*`, status config blindness, the submodule's OWN flagged entries, a symlink standing in for the directory) — and it stays a CONVERGING refusal, since clearing the bit is a recovery the guard prints and then falls silent on. An UNflagged submodule is judged by the ordinary probe exactly as before. **Fail-closed:** an undecidable git probe refuses with its own named cause, never a silent pass;
10
- 2. **a CONTENT-FREE fingerprint over a DIRTY index** — the payload carries no bytes while the index really carries a change, which means staged content the fingerprint domain cannot see: a submodule gitlink hidden from `git diff` by `submodule.<name>.ignore` or `diff.ignoreSubmodules`. No receipt can describe what that commit will carry, so the guard refuses and names the CONFIGURATION as the recovery — clear the ignore setting (or set it to `none`) until `git diff --cached --no-ext-diff` shows the change, then re-run `run-gates --final`. Re-staging is not the recovery: the change is already staged;
10
+ 2. **a CONTENT-FREE fingerprint over a DIRTY index** — the payload carries no bytes while the index really carries a change: staged content the payload cannot see. No receipt can describe what that commit will carry, so the guard refuses fail-closed. Recovery names no flag or config key: make the staged change visible to `git diff --cached`, then re-run the resolved `run-gates --final`; re-staging alone is not the recovery because the change is already staged;
11
11
  3. no completed final record for the CURRENT fingerprint (the tree moved after the final run — any edit re-stales it);
12
12
  4. a RED latest attempt (a dead green never revives — the latest attempt at a fingerprint is authoritative);
13
13
  5. fingerprint before ≠ after on the receipt (the tree moved UNDER the final run);
@@ -24,7 +24,7 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/commit-guard.mjs --check [--cwd <dir>]` —
24
24
 
25
25
  **Boundary — what «the commit captures the tree» means for submodules.** The guard proves the SUPERPROJECT commit captures the SUPERPROJECT working tree. A superproject commit stores a submodule as a gitlink OID and never captures its file content — that content is the submodule's own commit boundary. The dirty-submodule refusal is therefore a best-effort COURTESY beyond the guard's boundary, and its completeness is bounded by what the submodule itself reports: a file held behind a `skip-worktree` / `assume-unchanged` bit in the SUBMODULE's own index is invisible to the submodule's status and so to this refusal. Deepening the nested probe was tried and abandoned — three consecutive review rounds each produced a new way for it to answer wrongly — so the honest contract is the boundary, not a completeness claim. Tracked as its own class.
26
26
 
27
- **Stated residual — the fingerprint can still be blinded by config.** Refusal 1 makes the COMMIT capture the whole current working tree. It does not make the RECEIPT unforgeable: `computeFingerprintPayload` still runs its diffs without `--ignore-submodules=none`, so under `diff.ignoreSubmodules=all` a submodule can be changed and its gitlink staged AFTER a green `--final` while the fingerprint stays put, and the stale receipt is reused. That is a receipt-collision defect, not an under-capture one — no commit ships less than the working tree because of it — and closing it means moving the node payload and both bash twins together (the AD-044 lockstep). Tracked as its own class. **Narrowed:** the case where the hidden gitlink is the ONLY staged change is now closed by refusal 2 — there the payload is empty and the index is not, which is decidable without touching the payload definition. A hidden gitlink riding ALONGSIDE other visible changes still produces an ordinary fingerprint and still reuses its receipt; that remainder is what the lockstep owns.
27
+ **Stated residual — the payload flags close the config-blind class.** The fingerprint argv in the ONE node home, `core-evidence.mjs`, carries `--no-textconv --ignore-submodules=none` beside `--no-ext-diff`; both bash twins are parity-pinned to that set for their fingerprint and review-payload diffs, so host textconv and submodule-ignore configuration cannot hide staged bytes. Refusal 2 remains the fail-closed backstop for any DIRTY index whose staged content the payload still cannot see: it names making the change visible to `git diff --cached` and re-running the final gate, without guessing a flag or configuration key.
28
28
 
29
29
  **Human residual (stated, accepted):** `git commit --no-verify` bypasses any pre-commit hook — a self-discipline mechanism, not a security boundary.
30
30
 
@@ -0,0 +1,91 @@
1
+ ### Mode: control-bytes
2
+
3
+ <!-- opt-in-capability: none — a read-only gate entry; declaring the gate matrix is covered by gates-declaration -->
4
+
5
+ Read-only **control-byte gate** over the work tree — it makes *"a raw control byte in a source"* a
6
+ mechanical refusal instead of a reviewer's find. Measured 2026-08-30: an editor tool wrote a raw NUL
7
+ into a test source, git classed the file as binary, and a NUL-carrying blob is exactly what a
8
+ letter-level checker skips by kind. This mode judges BYTES, never letters, so it is language-neutral:
9
+ every consumer of the kit can declare it, whatever language the project is written in.
10
+
11
+ **What it judges.** The review-payload domain over the WORK TREE, from the work-tree root: tracked
12
+ paths (`git ls-files -z`) plus untracked-not-ignored paths (`git ls-files --others --exclude-standard
13
+ -z`). Three surfaces per path, in this order: the NAME is always judged; a regular file's CONTENT is
14
+ judged unless the attribute skip below covers it (the skip covers content only); a symlink's TARGET
15
+ string is always judged and the link is never followed. A path's kind is the index mode for a tracked
16
+ path (`git ls-files -s -z`; a gitlink is name-only) and the lstat for an untracked one; the descriptor
17
+ the read opens must report the same kind, or the path is unreadable. The predicate is over raw bytes:
18
+ any byte in 0x00–0x08, 0x0B, 0x0C, 0x0E–0x1F or 0x7F refuses; TAB, LF and CR are the only admitted C0
19
+ bytes; bytes at or above 0x80 are not judged (UTF-8 validity and C1 controls are not this tool's
20
+ business). A finding names the path, the surface, the byte offset and the byte in hex; the render is
21
+ injective — bytes decode as UTF-8 where valid, a control or line-separator code point (and a valid
22
+ U+FFFD) renders as `\u{XXXX}`, an invalid byte as `\xNN`, a backslash as `\\` — so no output line carries a raw control
23
+ byte or U+FFFD and two names differing only in an unrenderable byte render differently.
24
+
25
+ **Binary by KIND, never by guess.** A path whose git attribute says `binary` or `-diff` has its
26
+ CONTENT skipped by kind and is NAMED in the summary (one `git check-attr --stdin -z binary diff`
27
+ over the whole domain — git's own answer, global and system attributes files included, because the
28
+ tool runs under your env); its name and target are still judged. There is no extension allow-list
29
+ and no size heuristic — a NUL in an unattributed file is the finding this gate exists for. A fixture
30
+ that genuinely needs a control byte keeps it as an escape in source (`\x00`, `String.fromCharCode(0)`):
31
+ the runtime string is byte-identical and the SOURCE stays clean. A never-committable stat class
32
+ (device, FIFO, socket) and an untracked nested repository (git's `nested/` entry) are skipped by
33
+ kind and counted — name judged, no content surface. On a host that materialises symlinks
34
+ (`core.symlinks=false`) a tracked symlink's placeholder file is judged as its target string.
35
+
36
+ Run `node ${CLAUDE_SKILL_DIR}/tools/control-bytes.mjs [--check] [--cwd <dir>]` (`--cwd` anchors at
37
+ that directory's work-tree root):
38
+
39
+ 1. Plain run → the human report: the work-tree root, the counts (paths judged · skipped by attribute,
40
+ each named · skipped by kind · findings), then every finding as one line
41
+ `<path> <surface> offset <n>: 0x<hex>` (`(+N more)` when that surface carries more than one) —
42
+ under the SAME exit table as `--check`: the plain run is never a silent 0.
43
+ 2. **`--check`** → the gate spelling: the same report (counts, the named attribute skips, the
44
+ finding lines), then the gate exit code. **Exit 0** clean. **Exit 1** on a finding OR on a refusal —
45
+ refusals never pass and never skip silently, each with its own message: an UNREADABLE path
46
+ (EACCES, EIO, a descriptor whose kind disagrees with the named kind — distinct from an ABSENT
47
+ path git named that vanished before the read; a TRACKED absent path with the skip-worktree bit is
48
+ a sparse checkout, skipped by kind and counted); a git command that fails, is killed by a signal or
49
+ is not on PATH; an UNMERGED index; an EMPTY domain; a file over the read cap (16 MiB — read as at
50
+ most cap+1 bytes on the descriptor, never trusting the fstat size, so a file that grows after the
51
+ fstat still refuses; the message names the cap and the path); a git LOCATION that is not a work
52
+ tree. The location is a closed table of six states shared with the fingerprint and index
53
+ consumers through the kit's git-location leaf, judged on the REALPATH identity of the git dir,
54
+ the common dir and the work-tree top under the ambient env against the discovery from cwd with
55
+ every variable whose upper-cased name starts with `GIT_` removed (a case-insensitive host honours
56
+ `git_dir` too): `work-tree` (all three agree) proceeds; `not-a-repository` (git
57
+ itself answers not-a-git-repository, exit 128); `error` (ENOENT, a synchronous throw, a signal, a
58
+ realpath that cannot resolve, any other non-zero or unparsable answer); `redirected` (any of the
59
+ three differs — `GIT_WORK_TREE` at a second tree with the same git dir, or `GIT_COMMON_DIR` at a
60
+ second repository); `no-work-tree` (the git
61
+ dir agrees but `--show-toplevel` fails: bare, or cwd inside `.git`); `env-only` (the ambient env
62
+ reaches a repository the stripped discovery does not) — each refuses by its own state name. A
63
+ pre-commit hook's own env (`GIT_INDEX_FILE`, `GIT_PREFIX`, `GIT_CONFIG_PARAMETERS`,
64
+ `GIT_EXEC_PATH`) agrees and passes: the check is agreement, never a variable ban. **Exit 2**
65
+ usage — every operand is validated at parse; an unknown argument is usage. Every content read is
66
+ descriptor-bound (open, fstat, read on the descriptor), never a path read that a symlink swap
67
+ could redirect.
68
+ 3. **Wire it as a gate by hand — never without consent (AD-021/D9).** The candidate line for your
69
+ own `docs/ai/gates.json`: `{ "id": "control-bytes", "title": "No raw control byte in the work
70
+ tree", "cmd": "node \"<path-to-this-skill>/tools/control-bytes.mjs\" --check" }` — with the path
71
+ your project actually reaches the kit by, double-quoted inside the JSON string so a path with
72
+ spaces survives, executable from the project root. `gates-init` does not offer this entry; the full consent-fill contract for the
73
+ matrix is in `${CLAUDE_SKILL_DIR}/references/modes/gates.md`. Once declared, the opt-in
74
+ `${CLAUDE_SKILL_DIR}/references/modes/hook.md` auto-approves it like any other declared gate.
75
+
76
+ **Honest bounds (stated, accepted):** the work tree is judged at the moment of the read — a byte
77
+ written after the run is caught on the next run, which is why the gate belongs in the pre-commit
78
+ matrix; the attribute is the only "binary" signal, so a binary file with no attribute refuses until
79
+ the project declares it; the tool reports and never edits — the remedy is the escape in source; a
80
+ checkout PATH that is itself not valid UTF-8 is unreachable by any Node consumer (cwd is a string
81
+ before git ever answers) and a non-UTF-8 LINKED git dir or common dir path refuses as `error` — a
82
+ stated bound, not a case this tool judges; `O_NOFOLLOW` guards the
83
+ LEAF only — a parent directory swapped for a symlink between git's answer and the open redirects the
84
+ read (Node exposes no `openat2`/component-wise no-follow walk), so a mid-run parent swap is the same
85
+ moment-of-the-read race bound as a byte written after the run: caught on the next run, inside the
86
+ work tree the gate's verdict still refuses-or-reports, never silently skips.
87
+
88
+ **Invariants:** read-only · never writes · never commits · spawns read-only `git` queries only
89
+ (`ls-files`, `check-attr`, `config --get core.symlinks`, `rev-parse`) · judges bytes, never decoded text · skips by kind only, every
90
+ skip named or counted · exit 0 clean / 1 finding or refusal / 2 usage · `main(argv, deps)` returns,
91
+ never exits.
@@ -19,16 +19,28 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/flow-writer.mjs <arm> …`** — the arm s
19
19
  | `degrade-justification --backend <b> [--down-mark <d>] [--degrade-digest <d>]` | binds a core degrade to a then-active down-mark (#25) — both digests resolve automatically; an explicit digest only VERIFIES the resolved authority (a foreign digest never mints), and the store's locked preflight re-refuses a mark closed in the race window |
20
20
  | `maintainer-override <planId> --backend <b> --checkpoint-approved` | the checkpoint-approved ship-over-veto record — it **prints the FULL bound set** it is about to record and **requires the explicit flag** (#38); without the flag the bound set still prints and nothing is written |
21
21
  | `consult-attestation <planId> --backend <b> --nonce <n> --proposed-fix-digest <d>` | binds a consult to a real dispatch's findings (#11/#33): `findingDigest` is **computed from the `{backend, nonce}`-named finding manifest** beside the receipts file (never hand-supplied); `proposedFixDigest` is the explicit consult-time input; refuses without an open step, or without a readable identity-matching manifest |
22
- | `round-open <planId> --backend <b> [--backend <b> …] [--step <stepId>] [--new-cycle] [--justification <t>]` | the round's **pre-dispatch half** (#41): mints the round record BEFORE any backend runs — per `--backend` a fresh nonce + the receipts-file byte-length watermark; stdout prints one `dispatch backend=<b> nonce=<n> watermark=<w>` line per dispatch (pass the nonce as `--nonce <n>` on the wrapper invocation — the plain-argument lane onto the `AW_REVIEW_NONCE` seam; hand the pair to `receipt-deadline`). Boundary invocations need `--step`; `--new-cycle` reopens a converged step in the NEXT cycle (the redesign valve); in-step invocations open the NEXT round — **a fingerprint move never rides a revision**. Refuses while the current round holds a pending unjustified dispatch or an undispositioned non-ship receipt (a new round would strand it permanently) |
23
- | `round-land <planId> [--dispose folded\|queued\|rejected --finding <quote> …]` | the round's **post-arrival half** (#42/#13/#33): revises the ONE round record in place — exactly one fresh code-artifact non-probe line of the dispatched backend **carrying the dispatch's exact nonce** (the wrapper stamps `AW_REVIEW_NONCE` into the receipt — a delayed or cross-chain answer can never cross-bind) past the watermark is the candidate, and only the canonical **ATTESTING** class binds (`receiptDigest` = its canonical digest; `findingManifestDigest` = sha256 of the manifest bytes; both **computed from the files**); a defective answer (unmarked/malformed/ungrounded/unrecognized-verdict) refuses naming its class and the justified-degrade recovery; an ambiguous newer set, a foreign-tree receipt/manifest, or a missing/malformed/symlinked/foreign-identity manifest refuses. `--dispose` appends one disposition whose `findingDigest` = sha256 of the **quoted finding**, verified a substring of a landed manifest whose **byte digest still equals the ledger's** (folded proofs must resolve: a consult-attestation in the flow store or a red-proof in the core store) |
22
+ | `round-open <planId> --backend <b> [--backend <b> …] [--step <stepId>] [--new-cycle] [--justification <t>]` | the round's **pre-dispatch half** (#41): mints before any backend runs and applies the cap, walk and completeness gates detailed below |
23
+ | `round-land <planId> [--dispose <action> …]` | the round's **post-arrival half** (#42/#13/#33): binds exact arrivals and records one of the five dispositions detailed below |
24
24
  | `freeze <planId>` / `converged <planId>` | the step terminals, gated on **completeness + the sanctioned-move rule** (no premature terminal): every dispatch landed or **justified-degraded** at the dispatched tree (a core degrade + a mint-time-valid `degrade-justification` at the round's `{base, fingerprint}` — a bare degrade is not base-bound); every landed non-ship receipt rides a round with a non-empty disposition ledger (a form floor — semantic per-finding completeness is an honest limit); the tree sits at the last round's fingerprint or reached it through an **anchored** declared bookkeeping-delta chain + refresh (a pre-round chain never re-certifies a later move). Both walks **re-run on the locked store snapshot** at append time |
25
25
  | `unfreeze <planId> [--justification <t>]` | reopens the frozen step (in-step) or the just-converged terminal (boundary); **cap 1 per cycle** — the design's post-freeze checkpoint |
26
- | `internal-attestation <planId> --lens <l> … [--degraded <b> …] --model <m> [--effort <e>] [--tier <t>] --authority <a>` | the #28 internal-review attestation at the open step's round — **gated on the #68 arming predicate**: EVERY in-flight plan must be covered by an adopted chain (planId + content digest + owner); an uncovered plan refuses **naming the file**, never a relaxation |
26
+ | `internal-attestation <planId> --lens <l> … [--degraded <b> …] --model <m> [--effort <e>] [--tier <t>] --authority <a> [--walk <file>]` | the #28 internal-review attestation at the open step's round, gated on #68 adoption coverage. `--walk` is a capped, no-follow closed JSON file relative to the absolute git common dir; for every judged plan row every class in TAGGED ∪ PRESENT needs non-empty `checked`, while extra entries are ignored and PRESENT minus TAGGED is recorded as `walk.uncovered`. |
27
27
  | `write-plan-id <plan-file> --plan-id <id>` | adds the frontmatter `planId` line — bounded to an EXISTING regular file under `docs/plans/` (never a symlink), contained-atomic, same-id idempotent, **different-id refuses** (#58) |
28
28
 
29
+ `round-open` mints the round record BEFORE any backend runs — per `--backend` a fresh nonce + receipts watermark; stdout prints one `dispatch backend=<b> nonce=<n> watermark=<w>` line per dispatch (pass the nonce as `--nonce <n>` on the wrapper invocation — the plain-argument lane onto the `AW_REVIEW_NONCE` seam; hand the pair to `receipt-deadline`).
30
+
31
+ Every round after the first requires an authoritative walk-carrying attestation at the preceding round's `{planId, cycle, stepId, round}` and the CURRENT tree's `{base, fingerprint}` when the adopted plan has a judgeable ledger row; `--justification` may lift that absence as an echoed input, and the ABSENCE of the attestation is its durable trail. At `cap reached` or `crossover`, every blocking item from the latest head's landed receipts must be covered by equal item digest: uncovered items print verbatim, enumeration failure is an advisory plus the existing disposition floor, and a lost manifest is covered only by `custody-lost`; this cap refusal is never lifted by `--justification`.
32
+
33
+ Both lock-free and locked `round-open` checks run cap → walk → completeness floor. Boundary invocations need `--step`; `--new-cycle` opens the next cycle; a fingerprint move opens a new round.
34
+
35
+ For `round-land`, exactly one fresh code-artifact non-probe line of the dispatched backend **carrying the dispatch's exact nonce** (the wrapper stamps `AW_REVIEW_NONCE` into the receipt — a delayed or cross-chain answer can never cross-bind) past the watermark is the candidate, and only the canonical **ATTESTING** class binds. It revises the ONE round record in place with five closed actions.
36
+
37
+ `folded --finding <quote> --proof-kind … --proof-digest <d>` and `rejected --finding <quote> --reason <r>` retain their bindings; `queued --finding <quote> --claim <invariant> --proof-kind consult-attestation|red-proof --proof-digest <d>` requires `decideFoldScope` ACCEPT, derives `debtId` from the resolved row and computes `debtDigest` from its block, then binds the proof to this head, item and claim.
38
+
39
+ `escalated --finding <quote> --override-digest <d>` binds a veto override and only that receipt's findings; `custody-lost --receipt <receiptDigest>` carries no quote, is recordable whenever the carrier loss is real, covers that receipt for the cap, and excludes it from later manifest re-reads. Arrivals still bind only canonical attesting receipts and identity-matching manifest bytes.
40
+
29
41
  Operand shapes: a positional operand may follow a literal `--` and a value flag accepts `--flag=<value>` — the lanes a leading-dash id or value rides; the checker's printed recoveries compose exactly these shapes. Every arm computes its **tree context** (owner, base, fingerprint; cycle/round from the chain walk) — you never hand-supply tree identity. Chain arms **refuse a foreign worktree's chain** (#57): move a chain only from the worktree that adopted it.
30
42
 
31
- **Round-machinery caps (enforced at the arms; the store owns transition legality):** HARD_MAX **3 rounds per {cycle, step}** and **1 post-freeze unfreeze per cycle**. Every cap refusal is **self-servable** (Decision 8 — never a human wait-state): the over-cap mint requires an explicit non-empty `--justification <text>`, echoed in the mint report — the over-cap record itself is the durable trail, and the justification surfaces at the phase's commit ask; an in-cap `--justification` refuses (an input that binds nothing is never silently dropped). `round-open` also refuses while the current round holds a pending undegraded dispatch — a new round would strand it unlandable.
43
+ **Round-machinery caps (enforced at the arms; the store owns transition legality):** HARD_MAX **3 rounds per {cycle, step}** and **1 post-freeze unfreeze per cycle**. The walk obligation holds when the adopted plan's ledger parses with at least one valid row; a plan with no judgeable row owes no walk. Both lock-free and locked round-open checks run cap → walk → completeness floor. The justification predicate is `overCap || walkRefusalFired`: a non-empty justification lifts only a design-cap or missing-walk refusal and is echoed; an in-cap invocation with its walk satisfied refuses it as usage. The blocking-item cap is independent and never lifted. A `custody-lost` entry is recordable whenever the carrier loss is real. One-way door: records carrying `walk`, the queued proof trio, `escalated`, or `custody-lost` require kit >= 11 to read; pre-rung records remain valid.
32
44
 
33
45
  **Exit codes:** `0` success (incl. the `write-plan-id` idempotent no-op); `2` usage; `1` refusal (a store STOP verbatim, a derivation failure, or the missing checkpoint flag).
34
46
 
@@ -36,6 +36,8 @@ Run **`node ${CLAUDE_SKILL_DIR}/tools/procedures.mjs <activity> [--override <slo
36
36
 
37
37
  **Spec-store block (the feature-spec layer, `plan-execution` only).** After a `plan-execution` render the advisor also prints `Spec store (the feature-spec layer) — …`: the two POPULATED `spec-check` commands and the session register they read. **Session lane** — `--ops-file docs/plans/spec-ops.list` (or repeated `--op <add|modify|remove>=<path>` / `rename=<old>:<new>`) judges the closure of the declared change: the targets plus each one's listing parent, on their post-state, their reader verdict, their per-kind line cap, their scenario bindings and the containment of every path they name. **Store lane** — `--all` judges the whole store instead and adds the four cross-document invariants (an unlisted child, DISTINCT from an orphan · acyclicity · store-wide slug uniqueness · module overlap); an absent store root refuses the run rather than reporting an empty clean store. The two lanes are exclusive. **Register lifecycle:** `docs/plans/spec-ops.list` is *session scratch* — the session writes it, the plan's Phase Cleanup deletes it — and it is **never defaulted** by the tool, because an unnamed register would attest a post-state nobody declared (the `fold-scope --queue` precedent). `--json` carries the same lines under **`specCheck`** (an empty array outside `plan-execution`). Advisory like the finding-scope block: nothing records that it ran. Exit `0` clean · `1` findings · `2` usage.
38
38
 
39
+ **Robustness-brief block (`plan-execution` only).** The advisor renders `node ${CLAUDE_SKILL_DIR}/tools/robustness-brief.mjs --plan <plan>` populated when exactly one plan is in flight and names the zero/several-plan placeholder reason otherwise. `--json` carries the identical lines under **`robustnessBrief`**, an empty array outside `plan-execution`; human and structured renders share one block.
40
+
39
41
  **Cap-soft-skip degradation (the feature's only AUTO route).** The activity procedures are auto-discoverable only through the one-line **`workflow:methodology`** pointer (this kit + the engine carry `disable-model-invocation:true`, so NL like "write a plan" does **not** auto-load this skill). On a deployment whose methodology pointer was cap-soft-skipped — or whose pre-existing customized pointer lacks the procedures clause — the procedures are still reachable by **explicitly** invoking `/agent-workflow-kit procedures`; surface that plainly rather than treating it as a gap.
40
42
 
41
43
  **Invariants:** read-only · never writes · never commits · never runs a subscription CLI · the deterministic resolution is the kit's, the recipe execution is the orchestrator's.
@@ -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 non-git cwd, 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 non-git 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. 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.
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,61 @@
1
+ ### Mode: robustness-brief
2
+
3
+ <!-- opt-in-capability: none — a read-only generator over a plan file and the kit's own list -->
4
+
5
+ Read-only **executor-brief generator** — it turns a ledger row's `robust:<class>[,<class>]` tag into
6
+ the block of concrete robustness literals the executor must prove BEFORE the first line of code.
7
+ Measured 2026-08-30: a diff correct to its contract took 13 review rounds, each finding one more
8
+ standard invariant (a git location variable, a sequencer ref, `%B`'s newline, `textconv`, fsync, a
9
+ no-process spawn error) that nobody had written into the brief. The list is data
10
+ (`${CLAUDE_SKILL_DIR}/references/robustness-literals.json`, versioned, language-neutral); this mode
11
+ renders it, so a retro extends the list instead of a reviewer re-finding a member.
12
+
13
+ **What it reads.** The plan file named by `--plan`, opened as a regular file no-follow through the
14
+ kit's capped descriptor read (1 MiB — a plan is capped at 100 lines) with its realpath required
15
+ under the realpath of the cwd (the project root the advisor renders the command from; no git is
16
+ asked): an absolute path outside, a `..` traversal out of it, a symlink leading out, a non-regular file and
17
+ an over-cap plan each refuse **exit 2** by name — the generator PRINTS row text, so unlike
18
+ plan-shape-cli's unbounded no-follow read it is bounded. The plan is judged on every rule of plan-shape's structural half (headings, section caps,
19
+ row grammar, ids, bytes, lexical containment, sweep form, duplicate paths, the total line, the
20
+ acceptance and governing-spec presence rules) over EMPTY facts (`{}` — never undefined); the
21
+ facts-bound half — realpath containment and sweep expansion — stays `plan-shape --check`'s. A plan
22
+ that fails the structural half is refused, never half-rendered. And the kit's own list through its
23
+ reader leaf (a list that fails the closed schema is refused by name; the tag grammar is that leaf's
24
+ `parseRobustTag`, ONE home). Nothing else: no git, no project config, no network.
25
+
26
+ Run `node ${CLAUDE_SKILL_DIR}/tools/robustness-brief.mjs --plan <plan> [--row <id>] [--coverage]`:
27
+
28
+ 1. Plain run → the Markdown block, ready to paste into the dispatch brief: a header naming the list
29
+ version and the plan path; then, for every tagged row, its id and path, and per tagged class the
30
+ class's `prove` sentence followed by its members as `literal — note (source)`. Every
31
+ plan-derived path — the header's and each row's — is rendered through the kit's string display
32
+ escape (`escapeForDisplay`), so no output line carries a raw control byte, U+2028 or U+2029; a
33
+ U+FFFD passes as the printable replacement character (stated, not escaped). The brief is built
34
+ whole before a byte is printed — never a partial brief. **Exit 0.** A plan with no `robust:`
35
+ token prints ONE stated line and still exits 0 — an untagged ledger is a fact about the plan, not
36
+ a failure of the tool. A row whose tag the grammar refuses (`no-class`, `empty-class`,
37
+ `duplicate-class`, `invalid-class`, `multiple-tags`) or names an unknown class id is **exit 1**
38
+ naming the row — never a zero-tags success.
39
+ 2. **`--row <id>`** → the block for that one row (the slice brief of a subagent or a delegated
40
+ dispatch). **Exit 1** when the row is absent from the ledger or carries no tag, so a brief can
41
+ never be assembled from a row that names nothing to prove.
42
+ 3. **Exit 1** also when the plan fails the structural half (the refusal lines are printed first);
43
+ **exit 2** usage — every operand is validated at parse: an unknown argument, an uncontained or
44
+ over-cap `--plan` (above), a malformed-UTF-8 plan (`malformed-utf8` — a genuine U+FFFD passes)
45
+ and a list the reader refuses by name (the tool's own installation, the plan-shape-facts precedent) are usage.
46
+ 4. Where it belongs (procedures.md `plan-execution` step 2): the dispatch brief — delegated or
47
+ subagent — carries the generated block for every tagged row; the procedures advisor renders the
48
+ populated command in the plan-execution render. Tag a row by writing `robust:<class>` in its
49
+ responsibility sentence; `plan-shape --check` refuses a class id the list does not carry
50
+ (`robust-class`) and leaves an untagged row unjudged.
51
+ 5. **`--coverage`** judges every valid `create`/`modify` row except `.md`/`.txt` documents and the shipped list itself. A class is PRESENT when the row file's bytes carry a bounded member token of kind env/flag/ref/syscall/errno/argv; `state` is never searched, `uncovered = present − tagged`, delete and absent rows are listed, and sweep rows union every path expanded by `plan-shape-facts`' one glob compiler. Reads use the same 1 MiB capped no-follow primitive. The table exits `0` when covered, `1` with each `row:class:literal` when uncovered, and `2` on a named row-read refusal. Bootstrap: for rows up to the row that lands this mode, apply the rule by hand; thereafter run coverage before every dispatch and before `flow-writer adoption`.
52
+
53
+ **Honest bounds (stated, accepted):** the tag is the author's claim — the tool cannot know that an
54
+ untagged row touches git, the reviewer still can; the list names classes and carries only MEASURED
55
+ members, so a fact of someone else's runtime that nobody has measured yet is not in it until a retro
56
+ adds it with its `source`; `source` is provenance of the measurement, not a path an installed kit
57
+ opens; a class with only `state` members is blind and can never be PRESENT. The block informs the
58
+ brief and never judges a diff — the hostile harness and the diff review stay the last line.
59
+
60
+ **Invariants:** read-only · never writes · never commits · spawns nothing · the plain run reads the plan file and the kit list; `--coverage` additionally reads every judged row file through the capped no-follow read and the repository's regular-file listing for sweep expansion — nothing else, still no git, no spawn and no write · exit 0 printed / 1 refused / 2 usage ·
61
+ `main(argv, deps)` returns, never exits.
@@ -36,6 +36,8 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs [--dry-run | --apply] [
36
36
  - `node ${CLAUDE_SKILL_DIR}/tools/repo-search.mjs --pattern <literal> --path <p>` (wildcard — the LITERAL search lane; for a pattern carrying a shell-significant byte use `--pattern-file <p>` instead, and for a TARGET PATH carrying one use `--paths-file <p>` — either way the bytes never enter the command string. one of the TWO tier tools that carry hook residual coverage — see the honesty note below)
37
37
  - `node ${CLAUDE_SKILL_DIR}/tools/path-inventory.mjs --path <p>` (wildcard — the read-only inventory lane: exists / type / size / `wc -l` lines / one-level listing / small-file contents for N paths in ONE call, so small path questions never need a composed shell. `--paths-file <p>` is its out-of-band target lane; hook residual coverage as with repo-search)
38
38
  - `node ${CLAUDE_SKILL_DIR}/tools/review-rounds-cli.mjs --artifact <plan>` (wildcard — the read-only round table over the review receipts, the verdict half of the per-round emission the `procedures` advisor names every round; the path argument is resolved and compared, never opened as a pattern, so it rides the settings-level posture like the rest)
39
+ - `node ${CLAUDE_SKILL_DIR}/tools/control-bytes.mjs --check` (wildcard — the read-only control-byte gate over the work tree: read-only git queries and descriptor-bound reads, no write API; the agent's own hand run before the declared gate runs it)
40
+ - `node ${CLAUDE_SKILL_DIR}/tools/robustness-brief.mjs --plan <plan>` (wildcard — the read-only executor-brief generator over a plan and the kit's shipped literal list; no write API and no process spawn)
39
41
  - `node ${CLAUDE_SKILL_DIR}/tools/run-gates.mjs --cwd ${PROJECT_ROOT}` — **EXACT byte-string only**, and honestly **project-exec, not read-only**: it runs YOUR declared `docs/ai/gates.json` commands — the same trust boundary the opt-in hook grants byte-exact per-cmd. A wildcard would be BROADER than that boundary (`--cwd <dir>` executes another project's declared gates), so the bare cwd-defaulting form, any other `--cwd`, `--only`, and **`--final`** forms all still prompt (`--final` WRITES the final-run receipt into the core-evidence store — a recording run is never auto-approved).
40
42
  - `node ${CLAUDE_SKILL_DIR}/tools/source-size-check.mjs --check` — **EXACT byte-string only**, and only this READ-ONLY mode: the tool's `--write-baseline` and `--adopt` modes WRITE (the recorded ratchet, and a gate declaration) and are deliberately uncovered — they still prompt, always. Every other form, `--cwd` included, keeps prompting. **What it covers, precisely:** the agent's own direct invocation. It is **not** the byte-string a DECLARED gate carries — the fill emits that path double-quoted (a kit path with a space must survive), and a seedable allow rule may carry no quotes at all, so the two spellings cannot be one string; a declared gate is auto-approved by the opt-in hook instead, byte-exact against `docs/ai/gates.json`.
41
43
  - Writer previews, **exact arg-free dry-run byte-strings only** (the SEEDED tier byte-string is the arg-free preview of each): `node ${CLAUDE_SKILL_DIR}/tools/velocity-profile.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs` · `node ${CLAUDE_SKILL_DIR}/tools/gate-hook.mjs` — every `--apply`/`--write`/`--yes` still prompts, always. (`gate-hook` also has a **`--read-lane`** flagged preview — the opt-in read-only compound lane, `${CLAUDE_SKILL_DIR}/references/modes/hook.md`; that flagged form is NOT the seeded arg-free byte-string, so it may **prompt once** — it IS a consent flow, stated, no silent cap.)