@sabaiway/agent-workflow-kit 3.15.0 → 4.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +4 -4
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +12 -8
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +735 -55
  6. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +899 -51
  7. package/bridges/antigravity-cli-bridge/bin/agy.sh +4 -3
  8. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +23 -0
  9. package/bridges/antigravity-cli-bridge/capability.json +14 -4
  10. package/bridges/antigravity-cli-bridge/references/driving-agy.md +12 -4
  11. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
  12. package/bridges/antigravity-cli-bridge/references/review-prompt.md +65 -2
  13. package/bridges/codex-cli-bridge/SKILL.md +1 -1
  14. package/bridges/codex-cli-bridge/bin/codex-exec.sh +2 -1
  15. package/bridges/codex-cli-bridge/bin/codex-review.sh +63 -13
  16. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +38 -0
  17. package/bridges/codex-cli-bridge/capability.json +1 -1
  18. package/capability.json +1 -1
  19. package/package.json +1 -1
  20. package/references/agents/review-lens.md +39 -0
  21. package/references/hooks/gate-approve.mjs +54 -7
  22. package/references/modes/agents.md +11 -2
  23. package/references/modes/autonomy-doctor.md +2 -0
  24. package/references/modes/backends.md +2 -0
  25. package/references/modes/bootstrap.md +2 -0
  26. package/references/modes/bridge-settings.md +4 -1
  27. package/references/modes/commit-guard.md +2 -0
  28. package/references/modes/core-evidence.md +2 -0
  29. package/references/modes/coverage-check.md +2 -0
  30. package/references/modes/doc-parity.md +2 -0
  31. package/references/modes/gates.md +2 -0
  32. package/references/modes/grounding.md +2 -0
  33. package/references/modes/help.md +2 -0
  34. package/references/modes/hook.md +13 -2
  35. package/references/modes/migrate-adr-store.md +2 -0
  36. package/references/modes/procedures.md +2 -0
  37. package/references/modes/recipes.md +2 -0
  38. package/references/modes/recommendations.md +4 -3
  39. package/references/modes/review-state.md +2 -0
  40. package/references/modes/sandbox-masks.md +2 -0
  41. package/references/modes/set-autonomy.md +2 -0
  42. package/references/modes/set-recipe.md +3 -0
  43. package/references/modes/setup.md +2 -0
  44. package/references/modes/state-block-guard.md +2 -0
  45. package/references/modes/status.md +4 -1
  46. package/references/modes/uninstall.md +2 -0
  47. package/references/modes/upgrade.md +2 -0
  48. package/references/modes/velocity.md +7 -0
  49. package/references/modes/worktrees.md +2 -0
  50. package/tools/bridge-settings-read.mjs +40 -10
  51. package/tools/bridge-settings.mjs +22 -7
  52. package/tools/cheap-agents.mjs +15 -5
  53. package/tools/commands.mjs +2 -2
  54. package/tools/core-evidence.mjs +29 -2
  55. package/tools/detect-backends.mjs +1 -1
  56. package/tools/manifest/schema.md +7 -0
  57. package/tools/manifest/validate.mjs +8 -0
  58. package/tools/presentation.mjs +1 -1
  59. package/tools/procedures.mjs +9 -2
  60. package/tools/recipes.mjs +4 -1
  61. package/tools/recommendations.mjs +110 -59
  62. package/tools/renderers.mjs +10 -1
  63. package/tools/review-state.mjs +4 -0
  64. package/tools/velocity-profile.mjs +6 -3
  65. package/tools/view-model.mjs +3 -1
@@ -29,10 +29,13 @@
29
29
  // the command carries a documented runtime residual (output redirection, command
30
30
  // substitution, the bounded `--output` write-flag family) that a settings-level allow
31
31
  // rule cannot see. Most-restrictive-wins: this surfaces a human prompt even where a
32
- // seeded allow rule would have silently approved. Detection is deliberately string-level
33
- // and conservative (no shell parsing in a dependency-free hook): a quoted metacharacter
34
- // may over-ASK, never under-allow. Covers the kit-SEEDED core only, never arbitrary
35
- // user-added rules.
32
+ // seeded allow rule would have silently approved. Covers the kit-SEEDED core only, never
33
+ // arbitrary user-added rules.
34
+ // Detection is deliberately string-level and conservative (no shell parsing in a
35
+ // dependency-free hook): a quoted metacharacter may over-ASK, never under-allow. That
36
+ // over-ask is a KNOWN COST, not an oversight — searching for `=>`, or a plain read wearing
37
+ // `2>/dev/null`, prompts. Three mechanisms to narrow it were built and removed; the note
38
+ // above `detectResidualClasses` records all three with their counterexamples.
36
39
  // (c) read-lane allow (OPT-IN) → allow. Only when docs/ai/lanes.json enables it
37
40
  // (`{ "readLane": true }`, read LIVE per call, fail-closed): a command every separator-split
38
41
  // segment of which is a plain frozen read-only core prefix, with ZERO shell metaprogramming
@@ -42,6 +45,24 @@
42
45
  // (d) everything else → NO decision: exit 0, no output — the normal permission flow proceeds
43
46
  // unchanged. The hook NEVER emits `deny`.
44
47
  //
48
+ // WHY THERE IS NO DENY RUNG, AND WHY THE DIRECTION IS RETIRED (kit 4.0.0 built one, AD-078
49
+ // removed it; the class was closed from the other side instead). The withdrawn rung refused a
50
+ // seeded read-only command that provably DISCARDED its output, on the argument that such a refusal
51
+ // cannot destroy anything the caller wanted. The argument was sound; the byte-level PROOF was not.
52
+ // Five constructs defeated it in three rounds — `1<&2` (an fd dup routes stdout back out of
53
+ // /dev/null AFTER the approved `>`), a quoted literal `>/dev/null ` in an argument, a
54
+ // leading-token-only segment match (`… && npm test`), a bare `&` (backgrounds the read, runs the
55
+ // rest), and a `#` comment (bash never executes the redirect at all). Each was a FALSE REFUSAL,
56
+ // because on an ASK rung an incomplete scan merely over-asks — safe — while on a DENY rung the
57
+ // SAME incompleteness refuses real work.
58
+ // The direction is retired, not deferred, for a reason bigger than that: the prompts it was meant
59
+ // to prevent were being raised BY THIS HOOK. A deny cannot remove a prompt the hook itself is
60
+ // causing — correcting what the guard reports does, and it does it without ever refusing anything.
61
+ // Correcting what the guard REPORTS can, and refuses nothing — but three attempts at that
62
+ // correction were also built and removed (see the note above `detectResidualClasses`). The prompts
63
+ // are still there, and are stated rather than papered over.
64
+ // Design record: docs/plans/queue.md, BARE-LANE-DENY-RUNG.
65
+ //
45
66
  // Fail-safe invariant, decoupled per function: a DECLARATION anomaly (missing / unreadable /
46
67
  // malformed / schema-invalid gates.json) disables ONLY exact-gate approval (a) — the residual
47
68
  // guard (b) needs no declaration and keeps running (a broken gates.json must not silently
