create-agent-rig 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +207 -8
- package/README.md +50 -17
- package/package.json +2 -1
- package/packages/cli/dist/commands/create.js +8 -3
- package/packages/cli/dist/commands/init.js +82 -34
- package/packages/cli/dist/commands/upgrade.js +112 -30
- package/packages/cli/dist/index.js +38 -14
- package/packages/cli/dist/lib/copy-tree.js +35 -6
- package/packages/cli/dist/lib/init-settings.js +12 -0
- package/packages/cli/dist/lib/install-set.js +6 -8
- package/packages/cli/dist/lib/manifest.js +21 -9
- package/packages/cli/dist/lib/safe-path.js +30 -0
- package/templates/agent-os/init/AGENTS.md +191 -0
- package/templates/agent-os/init/CLAUDE.md +61 -9
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +98 -0
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +117 -0
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +34 -3
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +1 -1
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +34 -0
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +4 -0
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +233 -0
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +676 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +102 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.mjs +78 -0
- package/templates/agent-os/universal/.agents/skills/new-invariant/guard-invariant.example.test.mjs +89 -0
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +338 -0
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +73 -0
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +33 -1
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +64 -1
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +33 -0
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +274 -24
- package/templates/agent-os/universal/.claude/hooks/guard-bash.mjs +43 -5
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +25 -11
- package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +180 -0
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +32 -13
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +175 -7
- package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +480 -0
- package/templates/agent-os/universal/.claude/rules/autonomy.md +61 -1
- package/templates/agent-os/universal/.claude/rules/invariants.md +71 -16
- package/templates/agent-os/universal/.claude/rules/workflow.md +55 -5
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1242 -0
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +65 -2
- package/templates/agent-os/universal/.claude/scripts/git-env.mjs +49 -0
- package/templates/agent-os/universal/.claude/scripts/lib/secrets.mjs +490 -0
- package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +433 -0
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +6 -16
- package/templates/agent-os/universal/.claude/scripts/queue/checkout.mjs +89 -0
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +334 -23
- package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +160 -0
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +7 -2
- package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +413 -8
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +75 -13
- package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +222 -21
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +197 -0
- package/templates/agent-os/universal/.claude/scripts/run-journal.mjs +435 -0
- package/templates/agent-os/universal/.claude/scripts/run-state.mjs +441 -0
- package/templates/agent-os/universal/.claude/scripts/verdict.mjs +101 -0
- package/templates/agent-os/universal/.claude/settings.json +7 -2
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +115 -7
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +378 -31
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +300 -25
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +4 -0
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +4 -0
- package/templates/agent-os/universal/.codex/hooks.json +65 -0
- package/templates/agent-os/universal/AGENTS.md +164 -0
- package/templates/agent-os/universal/CLAUDE.md +47 -14
- package/templates/agent-os/universal/PLAN.md +7 -40
- package/templates/agent-os/universal/docs/decisions/closing-a-task.md +98 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +108 -0
- package/templates/agent-os/universal/docs/decisions/fail-open-guards.md +43 -0
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +72 -0
- package/templates/agent-os/universal/docs/decisions/run-directory.md +44 -0
- package/templates/agent-os/universal/docs/decisions/spacing-rations-mechanisms.md +125 -0
- package/templates/agent-os/universal/docs/decisions/stop-conditions-in-a-file.md +46 -0
- package/templates/agent-os/universal/docs/decisions/two-empty-endings.md +62 -0
- package/templates/agent-os/universal/journal/README.md +101 -0
- package/templates/agent-os/universal/layers.json +36 -2
- package/templates/hash-history.json +2 -1
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +34 -1
- package/templates/skeleton/aws-serverless/README.md +91 -9
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +9 -2
- package/templates/skeleton/aws-serverless/gitignore +37 -0
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +73 -13
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +136 -4
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +14 -1
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +301 -0
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +137 -0
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +36 -20
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +15 -12
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +33 -6
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +18 -5
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +11 -0
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +120 -1
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +72 -1
- package/templates/skeleton/node-service/README.md +11 -1
- package/templates/skeleton/node-service/gitignore +34 -0
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +47 -10
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +20 -0
- package/templates/skeleton/node-service/services/api/src/main.ts +2 -9
- package/templates/skeleton/node-service/services/api/src/server.ts +93 -10
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +20 -0
- package/templates/skeleton/node-service/services/api/test/server.test.ts +98 -13
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +28 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
> **Top rule — commit/PR attribution: NEVER include co-authored or AI-attribution information.**
|
|
4
|
+
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By: AI Assistant …`), `Generated with an AI coding agent`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
|
|
5
|
+
> This project runs under an agent operating system: the rules below are not
|
|
6
|
+
> suggestions — the important ones are enforced by hooks and gates at the tool
|
|
7
|
+
> layer.
|
|
8
|
+
|
|
9
|
+
## One operating system, two harnesses
|
|
10
|
+
|
|
11
|
+
This rulebook serves both Claude Code and Codex. The generator authors it as
|
|
12
|
+
`CLAUDE.md` and publishes the same text as `AGENTS.md`, so neither harness gets
|
|
13
|
+
a weaker policy. The `.claude/` directory keeps its historical name but holds
|
|
14
|
+
the shared rules, hooks, scripts and agent specifications. Claude Code discovers
|
|
15
|
+
its skills there; Codex receives the matching repository skills in
|
|
16
|
+
`.agents/skills/` and its native agent and hook configuration in `.codex/`.
|
|
17
|
+
The derivation and rollback contract is recorded in
|
|
18
|
+
`docs/decisions/codex-adapter.md`.
|
|
19
|
+
|
|
20
|
+
This project runs under an agent operating system: the rules below are not
|
|
21
|
+
suggestions — the important ones are enforced by hooks and gates at the tool
|
|
22
|
+
layer.
|
|
23
|
+
|
|
24
|
+
## If you read only four sections, read these
|
|
25
|
+
|
|
26
|
+
1. **Autonomy tiers** — what you may do alone vs. propose first:
|
|
27
|
+
`.claude/rules/autonomy.md` ("Tiers")
|
|
28
|
+
2. **Stop rules** — when stopping with a diagnosis is the correct move:
|
|
29
|
+
`.claude/rules/autonomy.md` ("Stop rules")
|
|
30
|
+
3. **The request path** — the mandatory usecase layer and the pure core:
|
|
31
|
+
`.claude/rules/architecture.md`
|
|
32
|
+
4. **Definition of Done** — the checklist a change must pass:
|
|
33
|
+
`.claude/rules/workflow.md` ("Definition of Done")
|
|
34
|
+
|
|
35
|
+
## The map
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
packages/core/ pure domain logic — schemas + functions; no I/O, no clock,
|
|
39
|
+
no randomness, no environment (hook-enforced)
|
|
40
|
+
packages/shared/ logger, env loading, typed errors — cross-cutting, no domain
|
|
41
|
+
packages/db/ the ONLY module that touches the storage SDK/driver
|
|
42
|
+
services/ entrypoints; every request: payload → handler → usecase → model
|
|
43
|
+
apps/web/ the frontend; imports core + shared ONLY, talks to services
|
|
44
|
+
over HTTP (hook-enforced)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The target-specific details (how to run, deploy, and verify runtime health)
|
|
48
|
+
live in `README.md`. Alongside the universal rules, `.claude/rules/` carries
|
|
49
|
+
the stack-specific conventions composed in for this project's target — read
|
|
50
|
+
them all; they are one rulebook.
|
|
51
|
+
|
|
52
|
+
## How work happens here
|
|
53
|
+
|
|
54
|
+
- **TDD, without exception.** The failing test comes first — use the
|
|
55
|
+
`test-writer` agent for it. See `.claude/rules/workflow.md`.
|
|
56
|
+
- **Check the premises at both ends.** A queue item is a claim about the code, and
|
|
57
|
+
nothing downstream re-reads the file it was wrong about — the `check-premises`
|
|
58
|
+
skill runs between taking the item and the failing test, and a false load-bearing
|
|
59
|
+
claim stops the task instead of quietly re-aiming it. It runs **again before the
|
|
60
|
+
gate**, on the prose the task itself wrote: a behaviour claim with nothing behind
|
|
61
|
+
it is `UNMEASURED`, and it is deleted or turned into a pointer to its test rather
|
|
62
|
+
than left for a reviewer to find.
|
|
63
|
+
- **One task, one branch — and merge via PR.** Every unit of work gets its own
|
|
64
|
+
short-lived branch; the default branch is never committed to directly. Once
|
|
65
|
+
the project has a remote and CI, changes reach it through the PR flow (local
|
|
66
|
+
checks → reviewer fan-out → merge on an explicit criterion). See
|
|
67
|
+
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
68
|
+
session may touch this repo at the same time, the branch lives in its own
|
|
69
|
+
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
70
|
+
- **Gates.** Every PR is routed before it is reviewed — the
|
|
71
|
+
`decision-router` picks the cheapest lane the change earns
|
|
72
|
+
(`deterministic` → `fast-path` → `model`), and risk flags escalate ahead of
|
|
73
|
+
all three. `code-reviewer` runs on the `model` lane, which is **everything the
|
|
74
|
+
two cheap lanes did not claim** — code, a rulebook document, an unclassifiable
|
|
75
|
+
path, a derived artifact git does not report as drift, or anything a risk flag
|
|
76
|
+
escalated;
|
|
77
|
+
`security-scanner` when a change touches auth, secrets, parsing, or outbound
|
|
78
|
+
calls; `prose-reviewer` when it touches the documents that instruct agents —
|
|
79
|
+
rules, skills, agent specs, decision records, this file, the README. Those last two are
|
|
80
|
+
**lane-independent and may only add** — the lane is a floor, never a ceiling.
|
|
81
|
+
`.claude/rules/workflow.md` carries the ladder and what the cheap lanes give
|
|
82
|
+
up. Blocking findings are resolved, not argued with, and the
|
|
83
|
+
`pr-ship` skill drives the fan-out. **No hook launches them** — a gate here is
|
|
84
|
+
a session following a written rule, so "the gate ran" is a claim, not a
|
|
85
|
+
guarantee. The mechanical enforcement below is a different thing, and the
|
|
86
|
+
difference is worth keeping straight.
|
|
87
|
+
- **Enforcement is mechanical.** `guard-core-purity` catches an impure edit to
|
|
88
|
+
the core the moment it lands; `guard-web-boundary` keeps the frontend off the
|
|
89
|
+
backend; `guard-secret-file` refuses an edit that writes a credential — by the
|
|
90
|
+
file's name or by a value in its text, from the one vocabulary in
|
|
91
|
+
`.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses pre-commit
|
|
92
|
+
bypasses; `guard-bash` refuses
|
|
93
|
+
the "Never" tier — force-pushing a shared branch, a production deploy, a
|
|
94
|
+
filesystem wipe — and carries the kill switch; `gate-stop-dod` refuses to end
|
|
95
|
+
the session while a Definition-of-Done check fails. If a hook blocks you, fix
|
|
96
|
+
the cause; never route around a hook.
|
|
97
|
+
- **Enforcement is a pattern you can apply again.** Each of those hooks is one
|
|
98
|
+
stated invariant + one mechanical check + one test — the pattern is written down
|
|
99
|
+
in `.claude/rules/invariants.md`, and the `new-invariant` skill walks you
|
|
100
|
+
through adding one. The hooks that ship here are **examples, not laws**: if the
|
|
101
|
+
invariant they guard is not load-bearing in this project, delete it and spend
|
|
102
|
+
the slot on one that is.
|
|
103
|
+
- **There is a brake, and it is a real file.** `touch
|
|
104
|
+
~/.claude/__PROJECT_NAME__-loop-STOP` and `guard-bash` denies every merge
|
|
105
|
+
until it is removed. Everything short of the merge stays allowed on purpose:
|
|
106
|
+
finish the task, push the branch, open the PR, write the journal, stop.
|
|
107
|
+
Stopping cleanly never means losing the work.
|
|
108
|
+
- **Work comes from the queue, through an adapter.** The `loop` skill selects via
|
|
109
|
+
`.claude/scripts/queue/index.mjs`, which reads whichever queue
|
|
110
|
+
`.claude/queue.json` names — the Agent queue in `PLAN.md` by default, issues in
|
|
111
|
+
this repository once it has a remote. An empty queue **ends the session**; it is
|
|
112
|
+
never a cue to invent work, and the agent never files its own work items.
|
|
113
|
+
|
|
114
|
+
## The elevated paths of this project
|
|
115
|
+
|
|
116
|
+
Tier 2 in `.claude/rules/autonomy.md` names *kinds* of change. This block names
|
|
117
|
+
the **paths** in this repository where those kinds live, and
|
|
118
|
+
`.claude/scripts/detect-missed-gate.mjs` reads it — so a path that is not declared
|
|
119
|
+
is a path the gate sweep cannot see.
|
|
120
|
+
|
|
121
|
+
```elevated-paths
|
|
122
|
+
packages/db/src/
|
|
123
|
+
.claude/
|
|
124
|
+
.agents/
|
|
125
|
+
.codex/
|
|
126
|
+
AGENTS.md
|
|
127
|
+
docs/decisions/
|
|
128
|
+
.github/workflows/
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The entries that earn their place first are the ones that *disarm* the rest —
|
|
132
|
+
wherever this project keeps its rulebook, its hooks and its CI definition. A
|
|
133
|
+
merge that rewrites the Never tier, unwires a hook or edits what CI runs should
|
|
134
|
+
never pass unreviewed. The rest of the block is whatever this particular shape
|
|
135
|
+
has, so read the list above rather than this paragraph: the two are maintained
|
|
136
|
+
separately, and a project that re-composes the block leaves prose describing
|
|
137
|
+
somebody else's repository.
|
|
138
|
+
|
|
139
|
+
**They are a seed, not a law — the list is yours to extend.** It is what every
|
|
140
|
+
generated shape has; a real project accumulates more (auth handlers, billing, a
|
|
141
|
+
credentials module, a migration directory). Add a path the same day you add the
|
|
142
|
+
code, because the gap between the two is exactly the window in which a change
|
|
143
|
+
slips through unreviewed.
|
|
144
|
+
|
|
145
|
+
The declaration is **composed, not centralised**: the sweep unions this block with
|
|
146
|
+
every `elevated-paths` block in `.claude/rules/`, so a stack layer declares the
|
|
147
|
+
paths that only exist in its shape. A gate declared over a directory this project
|
|
148
|
+
does not have would report "clean" while looking nowhere.
|
|
149
|
+
|
|
150
|
+
Nothing about this list is retroactive. Installing the sweep into a repo with
|
|
151
|
+
history means passing `--epoch <the day you installed it>` once, or the first run
|
|
152
|
+
reports every merge that predates the gate.
|
|
153
|
+
|
|
154
|
+
## Foot-guns
|
|
155
|
+
|
|
156
|
+
- Don't "simplify" a handler by calling a model directly — the usecase layer is
|
|
157
|
+
mandatory even when it looks like ceremony.
|
|
158
|
+
- Don't inline `Date.now()`/randomness into the core "just this once" — inject
|
|
159
|
+
them; the hook will refuse anyway.
|
|
160
|
+
- Don't weaken a failing test to get green — a red check is information, and
|
|
161
|
+
test integrity is a blocking review finding.
|
|
162
|
+
- After a deploy, CI-green ≠ runtime-healthy: verify per the README, and on
|
|
163
|
+
regression revert first (`.claude/rules/autonomy.md`, "Post-deploy
|
|
164
|
+
verification").
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# __PROJECT_NAME__
|
|
2
2
|
|
|
3
3
|
> **Top rule — commit/PR attribution: NEVER include co-authored or AI-attribution information.**
|
|
4
|
-
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By:
|
|
4
|
+
> Do not add `Co-Authored-By:` trailers (e.g. `Co-Authored-By: AI Assistant …`), `Generated with an AI coding agent`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
|
|
5
5
|
> This project runs under an agent operating system: the rules below are not
|
|
6
6
|
> suggestions — the important ones are enforced by hooks and gates at the tool
|
|
7
7
|
> layer.
|
|
8
8
|
|
|
9
|
+
## One operating system, two harnesses
|
|
10
|
+
|
|
11
|
+
This rulebook serves both Claude Code and Codex. The generator authors it as
|
|
12
|
+
`CLAUDE.md` and publishes the same text as `AGENTS.md`, so neither harness gets
|
|
13
|
+
a weaker policy. The `.claude/` directory keeps its historical name but holds
|
|
14
|
+
the shared rules, hooks, scripts and agent specifications. Claude Code discovers
|
|
15
|
+
its skills there; Codex receives the matching repository skills in
|
|
16
|
+
`.agents/skills/` and its native agent and hook configuration in `.codex/`.
|
|
17
|
+
The derivation and rollback contract is recorded in
|
|
18
|
+
`docs/decisions/codex-adapter.md`.
|
|
19
|
+
|
|
9
20
|
This project runs under an agent operating system: the rules below are not
|
|
10
21
|
suggestions — the important ones are enforced by hooks and gates at the tool
|
|
11
22
|
layer.
|
|
@@ -42,10 +53,13 @@ them all; they are one rulebook.
|
|
|
42
53
|
|
|
43
54
|
- **TDD, without exception.** The failing test comes first — use the
|
|
44
55
|
`test-writer` agent for it. See `.claude/rules/workflow.md`.
|
|
45
|
-
- **Check the
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
- **Check the premises at both ends.** A queue item is a claim about the code, and
|
|
57
|
+
nothing downstream re-reads the file it was wrong about — the `check-premises`
|
|
58
|
+
skill runs between taking the item and the failing test, and a false load-bearing
|
|
59
|
+
claim stops the task instead of quietly re-aiming it. It runs **again before the
|
|
60
|
+
gate**, on the prose the task itself wrote: a behaviour claim with nothing behind
|
|
61
|
+
it is `UNMEASURED`, and it is deleted or turned into a pointer to its test rather
|
|
62
|
+
than left for a reviewer to find.
|
|
49
63
|
- **One task, one branch — and merge via PR.** Every unit of work gets its own
|
|
50
64
|
short-lived branch; the default branch is never committed to directly. Once
|
|
51
65
|
the project has a remote and CI, changes reach it through the PR flow (local
|
|
@@ -53,17 +67,29 @@ them all; they are one rulebook.
|
|
|
53
67
|
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
54
68
|
session may touch this repo at the same time, the branch lives in its own
|
|
55
69
|
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
56
|
-
- **Gates.**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
- **Gates.** Every PR is routed before it is reviewed — the
|
|
71
|
+
`decision-router` picks the cheapest lane the change earns
|
|
72
|
+
(`deterministic` → `fast-path` → `model`), and risk flags escalate ahead of
|
|
73
|
+
all three. `code-reviewer` runs on the `model` lane, which is **everything the
|
|
74
|
+
two cheap lanes did not claim** — code, a rulebook document, an unclassifiable
|
|
75
|
+
path, a derived artifact git does not report as drift, or anything a risk flag
|
|
76
|
+
escalated;
|
|
77
|
+
`security-scanner` when a change touches auth, secrets, parsing, or outbound
|
|
78
|
+
calls; `prose-reviewer` when it touches the documents that instruct agents —
|
|
79
|
+
rules, skills, agent specs, decision records, this file, the README. Those last two are
|
|
80
|
+
**lane-independent and may only add** — the lane is a floor, never a ceiling.
|
|
81
|
+
`.claude/rules/workflow.md` carries the ladder and what the cheap lanes give
|
|
82
|
+
up. Blocking findings are resolved, not argued with, and the
|
|
60
83
|
`pr-ship` skill drives the fan-out. **No hook launches them** — a gate here is
|
|
61
84
|
a session following a written rule, so "the gate ran" is a claim, not a
|
|
62
85
|
guarantee. The mechanical enforcement below is a different thing, and the
|
|
63
86
|
difference is worth keeping straight.
|
|
64
87
|
- **Enforcement is mechanical.** `guard-core-purity` catches an impure edit to
|
|
65
88
|
the core the moment it lands; `guard-web-boundary` keeps the frontend off the
|
|
66
|
-
backend; `
|
|
89
|
+
backend; `guard-secret-file` refuses an edit that writes a credential — by the
|
|
90
|
+
file's name or by a value in its text, from the one vocabulary in
|
|
91
|
+
`.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses pre-commit
|
|
92
|
+
bypasses; `guard-bash` refuses
|
|
67
93
|
the "Never" tier — force-pushing a shared branch, a production deploy, a
|
|
68
94
|
filesystem wipe — and carries the kill switch; `gate-stop-dod` refuses to end
|
|
69
95
|
the session while a Definition-of-Done check fails. If a hook blocks you, fix
|
|
@@ -95,13 +121,20 @@ is a path the gate sweep cannot see.
|
|
|
95
121
|
```elevated-paths
|
|
96
122
|
packages/db/src/
|
|
97
123
|
.claude/
|
|
124
|
+
.agents/
|
|
125
|
+
.codex/
|
|
126
|
+
AGENTS.md
|
|
127
|
+
docs/decisions/
|
|
98
128
|
.github/workflows/
|
|
99
129
|
```
|
|
100
130
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
The entries that earn their place first are the ones that *disarm* the rest —
|
|
132
|
+
wherever this project keeps its rulebook, its hooks and its CI definition. A
|
|
133
|
+
merge that rewrites the Never tier, unwires a hook or edits what CI runs should
|
|
134
|
+
never pass unreviewed. The rest of the block is whatever this particular shape
|
|
135
|
+
has, so read the list above rather than this paragraph: the two are maintained
|
|
136
|
+
separately, and a project that re-composes the block leaves prose describing
|
|
137
|
+
somebody else's repository.
|
|
105
138
|
|
|
106
139
|
**They are a seed, not a law — the list is yours to extend.** It is what every
|
|
107
140
|
generated shape has; a real project accumulates more (auth handlers, billing, a
|
|
@@ -20,45 +20,12 @@ journal records history; the queues state only what is next.
|
|
|
20
20
|
<!-- Decisions and Tier-2 work waiting on a human. State what is needed, e.g.:
|
|
21
21
|
- decide: retention policy before real data (RemovalPolicy flip)
|
|
22
22
|
-->
|
|
23
|
+
## Where the journal is
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
`journal/YYYY-MM.md` — one file per month, newest-on-top inside each. The
|
|
26
|
+
convention and the field list are in `journal/README.md`.
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
The fields exist so an entry can be visibly **incomplete**. A journal with no
|
|
32
|
-
stated shape decays into a diary that reads fine and proves nothing.
|
|
33
|
-
|
|
34
|
-
<!-- Template — copy the block, drop the fields that do not apply (`unblocked` is
|
|
35
|
-
the exception: it is stated even when the answer is "nothing"):
|
|
36
|
-
|
|
37
|
-
### <one-line summary of the session>
|
|
38
|
-
|
|
39
|
-
- **done** — what landed, one line each, with the PR reference
|
|
40
|
-
- **escalated** — what stopped, and the diagnosis: what failed, what was tried,
|
|
41
|
-
the current hypothesis, and the one question whose answer unblocks it
|
|
42
|
-
- **reviewed** — changes that went through a reviewer gate, and what it returned
|
|
43
|
-
- **stopped at** — which stop condition ended the session (or "checkpoint,
|
|
44
|
-
still running")
|
|
45
|
-
- **unblocked** — what the session's closes released. **The field that is never
|
|
46
|
-
dropped** — a missing line and an unpaid debt read identically from outside,
|
|
47
|
-
and this is the only record of whether anyone looked. It has **three**
|
|
48
|
-
answers and they do not substitute for each other: the items that were
|
|
49
|
-
waiting, by name; "nothing was waiting", where the queue carries dependency
|
|
50
|
-
links and none pointed here; and "this queue has no dependency links", where
|
|
51
|
-
it cannot answer at all — a flat-list queue is **absent**, not satisfied, and
|
|
52
|
-
writing "nothing was waiting" there claims a look that no query could perform
|
|
53
|
-
- **queue hygiene** — queue state the session found unreliable and **reported**:
|
|
54
|
-
a stale marker, a dependency already satisfied, an item that describes work
|
|
55
|
-
already done. Reported, never corrected in passing — quietly fixing the
|
|
56
|
-
metadata destroys the evidence that the metadata is unreliable
|
|
57
|
-
- **cost** — the counts the session actually observed: reviewer subagents run,
|
|
58
|
-
CI runs consumed (re-runs included — the cheapest signal that a task fought
|
|
59
|
-
its tests), deploys triggered
|
|
60
|
-
|
|
61
|
-
A field the session cannot observe stays **visibly empty — never estimated**.
|
|
62
|
-
A plausible number will be believed, by the next reader and by the next run
|
|
63
|
-
reasoning about its own budget. Leave the gap; it is information.
|
|
64
|
-
-->
|
|
28
|
+
The heading here is deliberately **not** `## Journal`: a pointer under that name
|
|
29
|
+
still sends a session into this file to look, and keeping this file small is the
|
|
30
|
+
point. `plan-md.mjs` resolves the two queue headings above by name and is not
|
|
31
|
+
affected either way.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Why the close command looks the way it does
|
|
2
|
+
|
|
3
|
+
The rule lives in the `loop` skill, section 9 ("State updates bracket the task").
|
|
4
|
+
This file is the evidence behind each argument in the `recordCompletedTier`
|
|
5
|
+
call, and it is not loaded into any session. Read it before "simplifying" that
|
|
6
|
+
command.
|
|
7
|
+
|
|
8
|
+
Four of the five were live defects; the fifth (`execFileSync` with an argument
|
|
9
|
+
array) is prophylaxis. They do **not** fail the same way, and the difference is
|
|
10
|
+
the whole reason each is pinned rather than left to judgement:
|
|
11
|
+
|
|
12
|
+
| argument | how it fails when wrong |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `-z` | silently, **permissively** — records `normal` for an elevated change |
|
|
15
|
+
| `runDir` | silently, but toward a **stop** nobody can clear |
|
|
16
|
+
| the diff form | **loudly** — the call refuses on an empty file list |
|
|
17
|
+
| `env: withoutGitLocation()` | loudly, for the sha-to-sha form pinned here; it is prophylaxis for this call and load-bearing for every symbolic-ref spawn |
|
|
18
|
+
| `execFileSync` array | it does not; nothing is interpolated into a shell today |
|
|
19
|
+
|
|
20
|
+
Exactly one of the five fails silently in the permissive direction. Do not
|
|
21
|
+
round that up.
|
|
22
|
+
|
|
23
|
+
## `runDir`
|
|
24
|
+
|
|
25
|
+
It is what resets the escalation streak: the close is the "something landed in
|
|
26
|
+
between" that makes the run-level stop "two escalations **in a row**" mean two
|
|
27
|
+
in a row. Omit it and the counter only ever rises — two escalations an hour
|
|
28
|
+
apart end the run however many tasks closed between them.
|
|
29
|
+
|
|
30
|
+
The parameter is optional in the signature. So leaving it out does not throw; it
|
|
31
|
+
fails quietly, and in the direction of a stop nobody can clear.
|
|
32
|
+
|
|
33
|
+
## `<merge-sha>^1 <merge-sha>`, never `origin/<default>...<merge-sha>`
|
|
34
|
+
|
|
35
|
+
A three-dot diff is `merge-base..head`. Once the remote-tracking ref includes the
|
|
36
|
+
merge — which is exactly its state at the moment this step runs — the merge base
|
|
37
|
+
_is_ the merge, so the file list comes back **empty** and the call refuses.
|
|
38
|
+
|
|
39
|
+
The three-dot form appeared to work for a while. It only ever worked while the
|
|
40
|
+
local ref happened to be stale: an accident of ordering, not a property of the
|
|
41
|
+
command.
|
|
42
|
+
|
|
43
|
+
## `-z`, and splitting on `\0`
|
|
44
|
+
|
|
45
|
+
With `core.quotePath` on (the default) a path containing a non-ASCII byte
|
|
46
|
+
arrives quoted and octal-escaped — `".claude/caf\303\251.mjs"`. That string
|
|
47
|
+
matches no declared elevated prefix, so an elevated change records as `normal`.
|
|
48
|
+
Separately, a filename containing a newline splits into two junk paths. `-z`
|
|
49
|
+
removes both failure modes at once.
|
|
50
|
+
|
|
51
|
+
## `execFileSync` with an argument array, never a shell string
|
|
52
|
+
|
|
53
|
+
Nothing in the current command is interpolated into a shell. The point of the
|
|
54
|
+
argument array is that the next session cannot start doing so: the safe form has
|
|
55
|
+
to be the one already written down, or it will not be the one copied.
|
|
56
|
+
|
|
57
|
+
## `env: withoutGitLocation()`
|
|
58
|
+
|
|
59
|
+
Run under a git hook, this command inherits `GIT_DIR` — absolute, when the hook
|
|
60
|
+
fired in a worktree — and then resolves against **another repository**.
|
|
61
|
+
|
|
62
|
+
Measured, from repository A with `GIT_DIR` pointing at an unrelated B:
|
|
63
|
+
|
|
64
|
+
| form | result |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `<sha>^1 <sha>` — the form pinned above | `fatal: ambiguous argument … unknown revision`, so `execFileSync` throws |
|
|
67
|
+
| `HEAD^1 HEAD`, or any `origin/<default>…` form | B's file list, exit 0, nothing wrong-looking |
|
|
68
|
+
|
|
69
|
+
So for this call, as written, the redirect fails loudly: a sha-to-sha diff is an
|
|
70
|
+
object-database lookup, and B either lacks the object (fatal) or shares it and
|
|
71
|
+
answers identically. The silent wrong answer needs a **ref that resolves
|
|
72
|
+
differently in the other repo** — which is the diff form this record rejects
|
|
73
|
+
two sections above.
|
|
74
|
+
|
|
75
|
+
That is why the argument stays anyway. It costs nothing, it is the same line
|
|
76
|
+
every other script here uses, and the moment someone "simplifies" the diff back
|
|
77
|
+
to a symbolic ref it becomes the only thing standing between a git hook and a
|
|
78
|
+
tier recorded from somebody else's repository.
|
|
79
|
+
|
|
80
|
+
The sweep that checks `.mjs` sources for this line cannot read markdown, so the
|
|
81
|
+
copy inside the rulebook's fenced code blocks is covered by a second sweep over
|
|
82
|
+
those blocks. Both live in the generator that produced this project, not here —
|
|
83
|
+
if you change the command above, that is the pair to re-check.
|
|
84
|
+
|
|
85
|
+
## The shape the dangerous one has
|
|
86
|
+
|
|
87
|
+
`-z` is the one that never announced itself: it returned a plausible answer — a
|
|
88
|
+
tier of `normal` — and a plausible answer is what a run acts on, with nothing
|
|
89
|
+
left behind to say it was never measured.
|
|
90
|
+
|
|
91
|
+
The others are pinned because they are cheap to keep and expensive to
|
|
92
|
+
rediscover, not because they all failed the same way. Reading the list as five
|
|
93
|
+
silent bypasses is how the one that really is silent stops standing out.
|
|
94
|
+
|
|
95
|
+
The empty-file-list case is the deliberate counter-example: `recordCompletedTier`
|
|
96
|
+
**throws** rather than guessing `normal`, because an absence and a zero look
|
|
97
|
+
identical in a count and mean opposite things. That refusal is the behaviour the
|
|
98
|
+
loop skill relies on — do not soften it into a default.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Codex adapter: derived parity
|
|
2
|
+
|
|
3
|
+
Status: accepted for AR-113.
|
|
4
|
+
|
|
5
|
+
## Decision
|
|
6
|
+
|
|
7
|
+
Claude-shaped files remain the authoring surface. The generator derives the
|
|
8
|
+
Codex rulebook (`AGENTS.md`), skills (`.agents/`), custom agents and hook
|
|
9
|
+
wiring (`.codex/`); the generated files are checked for drift in the generator
|
|
10
|
+
repository and are materialised into a
|
|
11
|
+
generated project as its local, versioned operating system. A generated
|
|
12
|
+
project does not ship the generator's projector. Its Claude and Codex files are
|
|
13
|
+
versioned snapshots: a downstream project that deliberately changes one side
|
|
14
|
+
must either make the equivalent local change on the other side or take a newer
|
|
15
|
+
generated release. There is no downstream automatic drift check.
|
|
16
|
+
|
|
17
|
+
## Why
|
|
18
|
+
|
|
19
|
+
Two hand-maintained rulebooks inevitably diverge. Derivation keeps the generated
|
|
20
|
+
snapshots aligned while preserving native Codex formats. The adapter translates
|
|
21
|
+
the fields for which Codex has native controls and does not invent policy.
|
|
22
|
+
|
|
23
|
+
Claude agent `tools` allowlists have no equivalent custom-agent allowlist in the
|
|
24
|
+
documented Codex TOML schema. The adapter therefore uses those fields only to
|
|
25
|
+
choose `read-only` or `workspace-write` sandboxing; the exact Claude tool list is
|
|
26
|
+
not carried over. This is a known parity limit, not an implicit restriction.
|
|
27
|
+
|
|
28
|
+
## Risk and rollback
|
|
29
|
+
|
|
30
|
+
The main risks are generated-file drift, downstream edits to only one snapshot,
|
|
31
|
+
and unsupported Claude shapes. In the generator, the adapter fails loudly when
|
|
32
|
+
it cannot derive a portable hook command and its drift check catches stale
|
|
33
|
+
output. Generated projects rely on review for subsequent local parity. Rollback
|
|
34
|
+
is deleting the derived Codex files from the generated project and reverting the
|
|
35
|
+
generator change; Claude files remain usable.
|
|
36
|
+
|
|
37
|
+
Hook launch also depends on `git rev-parse --show-toplevel`, and that lookup has
|
|
38
|
+
two failure modes rather than one. Both are visible in `.codex/hooks.json`.
|
|
39
|
+
|
|
40
|
+
The mild one: if Git is absent or the session is outside a worktree, the
|
|
41
|
+
generated command exits before the hook runs and therefore cannot return the
|
|
42
|
+
blocking exit code — a fail-open boundary.
|
|
43
|
+
|
|
44
|
+
🔴 The sharp one, stated because a reader who only saw the first would infer
|
|
45
|
+
cover that is not there: **the root is resolved from the session's working
|
|
46
|
+
directory, which the agent controls, so the hook that runs is whichever file
|
|
47
|
+
sits at that path.** An agent that creates a repository of its own and works
|
|
48
|
+
from inside it supplies its own `.claude/hooks/*.mjs`. Measured on this branch:
|
|
49
|
+
the emitted `guard-bash` command exits 2 on a force-push payload from the real
|
|
50
|
+
repository root, and exits 0 from an agent-created repository holding a no-op
|
|
51
|
+
hook of the same name. That is guard *substitution*, not merely fail-open.
|
|
52
|
+
|
|
53
|
+
It is documented rather than fixed, deliberately, and the reasoning is worth
|
|
54
|
+
keeping because the obvious alternatives are worse. Codex provides no
|
|
55
|
+
project-root variable for repo-local hooks — the equivalent of Claude Code's
|
|
56
|
+
`$CLAUDE_PROJECT_DIR` does not exist, and the [official hooks
|
|
57
|
+
documentation](https://learn.chatgpt.com/docs/hooks) *recommends* resolving from
|
|
58
|
+
the Git root for exactly this case, with commands run in the session `cwd`. So
|
|
59
|
+
this adapter follows the documented pattern rather than inventing one. Pinning an
|
|
60
|
+
absolute path at install time would diverge from that pattern and break a rig
|
|
61
|
+
that is moved, copied or checked out as a worktree, and it would buy protection
|
|
62
|
+
only against an agent that deliberately builds a decoy repository — the
|
|
63
|
+
"determined evasion" `.claude/rules/invariants.md` puts outside every guard's
|
|
64
|
+
threat model: *the guard targets drift, not an adversary.*
|
|
65
|
+
|
|
66
|
+
What this costs is worth naming, and the first draft of this paragraph understated
|
|
67
|
+
it. "Drift-resistant, not adversary-resistant" is wrong: **no adversary is
|
|
68
|
+
required.** A session whose cwd sits inside any repository that is not this one —
|
|
69
|
+
a vendored dependency with its own `.git`, a fixture repo, a scratch `git init` —
|
|
70
|
+
resolves a root with no `.claude/hooks` in it, and the emitted command exits 1.
|
|
71
|
+
Measured: exit 2 from the rig root, **exit 1 from a nested repository**, exit 1
|
|
72
|
+
outside a worktree. Exit 1 is not the blocking code, so the hook layer is simply
|
|
73
|
+
gone, silently, for ordinary reasons.
|
|
74
|
+
|
|
75
|
+
So state it plainly: under Codex the hook layer holds when the session works from
|
|
76
|
+
the rig's own root and is absent otherwise, which is one layer less than under
|
|
77
|
+
Claude Code, where the harness sets the root. The layers behind it — review, the
|
|
78
|
+
test suite, CI — are unchanged, and they are what this rig relies on for the
|
|
79
|
+
Codex path.
|
|
80
|
+
|
|
81
|
+
## Schema and executable contracts
|
|
82
|
+
|
|
83
|
+
The emitted agent fields are `name`, `description`, `sandbox_mode`, and
|
|
84
|
+
`developer_instructions`, matching the documented Codex custom-agent TOML.
|
|
85
|
+
For hooks, the [official Codex hooks documentation](https://learn.chatgpt.com/docs/hooks)
|
|
86
|
+
documents `tool_input.command` for both `Bash` and `apply_patch` and requires a
|
|
87
|
+
string `command` when a hook replaces that input. The same document is what makes
|
|
88
|
+
the `Bash`-only tool-name gate in `guard-bash` and `block-no-verify` correct
|
|
89
|
+
rather than narrow: Codex's canonical matcher name for its shell/exec tool **is**
|
|
90
|
+
`Bash`, and its editing tool is `apply_patch`, which also matches the `Edit` and
|
|
91
|
+
`Write` aliases. Recorded here because it is an external fact no test in this
|
|
92
|
+
repository can pin, and a reader who assumes otherwise will widen those gates to
|
|
93
|
+
names the platform never sends. The guards therefore accept
|
|
94
|
+
that documented string form. A command that is ABSENT still fails open — a
|
|
95
|
+
payload the hook does not understand — while one that is present and is not a
|
|
96
|
+
shape the normalizer reads is REFUSED, because that is a condition it detects
|
|
97
|
+
and can report rather than an error it threw. See `codex.test.ts` › "refuses,
|
|
98
|
+
rather than failing open, when apply_patch command is supplied as %s".
|
|
99
|
+
Hook output is the JSON `description` plus event arrays accepted by
|
|
100
|
+
`.codex/hooks.json`; command portability is carried by generated POSIX and
|
|
101
|
+
Windows commands. In this generated project, `.claude/settings.json` and
|
|
102
|
+
`.codex/hooks.json` are the two concrete hook-wiring snapshots to review.
|
|
103
|
+
|
|
104
|
+
`apply_patch` inspection also has aggregate, per-patch work bounds. In
|
|
105
|
+
particular, `MAX_PATCH_PATH_COMPONENTS` counts destination path components
|
|
106
|
+
across the whole patch, so the file capacity of one patch decreases as path
|
|
107
|
+
depth increases. When that bound is reached, split the edit into multiple
|
|
108
|
+
smaller patches.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Why a fail-open guard must do provably bounded work
|
|
2
|
+
|
|
3
|
+
The rule this record explains lives in `.claude/rules/invariants.md`, under
|
|
4
|
+
"What the enforcement actually is — stated exactly". This file is the evidence
|
|
5
|
+
behind it, and it is not loaded into any session — read it when the rule looks
|
|
6
|
+
like an over-reaction.
|
|
7
|
+
|
|
8
|
+
## The trap
|
|
9
|
+
|
|
10
|
+
Fail-open is the right default: a guard that throws must not make the session
|
|
11
|
+
unusable. But fail-open turns **every line of work the guard does into a
|
|
12
|
+
potential total bypass**. An exception, a timeout, or a stack overflow anywhere
|
|
13
|
+
inside the guard resolves to _allow_ — and not just for the rule that broke, for
|
|
14
|
+
**all** of them. The guard does not fail loudly and get fixed; it goes quiet and
|
|
15
|
+
keeps reporting success.
|
|
16
|
+
|
|
17
|
+
## What actually happened
|
|
18
|
+
|
|
19
|
+
Three review rounds on one hook produced three separate total bypasses. All
|
|
20
|
+
three were the same shape: an input made the guard's own code throw, and the
|
|
21
|
+
fail-open catch turned that into permission.
|
|
22
|
+
|
|
23
|
+
| the code | the failure | the result |
|
|
24
|
+
| ---------------------------------------------------------- | -------------------------- | ---------- |
|
|
25
|
+
| an unbounded `spread` over an input-derived array | `RangeError` | allow |
|
|
26
|
+
| a recursive expansion whose bound was per-group, not total | stack overflow | allow |
|
|
27
|
+
| a quadratic loop | killed by the hook timeout | allow |
|
|
28
|
+
|
|
29
|
+
One of them was worse than a plain bypass: when its bound was hit it silently
|
|
30
|
+
dropped part of the input, so whole commands went unexamined while the guard
|
|
31
|
+
reported that it had looked.
|
|
32
|
+
|
|
33
|
+
That is why the test is not "is it fast enough on realistic input" but **"can
|
|
34
|
+
any input make it do unbounded work at all"**. The first question has a
|
|
35
|
+
comfortable answer for all three of the defects above.
|
|
36
|
+
|
|
37
|
+
## Why the corollary is "prefer deleting a rule to adding one"
|
|
38
|
+
|
|
39
|
+
Each of those three bypasses arrived in a commit whose stated purpose was to
|
|
40
|
+
make the guard _stricter_. That is the uncomfortable part, and it is the reason
|
|
41
|
+
the rule reads the way it does: subtraction cannot introduce this class of
|
|
42
|
+
defect, and addition routinely does. A guard that checks less, reliably, beats a
|
|
43
|
+
guard that checks more until the day an input makes it check nothing.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Why the router's prose set and the gate sweep's inert set differ
|
|
2
|
+
|
|
3
|
+
The rule lives in `.claude/rules/workflow.md`, under "PR flow". This file
|
|
4
|
+
explains the one part of it that reads like an inconsistency and is not, so that
|
|
5
|
+
nobody "tidies" the two sets into agreement. It is not loaded into any session.
|
|
6
|
+
|
|
7
|
+
## The sets
|
|
8
|
+
|
|
9
|
+
Three different mechanisms each carry their own notion of "this file is only
|
|
10
|
+
words":
|
|
11
|
+
|
|
12
|
+
**The first two carve out the rulebook**, and only then differ; the third runs
|
|
13
|
+
*after* the sweep has already answered, so it sees only what survived:
|
|
14
|
+
|
|
15
|
+
| mechanism | rulebook exemption | inert / prose after it | consequence |
|
|
16
|
+
| ------------------------ | ------------------------------------------ | ------------------------------------------ | --------------------------------------------------- |
|
|
17
|
+
| the Tier-2 gate sweep | `isRulebook` in `detect-missed-gate.mjs` | `.md`, `.mdx`, non-provisioning test paths | does not escalate on the elevated-path ground alone |
|
|
18
|
+
| `decision-router.mjs` | `isRulebookPath`, checked before extension | `.md`, `.txt` | may route to the `fast-path` prose lane |
|
|
19
|
+
| the queue's elevated ration (`queue/state.mjs`) | none of its own — it classifies what `elevatedPathsIn` already returned, so only **rulebook** markdown ever reaches it | `.md` (`executesNothing`) | records `elevated-prose`, which does **not** space the next item |
|
|
20
|
+
|
|
21
|
+
The third row is the one added last, and its question is different again: not
|
|
22
|
+
"does this need the gate" nor "can a prose reviewer judge it alone", but **"can
|
|
23
|
+
a merge of this compound into a broken runtime overnight"**. That is why it
|
|
24
|
+
excludes `.mdx` where the sweep includes it — same reasoning as the router's,
|
|
25
|
+
one row below — and why it is `.md` only rather than the sweep's set. Its two
|
|
26
|
+
predicates live in `detect-missed-gate.mjs` side by side so *those two* cannot
|
|
27
|
+
drift; that is a statement about one file, **not** a rule that every markdown
|
|
28
|
+
test in the rig belongs there. The router's sets stay where they are, for the
|
|
29
|
+
reason this whole record exists.
|
|
30
|
+
|
|
31
|
+
**Neither row applies to a decision record, including this one.** Both
|
|
32
|
+
predicates recognise `CLAUDE.md` anywhere, everything under `.claude/`, and —
|
|
33
|
+
since these records were extracted — everything under `docs/decisions/`, through
|
|
34
|
+
the one shared `isDecisionRecord`. A change here escalates the sweep and takes
|
|
35
|
+
the `model` lane, exactly as the rule it explains would. The router's call is
|
|
36
|
+
not a redundant copy of the sweep's; deleting it silently drops this file to the
|
|
37
|
+
prose lane.
|
|
38
|
+
|
|
39
|
+
Without that exemption, declaring a path elevated was a no-op for every `.md`
|
|
40
|
+
under it — the defect that put it there — and it is why moving this rationale
|
|
41
|
+
out of `.claude/` needed a code change rather than only a declaration:
|
|
42
|
+
`isInert` is consulted first, so the declaration alone would have reported clean
|
|
43
|
+
over every record.
|
|
44
|
+
|
|
45
|
+
The **remaining** difference is the inert/prose column, and no set contains
|
|
46
|
+
another. That is deliberate, and every difference earns its keep. One more
|
|
47
|
+
asymmetry belongs to the router alone: it **folds case** before its rulebook
|
|
48
|
+
check, because there folding can only escalate, while a sweep that folded would
|
|
49
|
+
name a path the repository does not have.
|
|
50
|
+
|
|
51
|
+
## Why the sweep does not also take `.txt`
|
|
52
|
+
|
|
53
|
+
Widening the sweep to `.txt` would stop `requirements.txt` inside an elevated
|
|
54
|
+
directory from escalating. A dependency manifest is the opposite of inert — it
|
|
55
|
+
is one of the risk flags — and its extension happens to be `.txt`.
|
|
56
|
+
|
|
57
|
+
## Why neither the router nor the ration takes `.mdx`
|
|
58
|
+
|
|
59
|
+
Copying the sweep's `.mdx` into the router's prose set would put executable MDX
|
|
60
|
+
back on the prose lane. MDX carries components and imports; it is a program that
|
|
61
|
+
renders, not a document that is read. The ration reads it the same way and for
|
|
62
|
+
the same reason (`spacing-rations-mechanisms.md`): calling it prose there would
|
|
63
|
+
clear the spacing hold on a file this rig treats as a program, on the permissive
|
|
64
|
+
side.
|
|
65
|
+
|
|
66
|
+
## The shape of the mistake this prevents
|
|
67
|
+
|
|
68
|
+
Both edits look like clean-ups. Each removes an apparent inconsistency between
|
|
69
|
+
two lists that "obviously" mean the same thing, and each one silently downgrades
|
|
70
|
+
a real review. The lists mean different things because the questions differ:
|
|
71
|
+
"does this change need the elevated gate?" is not "can a prose reviewer judge
|
|
72
|
+
this alone?"
|