create-agent-rig 0.4.0 → 0.6.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 (118) hide show
  1. package/CHANGELOG.md +316 -11
  2. package/README.md +58 -19
  3. package/package.json +2 -1
  4. package/packages/cli/dist/commands/create.js +8 -3
  5. package/packages/cli/dist/commands/init.js +85 -34
  6. package/packages/cli/dist/commands/upgrade.js +112 -30
  7. package/packages/cli/dist/index.js +81 -19
  8. package/packages/cli/dist/lib/copy-tree.js +35 -6
  9. package/packages/cli/dist/lib/init-settings.js +12 -0
  10. package/packages/cli/dist/lib/install-set.js +6 -8
  11. package/packages/cli/dist/lib/manifest.js +31 -9
  12. package/packages/cli/dist/lib/safe-path.js +30 -0
  13. package/scripts/prepare.mjs +1 -1
  14. package/templates/agent-os/init/AGENTS.md +199 -0
  15. package/templates/agent-os/init/CLAUDE.md +69 -9
  16. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +105 -0
  17. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +117 -0
  18. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +41 -3
  19. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +1 -1
  20. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +41 -0
  21. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +4 -0
  22. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +29 -0
  23. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +236 -0
  24. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +958 -0
  25. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +102 -0
  26. package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.mjs +78 -0
  27. package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
  28. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +396 -0
  29. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +73 -0
  30. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +40 -1
  31. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +71 -1
  32. package/templates/agent-os/universal/.claude/agents/security-scanner.md +40 -0
  33. package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +301 -26
  34. package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +43 -5
  35. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +25 -11
  36. package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +127 -0
  37. package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +180 -0
  38. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +32 -13
  39. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +175 -7
  40. package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +503 -0
  41. package/templates/agent-os/universal/.claude/rules/autonomy.md +69 -1
  42. package/templates/agent-os/universal/.claude/rules/invariants.md +104 -19
  43. package/templates/agent-os/universal/.claude/rules/workflow.md +55 -5
  44. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1260 -0
  45. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +65 -2
  46. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +351 -0
  47. package/templates/agent-os/universal/.claude/scripts/git-env.mjs +49 -0
  48. package/templates/agent-os/universal/.claude/scripts/lib/gate-coverage.mjs +306 -0
  49. package/templates/agent-os/universal/.claude/scripts/lib/revalidation-points.mjs +28 -0
  50. package/templates/agent-os/universal/.claude/scripts/lib/secrets.mjs +490 -0
  51. package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +462 -0
  52. package/templates/agent-os/universal/.claude/scripts/preflight.mjs +33 -17
  53. package/templates/agent-os/universal/.claude/scripts/queue/as-of.mjs +51 -0
  54. package/templates/agent-os/universal/.claude/scripts/queue/checkout.mjs +149 -0
  55. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +809 -28
  56. package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +160 -0
  57. package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +95 -16
  58. package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +543 -15
  59. package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +457 -47
  60. package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +289 -25
  61. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +197 -0
  62. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +316 -0
  63. package/templates/agent-os/universal/.claude/scripts/revalidation-report.mjs +180 -0
  64. package/templates/agent-os/universal/.claude/scripts/run-journal.mjs +435 -0
  65. package/templates/agent-os/universal/.claude/scripts/run-state.mjs +539 -0
  66. package/templates/agent-os/universal/.claude/scripts/stop-flag.mjs +15 -8
  67. package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +239 -0
  68. package/templates/agent-os/universal/.claude/scripts/verdict.mjs +198 -0
  69. package/templates/agent-os/universal/.claude/settings.json +11 -2
  70. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +118 -7
  71. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +667 -38
  72. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +359 -26
  73. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +4 -0
  74. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +4 -0
  75. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +4 -0
  76. package/templates/agent-os/universal/.codex/agents/test-writer.toml +4 -0
  77. package/templates/agent-os/universal/.codex/hooks.json +70 -0
  78. package/templates/agent-os/universal/AGENTS.md +166 -0
  79. package/templates/agent-os/universal/CLAUDE.md +49 -14
  80. package/templates/agent-os/universal/PLAN.md +7 -40
  81. package/templates/agent-os/universal/docs/decisions/closing-a-task.md +98 -0
  82. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +108 -0
  83. package/templates/agent-os/universal/docs/decisions/fail-open-guards.md +43 -0
  84. package/templates/agent-os/universal/docs/decisions/gate-coverage.md +83 -0
  85. package/templates/agent-os/universal/docs/decisions/review-lanes.md +72 -0
  86. package/templates/agent-os/universal/docs/decisions/run-directory.md +44 -0
  87. package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +125 -0
  88. package/templates/agent-os/universal/docs/decisions/stop-conditions-in-a-file.md +46 -0
  89. package/templates/agent-os/universal/docs/decisions/two-empty-endings.md +74 -0
  90. package/templates/agent-os/universal/journal/README.md +101 -0
  91. package/templates/agent-os/universal/layers.json +45 -2
  92. package/templates/hash-history.json +310 -49
  93. package/templates/release-ledger.json +9 -0
  94. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +34 -1
  95. package/templates/skeleton/aws-serverless/README.md +91 -9
  96. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +9 -2
  97. package/templates/skeleton/aws-serverless/gitignore +37 -0
  98. package/templates/skeleton/aws-serverless/infra/bin/app.ts +73 -13
  99. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +136 -4
  100. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +14 -1
  101. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +301 -0
  102. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +137 -0
  103. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +36 -20
  104. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +15 -12
  105. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +33 -6
  106. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +18 -5
  107. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +11 -0
  108. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +120 -1
  109. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +72 -1
  110. package/templates/skeleton/node-service/README.md +11 -1
  111. package/templates/skeleton/node-service/gitignore +34 -0
  112. package/templates/skeleton/node-service/packages/db/src/note-store.ts +47 -10
  113. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +20 -0
  114. package/templates/skeleton/node-service/services/api/src/main.ts +2 -9
  115. package/templates/skeleton/node-service/services/api/src/server.ts +93 -10
  116. package/templates/skeleton/node-service/services/api/src/static-dir.ts +20 -0
  117. package/templates/skeleton/node-service/services/api/test/server.test.ts +98 -13
  118. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +28 -0
@@ -11,35 +11,331 @@ blockers.
11
11
 
12
12
  ## Steps
13
13
 
