@sabaiway/agent-workflow-kit 10.6.0 → 11.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 (62) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +7 -6
  3. package/SKILL.md +9 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +1 -1
  5. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +6 -6
  6. package/bridges/antigravity-cli-bridge/capability.json +1 -1
  7. package/bridges/codex-cli-bridge/SKILL.md +1 -1
  8. package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
  9. package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
  10. package/bridges/codex-cli-bridge/capability.json +1 -1
  11. package/capability.json +1 -1
  12. package/launchers/windsurf-workflow.md +2 -2
  13. package/package.json +1 -1
  14. package/references/contracts.md +1 -1
  15. package/references/modes/bootstrap.md +5 -5
  16. package/references/modes/commit-guard.md +2 -2
  17. package/references/modes/control-bytes.md +91 -0
  18. package/references/modes/flow-writer.md +16 -4
  19. package/references/modes/procedures.md +2 -0
  20. package/references/modes/recommendations.md +1 -0
  21. package/references/modes/review-state.md +1 -1
  22. package/references/modes/robustness-brief.md +61 -0
  23. package/references/modes/velocity.md +2 -0
  24. package/references/robustness-literals.json +107 -0
  25. package/references/scripts/archiver-structure.test.mjs +48 -3
  26. package/references/scripts/install-git-hooks.test.mjs +31 -1
  27. package/references/shared/deploy-tail.md +1 -1
  28. package/references/templates/agent_rules.md +1 -1
  29. package/references/templates/changelog.md +1 -1
  30. package/references/templates/decisions.md +2 -2
  31. package/tools/changed-surface.mjs +1 -1
  32. package/tools/commands.mjs +14 -0
  33. package/tools/commit-guard.mjs +20 -9
  34. package/tools/control-bytes.mjs +264 -0
  35. package/tools/core-evidence.mjs +33 -20
  36. package/tools/coverage-check.mjs +1 -1
  37. package/tools/dispatch-store-read.mjs +2 -2
  38. package/tools/flow-check-git-lane.mjs +2 -1
  39. package/tools/flow-delta-proof.mjs +4 -4
  40. package/tools/flow-record-shape.mjs +44 -2
  41. package/tools/flow-round-gates.mjs +151 -0
  42. package/tools/flow-store-read.mjs +1 -1
  43. package/tools/flow-writer.mjs +235 -82
  44. package/tools/fold-scope.mjs +7 -3
  45. package/tools/fs-read-nofollow.mjs +56 -4
  46. package/tools/git-env.mjs +120 -0
  47. package/tools/grounding.mjs +4 -3
  48. package/tools/plan-shape-facts.mjs +23 -10
  49. package/tools/plan-shape.mjs +8 -0
  50. package/tools/procedures.mjs +18 -4
  51. package/tools/recommendations.mjs +53 -3
  52. package/tools/repo-lex.mjs +29 -0
  53. package/tools/review-state.mjs +43 -34
  54. package/tools/robustness-brief.mjs +209 -0
  55. package/tools/robustness-literals.mjs +101 -0
  56. package/tools/sandbox-masks.mjs +2 -1
  57. package/tools/set-flow.mjs +2 -1
  58. package/tools/source-size-scope.mjs +19 -25
  59. package/tools/tracked-tree-census.mjs +3 -3
  60. package/tools/velocity-profile.mjs +5 -2
  61. package/tools/worktree-handoff-return.mjs +2 -2
  62. package/tools/worktrees.mjs +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,139 @@ 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.1.0 — the bootstrap offers enforcement on every project, and the advisor closes the loop (AD-131)