@@ -114,13 +135,16 @@ export const SEEDED_READONLY_CORE = Object.freeze([
114
135
  // settings-level allow rule cannot see). Drift-guarded — never edit here alone.
115
136
  export const RESIDUAL_FORMS = Object.freeze({
116
137
  writeRedirections: Object.freeze(['>', '>>', '1>', '2>', '&>', '>|']),
117
- // `$(…)` + backtick + process substitution `<(…)` all RUN a nested command (`>(…)` is caught by
118
- // the `>` redirection scan). Bare `<` is input redirection (reads a file read-only commands may
138
+ // `$(…)` + backtick + BOTH process substitutions `<(…)` / `>(…)` RUN a nested command. `>(…)` is
139
+ // named here explicitly because that is the class it is IN: the redirection scan also happens to
140
+ // match its `>`, but a command that RUNS something must be reported as running something, and a
141
+ // classification must not depend on another class's coincidence. Bare `<` is input redirection
142
+ // (reads a file — read-only commands may
119
143
  // already do that), so it is deliberately NOT here. The bash-5.3 function substitutions `${ cmd; }`
120
144
  // (a blank — space/tab/newline/CR — right after `${`) and `${| cmd; }` also RUN a nested command —
121
145
  // matched as the literal openers `${ ` / `${\t` / `${\n` / `${\r` / `${|` (AD-055 Part II). Ordinary
122
146
  // `${VAR}` has no blank after `${`, so it trips none of these (kept rung-(b)-silent).
123
- commandSubstitutions: Object.freeze(['$(', '`', '<(', '${ ', '${\t', '${\n', '${\r', '${|']),
147
+ commandSubstitutions: Object.freeze(['$(', '`', '<(', '>(', '${ ', '${\t', '${\n', '${\r', '${|']),
124
148
  // A backslash immediately before a newline/CR is a bash LINE CONTINUATION: bash removes it and
125
149
  // splices the two lines into ONE word, reconstructing a residual token (`--output`, `$(`, `${ …; }`)
126
150
  // a raw substring scan on the pre-splice string misses. Guards a settings-allowed SINGLE (rung c
@@ -249,6 +273,29 @@ export const matchSeededCorePrefix = (command) => {
249
273
  // direction (rung (c) forbids every construction character per segment, so this guards rung (b) singles).
250
274
  const WORD_CONSTRUCTION_CHARS = /["'\\[\]{}*?]/gu;
251
275
 
276
+ // THREE MECHANISMS WERE BUILT TO NARROW THIS SCAN AND ALL THREE WERE REMOVED (AD-079). They are
277
+ // recorded here because each died to a DIFFERENT counterexample, and together they say something
278
+ // the next attempt needs: this hook cannot decide what a redirection byte MEANS — not by parsing
279
+ // it, and not even by deleting it — because JavaScript's idea of a token boundary and bash's do not
280
+ // agree. Each counterexample was verified LIVE, each ships as a test, and each was removed on a
281
+ // stop rule declared to the reviewing bridge BEFORE the round that met it.
282
+ // 1. A quote/escape-aware ACTIVE VIEW, so a `>` inside a search pattern would stop reading as an
283
+ // operator. Defeated by HEREDOCS: their bodies are not shell code, and one quote in each of
284
+ // TWO bodies opens a spurious span and later closes it, with a genuinely active `$(…)` sitting
285
+ // BETWEEN them. The walker ends balanced, so the ambiguity fallback never engages, and the
286
+ // guard goes silent on a nested command. Exact sequence: the test below.
287
+ // 2. An fd-DUPLICATION exemption (`2>&1` writes nothing). Defeated by a missing token boundary:
288
+ // `>&word` duplicates only when the word is a bare number, and `grep x f >&12file` writes the
289
+ // FILE `12file`, whose `>&12` prefix the pattern deleted.
290
+ // 3. A NULL-DEVICE exemption (a redirect into `/dev/null` writes nothing), with a boundary this
291
+ // time. Defeated by U+00A0: JavaScript `\s` counts a no-break space as a boundary and bash does
292
+ // not, so `grep x f >/dev/null<U+00A0>sink` names the FILE `/dev/null<U+00A0>sink` and the span
293
+ // was deleted anyway.
294
+ // What survives is the original scan, unchanged, plus `>(` named in the substitution class where it
295
+ // belongs. The over-asks stay — a `>` that is ordinary text, and an fd duplication — and they are
296
+ // documented as open rather than papered over. AD-078's lesson, now one layer deeper: on an ASK rung
297
+ // an incomplete scan merely over-asks, which is safe; every attempt to make it exact has been a
298
+ // claim about bash that a dependency-free string scanner is not in a position to make.
252
299
  export const detectResidualClasses = (command) => {
253
300
  const deSpliced = command.replace(WORD_CONSTRUCTION_CHARS, '');
254
301
  const scan = (form) => command.includes(form) || deSpliced.includes(form);
@@ -1,6 +1,15 @@
1
1
  ### Mode: agents
2
2
 
3
- The opt-in **cheap-lane subagent writer** — the family's second `.claude/` writer, on the velocity discipline. It places the bundled cheap-lane subagent definitions (`references/agents/*.md`) into the project's `.claude/agents/` so mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage — runs on a **cheap model** (`model: haiku`, `effort: low`, bounded read-only tools) instead of the frontier main lane. **Claude-Code-specific** (like velocity): other agent hosts ignore `.claude/agents/`. Judgment, review, real code, and user-facing copy never move to these vehicles — they are extraction/drafting only, and the orchestrator verifies their output.
3
+ <!-- opt-in-capability: agents -->
4
+
5
+ The opt-in **read-only subagent writer** — the family's second `.claude/` writer, on the velocity discipline. It places the bundled subagent definitions (`references/agents/*.md`) into the project's `.claude/agents/`. **Claude-Code-specific** (like velocity): other agent hosts ignore `.claude/agents/`.
6
+
7
+ **Every vehicle grants READ-ONLY tools and NO `Bash`** — that is the load-bearing property, not a detail. A read-only fan-out on a full-tool vehicle shells out for facts it could have read, and each shelled command is an approval prompt the maintainer never needed to see; a vehicle with no shell structurally cannot do that. Two lanes ride on it:
8
+
9
+ - **cheap lane** (`model: haiku`, `effort: low`) — `mechanical-sweep`, `changelog-skeleton`, `gate-triage`: extraction sweeps, changelog fact-skeletons, gate-failure triage. Extraction/drafting only; the orchestrator applies judgment and verifies the output.
10
+ - **review lens** (`review-lens`, review-capable model) — an ADDITIONAL independent read-only opinion on code the configured review backends have already seen. It exists because a third lens otherwise has **no vehicle at all**: the cheap vehicles are scoped away from judgment, and a review-capable full-tool subagent is the prompt-flood shape. It never replaces the configured review recipe, and it is advisory like every other review.
11
+
12
+ Writing code, running gates, and user-facing copy never move to these vehicles.
4
13
 
5
14
  Run `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs [--dry-run | --apply] [--cwd <dir>]`:
6
15
 
@@ -8,4 +17,4 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/cheap-agents.mjs [--dry-run | --apply] [--cw
8
17
  2. **Only on an explicit yes**, re-run with `--apply`. It writes **only** under `.claude/agents/` — never `settings.json` / `settings.local.json`, never a commit. `--apply` is deployment-gated (the stamp must be at the lineage head) and symlink-safe (a symlinked `.claude` / `.claude/agents` / target file is a STOP).
9
18
  3. **Hidden-mode deployments:** after apply, run the hide-footprint reconcile (`node ${CLAUDE_SKILL_DIR}/tools/hide-footprint.mjs --dir <project> --reconcile`) so the placed files stay invisible to `git status` — `/.claude/agents/` is in the known-footprint registry; the apply report reminds you.
10
19
 
11
- **Invariants:** writer (writes only `.claude/agents/`) · preview by default · a diverged existing file is reported and preserved, never clobbered · never touches settings · never commits · vehicles are pinned to `model: haiku` + `effort: low` + read-only tools (content-tested).
20
+ **Invariants:** writer (writes only `.claude/agents/`) · preview by default · a diverged existing file is reported and preserved, never clobbered · never touches settings · never commits · **no vehicle grants `Bash`** · the cheap-lane vehicles are pinned to `model: haiku` + `effort: low`, and the review lens is pinned OFF the cheap model (all content-tested).
@@ -1,5 +1,7 @@
1
1
  ### Mode: autonomy-doctor
2
2
 
3
+ <!-- opt-in-capability: sandbox-provision -->
4
+
3
5
  The **sandbox provisioner "doctor"** — the answer to *"can this machine run the Claude sandbox, and can you fix it?"* (AD-044: macOS Seatbelt built-in / Linux+WSL2 `bwrap`+`socat` / native Windows → WSL2). **Division of labor:** YOU narrate the diagnosis and relay the consent question; the KIT does the deterministic detect → consent-gated install → verify. It is **guarded**: the privileged lane runs ONLY with the per-run consent tuple, always faces the harness permission prompt (the mode sits outside every velocity auto-approve tier), **never auto-runs, never writes repo files, and never commits**.
4
6
 
5
7
  Run **`node ${CLAUDE_SKILL_DIR}/tools/autonomy-doctor.mjs [--verify | --apply <pm>:<pkg[,pkg...]>]`**:
@@ -1,5 +1,7 @@
1
1
  ### Mode: backends
2
2
 
3
+ <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
+
3
5
  Read-only. Answers *"which optional execution-backends are set up vs missing, and what's the next step?"* — for the family's subscription-CLI bridges (`codex-cli-bridge` → `codex`, `antigravity-cli-bridge` → `agy`). It **never writes, never commits, and never runs a subscription CLI**.
4
6
 
5
7
  1. Run `node ${CLAUDE_SKILL_DIR}/tools/detect-backends.mjs` and present its table verbatim. Each row reports two **decoupled** axes: `manifestState` (health of the bridge *skill* — `not-installed | unsupported-schema | invalid-manifest | foreign | stub | ok`) and the readiness signals `cli` / `credentials` / `wrappers`, probed independently — so a CLI that is installed and signed in but whose bridge *skill* is absent reads `needs-skill`, not "missing".
@@ -1,5 +1,7 @@
1
1
  ### Mode: bootstrap
2
2
 
3
+ <!-- opt-in-capability: none — the deployment action itself; the advisor only renders inside an existing deployment -->
4
+
3
5
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKILL_DIR}/references/shared/composition-handoff.md · ${CLAUDE_SKILL_DIR}/references/shared/deploy-tail.md · ${CLAUDE_SKILL_DIR}/references/shared/command-shapes.md
4
6
 
5
7
  > Bundled sources below (templates, scripts) live in **this skill's own directory** — `${CLAUDE_SKILL_DIR}/` in Claude Code, or the folder containing this `SKILL.md` in Codex / other agents. Use that as the copy/read source; the working directory is the **target project**, not the skill.
@@ -1,5 +1,7 @@
1
1
  ### Mode: bridge-settings
2
2
 
3
+ <!-- opt-in-capability: codex-fast -->
4
+
3
5
  The reader + consent-gated **writer** for the **host-level** bridge settings file — the answer to *"turn on the codex Fast tier (or another bridge knob) once, predictably, so it survives kit upgrades."* The four bridge wrappers read `${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf` (`KEY=VALUE` lines, **parsed never sourced**); this is the ONLY writer for it. The file lives **outside every kit-managed tree**, so a kit refresh never writes or clobbers it — upgrade-survival is structural (D2). It **previews by default**; `--apply` writes. Hand-editing the file stays fully supported — this is an offered convenience, never a lock.
4
6
 
5
7
  **The knobs are the bundled bridges' own `settings` blocks** (manifest-as-source, D6) — the tool never invents a key or a value rule, and what it writes always passes the wrappers' own validation. Model/effort are **NOT** settable here (the wrappers' quality-first guard is untouched, D4). Run **`node ${CLAUDE_SKILL_DIR}/tools/bridge-settings.mjs`** to see the live list; today:
@@ -10,7 +12,8 @@ The reader + consent-gated **writer** for the **host-level** bridge settings fil
10
12
  | `CODEX_HARD_TIMEOUT` | codex | integer `1..86400` | hard wall-clock cap (seconds) via `timeout(1)`. |
11
13
  | `CODEX_REVIEW_MAX_TOTAL_BYTES` | codex | integer `1..100000000` | codex-review payload size above which the diff rides a temp file (never truncated). |
12
14
  | `AGY_HARD_TIMEOUT` | agy | duration `5m`/`30m`/`90s` (unit required, nonzero) | hard wall-clock cap via `timeout(1)`. |
13
- | `AGY_REVIEW_ALLOW_ADDDIR` | agy | `0` \| `1` | `1` re-enables the oversized-review `--add-dir` offload (Issue-001 stall risk, bounded by the timeout). |
15
+ | `AGY_REVIEW_MAX_TOTAL_BYTES` | agy | integer `1..100000000` | the ceiling on the SUM of all outgoing prompt bytes an oversized `agy-review code` may feed (default 240000); past it the fed review refuses **before** spending turn 1. |
16
+ | `AGY_REVIEW_ALLOW_ADDDIR` | agy | `0` \| `1` | **RETIRED** — still recognized (an existing line never warns as unknown) but it **arms nothing**; the writer refuses a new `--set` and `--unset` clears it. Headless agy auto-denies its own `read_file`, so the offload it armed could return a confident fabrication; an oversized code review is a **chunked feed with a per-part delivery proof** now. |
14
17
 
15
18
  **Invocations:**
16
19
 
@@ -1,5 +1,7 @@
1
1
  ### Mode: commit-guard
2
2
 
3
+ <!-- opt-in-capability: commit-guard -->
4
+
3
5
  The **read-only pre-commit guard** (strip-the-kit D10) — the last line of the loop: a commit is permitted only against the LATEST completed `run-gates --final` receipt that binds EXACTLY the current tree. It re-runs NO gate or test subprocess — the heavy D3 verification lives in the final run; the guard recomputes the tree fingerprint (read-only git plumbing) and compares file contents.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/commit-guard.mjs --check [--cwd <dir>]` — it refuses, each with a named recovery, on:
@@ -1,5 +1,7 @@
1
1
  ### Mode: core-evidence
2
2
 
3
+ <!-- opt-in-capability: none — an on-demand evidence writer that leaves no persistent opt-in state -->
4
+
3
5
  The ONE **loop-evidence writer** (strip-the-kit D6a/D7) — every core evidence record lands in a single append-only JSONL store **inside the git dir** (`<git dir>/agent-workflow-core-evidence.jsonl` — never committable by construction; `AW_CORE_EVIDENCE` overrides, a test seam). Records carry a versioned schema and a per-kind KEY; the **LATEST record per key is authoritative** (supersession, file order); a byte-identical replayed line is refused as a duplicate; a malformed line fails every reader CLOSED. The store holds four kinds: `red-proof` and `degrade` (written HERE), plus `final-start`/`final` (written only by `run-gates --final` — the same sole-writer module, a different entry point).
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/core-evidence.mjs <verb> …`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: coverage-check
2
2
 
3
+ <!-- opt-in-capability: none — a read-only gate entry; declaring the gate matrix is covered by gates-declaration -->
4
+
3
5
  The **final-run checker** (strip-the-kit D3(c)+(d)) — two deterministic arms over ONE fixed artifact path, every refusal naming its locations `file:line`, never a bare count. It reads the lcov file the declared `unit-tests` gate cmd produced at `<git dir>/agent-workflow-lcov.info` (the constant this tool exports; `AW_LCOV_FILE` overrides, a test seam; the git dir is outside the fingerprint domain and never committable by construction) and prints `coverage-check: lcov-sha256=<hex|none>` — the sha of the EXACT bytes it consumed, which the `run-gates --final` receipt binds and re-hashes (exactly ONE such line attests).
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/coverage-check.mjs --check [--cwd <dir>]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: doc-parity
2
2
 
3
+ <!-- opt-in-capability: none — a read-only gate entry; declaring the gate matrix is covered by gates-declaration -->
4
+
3
5
  The DOC-PARITY lint (BUGFREE-3 / AD-049, session-economics item (b)) — the deterministic doc-drift killer. A recurring class of review churn came from a mode-contract doc silently lagging a code constant (a `--check` doc still reading "300" after the diff cap moved to 400, caught only in a later review round). This read-only tool closes that class mechanically: a **closed, exported registry** ties each live code constant to the exact token its `references/modes/*.md` contract must carry, and asserts the CURRENT value renders into every bound file.
4
6
 
5
7
  **Run** — `node ${CLAUDE_SKILL_DIR}/tools/doc-parity.mjs [--check | --json]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: gates
2
2
 
3
+ <!-- opt-in-capability: gates-declaration -->
4
+
3
5
  The **generic project gate runner** — it batches the project's OWN declared verification commands into one run. The runner itself **writes nothing on a plain run, never commits, and never runs a subscription CLI**; what it EXECUTES is the project's own declaration, with the caller's privileges (trust posture: a batching convenience over commands the project already runs by hand — **not a sandbox**). **`--final`** is the ONE writing mode: it runs the FULL declared matrix as the D3(a) final verification run and mints the receipt the commit guard consumes (step 4).
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/run-gates.mjs [--cwd <project>] [--only <id>]… [--final]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: grounding
2
2
 
3
+ <!-- opt-in-capability: none — an on-demand assembler that leaves no persistent state the advisor could observe -->
4
+
3
5
  The **grounded-review facts assembler** (AD-038) — an ungrounded `agy` review GUESSES, and while the grounding contract is mechanized (`agy-review code --facts @f`), populating the facts file was a manual chore. This mode emits the **mechanical** facts sources of a payload (constraints / autonomy / plan / ledger digest); the orchestrator still owns any judgment-bearing additions. **Catalogued honestly as a WRITER** — `--out <path>` writes one file — with the invariant: `--out` accepts **only system-temp destinations outside the repo ($TMPDIR / /tmp — rewritable scratch) or a FRESH gitignored in-repo path (create-only, exclusive write)** and REFUSES everything else: a tracked path, an in-repo not-ignored path (a new untracked file would itself move the review fingerprint the facts are about to ground), an EXISTING in-repo file even when gitignored (the `.env` clobber class — this writer is bridge-tier auto-allowable), any other outside-repo destination, and symlink/non-regular leaves; **stdout is the default**. It never commits and never runs a subscription CLI.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/grounding.mjs [--constraints] [--autonomy] [--plan <path>] [--reserve-bytes <n>] [--out <path>]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: help
2
2
 
3
+ <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
+
3
5
  Read-only. The single discoverable **command index** — it answers *"what can `/agent-workflow-kit` do, and which commands change things?"* It **never writes, never commits, and never runs a subscription CLI**.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/commands.mjs` (add `--json` for the machine-readable catalog) and present its grouped index — **Inspect / Configure / Orchestrate / Lifecycle**, each command tagged **read-only / writer / guarded / runs-project-commands** (the last: the kit writes nothing, but the mode executes the project's OWN declared commands — the `gates` runner) — in the user's conversational language. That catalog is the **single source of truth** for the command surface (the same one the bootstrap / upgrade report footers point at); `routeInvocation(token)` in the same file is the executable contract for which invocation maps to which mode.
@@ -1,11 +1,22 @@
1
1
  ### Mode: hook
2
2
 
3
+ <!-- opt-in-capability: gate-hook -->
4
+ <!-- opt-in-capability: read-lane -->
5
+
3
6
  The opt-in **gate-approval PreToolUse hook** — the family's third `.claude/` writer (velocity discipline), and the shipped closure of the velocity trust-posture residual (**AD-021 scope C, probe-proven in AD-037**). It places a **self-contained** hook runtime at `.claude/hooks/agent-workflow-gates.mjs` (no kit imports — it keeps working if the kit is uninstalled) and wires ONE `PreToolUse` `"Bash"` entry into `.claude/settings.json`. Per Bash call the hook then walks a decision ladder, first match wins:
4
7
 
5
8
  - **Auto-approve** a command **byte-identical** (leading/trailing trim only — no whitespace collapsing, no quote/glob/variable interpretation, no prefix or pattern matching, ever: patterns are what made AD-021 auto-seeding rejected) to a gate `cmd` declared in `docs/ai/gates.json` — read **LIVE on every call** (editing gates.json never needs re-wiring; one declaration, two consumers with `${CLAUDE_SKILL_DIR}/references/modes/gates.md`) — invoked **from the project root** (gates run from the root by contract; the same bytes from a subdirectory are NOT approved) and under `default`/`acceptEdits` permission mode (an approval never loosens `plan`/`bypassPermissions`).
6
- - **Ask** on a command whose leading tokens match the velocity **seeded read-only core** when it carries the documented runtime residual — output redirection, command substitution, or the bounded `--output` write-flag family — surfacing a human prompt even where a seeded allow rule would have silently approved (**hook `ask` overrides an allow rule — proven live**: on Claude Code 2.1.185 a seeded `Bash(git log:*)` silently wrote a file via `git log --output=…`; with the hook wired the same call prompts). Detection is string-level and conservative: a quoted metacharacter may over-ask, never under-allow.
9
+ - **Ask** on a command whose leading tokens match the velocity **seeded read-only core** when it carries the documented runtime residual — output redirection, command substitution, or the bounded `--output` write-flag family — surfacing a human prompt even where a seeded allow rule would have silently approved (**hook `ask` overrides an allow rule — proven live**: on Claude Code 2.1.185 a seeded `Bash(git log:*)` silently wrote a file via `git log --output=…`; with the hook wired the same call prompts). Detection is string-level and conservative: a quoted metacharacter may over-ask, never under-allow. **That over-ask is a KNOWN COST, not an oversight** — see the open-limits section below, which names the three mechanisms built to narrow it and why each was removed.
7
10
  - **Auto-approve a read-only COMPOUND** (opt-in — dark unless enabled): when `docs/ai/lanes.json` sets `{ "readLane": true }` (read **LIVE on every call**), a command whose EVERY separator-split segment is a plain seeded read-only core command, carrying **zero shell metaprogramming** anywhere (no `$`/expansion, quoting, backslash, brace, glob, redirection, substitution, `--output`, env-assignment prefix, or backgrounding), is auto-approved — a conservative **closed-world** allow (any doubt falls through, never a widening). Mode-fenced like gate auto-approval; **cwd-agnostic** (a read is a read from any directory). It runs AFTER the residual ask, so a core command that carries a residual still prompts (most-restrictive-wins). The lane is **bounded by the frozen audited read-only core** (the set velocity seeds) — a **standalone opt-in grant**, never a command OUTSIDE that audited core; enabling it auto-approves compounds (and singles) of that audited core **regardless of which of those commands you seeded** as individual settings rules (that is the trust the opt-in consent covers — not strictly a subset of your current settings).
8
- - **Stay silent otherwise** — the normal permission flow proceeds unchanged. The gate-approval hook **never emits `deny`**; nothing is hard-blocked.
11
+ - **Stay silent otherwise** — the normal permission flow proceeds unchanged. The gate-approval hook **never emits `deny`**; nothing is hard-blocked. A deny rung was built for kit 4.0.0 and REMOVED before release: it refused only a seeded read that provably discarded its output (`2>/dev/null`), and three council rounds found five shell constructs that defeated the byte-level discard PROOF (`1<&2`, a quoted literal `>/dev/null `, leading-token-only segment matching, a bare `&`, a `#` comment) — each one a FALSE REFUSAL. On an ask rung an incomplete scan merely over-asks; on a deny rung the same incompleteness refuses real work. **The direction is now RETIRED, not deferred** — the prompts a deny was meant to prevent were being raised by this hook's own ask, and a deny cannot remove a prompt the hook itself causes. Correcting what the guard reports can, and refuses nothing. Design record: the family's queue, `BARE-LANE-DENY-RUNG`.
12
+
13
+ **THE OVER-ASK IS OPEN, and 4.1.0 is the release that establishes WHY, rather than the one that fixes it.** A plain read wearing `2>/dev/null` or `2>&1`, and a search whose PATTERN merely contains `>` or a backtick, all still prompt. Three mechanisms to narrow that were built for this release and all three were REMOVED in review, each on a stop rule declared before the round that met it, each counterexample verified live and kept as a test:
14
+
15
+ 1. **A quote/escape-aware reading of the command**, so a `>` inside a search pattern would stop reading as an operator. Defeated by HEREDOCS: their bodies are not shell code, and one quote in each of two bodies opens a spurious quoted span and later closes it, with a genuinely active `$(…)` sitting between them — the walker ends balanced, never falls back, and the guard goes silent on a nested command.
16
+ 2. **An fd-duplication exemption** (`2>&1` creates no file). Defeated by a missing token boundary: `>&word` duplicates only when the word is a bare number, and `grep x f >&12file` writes the FILE `12file`.
17
+ 3. **A null-device exemption** (a redirect into `/dev/null` writes nothing), boundary included. Defeated by U+00A0: JavaScript's `\s` counts a no-break space as a word boundary and bash does not, so `grep x f >/dev/null` + U+00A0 + `sink` names a FILE and the span was deleted anyway.
18
+
19
+ **The transferable finding: this hook cannot decide what a redirection byte MEANS — not by parsing it, and not even by deleting it — because JavaScript's notion of a token boundary and bash's do not agree.** On an ASK rung that costs an extra prompt, which is safe; it is why the same scan must never be turned into a DENY. The honest workaround today is a search pattern that avoids the byte, and dropping a decoration your harness does not need.
9
20
 
10
21
  **Honest residual status (AD-037):** current engine builds already intercept `>` redirection and `$()` substitution upstream (observed headless on 2.1.185); the **`--output` family was proven open** and is the seam this hook demonstrably closes. The guard still covers all three documented classes (defense-in-depth — engine behavior may vary across surfaces/versions). **Fail-safe, decoupled:** a missing/broken/invalid `gates.json` disables ONLY gate auto-approval — the residual guard keeps running; every anomaly path exits 0 (the hook is never the blocker or the noise — the `gates` runner reports a broken declaration at its own point of use). **Not a sandbox:** it closes the named residual for the seeded core and auto-approves declared gates; it does not police arbitrary commands or user-added rules.
11
22
 
@@ -1,5 +1,7 @@
1
1
  ### Mode: migrate-adr-store
2
2
 
3
+ <!-- opt-in-capability: none — the old-layout condition is already reported by status and upgrade; the advisor does not duplicate a report the same run prints -->
4
+
3
5
  The **guarded ADR-store migration** — a one-time, opt-in move of an existing project's `docs/ai` from the retired 3-tier ADR cascade (HOT `decisions.md` → the WARM/COLD `decisions-archive*.md` monoliths) to the durable **one-file-per-ADR store** (HOT `decisions.md` + `docs/ai/adr/AD-NNN-slug.md` records + the `docs/ai/adr/log.md` navigator). **In-agent, opt-in**, and reached ONLY here: a normal `upgrade` never installs the new-scheme rotator into an un-migrated project — the new rotator arrives ONLY through this mode, which migrates in the same step (AD-051). Run **`--dry-run` first, always**, show the user the plan in plain language, get explicit consent, then re-run with `--apply`. It **never commits**.
4
6
 
5
7
  When to run it: `status` (or `upgrade`) reports an *old ADR layout* (a `decisions-archive*.md` monolith is still on disk). A project already on the one-file-per-ADR store — or a fresh deployment seeded with it — needs nothing here (the mode is a stated no-op).
@@ -1,5 +1,7 @@
1
1
  ### Mode: procedures
2
2
 
3
+ <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
+
3
5
  Read-only **activity-procedures advisor**. Answers *"what are the steps of this named activity, and which recipe applies at each slot here?"* It composes the orchestration recipes (`${CLAUDE_SKILL_DIR}/references/modes/recipes.md`) into **named activities** with **typed recipe slots**. It **never writes, never commits, never runs a subscription CLI** — the deterministic resolution lives in the kit; the orchestrator runs the resolved recipe via the bridge skills and **owns any commit when the activity has a commit boundary** (a backend never commits). Not every activity commits: `plan-authoring` ends at approval and produces no commit (plans are ephemeral, never committed); `plan-execution` commits per Step.
4
6
 
5
7
  The two v1 activities (canon in the **installed engine**, `references/procedures.md`):
@@ -1,5 +1,7 @@
1
1
  ### Mode: recipes
2
2
 
3
+ <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
+
3
5
  Read-only **orchestration advisor**. Answers *"how should I compose the optional execution-backends into plan → execute → review here, and which recipe fits?"* It **never writes, never commits, never runs a subscription CLI, and never executes a recipe** — the orchestrator (you) runs the chosen recipe through the bridge skills and makes the single commit; a backend is advisory or delegated, never autonomous.
4
6
 
5
7
  The four recipes (defined over each bridge's `provides` roles — `codex`: execute + review; `agy`: review + probe), canonical narrative in the **installed engine** (`references/orchestration.md`):
@@ -1,8 +1,10 @@
1
1
  ### Mode: recommendations
2
2
 
3
+ <!-- opt-in-capability: none — this IS the advisor; an offer to enable it would be circular -->
4
+
3
5
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md
4
6
 
5
- The **read-only deployment advisor** — the deterministic section every `upgrade` run ends with, also invocable on its own. It computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe) and renders **verdict-first**: one composed verdict line, then each item as **{severity · what is sub-optimal · the benefit in ONE plain line · an optional `recipe:` line (the `sandbox-lane` live recipe — egress hosts + resolved writable dirs — or the `worktrees-dir` hand-apply-first grant advice) · the exact consent-gated apply one-liner}**. The tool computes deterministic English DATA; **you PRESENT the section in the user's conversational language** — every fact, count and item from the tool, nothing added or dropped; commands, paths, hosts and rule strings stay **byte-exact**; show the raw tool block on request (the AD-032 report-contract lane — the tool cannot know the dialogue language, so the language rendering is your presentation layer).
7
+ The **read-only deployment advisor** — the deterministic section every `upgrade` run ends with, also invocable on its own. It computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe) and renders **verdict-first**: one composed verdict line, then each item as **{severity · what is sub-optimal · the benefit in ONE plain line · an optional `recipe:` line (the `sandbox-lane` live recipe — egress hosts + resolved writable dirs — the `worktrees-dir` hand-apply-first grant advice, or the `agents` hidden-mode reconcile follow-up) · the exact consent-gated apply one-liner}**. The tool computes deterministic English DATA; **you PRESENT the section in the user's conversational language** — every fact, count and item from the tool, nothing added or dropped; commands, paths, hosts and rule strings stay **byte-exact**; show the raw tool block on request (the AD-032 report-contract lane — the tool cannot know the dialogue language, so the language rendering is your presentation layer).
6
8
 
7
9
  **Live host/session facts are tool-composed only.** Every fact this section states about the current
8
10
  host or session — prompts fired, sandbox scope, whether a bypass was needed, network reachability,
@@ -15,12 +17,11 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/recommendations.mjs --cwd <project-root> [--
15
17
 
16
18
  1. **`--cwd` is REQUIRED** — the target project is explicit, never inferred from the shell's current directory (a subdirectory invocation still advises on the named root).
17
19
  2. **Verdict first (present-even-when-empty, opening at the `## Recommendations (agent-workflow)` header).** With everything optimal the body is exactly `no recommendations — flow optimal.` — the empty-state line ALONE is the verdict, zero added lines. Every other state opens the body with ONE verdict line composed from the frozen templates: `{K} item(s) need attention` leads when any item's severity is `attention` (a CONFIGURED declaration that is broken, drifted, degrading or invalid — the deployment needs review); `{N} optional recommendation(s), apply any you want` covers the `optional` class (offers to enable an unconfigured capability), led by `nothing is broken` ONLY when no item needs attention AND no probe check was skipped (a skipped probe could hide an attention-class problem — the claim never overreaches); `optimality NOT attested — {M} probe check(s) skipped` is appended last whenever probe checks were skipped. (The `(s)` invariant form IS the pinned pluralization — no singular/plural branching.) Items render attention-first, each tagged `needs attention:` / `optional:`. A failed probe renders as a stated `⚠ skipped item …` line — never a crash, never a fabricated item.
18
- 3. **The apply-through-agent lane — an explicit informed-consent checkpoint:** after presenting, OFFER to apply; the user selects items in plain language; for each selected item you **surface its posture note inline** where one exists (the per-item notes below cover exactly the risk-marked keys; for `sandbox-lane` the note INCLUDES the sandbox-lanes ladder — present the whole ladder inline at the consent moment, never as a bare pointer). The user **explicitly confirms**, and **only then** — **no command runs before confirmation** — do you run the rendered command. Do NOT infer safety from the presence or absence of an `--apply` flag: most items' rendered command IS the mutation and completes on that one run, **including a no-`--apply` mutation** such as `family-freshness`'s `npx … init`. Some items instead render a **dry-run preview** that changes nothing and prints an explicit follow-up `--apply`/mutating command to run NEXT (e.g. `sandbox-lane`'s ack-write — a NEUTRAL recipe fingerprint into the family-owned `docs/ai/acks.json`, never a security key — and the `gates-declaration` seeder): after the SAME confirmation you run that printed follow-up command — no second ask, no improvisation (each writer keeps its own consent semantics: previews, `--apply` flags, refusals). An item marked **HAND-APPLY** (e.g. `agy-adddir`) is **never run by you and never written by the kit** — hand the user its rendered line together with its posture note; HAND-APPLY is **maintainer territory wherever it renders** — the apply slot or the `recipe:` line. For `worktrees-dir` the two classes COMPOSE in one item and the ORDER is part of the contract: its `recipe:` line is the HAND-APPLY grant advice, its apply one-liner is the dry-run ack preview, and you **wait for the maintainer to confirm the grant is applied (or the terminal fallback chosen)** before running that preview and the exact `--apply` command it prints — the ack RECORDS a choice that must already have been made, and against a trusted host NO the item renders no ack at all.
20
+ 3. **The apply-through-agent lane — an explicit informed-consent checkpoint:** after presenting, OFFER to apply; the user selects items in plain language; for each selected item you **surface its posture note inline** where one exists (the per-item notes below cover exactly the risk-marked keys; for `sandbox-lane` the note INCLUDES the sandbox-lanes ladder — present the whole ladder inline at the consent moment, never as a bare pointer). The user **explicitly confirms**, and **only then** — **no command runs before confirmation** — do you run the rendered command. Do NOT infer safety from the presence or absence of an `--apply` flag: most items' rendered command IS the mutation and completes on that one run, **including a no-`--apply` mutation** such as `family-freshness`'s `npx … init`. Some items instead render a **dry-run preview** that changes nothing and prints an explicit follow-up `--apply`/mutating command to run NEXT (e.g. `sandbox-lane`'s ack-write — a NEUTRAL recipe fingerprint into the family-owned `docs/ai/acks.json`, never a security key — and the `gates-declaration` seeder): after the SAME confirmation you run that printed follow-up command — no second ask, no improvisation (each writer keeps its own consent semantics: previews, `--apply` flags, refusals). An item marked **HAND-APPLY** (e.g. the `read-lane` stale/missing arms) is **never run by you and never written by the kit** — hand the user its rendered line together with its posture note; HAND-APPLY is **maintainer territory wherever it renders** — the apply slot or the `recipe:` line. For `worktrees-dir` the two classes COMPOSE in one item and the ORDER is part of the contract: its `recipe:` line is the HAND-APPLY grant advice, its apply one-liner is the dry-run ack preview, and you **wait for the maintainer to confirm the grant is applied (or the terminal fallback chosen)** before running that preview and the exact `--apply` command it prints — the ack RECORDS a choice that must already have been made, and against a trusted host NO the item renders no ack at all.
19
21
  4. Registry strings (benefits + item texts) are frozen tool data, fact-true, ONE line under the pinned shape cap — posture/risk detail lives in the notes below at the consent moment, never inline in the overview. The dual velocity+security wording (`safer — blast radius bounded by the OS sandbox, not human attention`) rides ONLY the items with a real security delta (the autonomy render, the sandbox provisioning); the bridge-wrappers item claims **velocity only**.
20
22
 
21
23
  **Per-item posture notes (the consent moment — surface BEFORE running or handing over the apply):**
22
24
 
23
- - `agy-adddir` — enabling the add-dir offload re-enables the Issue-001 stall risk (the wrapper's hard timeout bounds it); with the knob off an oversized agy code review refuses instead. Risk profile: availability only, no security delta.
24
25
  - `sandbox-lane` — surface this note TOGETHER with the sandbox-lanes ladder below (the ladder IS the practical half of the note — inline, never a pointer). Pure DISCOVERABILITY: it surfaces the manifest-declared observed session-sandbox recipe (egress hosts ∪ resolved writable state dirs — `networkHosts` ∪ `writableDirs` of the wired bridges' `capability.json`, the single documentation source) and converges on a NEUTRAL fingerprint acknowledgement recorded by the consent-gated **ack writer** into the family-owned `docs/ai/acks.json` (`sandboxLaneAck`; a changed recipe re-fires the item). The store is family-owned so no host settings validator guards it (AD-055 relocated the ack off the Claude Code settings schema, which rejected the unknown key); the legacy `"agentWorkflow": { "sandboxLaneAck": … }` settings-scope key is still READ for one deprecation window (until the next kit MAJOR). It never claims the settings security keys take effect on any host class, never recommends writing them, and the kit never seeds `sandbox.network.allowedDomains` / `sandbox.filesystem.allowWrite` (bridge council 2026-07-11, both backends concur: a network pre-allow widens egress for EVERY sandboxed command; a write allowance on CLI state dirs would expose credential dirs). Posture history: an IDE-managed session sandbox was live-observed (2026-07-11/12) ignoring hand-applied settings security keys in BOTH scopes, and codex needs a writable HOME (EROFS `~/.codex` in-sandbox); whether a session's sandbox honors the settings keys is runtime-unknowable from the advisor (a denial-only signal) — which is exactly why the item states only detectable facts and no zero-prompt promise on any host class.
25
26
 
26
27
  - `read-lane` — enabling the opt-in read-only compound lane auto-approves *compounds* (and singles) of the seeded read-only core that carry ZERO shell metaprogramming: an UNATTENDED trust extension, bounded by the audited read-only core (never a command outside it; prompt-bypass only, never a sandbox bypass) and applied regardless of which of those core commands you seeded as individual settings rules. It is a PROJECT-PERSISTENT declaration in `docs/ai/lanes.json` — every future session, subagents' Bash too where the host fires hooks on subagent Bash, and (committed) every checkout. The apply depends on state: when the lane is OFF, it is the `gate-hook --read-lane` preview (whose own currency check refuses a stale hook — a pre-1.48 hook never reads `lanes.json`); when the placed hook is STALE (an enabled lane over an old hook) or MISSING, the item instead surfaces a **delete-to-reseed** / re-place recovery (a destructive `rm` + `--apply`, an attention item — never the safe preview). Risk profile: a bounded read-only trust-posture extension — no write/exec exposure beyond the audited core.
@@ -1,5 +1,7 @@
1
1
  ### Mode: review-state
2
2
 
3
+ <!-- opt-in-capability: none — a read-only gate entry; declaring the gate matrix is covered by gates-declaration -->
4
+
3
5
  Read-only **review-receipt checker** (AD-038) — it makes *"reviewed ≠ shipped"* mechanically detectable. The bridge review wrappers (`codex-review` / `agy-review`, from bridge version 2.2.0) append one receipt line per **successful** review to a file **inside the git dir** (`<git dir>/agent-workflow-review-receipts.jsonl` — never committable by construction; `AW_REVIEW_RECEIPTS` overrides). This mode resolves the effective `plan-execution.review` recipe (the SAME config + read-only detector the `procedures` advisor reads), recomputes the canonical **uncommitted-state fingerprint** (staged + unstaged + untracked-not-ignored contents — exactly the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, so sandbox-injected device masks can never move the fingerprint; the prose definition lives in each bridge's `capability.json` review contract, implementations cross-checked by the kit's fingerprint-parity test), and reports, per recipe-named backend, whether a **fresh, grounded, current-fingerprint** receipt exists. It **never writes, never commits, never runs a subscription CLI**; it does spawn **read-only `git` queries** to compute the fingerprint — stated honestly (that is still read-only, but it is a subprocess).
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/review-state.mjs [--check] [--json]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: sandbox-masks
2
2
 
3
+ <!-- opt-in-capability: sandbox-masks -->
4
+
3
5
  **Cosmetic exclude lane for sandbox-injected device masks** (AD-044). An OS sandbox (Claude Code) injects character-device masks into the work tree (`.bashrc`, `.gitconfig`, `.vscode`, …); git lists them as untracked noise. The **review domain already ignores them by construction** — never-committable untracked classes (character/block devices, FIFOs, sockets) are excluded from the fingerprint, the assembled review payload, and the clean checks in both bridge wrappers and the kit checker. This mode is the optional **cosmetic** half: it hides the masks from `git status` (and every other `--exclude-standard` untracked walk) via **one managed fenced block** in the file `git rev-parse --git-path info/exclude` names.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/sandbox-masks.mjs [--cwd <project-root>]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: set-autonomy
2
2
 
3
+ <!-- opt-in-capability: autonomy-policy -->
4
+
3
5
  The **policy writer** for `docs/ai/autonomy.json` — the answer to *"set my autonomy policy without hand-editing JSON."* **Division of labor:** YOU turn the user's plain language into explicit ops; the KIT does the deterministic validate → merge → preview → write. It **previews by default** (writes nothing); `--write` applies. It **never renders enforcement** (that is the separate velocity autonomy mode — this writer touches only the policy file), **never runs a backend, and never commits**. Hand-editing `docs/ai/autonomy.json` stays fully supported — an offered convenience, never a lock.
4
6
 
5
7
  **The policy** has two parts: **red-lines** (always hold, segment-independent) + a **per-activity autonomy level**.
@@ -1,5 +1,8 @@
1
1
  ### Mode: set-recipe
2
2
 
3
+ <!-- opt-in-capability: review-recipe -->
4
+ <!-- opt-in-capability: delegated-execution -->
5
+
3
6
  The **config writer** for `docs/ai/orchestration.json` — the answer to *"set my standing recipe preference without hand-editing JSON."* **Division of labor (AD-025):** YOU turn the user's plain language into explicit ops; the KIT does the deterministic validate → merge → preview → write. It **previews by default** (writes nothing); `--write` applies. It **never runs a backend and never commits**. Hand-editing `docs/ai/orchestration.json` stays fully supported — this is an offered convenience, never a lock.
4
7
 
5
8
  **Map the user's plain language → explicit ops** (the kit ships no NL parser; it performs no `all`-magic, so you expand scope explicitly, asking when unclear):
@@ -1,5 +1,7 @@
1
1
  ### Mode: setup
2
2
 
3
+ <!-- opt-in-capability: none — placing a third-party subscription CLI is the user purchase decision; the advisor surfaces it only when a CONFIGURED recipe degrades (review-recipe) -->
4
+
3
5
  The **only writer** among the backend modes, and **opt-in / in-agent only** — **placement** is **never** part of `init`. The npx installer deploys the *kit* and bundles the bridge skills in its tarball, but **does not place** them (that honesty claim is load-bearing — see `decisions.md` AD-009 / AD-011); **once placed** by `setup`, `init` and `${CLAUDE_SKILL_DIR}/references/modes/upgrade.md` keep the placed copy fresh via the refresh-only `--refresh-placed` (below) — **never a first placement, never a downgrade**. `setup` owns exactly the two deterministic, secret-free steps and **guides** the rest. It **never commits and never runs a subscription CLI**.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/setup-backends.mjs [<backend>] [--bindir <path>] [--dry-run]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: state-block-guard
2
2
 
3
+ <!-- opt-in-capability: state-block -->
4
+
3
5
  The opt-in **closing-state-block detector**, wired as a Claude Code `Stop` hook. It ships as a
4
6
  **self-contained** runtime at `references/hooks/state-block-guard.mjs` (no kit imports — a placed
5
7
  copy keeps working if the kit is uninstalled) and it **writes nothing**: this mode is read-only, and
@@ -1,5 +1,7 @@
1
1
  ### Mode: status
2
2
 
3
+ <!-- opt-in-capability: none — an inspection surface that configures nothing -->
4
+
3
5
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md
4
6
 
5
7
  Read-only. The **single answer to "versions + deployment + settings + bridges"** across the whole family — `tools/family-registry.mjs` aggregates every member's `capability.json` and surveys the project. It **never writes, never commits, and never runs a subscription CLI**.
@@ -14,8 +16,9 @@ Run `node ${CLAUDE_SKILL_DIR}/tools/family-registry.mjs --json [--dir <project>]
14
16
  - **recipes** — the effective recipe per slot (detail → `/agent-workflow-kit procedures` / `recipes`); a `recipes.detectError` → say the backends couldn't be checked, so recipes floored at solo.
15
17
  - **attribution** — `includeCoAuthoredBy` effective; call out a **local override** only when `local` is non-null **and** differs from `project` (a `null` `local` means the key is absent there, so the project value stands — that is not an override).
16
18
  - **velocity** — the effective `permissions.defaultMode` + whether an allowlist is seeded (detail → `/agent-workflow-kit velocity`).
17
- - **cheap agents** — how many of the kit's cheap-lane subagent vehicles are placed (`agents.placed` of `agents.bundled`; zero placed → the optional `/agent-workflow-kit agents` opt-in).
19
+ - **subagents** — how many of the kit's read-only subagent vehicles are placed (`agents.placed` of `agents.bundled`; zero placed → the optional `/agent-workflow-kit agents` opt-in). None of them grants a shell; three ride a cheap model for mechanical work and one is a read-only review lens.
18
20
  - **gate hook** — wired / hook file placed / declaration present, plus **`hook.declaredGates`** (0 = absent or an empty list; `null` = present but unreadable → say *couldn't be counted*, never a number; detail → `/agent-workflow-kit hook` and the gates guide).
21
+ - **bridge knobs** — a settings entry carrying **`retired`** is a key that is CONFIGURED but arms nothing: say so plainly (the reason is in the field) and point at `bridge-settings --unset <KEY> --apply` to clear it. Never render a retired key as an active setting.
19
22
  - Any area's **`error`** field → surface it **loudly** in plain language; the rest of `status` still renders (never a crash).
20
23
  4. **Bridges (host, one line)** (from `bridges[]`): per bridge — readiness + wrapper PATH-presence; render each wrapper's `state` as *on PATH* (`present`) / *not on PATH* (`missing`) / *couldn't check* (`unknown`) (detail → `/agent-workflow-kit backends` / `setup`). **No default-model claim.** "credentials present" means a marker file exists, not a live login.
21
24
 
@@ -1,5 +1,7 @@
1
1
  ### Mode: uninstall
2
2
 
3
+ <!-- opt-in-capability: none — removal of what setup placed, never a capability a user could enable -->
4
+
3
5
  The **guarded teardown** — the inverse of `init` (the kit + engine skills) + `setup` (the bridges) + a hidden deploy. **In-agent, opt-in**, and built around one hard rule: **it never deletes user-authored content.** Run **`--dry-run` first, always**, show the user the classified plan in plain language, get explicit consent, then re-run with `--yes`. It **never commits**.
4
6
 
5
7
  Run `node ${CLAUDE_SKILL_DIR}/tools/uninstall.mjs [<member>] [--dir <project>] [--bindir <path>] [--dry-run | --yes]`:
@@ -1,5 +1,7 @@
1
1
  ### Mode: upgrade
2
2
 
3
+ <!-- opt-in-capability: family-freshness -->
4
+
3
5
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKILL_DIR}/references/shared/composition-handoff.md · ${CLAUDE_SKILL_DIR}/references/shared/deploy-tail.md · ${CLAUDE_SKILL_DIR}/references/shared/command-shapes.md
4
6
 
5
7
  1. Read `docs/ai/.workflow-version` (the project's stamped lineage). If missing, treat as a pre-versioned deployment and offer to re-bootstrap conservatively.
@@ -1,5 +1,12 @@
1
1
  ### Mode: velocity
2
2
 
3
+ <!-- opt-in-capability: velocity-core -->
4
+ <!-- opt-in-capability: kit-tools-tier -->
5
+ <!-- opt-in-capability: bridge-tier -->
6
+ <!-- opt-in-capability: autonomy-render -->
7
+ <!-- opt-in-capability: sandbox-lane -->
8
+ <!-- opt-in-capability: accept-edits -->
9
+
3
10
  Requires: ${CLAUDE_SKILL_DIR}/references/shared/command-shapes.md
4
11
 
5
12
  The opt-in onboarding **velocity profile** — it seeds a fixed, audited **read-only** Claude Code allowlist into `.claude/settings.json` so an agent stops idling on approval prompts for routine read-only commands while the maintainer is away. It is the family's **first programmatic `.claude/settings.json` writer** (attribution stayed an agent-driven prose merge). **In-agent, opt-in, writes only `.claude/settings.json`**, on one hard rule: **it never allowlists `commit`/`push`/`publish`** — so a direct commit/push/publish still ASKs; the only caveat is the trust-posture residual (below) — its closure is **shipped, opt-in: `${CLAUDE_SKILL_DIR}/references/modes/hook.md`**.
@@ -1,5 +1,7 @@
1
1
  ### Mode: worktrees
2
2
 
3
+ <!-- opt-in-capability: worktrees-dir -->
4
+
3
5
  Parallel feature worktrees (v1) — several features implemented simultaneously in DIFFERENT agent
4
6
  sessions on one machine/repo, zero interference on working-tree files (the ONE exception: the
5
7
  default `node_modules` symlink is a shared MUTABLE dependency cache — see below), unambiguous
@@ -151,15 +151,27 @@ export const duplicateKeys = (parsed) => [...parsed.byKey.entries()].filter(([,
151
151
  // The env keys the wrappers resolver-validate AFTER precedence (AD-061 aw_effective_timeout) —
152
152
  // exactly these mirror the wrapper's invalid-env → warn + built-in-default lane.
153
153
  const RESOLVER_VALIDATED_ENV = new Set(['CODEX_HARD_TIMEOUT', 'AGY_HARD_TIMEOUT']);
154
+ // Keys whose wrapper REFUSES the run pre-spend on an invalid explicit env value instead of falling
155
+ // back to a default. Reporting such an override as an active raw value would show a dead setting as
156
+ // live — worse here than for a timeout, because this key exists to bound SPENDING. The wrapper also
157
+ // canonicalizes a digits-only value (leading zeros stripped) before judging it, so the advisor must
158
+ // canonicalize identically or it would disagree with the run it is describing.
159
+ const ENV_REFUSING_KEYS = new Set(['AGY_REVIEW_MAX_TOTAL_BYTES']);
160
+ const canonicalizeDigits = (v) => (/^[0-9]+$/.test(v) ? String(Number(v)) : v);
154
161
  // A control byte (C0 range + DEL) in a screened knob makes the wrapper EXIT 2 pre-spend — the
155
162
  // advisor must report that refusal, never a benign fallback, and never echo the raw byte into
156
163
  // the report (a newline could otherwise forge a `review posture:`/status line). safeShow escapes
157
164
  // every note value: JSON.stringify handles C0, an explicit pass handles DEL (JSON leaves it raw).
158
165
  const hasControlByte = (s) => /[\x01-\x1f\x7f]/.test(s);
159
166
  const safeShow = (s) => JSON.stringify(s).slice(1, -1).replace(/\x7f/g, '\\u007f');
160
- const REFUSED = (key, v) => ({ value: null, source: 'default', note: `env value "${safeShow(v)}" carries control bytes for ${key} — the wrapper REFUSES the run pre-spend` });
167
+ const REFUSED = (key, v) => ({ value: null, source: 'default', configuredIn: 'env', note: `env value "${safeShow(v)}" carries control bytes for ${key} — the wrapper REFUSES the run pre-spend` });
161
168
 
162
169
  // The effective value of one knob + where it comes from. Fact-only; never a model claim.
170
+ // `source` is where the EFFECTIVE value comes from; `configuredIn` is where the operator PUT the key,
171
+ // which is NOT the same fact. An explicitly-set but INVALID or EMPTY env value resolves to the
172
+ // built-in default (`source: 'default'`) while still being an explicit line the operator wrote — and
173
+ // for a RETIRED knob that is exactly the line the retirement asks them to clear, so it must not
174
+ // vanish from the surfaces (council fold: `AGY_REVIEW_ALLOW_ADDDIR=2` and `=` both disappeared).
163
175
  export const effectiveOf = (entry, parsed, getenv) => {
164
176
  const key = entry.key;
165
177
  if (Object.prototype.hasOwnProperty.call(getenv, key)) {
@@ -169,7 +181,7 @@ export const effectiveOf = (entry, parsed, getenv) => {
169
181
  // NOT "flag absent" (only the tier's built-in default happens to be "no flag"; the timeout / bytes /
170
182
  // add-dir knobs fall to their real built-in default). Report the manifest default (null ⇒ "wrapper
171
183
  // built-in applies"), not a misleading null-for-everything.
172
- if (v === '') return { value: entry.default, source: 'default', note: 'the env KEY= suppresses the file override — the wrapper built-in applies' };
184
+ if (v === '') return { value: entry.default, source: 'default', configuredIn: 'env', note: 'the env KEY= suppresses the file override — the wrapper built-in applies' };
173
185
  // A control byte in a SCREENED knob (the tier + the resolver-validated timeouts) is a pre-spend
174
186
  // refusal in the wrapper — report it as such (the default value is inert; the run never happens).
175
187
  if (hasControlByte(v) && (entry.kind === 'enum' || RESOLVER_VALIDATED_ENV.has(key))) {
@@ -182,20 +194,27 @@ export const effectiveOf = (entry, parsed, getenv) => {
182
194
  // the advisor must never display a dead override as active. Every OTHER non-enum env value stays
183
195
  // the operator's RAW override (the wrappers run no resolver on it), shown as-is.
184
196
  if (entry.kind === 'enum' && !settingValueValid(entry, v)) {
185
- return { value: entry.default, source: 'default', note: `env value "${safeShow(v)}" is not a supported ${key} — the wrapper runs the built-in default` };
197
+ return { value: entry.default, source: 'default', configuredIn: 'env', note: `env value "${safeShow(v)}" is not a supported ${key} — the wrapper runs the built-in default` };
186
198
  }
187
199
  if (RESOLVER_VALIDATED_ENV.has(key) && (!settingValueValid(entry, v) || v.match(/^\d*/)[0].length > 7)) {
188
- return { value: entry.default, source: 'default', note: `env value "${safeShow(v)}" is invalid for ${key} — the wrapper falls back to the built-in default` };
200
+ return { value: entry.default, source: 'default', configuredIn: 'env', note: `env value "${safeShow(v)}" is invalid for ${key} — the wrapper falls back to the built-in default` };
189
201
  }
190
- return { value: v, source: 'env' };
202
+ if (ENV_REFUSING_KEYS.has(key)) {
203
+ const canonical = canonicalizeDigits(v);
204
+ if (!settingValueValid(entry, canonical)) {
205
+ return { value: entry.default, source: 'default', configuredIn: 'env', note: `env value "${safeShow(v)}" is invalid for ${key} — the wrapper REFUSES the run pre-spend rather than ignoring a ceiling you set` };
206
+ }
207
+ return { value: canonical, source: 'env', configuredIn: 'env' };
208
+ }
209
+ return { value: v, source: 'env', configuredIn: 'env' };
191
210
  }
192
211
  const fileEntries = parsed.byKey.get(key);
193
212
  if (fileEntries && fileEntries.length) {
194
213
  const v = fileEntries[fileEntries.length - 1].value; // last occurrence wins
195
- if (settingValueValid(entry, v)) return { value: v, source: 'file' };
196
- return { value: entry.default, source: 'default', note: `file value "${safeShow(v)}" is invalid — falls back to the built-in default` };
214
+ if (settingValueValid(entry, v)) return { value: v, source: 'file', configuredIn: 'file' };
215
+ return { value: entry.default, source: 'default', configuredIn: 'file', note: `file value "${safeShow(v)}" is invalid — falls back to the built-in default` };
197
216
  }
198
- return { value: entry.default, source: 'default' };
217
+ return { value: entry.default, source: 'default', configuredIn: null };
199
218
  };
200
219
 
201
220
  export const displayValue = (v) => (v == null ? '(unset — wrapper built-in applies)' : v);
@@ -224,8 +243,19 @@ export const settingsSnapshot = (ctx = {}) => {
224
243
  // ACTIVE = a non-default value is genuinely in effect (source env/file AND differs from the
225
244
  // built-in default). A value that merely equals the default keeps every surface byte-identical to
226
245
  // "nothing set" — the "status line unchanged unless a knob is active" contract.
227
- if ((eff.source === 'env' || eff.source === 'file') && eff.value != null && eff.value !== entry.default) {
228
- active.push({ key: entry.key, value: eff.value, source: eff.source, bridge: entry.bridge });
246
+ // A RETIRED knob surfaces whenever it is EXPLICITLY CONFIGURED, independently of whether its
247
+ // value differs from the default: `AGY_REVIEW_ALLOW_ADDDIR=0` is still a dead line in the
248
+ // user's file that the retirement asks them to clear, and the "retired keys still show"
249
+ // contract would be broken by silently filtering it out for equalling the default.
250
+ // A retired knob is surfaced on EXPLICIT CONFIGURATION, which is `configuredIn` — not `source`.
251
+ // An invalid or empty env value resolves to the built-in default, so keying off `source` hid the
252
+ // very lines the retirement exists to get cleared (council fold).
253
+ const worthShowing = eff.value != null && eff.value !== entry.default;
254
+ const surfaced = entry.retired
255
+ ? eff.configuredIn !== null
256
+ : (eff.source === 'env' || eff.source === 'file') && worthShowing;
257
+ if (surfaced) {
258
+ active.push({ key: entry.key, value: eff.value, source: eff.source, bridge: entry.bridge, retired: entry.retired ?? null });
229
259
  }
230
260
  }
231
261
  return {