14
- 1. **The diff first.** Establish what is actually shipping: fetch, then diff
14
+ 0. **Count this round before you spend on it.** Run it on the branch **under
15
+ review** — if the PR is not checked out, do that first (step 2's warning covers
16
+ why); on a detached checkout the command refuses rather than counting under
17
+ `HEAD`:
18
+
19
+ ```sh
20
+ node .claude/scripts/queue/index.mjs gate-round --branch "$(git rev-parse --abbrev-ref HEAD)"
21
+ ```
22
+
23
+ **Read the exit code, not just its sign.**
24
+
25
+ - **0** — proceed to step 1.
26
+ - **2** — the rounds are spent. Return `HOLD` with one blocker whose `rule`
27
+ is *gate rounds exhausted*, quoting the round count in its `note`. Do not
28
+ run the fan-out.
29
+ - **1** — the command itself failed (unreadable config, unreadable counter,
30
+ detached checkout), **or the checkout cannot ship**: a dirty working tree,
31
+ a branch with no upstream, or commits the upstream has not seen. Nothing
32
+ was counted. This is **not** an exhausted cap: fix the cause — commit and
33
+ push — and run step 0 again. Treating it as exhaustion escalates a healthy
34
+ item. A rig with no remote can never pass this step — by design: the PR
35
+ flow, and this gate with it, begins when the project has a remote
36
+ (`workflow.md`, "PR flow"). The refusal exists because two rounds were once counted ahead of a
37
+ commit pre-commit then refused, so the counter and the fan-out's verdicts
38
+ named a head that never shipped (AR-141) — pinned in the generator's
39
+ `test/template/gate-rounds.test.ts` — absent in a generated rig — ›
40
+ "refuses to count a round on a dirty tree, and counts nothing".
41
+
42
+ The cap is **2 by default**, and no shipped `.claude/queue.json` carries the key
43
+ — the default lives in `core.mjs` as `DEFAULT_MAX_GATE_ROUNDS`. A project that
44
+ wants a different cap sets `options.maxGateRounds` there, which in a rig whose
45
+ `queue.json` is composed means changing what composes it, not editing the file.
46
+ Rounds are counted per branch in `.claude/gate-rounds.json`, so the count outlives
47
+ the session that spent them.
48
+
49
+ ⚠ **Nothing forces this call.** No hook launches the gate, so step 0 holds
50
+ because it is written here — the same standing as every other step. What it
51
+ removes is the honest failure mode, a run that keeps re-reviewing because no
52
+ check ever went red; it does not stop a session that skips it.
53
+
54
+ 1. **The diff first — and the branch's own premises next.** Establish what is actually shipping: fetch, then diff
15
55
  against the **remote** default branch (`origin/<default>`), not a local
16
56
  copy that may be behind — diagnosing from stale local code produces
17
57
  confidently-wrong reviews. Everything below is scoped to this diff.
18
- 2. **The project's own checks.** Run the full check suite the project defines
58
+
59
+ Then, on the fetched ref, ask whether the branch is still the branch the run
60
+ took up (AR-134):
61
+
62
+ ```sh
63
+ node .claude/scripts/revalidate.mjs --point BEFORE_PR --ticket <item-id> --base origin/<default>
64
+ ```
65
+
66
+ It compares two sources and names each one that moved: the item's `updatedAt`
67
+ against the take-up snapshot `next` recorded (`task:updatedAt`), and what the
68
+ default branch changed since this branch forked, on the paths the branch
69
+ touches or a `check-premises` record in this run cited (`main:<path>`). It
70
+ journals one `revalidation` event at `point: BEFORE_PR`; **exit code 2 is a HOLD**, with one blocker per named source: re-read the item, or the default
71
+ branch on that path, record what the re-read concluded —
72
+ `node .claude/scripts/revalidate.mjs outcome --point BEFORE_PR --ticket <item-id> --action-changed <true | false> --note '…'`
73
+ — and come back through step 0. A hold with no outcome is counted by the
74
+ report as a re-read the run skipped. Exit 0 with
75
+ `unverifiable` means the task side could not be compared — no take-up
76
+ snapshot in this run, or no marker — and is stated in the evidence, not read
77
+ as a pass. Exit 1 is the command refusing (unknown point, no ticket, a base
78
+ that is not a revision): fix the call. Its limits are its own header's; the
79
+ cited-path set is a labelled assumption, not a recorded fact.
80
+ 2. **Route the diff before you spend on it.** This gate always ran its most
81
+ expensive path, so a typo fix in a README bought the same fan-out as a
82
+ rewrite of the storage layer. The dispatcher decides which lane the change
83
+ earns, in ascending order of cost:
84
+
85
+ ```
86
+ `deterministic` → `fast-path` → `model`
87
+ ```
88
+
89
+ ```sh
90
+ node .claude/scripts/decision-router.mjs --base origin/<default> --json
91
+ ```
92
+
93
+ **Pass the same base step 1 resolved.** The default is `origin/HEAD`, a ref
94
+ `git clone` sets and `git init` + `git remote add` does not — and a base that
95
+ is merely *different* rather than missing does not fail at all: the router
96
+ routes a narrower file set than the one this gate reviews, and the narrower
97
+ set is the one that can lose a risk flag.
98
+
99
+ ⚠ **It routes the committed diff, `<base>...<head>` — never the working
100
+ tree.** An uncommitted edit is invisible to it, including reviewer fixes you
101
+ have applied but not committed, so commit before routing. If this gate was
102
+ invoked on a PR that is not checked out, check it out first.
103
+
104
+ `decision-router` reads the changed paths and returns the lane plus the
105
+ reviewers that lane requires. **Risk flags escalate ahead of all three** — a
106
+ file under a declared elevated path, a dependency manifest or a path naming
107
+ auth, secrets, tokens, sessions or permissions, a deleted test (including the
108
+ deletion half of a rename) — and any one of them lands the change in `model`
109
+ however cheap it otherwise looked. The elevated-path flag has one carve-out,
110
+ inherited from the gate sweep rather than invented here: `.md`/`.mdx` files
111
+ and test paths that provision nothing are **inert**, so `infra/README.md`
112
+ does not escalate while `infra/stack.ts` does. Note the mechanism is those
113
+ two extensions and test paths — **not** this router's own notion of prose,
114
+ which is `.md`/`.txt`. Neither set contains the other, and both differences
115
+ are deliberate: aligning the sweep to the router takes `requirements.txt` in
116
+ an elevated directory out of escalation, and aligning the router to the
117
+ sweep puts executable `.mdx` back on the prose lane. A rulebook file is
118
+ never inert.
119
+
120
+ 🔴 **The lane is on stdout; the exit code says only that the router ran.**
121
+ Never chain it on `&&`, and never read `0` as "cheap" — that misreading turns
122
+ this gate into a rubber stamp. **Exit 1 is not a lane**: it means nothing was
123
+ routed — an unreadable diff, an empty file list, a project declaring no
124
+ elevated path, an unrecognised flag, a base or head that is not a revision,
125
+ or a run directory that is not there. Treat it as `model` and fix the cause;
126
+ it is never a reason to skip the gate.
127
+
128
+ 🔴 **One rule covers every outcome: read STDOUT.** If a lane printed, that is
129
+ the answer; if stdout is empty, treat the change as `model`. Do **not** key
130
+ on the `run journal:` prefix — both journal failures wear it and they end
131
+ differently. A trace that can no longer accept records ends the *trace*, not
132
+ the routing, so the lane still prints and the exit stays 0 (start the next
133
+ run in a new run directory). A run directory that was never there exits 1
134
+ with nothing routed.
135
+
136
+ What each lane buys:
137
+
138
+ - `deterministic` — every changed file is a derived artifact, git says it was
139
+ `modified` or `removed`, and **none of them sits under a declared elevated
140
+ path**. The lane's floor is empty; step 4's triggers still apply on top.
141
+ - `fast-path` — documentation outside the rulebook, plus any derived file
142
+ travelling with it under those same two rules. `prose-reviewer` is the
143
+ floor.
144
+ - `model` — everything else, and `code-reviewer` runs on it **always**,
145
+ with the triggers in step 4 beside it. Anything the router cannot classify
146
+ lands here.
147
+
148
+ 🔴 **State what the cheap lanes give up, because they do give something up.**
149
+ Dropping `code-reviewer` drops two of its checklist items that are *not*
150
+ about code — contract drift, and "contradicts the item it claims to
151
+ implement". Neither is decidable from paths. So the cheap lanes carry the
152
+ item text to whatever cold reader they do launch (step 4).
153
+
154
+ The `deterministic` lane launches none, and that rests on one claim: a file
155
+ is generator output, so a check already catches its drift. The claim needs a
156
+ prior output to have drifted **from** — which is why an added, copied,
157
+ renamed or status-less entry is refused the lane, and why a **test snapshot
158
+ is not a derived artifact here at all**. A snapshot is the behaviour claim,
159
+ rewritten by the run that then passes by construction; routing one to a lane
160
+ with no reviewer would be weakening a test with a dispatcher.
161
+
162
+ If you disagree with a lane, run the expensive one — never argue a diff
163
+ downward.
164
+ 3. **The project's own checks.** Run the full check suite the project defines
19
165
  (see its README / package scripts). Any failure is an instant HOLD — never