8
+
9
+ Bootstrap steps 8–9 (the enforcement scripts + the pre-commit hook) used to decide by the step-1
10
+ recon — a Python, Go or Rust project was denied the caps gate, the archivers and the hook for no
11
+ proven reason, then `upgrade` reported `skipped-no-node-evidence` on the very tree the bootstrap left
12
+ bare. The scripts are dependency-free Node the agent host already runs for step 7's navigator.
13
+ Contract: `node-evidence.md` rev 2 (S8–S10 bound).
14
+
15
+ **The offer, on every project.** `references/modes/bootstrap.md` steps 8–9 offer the scripts and the
16
+ hook preview-first with a one-line why: the bootstrap seeds on `package-json` OR
17
+ `deployed-node-scripts` OR the fact that the agent host is running the kit; the only skip condition
18
+ is that the agent host cannot run them; the residual is stated (the hook runs `node scripts/<x>.mjs`,
19
+ so a committer without `node` on PATH gets the hook's own loud failure). `deploy-tail.md` carries the
20
+ same sentence once; the Windsurf launcher's step list names the offer once and copies nothing
21
+ unconditionally; the by-hand "Non-Node projects follow the policy by hand" claims left the READMEs.
22
+ The step-1 recon is a stack fact, never the criterion.
23
+
24
+ **The advisor's `enforcement` item.** On a `docs/ai` tree whose node evidence answers `none` and
25
+ whose `scripts/` is absent or holds no basename of the kit's own bundle (listed at run time through
26
+ the probe's own `scriptsBundleRoot` seam, never a hardcoded list), Recommendations renders ONE
27
+ optional offer: the exact bootstrap tail as a `HAND-APPLY:` line with both paths shell-quoted.
28
+ Every other cell is silence or a stated skip naming the path — `package-json` /
29
+ `deployed-node-scripts` render nothing and never list the bundle; a wrong-kind path, an unreadable
30
+ probe (`<root>: <code> on <rel>`), an existing, linked or unreadable destination each skip by name;
31
+ no `docs/ai` renders nothing. The probe spawns no git; the installer's refusal over a hook it did not
32
+ write is stated in the posture note (pinned). The commit-guard item's installer-absent skip carries
33
+ its own 155-char remedy (deploy the scripts, bootstrap steps 8–9, install the hook, re-run) on ANY
34
+ evidence — never "run upgrade first".
35
+
36
+ **A declined offer leaves no false claim.** Every install claim in the family is conditional on
37
+ the yes — `installed on a yes` in the READMEs and the seeded `changelog.md` / `decisions.md`
38
+ templates, `where installed` in the seeded `agent_rules.md`, `and the enforcement offer was
39
+ accepted` in both visibility contracts — pinned by one (file, literal, count) table in
40
+ `test/enforcement-offer-docs.test.mjs`; the visible wiring of `package.json` scripts is conditional
41
+ on an existing `package.json` AND an accepted offer; on a no the docs command slots of
42
+ `env_commands.md` and `AGENTS.md` carry a note (the caps + index gate run from the agent's own skill
43
+ copy; the archive rotations need the scripts deployed) and never the skill's absolute path.
44
+
45
+ **Pins, unchanged scripts.** The deployed scripts' basename fallback (no or a malformed
46
+ `package.json` → the directory basename) was already there — `install-git-hooks.test.mjs` and
47
+ `archiver-structure.test.mjs` pin it with a package.json-named control; no script changed.
48
+
49
+ **Measured on itself (the first-pass-quality series' acceptance metric).** Executed delegated on ONE
50
+ held codex session, eight accounted dispatches, every fold through it. Diff council: round 1 codex
51
+ revise 0 blockers + 3 majors, agy SHIP, lens 0 + 2 majors; rounds 2–3 resolved the decline-path
52
+ subarea at altitude; round 4 CLEAN from all three — four rounds against the 13-round baseline
53
+ (AD-130). Three fold-induced findings, each caught by a test or gate before a receipt attested it.
54
+
55
+ ## 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)
56
+
57
+ Plan 2's two attestation rounds on CONVERGED bytes each found one member of the kit's own robustness
58
+ list that the executor never received: the tagged sweep row got its brief, the untagged suite and
59
+ generator rows did not. Nothing asked "does the tag cover the row" before the dispatch, nothing
60
+ recorded the adversarial walk a fold owes, and past the round table's `cap reached` / `crossover`
61
+ signal nothing refused the next round — on the release-run feature the table printed CROSSOVER from
62
+ round 2 for eleven rounds and nothing acted on it. Rungs 4 and 5 of the first-pass-quality series
63
+ close that, and the two fingerprint classes plan 1 measured RED (Issue-023 / Issue-024) close with
64
+ them. Contracts: `robustness-literals.md` rev 2 (S10–S11 bound), `plan-review-loop/index.md` rev 6
65
+ (S30–S33 bound), `release-run/index.md` rev 2.
66
+
67
+ **Coverage is the generator's second mode.** `robustness-brief.mjs --plan <plan> --coverage` judges
68
+ every `create`/`modify` row except a document (`.md`, `.txt`) or the shipped list itself: a class is
69
+ PRESENT when the row file's BYTES carry a member literal of kind env/flag/ref/syscall/errno/argv as a
70
+ BOUNDED token (a comment counts, `state` members are never searched), `uncovered` = present minus
71
+ tagged, a `delete` row and an absent file are LISTED, a sweep row is judged over every path the facts
72
+ leaf expands (`expandSweepPaths`, one home in `plan-shape-facts.mjs`); exit 0 covered / 1 naming each
73
+ `row:class:literal` / 2 on a row file the 1 MiB no-follow read cannot deliver. Measured on itself: the
74
+ first run over this very plan named `R01:spawn-outcome:ENOENT` and the tag grew before the next
75
+ dispatch. `references/modes/robustness-brief.md` states the mode, its read surface and the bounds.
76
+
77
+ **Every fold owes a walk, recorded.** `flow-writer internal-attestation <planId> --walk <file>` reads a
78
+ closed JSON walk (`{ listVersion, rows: [{ id, class, checked }] }`) relative to the git COMMON dir,
79
+ derives the obligation from the ADOPTED plan (every judged row, every class in TAGGED ∪ PRESENT needs a
80
+ non-empty `checked`), records `uncovered` and never refuses on it; `round-open` for every round after
81
+ the first refuses without an authoritative walk-carrying attestation at round N's coordinates, and
82
+ `--justification` lifts that refusal as an echoed INPUT whose durable trail is the attestation's
83
+ absence. A plan with no judgeable row owes no walk; a plan that lost adoption coverage fails closed
84
+ by name (`resolveAdoptedPlan`, one home).
85
+
86
+ **The cap is the round table's own signal, and terminal is digest equality.** The new pure leaf
87
+ `tools/flow-round-gates.mjs` (`blockingItems`, `roundsForSignal`, `capIssue`, `walkIssue`,
88
+ `escalationIssue`, `validateWalk`; `signalFor` imported from `review-rounds.mjs`, never modified)
89
+ builds one round per authoritative head from its LANDED dispatches; on `cap reached` or `crossover`
90
+ only, `round-open` refuses unless every blocking ITEM of every landed receipt of the latest head is
91
+ covered by a disposition whose `findingDigest` equals `sha256(itemText)` — text mode the wrapper's own
92
+ line, schema mode the `issue` field verbatim — and prints every uncovered item beside its digest. An
93
+ enumeration failure on a readable manifest falls back to the `>= 1` floor with an ADVISORY; the
94
+ checks run cap → walk → floor in the lock-free pass and again on the locked snapshot.
95
+
96
+ **A finding with no proven narrow fix is never queued; a lost manifest is a record.** `round-land
97
+ --dispose queued --finding <quote> --claim <invariant> --proof-kind consult-attestation|red-proof
98
+ --proof-digest <d>` mints only on `decideFoldScope` ACCEPT `new-invariant`, with `debtId`/`debtDigest`
99
+ DERIVED from the ONE resolved queue row `fold-scope` now returns (`normalize` exported, one home); a
100
+ consult-attestation proof binds through `proposedFixDigest === sha256(itemDigest + "\n" + claim)`, a
101
+ red-proof through the head's `base`, the tree `round-land` runs on and the row's `proof` field. The
102
+ fourth arm `escalated --finding <quote> --override-digest <d>` resolves a `maintainer-override` of THIS round's vetoing
103
+ receipt; the FIFTH arm `custody-lost --receipt <receiptDigest>` mints only on a REAL loss (`absent |
104
+ foreign | error | malformed | foreign-identity | swapped`), covers every item of that receipt for the
105
+ cap and is recordable whenever the loss is real. `flow-record-shape.mjs` admits `walk`, the queued
106
+ trio (all-or-none), `escalated` and `custody-lost`; `FLOW_SCHEMA_VERSION` does not move.
107
+
108
+ **The fingerprint argv has one home.** `core-evidence.mjs` exports `FINGERPRINT_CACHED_DIFF_ARGV` /
109
+ `FINGERPRINT_UNSTAGED_DIFF_ARGV` carrying `--no-ext-diff --no-textconv --ignore-submodules=none`;
110
+ `flow-delta-proof.mjs` imports them; both wrappers' bash twins (`emit_fingerprint_payload`,
111
+ `assemble_code_diff`, the no-diff preflight) carry the flags, mirrored into `bridges/`, and
112
+ `test/review-fingerprint-parity.test.mjs` EXTRACTS the preflight line and proves it on real git with a
113
+ gitlink under `diff.ignoreSubmodules=all`. Issue-023 and Issue-024 close; `commit-guard`'s
114
+ content-free refusal 2 narrows to a fail-closed backstop with a generic message that names no flag
115
+ and no config key (`references/modes/commit-guard.md`).
116
+
117
+ **Tests and records.** New suite `flow-round-gates.test.mjs`; the touched suites (`robustness-brief`,
118
+ `flow-record`, `flow-writer`, `flow-dogfood.integration`, `core-evidence-hostile`,
119
+ `commit-guard-content-free`, `review-fingerprint-parity`, `package-content` — the tarball pin 284 →
120
+ 285 — and `fold-scope`) green with real git; thirteen red-proof cases (34 `core-evidence red-proof`
121
+ re-observations on the parked pre-fix trees); the source-size baseline re-recorded under one stated reason. Every fold of the three diff
122
+ rounds rode the delegate's held session (AD-127): fourteen accounted dispatches, one session.
123
+
124
+ ### BREAKING
125
+
126
+ - **Minimum bridge versions: codex-cli-bridge 3.7.0, antigravity-cli-bridge 5.6.0.** The fingerprint
127
+ domain moves on a repository configuring `textconv` (neutralised by `--no-textconv`) or
128
+ `diff.ignoreSubmodules` / `submodule.<name>.ignore` in `.git/config` or `.gitmodules` (both overridden
129
+ by `--ignore-submodules=none`): a kit-11 receipt and a pre-3.7.0 / pre-5.6.0 wrapper receipt hash the
130
+ same tree differently. `init` and `upgrade`
131
+ refresh a placed bridge (`bin/install.mjs`); a bridge never placed is never placed by them.
132
+ - **`round-land --dispose queued` changes its operands:** `--debt-id` / `--debt-digest` are gone;
133
+ `--claim`, `--proof-kind` and `--proof-digest` are required together with `--finding`.
134
+ - **`round-open` refuses where `--justification` used to lift:** past the round table's signal the
135
+ cap is never lifted by a justification; a missing walk is.
136
+ - **The one-way door:** a flow store carrying any post-rung record form — an `internal-attestation`
137
+ with `walk`, a `queued` disposition with `claim`/`proofKind`/`proofDigest`, an `escalated` or a
138
+ `custody-lost` disposition — reads under kit >= 11 only. A pre-rung store still reads.
139
+
7
140
  ## 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
