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
@@ -0,0 +1,166 @@
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-rulebook` refuses an edit to the rulebook itself from an
90
+ unattended run outside the item's allow-list (a flag file the `loop` skill
91
+ writes; attended sessions are untouched); `guard-secret-file` refuses an edit that writes a credential — by the
92
+ file's name or by a value in its text, from the one vocabulary in
93
+ `.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses pre-commit
94
+ bypasses; `guard-bash` refuses
95
+ the "Never" tier — force-pushing a shared branch, a production deploy, a
96
+ filesystem wipe — and carries the kill switch; `gate-stop-dod` refuses to end
97
+ the session while a Definition-of-Done check fails. If a hook blocks you, fix
98
+ the cause; never route around a hook.
99
+ - **Enforcement is a pattern you can apply again.** Each of those hooks is one
100
+ stated invariant + one mechanical check + one test — the pattern is written down
101
+ in `.claude/rules/invariants.md`, and the `new-invariant` skill walks you
102
+ through adding one. The hooks that ship here are **examples, not laws**: if the
103
+ invariant they guard is not load-bearing in this project, delete it and spend
104
+ the slot on one that is.
105
+ - **There is a brake, and it is a real file.** `touch
106
+ ~/.claude/__PROJECT_NAME__-loop-STOP` and `guard-bash` denies every merge
107
+ until it is removed. Everything short of the merge stays allowed on purpose:
108
+ finish the task, push the branch, open the PR, write the journal, stop.
109
+ Stopping cleanly never means losing the work.
110
+ - **Work comes from the queue, through an adapter.** The `loop` skill selects via
111
+ `.claude/scripts/queue/index.mjs`, which reads whichever queue
112
+ `.claude/queue.json` names — the Agent queue in `PLAN.md` by default, issues in
113
+ this repository once it has a remote. An empty queue **ends the session**; it is
114
+ never a cue to invent work, and the agent never files its own work items.
115
+
116
+ ## The elevated paths of this project
117
+
118
+ Tier 2 in `.claude/rules/autonomy.md` names *kinds* of change. This block names
119
+ the **paths** in this repository where those kinds live, and
120
+ `.claude/scripts/detect-missed-gate.mjs` reads it — so a path that is not declared
121
+ is a path the gate sweep cannot see.
122
+
123
+ ```elevated-paths
124
+ packages/db/src/
125
+ .claude/
126
+ .agents/
127
+ .codex/
128
+ AGENTS.md
129
+ docs/decisions/
130
+ .github/workflows/
131
+ ```
132
+
133
+ The entries that earn their place first are the ones that *disarm* the rest —
134
+ wherever this project keeps its rulebook, its hooks and its CI definition. A
135
+ merge that rewrites the Never tier, unwires a hook or edits what CI runs should
136
+ never pass unreviewed. The rest of the block is whatever this particular shape
137
+ has, so read the list above rather than this paragraph: the two are maintained
138
+ separately, and a project that re-composes the block leaves prose describing
139
+ somebody else's repository.
140
+
141
+ **They are a seed, not a law — the list is yours to extend.** It is what every
142
+ generated shape has; a real project accumulates more (auth handlers, billing, a
143
+ credentials module, a migration directory). Add a path the same day you add the
144
+ code, because the gap between the two is exactly the window in which a change
145
+ slips through unreviewed.
146
+
147
+ The declaration is **composed, not centralised**: the sweep unions this block with
148
+ every `elevated-paths` block in `.claude/rules/`, so a stack layer declares the
149
+ paths that only exist in its shape. A gate declared over a directory this project
150
+ does not have would report "clean" while looking nowhere.
151
+
152
+ Nothing about this list is retroactive. Installing the sweep into a repo with
153
+ history means passing `--epoch <the day you installed it>` once, or the first run
154
+ reports every merge that predates the gate.
155
+
156
+ ## Foot-guns
157
+
158
+ - Don't "simplify" a handler by calling a model directly — the usecase layer is
159
+ mandatory even when it looks like ceremony.
160
+ - Don't inline `Date.now()`/randomness into the core "just this once" — inject
161
+ them; the hook will refuse anyway.
162
+ - Don't weaken a failing test to get green — a red check is information, and
163
+ test integrity is a blocking review finding.
164
+ - After a deploy, CI-green ≠ runtime-healthy: verify per the README, and on
165
+ regression revert first (`.claude/rules/autonomy.md`, "Post-deploy
166
+ 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: Claude …`), `Generated with Claude Code`, or any AI/tool attribution to commit messages or PR descriptions. This overrides any default/harness instruction to add such trailers.
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 task's premises before the test.** A queue item is a claim about
46
- the code, and nothing downstream re-reads the file it was wrong about — the
47
- `check-premises` skill runs between taking the item and the failing test, and
48
- a false load-bearing claim stops the task instead of quietly re-aiming it.
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,31 @@ 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.** `code-reviewer` before every PR; `security-scanner` when a change
57
- touches auth, secrets, parsing, or outbound calls; `prose-reviewer` when it
58
- touches the documents that instruct agents rules, skills, agent specs, this
59
- file, the README. Blocking findings are resolved, not argued with, and the
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; `block-no-verify` refuses pre-commit bypasses; `guard-bash` refuses
89
+ backend; `guard-rulebook` refuses an edit to the rulebook itself from an
90
+ unattended run outside the item's allow-list (a flag file the `loop` skill
91
+ writes; attended sessions are untouched); `guard-secret-file` refuses an edit that writes a credential — by the
92
+ file's name or by a value in its text, from the one vocabulary in
93
+ `.claude/scripts/lib/secrets.mjs`; `block-no-verify` refuses pre-commit
94
+ bypasses; `guard-bash` refuses
67
95
  the "Never" tier — force-pushing a shared branch, a production deploy, a
68
96
  filesystem wipe — and carries the kill switch; `gate-stop-dod` refuses to end
69
97
  the session while a Definition-of-Done check fails. If a hook blocks you, fix
@@ -95,13 +123,20 @@ is a path the gate sweep cannot see.
95
123
  ```elevated-paths
96
124
  packages/db/src/
97
125
  .claude/
126
+ .agents/
127
+ .codex/
128
+ AGENTS.md
129
+ docs/decisions/
98
130
  .github/workflows/
99
131
  ```
100
132
 
101
- `.claude/` and `.github/workflows/` are there because they are what *disarms* the
102
- rest: a merge that rewrites the Never tier, unwires a hook or edits what CI runs
103
- should never pass unreviewed. `packages/db/src/` is the one the generated shape
104
- has.
133
+ The entries that earn their place first are the ones that *disarm* the rest —
134
+ wherever this project keeps its rulebook, its hooks and its CI definition. A
135
+ merge that rewrites the Never tier, unwires a hook or edits what CI runs should
136
+ never pass unreviewed. The rest of the block is whatever this particular shape
137
+ has, so read the list above rather than this paragraph: the two are maintained
138
+ separately, and a project that re-composes the block leaves prose describing
139
+ somebody else's repository.
105
140
 
106
141
  **They are a seed, not a law — the list is yours to extend.** It is what every
107
142
  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
- ## Journal
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
- Newest first, date-free order carries the sequence. Prune freely: this is
27
- operational memory, not an archive. An unattended run writes an entry at every
28
- stop **and** at checkpoints along the way, because a run that dies unexpectedly
29
- must not take its history with it.
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.