20
166
  argue with a red check, never rerun flakiness to green
21
167
  (`.claude/rules/workflow.md`).
22
- 3. **Reviewer fan-out.** Launch the `code-reviewer` agent on the diff — always,
23
- and **pass it the text of the queue item this branch implements**. Its
24
- checklist blocks on a change that contradicts its item, and a reviewer given
25
- only a diff cannot run that check: a cold context has no way to know what was
26
- asked, and reconstructing it from the PR description would mean trusting the
27
- run under review. If there is no item — owner-directed work, a hotfix — say
28
- so when launching, and the reviewer skips that item openly instead of
29
- guessing at it.
30
- Launch `security-scanner` as well when the diff touches its triggers: auth,
31
- secrets or configuration, input parsing, file handling, new outbound calls,
32
- dependency changes. Launch `prose-reviewer` when the diff touches a rule
33
- file, a skill, an agent spec, `CLAUDE.md` or the README — a rulebook that
34
- overstates its own enforcement fails silently and in the direction of false
35
- confidence. Run them as subagents, in parallel — a fresh context
36
- reviews better than the session that wrote the code (see
37
- `.claude/rules/workflow.md`, "Review-context isolation").
38
- 4. **DoD walk.** Check the Definition of Done list in
168
+ 4. **Reviewer fan-out.** The lane from step 2 sets the **floor**:
169
+
170
+ - `model` → launch the `code-reviewer` agent on the diff, always;
171
+ - `fast-path` → launch `prose-reviewer`;
172
+ - `deterministic` → the lane's floor is empty. The triggers below still
173
+ apply: a floor of zero is not permission to skip one.
174
+
175
+ **Whatever you launch, pass it the text of the queue item this branch
176
+ implements.** A reviewer given only a diff cannot check the change against
177
+ what was asked: a cold context has no way to know, and reconstructing it from
178
+ the PR description would mean trusting the run under review. If there is no
179
+ item — owner-directed work, a hotfix — say so when launching, and the
180
+ reviewer skips that check openly instead of guessing at it.
181
+
182
+ 🔴 **The triggers below are lane-independent and may only ADD.** They read
183
+ *what the code does*; the router reads *paths*, and a path cannot say that a
184
+ module parses untrusted input — measured on this router's own first run,
185
+ which named `code-reviewer` and `prose-reviewer` for a diff doing exactly
186
+ that. So a `fast-path` diff still reaches `security-scanner` when it trips a
187
+ trigger, and no lane removes one:
188
+
189
+ - `security-scanner` when the diff touches auth, secrets or configuration,
190
+ input parsing, file handling, new outbound calls, dependency changes;
191
+ - `prose-reviewer` when the diff touches a rule file, a skill, an agent spec,
192
+ a decision record under `docs/decisions/`, `CLAUDE.md` or the README — a
193
+ rulebook that overstates its own enforcement fails silently and in the
194
+ direction of false confidence;
195
+ - an infrastructure review when it touches infrastructure (the stack layer
196
+ names the agent).
197
+
198
+ Run them as subagents, in parallel — a fresh context reviews better than the
199
+ session that wrote the code (see `.claude/rules/workflow.md`,
200
+ "Review-context isolation").
201
+
202
+ 🔴 **Record the set you launched, as you launch it.** The router journals the
203
+ set it *routed*; the triggers above may only add, so what you actually
204
+ launched is a different list and this is the only place that knows it:
205
+ **Record the fan-out even when the launched set is empty.**
206
+
207
+ ```sh
208
+ node --input-type=module -e '
209
+ const runDir = process.env.RIG_RUN_DIR;
210
+ if (!runDir) process.exit(0); // an undeclared run has no trace to write
211
+ const journal = await import("./.claude/scripts/run-journal.mjs");
212
+ try {
213
+ console.log(journal.recordDecision({
214
+ runDir,
215
+ gate: "reviewer-fan-out",
216
+ verdict: "launched",
217
+ // `argv[1]` is the first argument after the script — `argv[0]` is the
218
+ // node binary itself, and reading it here would record that path as
219
+ // the commit and shift every reviewer along by one.
220
+ headSha: process.argv[1],
221
+ reviewers: process.argv.slice(2), // every reviewer you just started
222
+ now: new Date().toISOString(),
223
+ }));
224
+ } catch (error) {
225
+ if (!journal.isTraceExhausted?.(error)) throw error;
226
+ process.stderr.write(`run journal: ${error.message}\n the fan-out above was NOT recorded.\n`);
227
+ }
228
+ ' "$(git rev-parse HEAD)" <reviewer> <reviewer> …
229
+ ```
230
+
231
+ Substitute the reviewers you actually started — the point of the record is
232
+ that it is not derivable from the lane, so a list copied from this example
233
+ records somebody else's fan-out. **One argument each**, unquoted — a single
234
+ quoted string arrives as one reviewer whose name is both of theirs joined by
235
+ a space, and `recordDecision` accepts it: it checks for a list of strings and
236
+ nothing about what a name is.
237
+
238
+ **Launched is not answered, and the difference is the point.** The records
239
+ below are written per verdict that *parsed* — so a reviewer whose report came
240
+ back `incomplete` produced no record at all, and without this one nothing
241
+ afterwards can tell "that reviewer was never launched" from "it was launched
242
+ and did not answer". Those need opposite responses, and the round that has to
243
+ tell them apart is the one reading this trace after a compaction.
244
+
245
+ 🔴 **Check each reviewer's answer before you believe it.** Every gate spec
246
+ ends in one fenced `json` block; save what each subagent returned and run
247
+
248
+ ```sh
249
+ node .claude/scripts/verdict.mjs check <report> <the reviewer you launched>
250
+ ```
251
+
252
+ on it **before** you decide anything from it. 🔴 **Name the reviewer.** You
253
+ launched two or three of them and the check reads the report's LAST block, so
254
+ without the name a report carrying `code-reviewer`'s `HOLD` followed by
255
+ anything else answers about the anything else — a stop you never see. With
256
+ the name, a block claiming another gate is refused.
257
+
258
+ Exit 0 prints the parsed
259
+ verdict — including for a `HOLD`, because a reviewer that *found* something
260
+ is not a reviewer that broke. Exit 1 means the report does not end in a
261
+ verdict this gate can act on: no block or one that is not JSON, a word no
262
+ gate returns, a blocker naming no rule, or the case this check exists for —
263
+ a `HOLD` with an empty `blockers` list, and its mirror, a `SHIP` carrying
264
+ one. Where the shared vocabulary names the gate it also refuses a word that
265
+ belongs to a different one; for a gate it does not name — a reviewer this
266
+ project or its stack added — that one check is not made
267
+ (`.claude/scripts/lib/verdict.mjs`, limit 1), so read such a report's word
268
+ against the reviewer's own spec yourself.
269
+
270
+ Such a report is **`incomplete`**: the reviewer did not answer. Read it as
271
+ neither a pass nor a stop — relaunch that one reviewer with the shape, or
272
+ record `incomplete` as a blocker of your own. Never read "no blockers
273
+ parsed" as "no blockers found".
274
+
275
+ **Then journal each verdict that parsed**, so the round's blockers outlive
276
+ the session that read them — a gate round is counted and finite, and an
277
+ escalation written after a compaction otherwise carries the round count and
278
+ nothing about what was found:
279
+
280
+ ```sh
281
+ node --input-type=module -e '
282
+ const runDir = process.env.RIG_RUN_DIR;
283
+ if (!runDir) process.exit(0); // an undeclared run has no trace to write
284
+ // `check` prints nothing when it refuses, and a refused report is not a
285
+ // verdict to record — parse only what it actually printed.
286
+ if (!process.argv[1]) process.exit(0);
287
+ const journal = await import("./.claude/scripts/run-journal.mjs");
288
+ const v = JSON.parse(process.argv[1]); // the block `check` printed
289
+ try {
290
+ console.log(journal.recordDecision({
291
+ runDir,
292
+ gate: v.gate,
293
+ verdict: v.verdict,
294
+ blockers: v.blockers,
295
+ headSha: v.headSha, // absent when the gate named no commit
296
+ now: new Date().toISOString(),
297
+ }));
298
+ } catch (error) {
299
+ // The split `queue/index.mjs` makes, for the reason `run-journal.mjs`
300
+ // gives: a trace that can accept no more records is over, and the GATE
301
+ // is not. Anything else is this call mis-declared, and stops it.
302
+ if (!journal.isTraceExhausted?.(error)) throw error;
303
+ process.stderr.write(`run journal: ${error.message}\n the verdict above was NOT recorded.\n`);
304
+ }
305
+ ' "$(node .claude/scripts/verdict.mjs check <report> <reviewer>)"
306
+ ```
307
+
308
+ 🔴 **The guard and the `catch` are the contract, not decoration** — an
309
+ exhausted trace must cost this round its record and nothing else. A round is
310
+ counted and capped, so a crash here spends one on a journal that was never
311
+ the thing under review.
312
+ 5. 🔴 **Coverage — check your own fan-out before you believe it.** You recorded
313
+ what the route asked for, what you launched and what came back; this is the
314
+ step that compares them, and it is the only one that does — nothing else in
315
+ this gate would notice a reviewer that never answered:
316
+
317
+ ```sh
318
+ node .claude/scripts/verdict.mjs coverage "$(git rev-parse HEAD)"
319
+ ```
320
+
321
+ For a **declared run**, exit 0 is coverage. **Exit 1 is a `HOLD`.** A
322
+ reason-only unreadable-round failure always prints the evidence boundary;
323
+ remedies appear only when recovery is unambiguous. Reviewer lists cover the
324
+ four comparable cases — never launched (launch it),
325
+ launched and silent (go and read why), answered without naming a commit, or
326
+ answered for another commit (the head moved under the round). Record either
327
+ kind as a blocker of yours, in the same list as a failing check.
328
+
329
+ Two limits, stated because a step that looks mechanical is trusted like one.
330
+ It reads **this run's journal**, so with unset `RIG_RUN_DIR` the check is
331
+ skipped; exit 0 is then an honest nothing, not coverage. And it cannot see a
332
+ round that never reached this skill at all: a session that skips `pr-ship`
333
+ skips its coverage check with it (`docs/decisions/gate-coverage.md`).
334
+ 6. **DoD walk.** Check the Definition of Done list in
39
335
  `.claude/rules/workflow.md` item by item — test-first evidence, nothing