141
 
9
142
  The 2026-08-30 `release-run` execution took thirteen diff-review rounds, and every fold was made by
package/README.md CHANGED
@@ -117,12 +117,12 @@ your-repo/
117
117
  ├── tech_reference.md ← configs & patterns
118
118
  ├── pages/ ← one spec per page/route
119
119
  └── history/ ← changelog archive (HOT→WARM→COLD)
120
- + scripts/ ← caps · index · archive (Node path)
121
- + pre-commit hook ← keeps it all honest (Node path)
120
+ + scripts/ ← caps · index · archive (installed on a yes)
121
+ + pre-commit hook ← keeps it all honest (installed on a yes)
122
122
  ```
123
123
 
124
124
  The Markdown memory is **stack-agnostic**; the `scripts/` + pre-commit hook are the **Node path**
125
- (dependency-free, `node --test`). Non-Node projects keep the same policy by hand.
125
+ (dependency-free, `node --test`). They are offered on every project because the agent host runs them; a committer without Node on PATH gets a loud hook failure.
126
126
 
127
127
  Two **visibility** modes, chosen at deploy time: **visible** (committed with the repo) or **hidden**
128
128
  (same files in-tree but git-ignored via the project-local `.git/info/exclude`, so the repo "looks
@@ -225,7 +225,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
225
225
 
226
226
  | Command | When | What happens |
227
227
  |---------|------|--------------|
228
- | `/agent-workflow-kit` | new / empty project | recon → **asks visible-or-hidden** + **conversational language** + **agent attribution** (default off) → deploys `AGENTS.md` + `docs/ai/` filled with real recon data → installs enforcement → **asks before committing** |
228
+ | `/agent-workflow-kit` | new / empty project | recon → **asks visible-or-hidden** + **conversational language** + **agent attribution** (default off) → deploys `AGENTS.md` + `docs/ai/` filled with real recon data → offers enforcement (installed on a yes) → **asks before committing** |
229
229
  | `/agent-workflow-kit upgrade` | existing deployment | reads `docs/ai/.workflow-version`, shows the changelog diff, preserves your authored memory, applies migrations, re-stamps — then prints a **read-only** one-line backend-status line (what's set up vs missing); refreshes the already-placed bridges from the kit's bundled copies (never installs a new one — set one up with `/agent-workflow-kit setup`) |
230
230
  | `/agent-workflow-kit help` | any time | **read-only command index** — every command, grouped (Inspect / Configure / Orchestrate / Lifecycle) and tagged read-only / writer / guarded. The discoverable entry point, and where any unrecognized invocation lands (always read-only — a garbage invocation never writes). Never writes, never commits, never runs a subscription CLI. |
231
231
  | `/agent-workflow-kit backends` | any time | **read-only** check of the optional execution-backends (the `codex` / `agy` bridges): what's set up vs missing and the next step. Never writes, never commits, never runs a subscription CLI (credentials = marker-file presence, not a live login). |
@@ -270,7 +270,7 @@ It **never auto-commits** and **never overwrites** an existing `AGENTS.md` witho
270
270
  - **Plan lifecycle** — a capped plan (goal and boundary, module ledger, verification), ephemeral plan files, and a mandatory Cleanup phase.
271
271
  - **No silent failures** — every guard that rejects an action logs structured context.
272
272
 
273
- Enforcement ships as dependency-free **Node** scripts (`node --test`, no package manager assumed). Non-Node projects follow the same policy by hand.
273
+ The dependency-free **Node** enforcement scripts (`node --test`, no package manager assumed) and their hook are offered on every project because the agent host runs them; a committer without Node on PATH gets a loud hook failure.
274
274
 
275
275
  ---
276
276
 
@@ -284,7 +284,8 @@ agent-workflow-kit — the composition root (installed via npx … init)
284
284
  on /agent-workflow-kit in a repo, the kit:
285
285
  ├─ delegates ─▶ memory substrate (healthy copy, else bundled fallback)
286
286
  ├─ injects ─▶ workflow methodology (live from the installed engine)
287
- ├─ deploys ─▶ AGENTS.md + docs/ai/ + Node scripts + pre-commit hook
287
+ ├─ deploys ─▶ AGENTS.md + docs/ai/
288
+ │ + Node scripts + pre-commit hook (installed on a yes)
288
289
  ├─ detects ─▶ optional backends (codex / agy, read-only)
289
290
  └─ sets up ─▶ a bridge (opt-in) (place skill + link wrappers)
290
291
  ```
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.1.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.1.0",
7
7
  "provides": [],