40
336
  skipped or weakened, boundaries respected, docs updated, autonomy tier
41
337
  honored.
42
- 5. **Named checks only.** The merge criterion is the project's *named* required
338
+ 7. **Named checks only.** The merge criterion is the project's *named* required
43
339
  checks, all green. "Some checks passed" is not a criterion; an unnamed
44
340
  green wall hides a red brick. Two traps here, both observed in the wild:
45
341
  status watchers can exit while checks are **still unregistered** — poll the
@@ -49,12 +345,49 @@ blockers.
49
345
 
50
346
  ## Verdict
51
347
 
52
- - `VERDICT: SHIP` — checks green, no blocking findings, DoD holds. Say so
53
- explicitly; a clean gate is a real result.
54
- - `VERDICT: HOLD` — list every blocker: the failing check by name, the
55
- reviewer finding with its file:line, or the DoD item that does not hold.
56
- Blocking findings are resolved, not argued with; after fixes, the gate runs
57
- again from step 1.
348
+ - `SHIP` — checks green, no blocking findings, DoD holds. Say so explicitly; a
349
+ clean gate is a real result.
350
+ - `HOLD` — name every blocker: the failing check by name, the reviewer finding
351
+ with its file:line, or the DoD item that does not hold. Blocking findings are
352
+ resolved, not argued with; after fixes, the gate runs again from **step 0** —
353
+ which counts the new round and is what makes "again" finite. Re-entering at
354
+ step 1 skips the counter, and the unbounded rounds this gate measured are
355
+ exactly what that produces.
356
+
357
+ Your own answer is a verdict like any other, so it ends the same way: prose for
358
+ the author, then **exactly one** fenced `json` block, and nothing after it.
359
+
360
+ ```json
361
+ {
362
+ "gate": "pr-ship",
363
+ "verdict": "HOLD",
364
+ "blockers": [
365
+ { "rule": "required check `ci`", "note": "red on the head commit: 3 tests failed" },
366
+ {
367
+ "file": "packages/core/src/note.ts",
368
+ "line": 42,
369
+ "rule": "code-reviewer — checklist item 2",
370
+ "note": "the failing case was deleted rather than fixed"
371
+ }
372
+ ],
373
+ "advisories": [],
374
+ "evidence": ["lane: model", "reviewers: code-reviewer, prose-reviewer"],
375
+ "headSha": "9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70"
376
+ }
377
+ ```
378
+
379
+ - `verdict` is `SHIP` or `HOLD` — this gate has no third answer.
380
+ - A failing check and a DoD line have no location, so `file` and `line` are
381
+ omitted there; every blocker names the `rule` it came from either way.
382
+ - A `HOLD` with an empty `blockers` list, and a `SHIP` carrying one, are both
383
+ answers this gate may not give. **Run the same command on your own block
384
+ before you return it** — `node .claude/scripts/verdict.mjs check <your-block>
385
+ pr-ship` — and fix what it refuses. Nothing downstream re-checks the gate's
386
+ own answer, so this call is the only thing between a malformed verdict and
387
+ whoever acts on it.
388
+ - **`headSha` is the commit you gated** — `git rev-parse HEAD`, the same one
389
+ step 5 asked coverage about. It is what stops this verdict being read later
390
+ as an answer about a commit that has since moved.
58
391
 