8
8
  "roles": {},
9
9
  "detect": {
@@ -26,8 +26,8 @@ reinvent any of it here; the kit's `SKILL.md` is the single source of truth.
26
26
  attribution) as ONE structured multi-question prompt; record each answer individually,
27
27
  write nothing until ALL are answered** →
28
28
  create `AGENTS.md` (+ `CLAUDE.md` symlink) from `<KIT_DIR>/references/templates/` →
29
- deploy `docs/ai/` → copy `<KIT_DIR>/references/scripts/*.mjs` (Node projects) →
30
- wire/hide per visibility → install the pre-commit hook → stamp `docs/ai/.workflow-version`.
29
+ deploy `docs/ai/` → offer the enforcement scripts + pre-commit hook (on a yes: copy `<KIT_DIR>/references/scripts/*.mjs`, install the hook) →
30
+ wire/hide per visibility → stamp `docs/ai/.workflow-version`.
31
31
  4. Report what was filled with real data vs left as TODO, then **ask before committing** —
32
32
  never auto-commit.
33
33
 
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.1.0",
4
4
  "description": "Portable, cross-agent memory & workflow for AI coding agents — Claude Code, Codex, Cursor, Devin Desktop. One command deploys an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement into any repo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -17,7 +17,7 @@ The user chooses at bootstrap whether the AI artifacts are visible in the repo o
17
17
  **explicit up-front question** (bootstrap step 2), never an assumed default. The two modes then
18
18
  diverge:
19
19
 
20
- - **visible** — artifacts are committed. Wire the project's `package.json` scripts (`docs:check` / `docs:index` / `docs:index:check` / `docs:archive` / `docs:archive:check` / `docs:archive:issues` / `docs:archive:issues:check` / `prepare: node scripts/install-git-hooks.mjs`) and add a minimal `.gitignore` (`docs/plans/`, `.claude/settings.local.json`). This is the canonical model.
20
+ - **visible** — artifacts are committed. When a `package.json` exists and the enforcement offer was accepted, wire the project's `package.json` scripts (`docs:check` / `docs:index` / `docs:index:check` / `docs:archive` / `docs:archive:check` / `docs:archive:issues` / `docs:archive:issues:check` / `prepare: node scripts/install-git-hooks.mjs`); add a minimal `.gitignore` (`docs/plans/`, `.claude/settings.local.json`) either way. This is the canonical model.
21
21
  - **hidden** (in-tree) — same files on disk, but the repo "looks normal": the AI/agent footprint is git-ignored via **one managed block in the project-local `.git/info/exclude`** (resolved with `git rev-parse --git-path info/exclude` — never the machine-global `core.excludesFile`, which would affect every repo on the host; **AD-014** amends **AD-006**). The kit's `tools/hide-footprint.mjs` is the single writer: it covers `KIT_OWN_PATHS ∪ the present subset of KNOWN_FOOTPRINT` (the full footprint table below — the kit's own artifacts **and** every other AI/agent tool's files). Per path: a **tracked** file → **ASK** (an exclude does nothing for it; only `git rm --cached` un-tracks it — the tool prints that command and never runs it); an untracked path already covered by a **tracked `.gitignore`** → dropped (redundant); a **present** file whose name is generic enough to be ambiguous (`falsePositiveRisk`) → **ASK**; everything else → **hidden**. `asks` are excluded from the block unless explicitly opted in. **Verify** treats a path as hidden only when it is **untracked AND ignored by our project-local block** (or a tracked `.gitignore`) — being ignored by the global excludes does **not** count. Re-running re-derives the block wholesale (sorted/deduped) → a clean re-run is a **zero-diff** no-op. On an existing global-excludes deployment the tool **detects + reports the residual legacy machine-global block and keeps it by default** (a harmless double-ignore; the local block wins precedence); removal is the explicit `--remove-global` (it prints the removed lines as a restorable backup), which **the agent only runs after asking** — another of the user's hidden repos on the same host may rely on the same root-anchored global lines. **Do not edit `package.json`** — that is a tracked change and would leak; the pre-commit hook (always untracked in `.git/hooks/`) calls the scripts via `node scripts/<x>.mjs` directly. Windows is supported (text edit, no symlinks; CRLF preserved).