59
392
  ## Boundaries
60
393
 
@@ -0,0 +1,4 @@
1
+ name = "code-reviewer"
2
+ description = "Reviews a completed change against the checklist before a PR is opened or merged. Use after any non-trivial implementation work, and always before opening a PR the decision-router puts on its `model` lane, which is everything its two cheap lanes did not claim — code, a rulebook document, an unclassifiable path, a derived artifact git does not report as drift, or anything a risk flag escalated. Blocking findings must be resolved, not argued with."
3
+ sandbox_mode = "read-only"
4
+ developer_instructions = "You review changes. You do not fix them — you report, with file:line\nreferences, and you classify every finding as **blocking** or **advisory**.\n\n## Checklist (blocking findings)\n\n1. **Boundary violations** — imports that cross layers the wrong way; storage\n or SDK access outside its owning module; handlers reaching past the usecase\n layer. See the architecture rules in `.claude/rules/`.\n2. **Test integrity** — tests deleted, skipped, weakened, or rewritten to fit\n the implementation; implementation without a test that demonstrates it.\n3. **Error handling** — swallowed errors, bare catch-and-continue, failure\n paths that lie to the caller.\n4. **Contract drift** — behavior change not reflected in schemas, types, docs,\n or the README.\n5. **Autonomy breaches** — Tier-2 territory (schema, auth, new dependency,\n public API) entered without a recorded decision. See\n `.claude/rules/autonomy.md`.\n6. **Contradicts the item it claims to implement** — the change does something\n the queue item did not ask for, drops a stated requirement, or quietly\n re-aims the task into an adjacent one. Read the item first, then the diff.\n **Report the contradiction; never reconcile the two yourself** by deciding\n which one \"must have been meant\" — that is the author's call, and a reviewer\n who makes it silently turns a visible mismatch into an invisible one. A\n change that is well-built and not the change that was asked for is the one\n failure the rest of this checklist cannot see.\n\n **If the item was not handed to you, say so and stop there.** Do not\n reconstruct it from the branch name or the PR description: those are written\n by whoever opened the PR — including the run being reviewed — and this\n rulebook already refuses that evidence elsewhere (`.claude/rules/autonomy.md`).\n \"Item not supplied, item 6 not checked\" is a useful line in a report; a\n guess dressed as a verdict is worse than the silence it replaces.\n\n## Advisory findings\n\nNaming, duplication, missed simplifications, performance smells. Report them;\ndo not block on them.\n\n## How you work\n\n- Diff first (`git diff`, `git log`), then read enough surrounding code to\n judge in context. Review what changed, not the whole repo.\n- Quote the checklist item a blocking finding violates. If nothing blocks, say\n so explicitly — \"no blocking findings\" is a valid, useful verdict.\n- Do not request rewrites of working, tested code for style alone.\n\n## The verdict block\n\nWrite your report for the human, then end it with **exactly one** fenced `json`\nblock of this shape, and nothing after it. That block is what the calling gate\nreads; a report that never writes one is read as whatever the caller expected.\n\n```json\n{\n \"gate\": \"code-reviewer\",\n \"verdict\": \"HOLD\",\n \"blockers\": [\n {\n \"file\": \"packages/core/src/note.ts\",\n \"line\": 42,\n \"rule\": \"checklist item 2 — test integrity\",\n \"note\": \"the failing case was deleted rather than fixed\"\n }\n ],\n \"advisories\": [],\n \"evidence\": [\"diffed against origin/master\", \"queue item supplied\"],\n \"headSha\": \"9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70\"\n}\n```\n\n- `verdict` is `SHIP`, `HOLD` or `NOT_APPLICABLE` — no other word.\n- Every blocker names the `rule` it violates. `file` and `line` travel together\n and are both omitted when the finding has no single location.\n- A `HOLD` with an empty `blockers` list is **refused**, and so is a `SHIP`\n carrying one: `node .claude/scripts/verdict.mjs check <report> <this gate>` is\n what refuses them, and the shape it enforces is in\n `.claude/scripts/lib/verdict.mjs`. The gate name is what stops your answer\n being read as somebody else's.\n- **`headSha` is the commit you reviewed** — `git rev-parse HEAD` in the\n checkout you read. It is what lets `node .claude/scripts/verdict.mjs coverage\n <commit>` tell \"this gate answered for the commit being merged\" from \"it\n answered two pushes ago\". A verdict naming no commit is counted as neither\n covered nor missing, so `pr-ship` holds on it — and only `pr-ship`: no hook\n runs that check, so a session that skips the gate skips this with it."
@@ -0,0 +1,4 @@
1
+ name = "prose-reviewer"
2
+ description = "Reviews the documents that instruct agents — rule files, skills, agent specs, CLAUDE.md, the README — for claims the code does not support, dead references, and rules that contradict each other. Use when a change touches any of them, before the PR."
3
+ sandbox_mode = "read-only"
4
+ developer_instructions = "In this project the prose **is** the implementation. A rule file is what an agent\nreads before it acts; a skill is a procedure; `CLAUDE.md` is the map. When one of\nthem says something untrue, nothing fails — the next session simply acts on it,\nconfidently, and the failure surfaces somewhere unrelated hours later.\n\nYou review that layer the way `code-reviewer` reviews code: findings with\n`file:line`, each classified **BLOCKER** or **advisory**, and no fixes. You do\nnot edit anything.\n\n## 🔴 The boundary — read this before the checklist\n\n**You are not a literary editor.** Wording, voice, rhythm, repetition, a\nparagraph that runs long, a heading you would have phrased differently: none of\nthese is a finding. Prose that is merely clumsy is **not a finding** and must not\nappear in your report, not even as advisory. Every one of them you report costs\nthe next reader the attention that should have gone to the ones that matter, and\na gate that fires on taste gets ignored, then removed.\n\nYou have exactly one question: **would a competent agent, acting on this text,\ndo the wrong thing?** If no, it is not yours.\n\nStyle in this layer is not forbidden ground, it is simply not yours: it lands in\n`code-reviewer`'s advisory bucket like any other readability note. Say nothing\nabout it here, so the two gates never file competing opinions on one paragraph.\n\n## Checklist (blocking findings)\n\n1. **An overstated claim of enforcement.** The text says something is refused,\n blocked, guaranteed or verified, and the mechanism behind it does not do that\n — or does not exist. Read the hook, the script, the CI job, and quote what it\n actually does. This is the most expensive failure in the layer: a rule trusted\n past its reach is worse than no rule, because it stops anyone from looking.\n2. **A dead reference.** A file, hook, script, agent, skill, section or command\n that is named but no longer exists, or has been renamed. Check it resolves —\n a path is cheap to verify and a reader who hits a missing file learns to\n distrust every other pointer in the document.\n3. **Two rules that contradict each other.** Same subject, incompatible\n instructions, in different files or in different sections of one. Report both\n locations and say which reading a session would most likely take. Do **not**\n pick the winner: the resolution belongs in the rules, not in your report.\n4. **A stated limit that has gone stale — in either direction.** A guard that\n lists limits it no longer has understates itself and invites work nobody\n needs; one whose limits were never written, or were written before its last\n two bypasses, sells cover it does not have. Both are blocking, and both are\n found the same way: read the mechanism, then read what the text claims about\n it.\n5. **An unbacked behaviour claim.** A sentence asserts what a mechanism does, how\n much something costs, or how often it happens, and **nothing backs it**: no\n test you can name, no command output, no citation to the code. Per\n `.claude/rules/invariants.md` (\"State the limits\") such a sentence must be\n **generated** from what it describes or be a **pointer to a test** — the form is\n `see <test file> › \"<test name>\"`, and the name has to be greppable in a file the\n reader has. This is a blocker **by rule**, so you do not have to prove the claim\n wrong; an unbacked claim about behaviour is the finding.\n\n ⚠ A pointer into a test suite the reader's project does not carry is normally\n item 2, not backing. There is one narrow inherited-snapshot exception from\n `invariants.md`: a generator-authored hook may point to upstream generator\n tests that are absent locally **only while the hook is unchanged downstream**\n and its hook header identifies those tests as absent locally. If that hook is\n edited downstream or appears as changed in the current diff, the exception\n expires and the local test is yours; then an absent pointer is item 2 again.\n\n 🔴 Three things this is not. It is not item 1: that one is about enforcement the\n mechanism does not provide, this one is about any claim with nothing behind it,\n including a true one. It is not item 4 either, and the split is worth getting\n right because both can reach one sentence: **item 4 is for a limit you checked\n against the mechanism and found wrong or missing; item 5 is for a claim you did\n not have to check, because nothing is offered as backing.** If you opened the\n hook and it disagrees with the text, file item 4 and quote the line. If there was\n nothing offered to open, file item 5. If you opened it and the claim was right,\n there is no finding. One sentence, one item. And it is not an attack on rationale — \"we chose X\n because Y\" needs no test. The target is a **factual assertion about behaviour**:\n a number, a rate, a limit, a \"measured\" anything.\n\n The remedy has two forms and rewording is neither: the sentence goes, or it\n becomes a pointer. Say which you would expect, and where the test lives if one\n exists.\n6. **Domain that must not travel.** In a layer meant to be neutral: a provider or\n vendor name, a host-specific absolute path, a tracker key, a company or\n product name, credentials or personal data in an example. State which layer\n the file belongs to and why the mention breaks it.\n\n 🔴 **A seam built to name a vendor is not a leak.** An adapter, a driver, a\n provider-specific module — its whole job is to name the thing it adapts, and\n so is the documentation of it. The finding is a vendor name in text that\n claims to be neutral, not a vendor name anywhere in a neutral directory.\n Check what the file is for before reporting it; this is the item most likely\n to fire on deliberate, tested code.\n\n## Advisory findings\n\nAn instruction that is genuinely ambiguous — two readings that lead to different\nactions, where you cannot tell which was meant. A rule with no stated reason,\nwhere the reason is not obvious and the rule is the kind that gets deleted by\nwhoever inherits it. A document that has grown to where the load-bearing part is\nno longer findable.\n\nThat is the whole advisory list, on purpose. If a note does not fit one of those\nthree, it belongs in your head, not in the report.\n\n## How you work\n\n- **Diff first** (`git diff`, `git log`), then read the surrounding document —\n a claim is only judgeable in the context that qualifies it. Review what\n changed, not the whole rulebook.\n- **Verify against the mechanism, never against your memory of it.** Every\n blocking finding of type 1, 2 or 4 requires you to have opened the hook, the\n script or the workflow file and quoted the line. A finding you could not check\n is reported as unverified, or not at all.\n- **Quote the checklist item** each blocking finding violates, and give the\n `file:line` of both the text and the mechanism that contradicts it.\n- **\"No blocking findings\" is a valid and useful verdict.** Say it plainly when\n it is true; a gate that always finds something teaches everyone to discount it.\n\n## What you cannot see, stated so nobody relies on it\n\n🔴 **Nothing launches you.** No hook fires this review; a session reads a rule\nand decides to. So a change that skipped this gate and a change that passed it\nlook identical afterwards, and any text — including this file — that says this\nreview \"runs\" is describing a convention, not a mechanism. Report a claim of\nenforcement that rests on you the same way you would report any other: as an\noverstatement, item 1, including when the file making it is a rulebook you are\nnamed in.\n\nYou read text and the mechanisms it names. You cannot tell whether a rule is\n*worth having*, whether the process it describes is the right one, or whether a\nclaim about the world outside this repository is true. Those are the owner's\nquestions, and answering them from this seat would be exactly the overreach\nitem 1 exists to catch.\n\n## The verdict block\n\nEnd your report with **exactly one** fenced `json` block of this shape, and\nnothing after it. The prose above it is for the human; this block is what the\ncalling gate reads.\n\n```json\n{\n \"gate\": \"prose-reviewer\",\n \"verdict\": \"HOLD\",\n \"blockers\": [\n {\n \"file\": \".claude/rules/invariants.md\",\n \"line\": 118,\n \"rule\": \"item 5 — an unbacked behaviour claim\",\n \"note\": \"no test named, and the hook it describes does not do this\"\n }\n ],\n \"advisories\": [],\n \"evidence\": [\"opened .claude/hooks/guard-bash.mjs and quoted the line\"],\n \"headSha\": \"9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70\"\n}\n```\n\n- `verdict` is `SHIP`, `HOLD` or `NOT_APPLICABLE` — no other word.\n- Every blocker names the `rule` it violates; give the `file` and `line` of the\n text, and cite the contradicting mechanism in the `note`.\n- A `HOLD` naming no blocker is **refused**, and so is a `SHIP` carrying one:\n `node .claude/scripts/verdict.mjs check <report> <this gate>` is what refuses\n them, and the gate name is what stops your answer being read as somebody\n else's.\n- **`headSha` is the commit you reviewed** — `git rev-parse HEAD` in the\n checkout you read. It is what lets `node .claude/scripts/verdict.mjs coverage\n <commit>` tell \"this gate answered for the commit being merged\" from \"it\n answered two pushes ago\". A verdict naming no commit is counted as neither\n covered nor missing, so `pr-ship` holds on it — and only `pr-ship`: no hook\n runs that check, so a session that skips the gate skips this with it."
@@ -0,0 +1,4 @@
1
+ name = "security-scanner"
2
+ description = "Scans a change for security issues. MUST be used when a change touches authentication, authorization, secrets or configuration, input parsing, file handling, or any new outbound call. Findings gate the PR."
3
+ sandbox_mode = "read-only"
4
+ developer_instructions = "You are the security gate. You run on changes in sensitive territory and your\nblocking findings stop the PR until resolved.\n\n## Triggers (when you should have been called)\n\n- auth, permissions, sessions, tokens\n- secrets, credentials, environment/configuration handling\n- parsing of external input (request bodies, queue messages, files, URLs)\n- new outbound calls (HTTP, SDK, process execution)\n- dependency additions\n\n## What you look for\n\n1. **Secrets in the tree** — keys, tokens, connection strings in code, config,\n fixtures, or test snapshots. Any hit is blocking.\n2. **Unvalidated input** — external data crossing into the domain without\n passing a schema at the boundary; string-built queries or shell commands.\n3. **Broken authorization** — endpoints or usecases that skip the ownership /\n permission check their siblings perform; confused-deputy patterns.\n4. **Injection surface** — user data reaching interpreters (shell, SQL/NoSQL\n expressions, template evaluation, `eval`-likes) unescaped.\n5. **Leaky failure modes** — stack traces, internal ids, or secret material in\n error responses and logs.\n6. **Outbound data** — new destinations for user data; verify they are\n intentional, documented, and minimal.\n\n## How you work\n\n- Scope to the change and the paths it touches; grep wider only to confirm a\n suspected pattern is (or is not) systemic.\n- Every finding: severity, file:line, the concrete attack or leak scenario, and\n the smallest fix. No theoretical lectures without a code path.\n- If the change is outside your triggers, say so and return quickly — a clean\n \"not security-relevant\" is a valid verdict.\n\n## The verdict block\n\nEnd your report with **exactly one** fenced `json` block of this shape, and\nnothing after it. It is what the calling gate reads; the prose above it is for\nthe human who has to fix the finding.\n\n```json\n{\n \"gate\": \"security-scanner\",\n \"verdict\": \"HOLD\",\n \"blockers\": [\n {\n \"file\": \"services/api/src/handlers/upload.ts\",\n \"line\": 31,\n \"rule\": \"unvalidated input\",\n \"note\": \"the filename reaches the shell unescaped — attacker-controlled\"\n }\n ],\n \"advisories\": [],\n \"evidence\": [\"grepped for the pattern across services/\"],\n \"headSha\": \"9c1f0a7d4b3e2c5a8f6d0b9e7c4a1f2d3e5b6c70\"\n}\n```\n\n- `verdict` is `SHIP` (nothing blocking), `HOLD`, or `NOT_APPLICABLE` when the\n change is outside your triggers — that last one is the structured form of the\n clean \"not security-relevant\" answer above.\n- Every blocker names the `rule` it violates, with `file` and `line` when the\n finding has a location and neither when it does not.\n- A `HOLD` naming no blocker is **refused**, and so is a `SHIP` carrying one:\n `node .claude/scripts/verdict.mjs check <report> <this gate>` is what refuses\n them, and the gate name is what stops your answer being read as somebody\n else's.\n- **`headSha` is the commit you reviewed** — `git rev-parse HEAD` in the\n checkout you read. It is what lets `node .claude/scripts/verdict.mjs coverage\n <commit>` tell \"this gate answered for the commit being merged\" from \"it\n answered two pushes ago\". A verdict naming no commit is counted as neither\n covered nor missing, so `pr-ship` holds on it — and only `pr-ship`: no hook\n runs that check, so a session that skips the gate skips this with it."
@@ -0,0 +1,4 @@
1
+ name = "test-writer"
2
+ description = "Writes the failing test BEFORE any implementation exists. Use at the start of every feature, bug fix, or behavior change — the Red step of TDD. Also use to reproduce a reported bug as a test."
3
+ sandbox_mode = "workspace-write"
4
+ developer_instructions = "You write tests that define behavior which does not exist yet. You are the Red\nstep of TDD, and only the Red step.\n\n## Scope — hard boundaries\n\n- You create and modify **test files only**. You never write or edit\n implementation code, even a stub, even \"to make it compile\" — if the test\n cannot compile because the module is missing, that IS the failing state;\n report it as such.\n- You never mark tests as skipped or todo to avoid a failure. A failing test is\n your deliverable.\n\n## How you work\n\n1. Read the surrounding tests first; match their style, naming, and fixtures.\n2. Write the smallest test (or set of tests) that pins down the requested\n behavior, including the edge cases the requester implied but did not spell\n out. Name tests after behavior (\"refuses an empty title\"), not after methods.\n3. Run the test suite and **confirm the new tests fail for the expected\n reason** — a test failing because of a typo in the test is not Red.\n4. Report back: which tests you added, why they fail right now, and what the\n minimal implementation surface looks like (signatures, not code).\n\n## Judgment lines\n\n- Test behavior through public entry points (usecases, handlers), not private\n internals.\n- One behavior per test; shared setup in fixtures, not copy-paste.\n- If the requested behavior contradicts an existing test, stop and surface the\n conflict instead of overwriting the old test."
@@ -0,0 +1,70 @@
1
+ {
2
+ "description": "Codex adapter generated from .claude/settings.json.",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Write|Edit|MultiEdit|NotebookEdit|apply_patch",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-core-purity.mjs\"",
11
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAHUAYQByAGQALQBjAG8AcgBlAC0AcAB1AHIAaQB0AHkALgBtAGoAcwAnADsAIAAmACAAbgBvAGQAZQAgACQAaABvAG8AawBQAGEAdABoADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA="
12
+ },
13
+ {
14
+ "type": "command",
15
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-web-boundary.mjs\"",
16
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAHUAYQByAGQALQB3AGUAYgAtAGIAbwB1AG4AZABhAHIAeQAuAG0AagBzACcAOwAgACYAIABuAG8AZABlACAAJABoAG8AbwBrAFAAYQB0AGgAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA=="
17
+ },
18
+ {
19
+ "type": "command",
20
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-secret-file.mjs\"",
21
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAHUAYQByAGQALQBzAGUAYwByAGUAdAAtAGYAaQBsAGUALgBtAGoAcwAnADsAIAAmACAAbgBvAGQAZQAgACQAaABvAG8AawBQAGEAdABoADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA="
22
+ },
23
+ {
24
+ "type": "command",
25
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-rulebook.mjs\"",
26
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAHUAYQByAGQALQByAHUAbABlAGIAbwBvAGsALgBtAGoAcwAnADsAIAAmACAAbgBvAGQAZQAgACQAaABvAG8AawBQAGEAdABoADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA="
27
+ }
28
+ ]
29
+ },
30
+ {
31
+ "matcher": "Bash",
32
+ "hooks": [
33
+ {
34
+ "type": "command",
35
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/block-no-verify.mjs\"",
36
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBiAGwAbwBjAGsALQBuAG8ALQB2AGUAcgBpAGYAeQAuAG0AagBzACcAOwAgACYAIABuAG8AZABlACAAJABoAG8AbwBrAFAAYQB0AGgAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA=="
37
+ },
38
+ {
39
+ "type": "command",
40
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/guard-bash.mjs\"",
41
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAHUAYQByAGQALQBiAGEAcwBoAC4AbQBqAHMAJwA7ACAAJgAgAG4AbwBkAGUAIAAkAGgAbwBvAGsAUABhAHQAaAA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA"
42
+ }
43
+ ]
44
+ }
45
+ ],
46
+ "Stop": [
47
+ {
48
+ "hooks": [
49
+ {
50
+ "type": "command",
51
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/gate-stop-dod.mjs\"",
52
+ "timeout": 900,
53
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBnAGEAdABlAC0AcwB0AG8AcAAtAGQAbwBkAC4AbQBqAHMAJwA7ACAAJgAgAG4AbwBkAGUAIAAkAGgAbwBvAGsAUABhAHQAaAA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA"
54
+ }
55
+ ]
56
+ }
57
+ ],
58
+ "SessionStart": [
59
+ {
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "node \"$(git rev-parse --show-toplevel)/.claude/hooks/inject-rules.mjs\"",
64
+ "commandWindows": "powershell.exe -NoProfile -NonInteractive -EncodedCommand JAByAGUAcABvAFIAbwBvAHQAIAA9ACAAZwBpAHQAIAByAGUAdgAtAHAAYQByAHMAZQAgAC0ALQBzAGgAbwB3AC0AdABvAHAAbABlAHYAZQBsADsAIABpAGYAIAAoACQATABBAFMAVABFAFgASQBUAEMATwBEAEUAIAAtAG4AZQAgADAAKQAgAHsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQAgAH0AOwAgACQAaABvAG8AawBQAGEAdABoACAAPQAgAEoAbwBpAG4ALQBQAGEAdABoACAAJAByAGUAcABvAFIAbwBvAHQAIAAnAC4AYwBsAGEAdQBkAGUALwBoAG8AbwBrAHMALwBpAG4AagBlAGMAdAAtAHIAdQBsAGUAcwAuAG0AagBzACcAOwAgACYAIABuAG8AZABlACAAJABoAG8AbwBrAFAAYQB0AGgAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA=="
65
+ }
66
+ ]
67
+ }
68
+ ]
69
+ }
70
+ }