22
22
 
23
23
  **Visibility changes what git sees — never how the agent works (the load-bearing invariant).** Both modes carry the **same** working memory and run the **same** session protocol: `docs/ai` and the deployed workflow artifacts (`AGENTS.md`, the added `scripts/`) are read at session start and maintained on task completion **identically** whether visible or hidden. (Hidden mode git-ignores a *wider* footprint — other AI/agent tools' files too — but that is only a **visibility** surface; the working memory the agent actually reads and maintains is `docs/ai` plus those artifacts.) Hidden mode only makes the repo *look* normal to git; it is **never** a license to skip, defer, or down-prioritise a doc/state update. The single difference is the destination: in hidden mode those updates live on disk and never enter a commit (the footprint is excluded), so a commit there carries only the project's own tracked files while `docs/ai` is still kept current locally — **git-ignored ≠ optional-to-maintain**. An agent that reads "git-ignored / `git status` clean" and infers the working memory is optional has the model backwards. (This is why bootstrap/upgrade never wire hidden-mode docs into a commit, yet the session protocol still mandates maintaining them.)
@@ -1,6 +1,6 @@
1
1
  ### Mode: bootstrap
2
2
 
3
- <!-- opt-in-capability: none — the deployment action itself; the advisor only renders inside an existing deployment -->
3
+ <!-- opt-in-capability: enforcement -->
4
4
 
5
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
6
6
 
@@ -23,9 +23,9 @@ Requires: ${CLAUDE_SKILL_DIR}/references/shared/report-footer.md · ${CLAUDE_SKI
23
23
  5. **Entry-point doc.** If `AGENTS.md` / `CLAUDE.md` already exist (step-1 recon), do **not** overwrite — show the user and ask whether to merge or replace. Otherwise create `AGENTS.md` (the cross-agent standard — Codex / Cursor / Devin Desktop / Copilot read it natively) from `${CLAUDE_SKILL_DIR}/references/templates/AGENTS.md`, and symlink `CLAUDE.md -> AGENTS.md` (`ln -s AGENTS.md CLAUDE.md`) for Claude Code — single source, no duplication. For nested context, add a subdir `AGENTS.md` (+ a `CLAUDE.md` symlink beside it for Claude Code).
24
24
  6. **Deploy `docs/ai/`.** Create every `docs/ai/` file + `pages/` + the seed `adr/` store + the seed `specs/` store from `${CLAUDE_SKILL_DIR}/references/templates/` (the template loop deploys every template except `AGENTS.md`, `adr-record.md` and `SPEC_TEMPLATE.md` — the latter two are skill-home authoring references, never deployed). This deploys the HOT ADR window **`docs/ai/decisions.md`** and the seed navigator **`docs/ai/adr/log.md`** (its `adr/` subdir copies in wholesale), the spec store root **`docs/ai/specs/index.md`** (its `specs/` subdir copies in wholesale), the `.md` docs, **and** the three seeded, user-editable strict-JSON configs: **`docs/ai/orchestration.json`** (the per-project recipe defaults the `procedures` advisor reads), **`docs/ai/gates.json`** (the project's gate declaration — seeded as the no-consent EMPTY list; FILLING it is the consented `gates-init` preview, `${CLAUDE_SKILL_DIR}/references/modes/gates.md`), and **`docs/ai/autonomy.json`** (the per-project autonomy policy — seeded SPARSE with the onboarding note only, so it is exactly the computed defaults until the user declares levels via `/agent-workflow-kit set-autonomy` or by hand). Keep each `.md` file's frontmatter (`type / lastUpdated / scope / staleAfter / owner / maxLines`); the `.json` seeds carry no frontmatter (the docs cap-validator globs `*.md` only, so they are inherently skipped).
25
25
  7. **Fill templates** per the table below. **Then materialize the navigator, as the last `docs/ai` step:** the entry point declares `docs/ai/index.md` always-loaded and it is a GENERATED artifact with no template, so run `node ${CLAUDE_SKILL_DIR}/references/scripts/check-docs-size.mjs --ensure-index --root=<project>` and relay its one outcome line (`ensure-index: regenerated` / `already-current`, or a `write-refused` / `probe-failed` line naming the offending path — either is a STOP: resolve it before reporting the deploy, since the navigator the entry point declares does not exist yet). The generator runs from **this skill's own directory**, so a project with **no Node runtime** is covered too: the agent host runs it, and step 8's copy is not a precondition.
26
- 8. **Install enforcement (Node projects).** Copy `${CLAUDE_SKILL_DIR}/references/scripts/*.mjs` (+ `*.test.mjs`) into the project's `scripts/`. They self-configure (project name from `package.json`, hierarchical/on-demand sections auto-discovered). **If the project has no Node runtime** (step-1 recon), skip this step and the hook in step 9 — follow the cap/archive/index policy manually, or port the scripts to the project's language.
27
- 9. **Wire / hide** per visibility (see [Visibility contract](${CLAUDE_SKILL_DIR}/references/contracts.md#visibility-contract)). Install the pre-commit hook (Node projects): `node scripts/install-git-hooks.mjs`. If the installer reports a pre-existing non-marker hook, stop and ask the user to merge it manually rather than overwriting. Once the gate declaration is final-run-capable, the installer's optional consented `--commit-guard <kit commit-guard.mjs path>` arm binds commits to the green `run-gates --final` receipt (the Recommendations advisor surfaces the exact one-liner).
28
- - **visible** — wire the `package.json` scripts + add the minimal `.gitignore`. **Do not run the hide tool.**
26
+ 8. **Offer enforcement on every project, preview-first.** Explain in one line that the dependency-free Node scripts are available because the agent host already runs their step-7 navigator; the only skip condition is that the agent host cannot run them. Preview the source and destination list and ask; on a yes copy `${CLAUDE_SKILL_DIR}/references/scripts/*.mjs` (+ `*.test.mjs`) into the project's `scripts/`. On a no, skip the hook in step 9 and say so; the docs command slots then name no project-local command — fill `env_commands.md` and the `Docs caps + index freshness` row of `AGENTS.md` with the same note: the caps + index gate run from the agent's own skill copy (the step-7 `--root=<project>` form) and the archive rotations need the scripts deployed. Never write the skill's absolute path into a deployed doc. They self-configure (project name from `package.json` or, without one, the directory basename; hierarchical/on-demand sections auto-discovered). The hook calls `node scripts/<x>.mjs` directly, so a committer without `node` on PATH gets the hook's own loud failure.
27
+ 9. **Wire / hide** per visibility (see [Visibility contract](${CLAUDE_SKILL_DIR}/references/contracts.md#visibility-contract)). When the user accepts the enforcement offer, install the pre-commit hook: `node scripts/install-git-hooks.mjs`. If the installer reports a pre-existing non-marker hook, stop and ask the user to merge it manually rather than overwriting. Once the gate declaration is final-run-capable, the installer's optional consented `--commit-guard <kit commit-guard.mjs path>` arm binds commits to the green `run-gates --final` receipt (the Recommendations advisor surfaces the exact one-liner).
28
+ - **visible** — when a `package.json` exists and the offer was accepted, wire its scripts (never create one for this); add the minimal `.gitignore` either way. **Do not run the hide tool.**
29
29
  - **hidden** — run the kit's hide writer (one managed block in the **project-local** `.git/info/exclude`, covering the kit's own artifacts **and** every known AI/agent tool's footprint — never the machine-global excludes): `node ${CLAUDE_SKILL_DIR}/tools/hide-footprint.mjs --dir <project> --dry-run` **first** (changes nothing, prints the plan), then the same without `--dry-run`. Handle what it surfaces, in plain language (never the tool's internal terms):
30
30
  - A path it reports as **already committed** can't be hidden by ignoring it and is **never un-tracked silently** — show the user the printed `git rm --cached <path>`, let them decide, then opt it in with `--include=<path>`.
31
31
  - A **present file with a generic name** it flags (e.g. `GEMINI.md`) → ask before `--include=<path>`.
@@ -58,5 +58,5 @@ Fill strategy:
58
58
  | `tech_reference.md` | Carry over real configs/patterns found in deps. |
59
59
  | `active_plan.md`, `handover.md` | TODO seed (e.g. "Bootstrap session — fill domain sections after first real work"). |
60
60
  | `decisions.md` | The HOT ADR window — seed `AD-001` (adopting this memory system), newest at the bottom. |
61
- | `adr/log.md` | The seed navigator — deployed as-is (the generator over the seeded HOT); regenerated by `archive-decisions.mjs`, never hand-edited. |
61
+ | `adr/log.md` | The seed navigator — deployed as-is (the generator over the seeded HOT); regenerated by `archive-decisions.mjs` where the enforcement scripts are deployed (installed on a yes), otherwise it stays the seed — never hand-edited either way. |
62
62
  | `known_issues.md`, `changelog.md`, `pages/shared-patterns.md` | Empty template / first bootstrap entry. |
@@ -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.