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
package/CHANGELOG.md CHANGED
@@ -11,6 +11,292 @@ Numbering is ordinary semver — **additive is a minor, a fix is a patch** — s
11
11
  that "I only take minors" remains a usable policy; 0.3.2 shipped additive
12
12
  content as a patch by the owner's call and stays recorded as one.
13
13
 
14
+ ## 0.6.0
15
+
16
+ **The loop now checks its premises against the tracker at three points, and
17
+ the rulebook cannot be edited from an unattended run.** Everything below is what
18
+ a newly scaffolded or `init`ed project receives.
19
+
20
+ **The released-hash table no longer depends on tags** (AR-35). It is built
21
+ from `templates/release-ledger.json` — the commit each version was published
22
+ from — so it now carries 0.5.0's bytes, 0.4.0's real bytes (the stale `v0.4.0`
23
+ tag is reported and ignored) and a 0.2.0 row; the consequence 0.5.0's notes
24
+ state for a rig upgraded without a readable manifest is closed for every
25
+ release the ledger records. This release itself is excluded, as every release
26
+ being prepared is: commit `.claude/.rig-manifest.json`.
27
+
28
+ ### Added
29
+
30
+ - **Revalidation at SELECT, BEFORE_PR and BEFORE_CLOSE** (AR-133, AR-134,
31
+ AR-135, AR-136). `.claude/scripts/revalidate.mjs` compares the item the run
32
+ took against the tracker's current state — at selection against this run's
33
+ take-up, before a PR against the item and the default branch, and before a
34
+ close against the item's fields and its dependants — and every point records
35
+ one evidence shape in the run directory; `revalidation-report.mjs` reads them
36
+ back. A close now proves it transitioned rather than reporting the write. The
37
+ points themselves have one spelling, `.claude/scripts/lib/revalidation-points.mjs`,
38
+ and the `loop` and `pr-ship` skills are checked against it in both directions
39
+ (AR-137).
40
+ - **`guard-rulebook`** (AR-51): a `PreToolUse` hook that refuses an edit to the
41
+ hooks, their wiring, `.claude/queue.json`, the queue adapters, the router, the
42
+ gate sweep, the rules or `CLAUDE.md` while the unattended flag the `loop` skill
43
+ writes at claim time is on disk (`.claude/scripts/unattended-flag.mjs`), unless
44
+ the item's allow-list names the path. Attended sessions are untouched. Its
45
+ header states its limits, each one under test in the generator.
46
+ - **`doctor`** (AR-5): `node .claude/scripts/doctor.mjs` reads
47
+ `.claude/.rig-manifest.json` and reports every hook the project owns — bytes
48
+ that differ from what the generator installed, or no manifest entry — that has
49
+ no `<hook>.test.mjs` beside it. Exemptions are an explicit list with reasons in
50
+ `.claude/doctor-exemptions.json`, a file the project writes (none ships); a
51
+ doctor that looked nowhere never says GO.
52
+ - **Fan-out coverage is checked, not just recorded** (AR-79, AR-118): `pr-ship`
53
+ compares the reviewers that answered against the route the router gave the
54
+ head, bound to that head; `docs/decisions/gate-coverage.md` records the shape
55
+ and the unreadable states.
56
+ - **Queue items carry more of the tracker's meaning into selection:**
57
+ - an item marked for another repository (`owner-<name>`; `[owner:<name>]` in
58
+ `PLAN.md`) is held, never taken — a checkout names itself in
59
+ `options.owner` (AR-132);
60
+ - the lifecycle vocabulary `keep-core` / `re-scope` / `obsolete` and the
61
+ `parked` pile are read above the adapter seam, and the loop infers none of
62
+ it (AR-144);
63
+ - a proposal the loop files records the commit it was measured against
64
+ (`asOf`), and `hygiene` reports the one git has overtaken (AR-116); it also
65
+ names what it measured and what it inferred, and an inference past the
66
+ measurement is refused at filing (AR-142);
67
+ - the take-up baseline reaches into earlier runs, so a marker the adapter's
68
+ own write produced is not read back as a catch (AR-138, AR-140);
69
+ - `gate-round` refuses to count a round on a checkout that cannot ship, and
70
+ states the cap as a spent count rather than a convergence verdict (AR-141,
71
+ AR-115).
72
+
73
+ ### Changed
74
+
75
+ - **The Jira adapter is harder to knock over** (AR-54): a request timeout that
76
+ stays armed through the body read, transient retry honouring `Retry-After`
77
+ (capped at 60 s), cursor pagination with a stated page cap, a priority-id
78
+ fallback, and a JQL that is always project-qualified — an explicit
79
+ `options.jql` must begin with `project = <KEY>`.
80
+ - **The adapter contract gained `find` and `listProposals`** (AR-135, AR-116)
81
+ and the ticket shape gained `updatedAt`, `owner`, `lifecycle` and `parked`.
82
+ On `jira`, `limit` is now the **page** size, not a result cap.
83
+ - **A close is a close only when the tracker says so** (AR-135): all three
84
+ adapters read the item back and return `transitioned` from what they read,
85
+ instead of from the argument they were given or a `gh` exit code.
86
+ - **`gate-stop-dod` measures the project the hook belongs to, not the cwd**, and
87
+ names the tree in its refusal (AR-119).
88
+ - **`.claude/rules/node-ts.md` names the third state of a PR head** — one that
89
+ gets no workflow run at all — and says it is retriggered per required check,
90
+ never merged on an older head's green (AR-149).
91
+ - **The autonomy and invariants rules state the enforcement they have exactly**:
92
+ `guard-secret-file`'s four blind spots, the unattended flag as what arms
93
+ `guard-rulebook`, and the unbacked-claim rule with its two exits (delete, or
94
+ point at the test).
95
+
96
+ ### Fixed
97
+
98
+ - **`MultiEdit` and `NotebookEdit` reached every content guard and produced no
99
+ fragment**, so an impure edit to the core through either passed unchecked.
100
+ `hooks/lib/edit-input.mjs` now yields one fragment per edit for both (AR-51).
101
+ - **`manifest.version` is held to the same value check its siblings get**, and
102
+ the comment no longer claims a prerelease the rig never wrote (AR-128).
103
+ - **Hooks resolve the project root inside `main()`**, so a throw there announces
104
+ itself instead of failing open silently (AR-119).
105
+
106
+ - **The upgrade plan's header told you your rig was old when it could not know
107
+ that.** It greeted every rig it could not read a manifest for with "no manifest
108
+ here (a pre-0.4.0 rig)". There are three ways to reach that branch and the
109
+ claim is false for two of them: a manifest you deleted, and one that is on disk
110
+ and voided by its own reader — the case 0.5.0's notes below single out. The
111
+ header now states the condition the code actually has, **no readable manifest
112
+ here**, and offers the three causes without asserting any of them.
113
+ - **`--no-color` is accepted by `upgrade` and `init`**, not only by the
114
+ scaffolder. It was advertised under Options without being scoped to one
115
+ command, and the other two exited 1 with "Unknown option". **Nothing about
116
+ their output changes** — the CLI builds its palette on the `create` path alone,
117
+ so neither command had colour to switch off, and neither read `NO_COLOR`
118
+ either. What changes is that a flag the help offers is no longer a refusal.
119
+ - **The plan's summary now accounts for every action, not four kinds out of
120
+ six.** It counted files to replace, new files, yours-kept and already-current,
121
+ while the plan above it also prints a line for a hook wiring hand-over and for
122
+ a file you removed — so the four buckets could not add up to what was printed.
123
+ Both are counted now, in the order the plan lists them, and they appear only
124
+ when they occurred: a plan without them reads exactly as it did.
125
+
126
+ ## 0.5.0
127
+
128
+ **Codex is a harness of this rig now, not a thing you adapt it to.** A generated
129
+ project carries one rulebook and two readers: `CLAUDE.md` for Claude Code and
130
+ the same text as `AGENTS.md` for Codex, with repository skills in
131
+ `.agents/skills/`, agent profiles in `.codex/agents/` and portable hook wiring
132
+ in `.codex/hooks.json`. Neither harness gets the weaker policy, and the derived
133
+ half is drift-checked rather than maintained twice.
134
+
135
+ **This release ships untagged, by the owner's decision, and it has exactly one
136
+ consequence — read it if you ever run `upgrade` on a rig whose
137
+ `.claude/.rig-manifest.json` is missing or unreadable.**
138
+ `templates/hash-history.json`, the table such a rig is measured against, is
139
+ built from `v*` tags. So 0.5.0's bytes never enter it, and the `0.4.0` row it
140
+ gained in this release carries the _previous_ release's bytes: that tag points at
141
+ 0.3.2's content, which is why the row adds no hash to any path. Both paths 0.4.0
142
+ actually changed — `.claude/skills/loop/SKILL.md` and `PLAN.md` — are in the
143
+ table with their 0.3.x hashes; what is absent is 0.4.0's bytes from their hash
144
+ lists.
145
+
146
+ **Who that reaches, and who it does not.** `create`, `init` and `upgrade` each
147
+ write the manifest, and `upgrade` matches it **before** it consults the table, so
148
+ a rig whose manifest is present and parseable is unaffected whatever the table
149
+ says. Without a readable manifest the table decides, and it decides in the
150
+ conservative direction: bytes it recognises are replaced, bytes it does not are
151
+ kept and reported as yours — see `packages/cli/test/upgrade.test.ts` › "replaces
152
+ a file that matches a released version, and reports the rest".
153
+
154
+ A rig installed **before** 0.4.0 is not the exposed case: its 0.3.x bytes are in
155
+ the table, so those two files are recognised and replaced. The rig that keeps
156
+ them is one installed at **0.4.0** whose manifest is unreadable.
157
+
158
+ **And the scale of it grows with this release, which matters more than those two
159
+ files.** Because 0.5.0 is untagged, nothing it ships enters the table either — so
160
+ a rig installed at 0.5.0 and later upgraded **without a readable manifest** has
161
+ most of its agent-os files unrecognised, and many of them are paths the table has
162
+ no row for at all. Every one is kept and reported as yours, so no edit is lost
163
+ and no file is silently skipped, but almost nothing would be refreshed either. **Commit `.claude/.rig-manifest.json`** — that single habit makes the
164
+ table irrelevant to you, and it is what `README.md` puts in bold.
165
+
166
+ ### Added
167
+
168
+ - **Codex is now a native target of the Agent OS.** Generated and `init`ed
169
+ projects receive `AGENTS.md`, repository skills in `.agents/skills/`, custom
170
+ agent profiles in `.codex/agents/`, and portable `.codex/hooks.json` wiring.
171
+ These files are derived from the Claude Code sources and drift-checked.
172
+ - Architecture guards now understand Codex `apply_patch` payloads, inspecting
173
+ additions and bounded existing content for moves, so removing an old
174
+ violation does not create a false block.
175
+
176
+ ### Changed
177
+
178
+ - **`upgrade` now replaces `.claude/settings.json` when the manifest proves you
179
+ never touched it** — closing the decision 0.4.0's notes left open below. The
180
+ case it exists for is a release that adds a hook: the hook file arrived and
181
+ the wiring that calls it did not, so the guard sat on disk doing nothing. When
182
+ the on-disk bytes hash-match the entry the manifest recorded for the installed
183
+ release, they are provably the rig's own and the release's version is written.
184
+
185
+ **Three limits. The first two are there because not having them was tried,
186
+ and each produced a regression two reviewers reproduced independently.**
187
+
188
+ 1. **The released-hash fallback does not apply to this file.** Every other
189
+ file the rig installed can be recognised by matching a tagged release even
190
+ with no manifest entry. This one cannot: a rig with no manifest that has
191
+ run `init` is recorded as `kind: "init"`, and the wiring that flavour
192
+ writes deliberately omits the hooks `init` does not install.
193
+ 2. **A replacement that would stop calling a hook still present in
194
+ `.claude/hooks/` is handed over instead**, whatever the manifest says. This
195
+ is the guard that does not depend on getting `kind` right — a manifest
196
+ saying `init` on a rig `create` produced reaches the same wrong wiring
197
+ through the hash arm alone.
198
+ 3. **Anything else is unchanged:** the new entries are printed for you to
199
+ merge, and nothing is written.
200
+
201
+ - **`init --force` is deprecated.** It refuses, names `upgrade` as the command
202
+ that refreshes a rig, and writes nothing. It only ever replaced `CLAUDE.md`,
203
+ which `upgrade` now does per file and with the manifest behind it. **The flag
204
+ is removed in 0.6** — this release is the one warning you get.
205
+
206
+ The way into a `create` rig that `--force` used to provide is a deleted
207
+ `CLAUDE.md`; that is what `init`'s refusal is actually about, and it is the
208
+ case the manifest-preserving fix below was written for.
209
+
210
+ ### Security
211
+
212
+ - 🔴 **A committed `.claude/.rig-manifest.json` could run code on the machine
213
+ of whoever upgraded the rig.** `project.name`, `project.scope`,
214
+ `project.region` and `stacks` were each validated — but only as _path_
215
+ segments, a predicate that asks whether a value can steer a write. Two of
216
+ them are also substituted into installed **files**:
217
+ `.claude/scripts/stop-flag.mjs` embeds the name inside a single-quoted
218
+ JavaScript string literal that `guard-bash` imports on every Bash call. A
219
+ value closing that quote steers no path at all and passed — it executed in
220
+ the hook process, **and** moved the kill switch's path off
221
+ `~/.claude/<name>-loop-STOP`, so the brake read as installed while doing
222
+ nothing. The manifest travels in pull requests, so the delivery was an
223
+ ordinary PR plus an `upgrade`. All four are now held to the shape the rig
224
+ actually produces (`^[a-z0-9_][a-z0-9._-]*$`), and a manifest carrying
225
+ anything else is void as a whole rather than corrected.
226
+
227
+ **Checking a rig you upgraded from a manifest you did not write — three
228
+ places, because the name is not the only value that travelled.**
229
+ `.claude/scripts/stop-flag.mjs` is the executable sink: its kill-switch line
230
+ must read your own project name. `region` lands in
231
+ `.claude/skills/ro-debug/SKILL.md` as `export AWS_REGION=…` on rigs carrying
232
+ the `aws-cdk` overlay — and a manifest also declares `stacks`, so it can
233
+ request that overlay on a rig that never had it. The name is substituted into
234
+ the documents the agent obeys as well (`CLAUDE.md`, `PLAN.md`, the `loop`
235
+ skill), where a hostile value arrives as injected text rather than as code.
236
+
237
+ Nothing `create` or `init` writes is rejected by the new rule — including an
238
+ empty `region` and a name with a leading underscore, which
239
+ `projectNameFor` really can produce.
240
+
241
+ ### Fixed
242
+
243
+ - **`init --force` inside a generated project used to make `upgrade` stop
244
+ refreshing the stack overlays — silently.** ⚠ Read this next to the
245
+ deprecation above: `--force` is refused in this same release, so
246
+ the route described here is gone. The fix is not idle — the manifest is
247
+ preserved on **every** `init` over a `create` rig, and the remaining route in
248
+ is a deleted `CLAUDE.md`. `init` rewrote the rig manifest
249
+ as `kind: "init"`, `stacks: []`, empty `region`, and `upgrade` trusts a
250
+ manifest wholesale rather than re-detecting: the stack files simply left the
251
+ plan, reported neither as deleted nor as a conflict, and `CLAUDE.md` came
252
+ back in the `init` flavour. `init` now carries the `kind`, `project` and
253
+ `stacks` it found in the manifest through unchanged, and adds an entry for
254
+ each file it wrote without dropping the entries already there. It also says,
255
+ before writing anything, that this rig came from `create` and `upgrade` is
256
+ the command that refreshes it.
257
+
258
+ ⚠ **Both halves read the manifest, so a rig that has none — anything
259
+ installed before 0.4.0 — is not covered.** There `init` still writes
260
+ `kind: "init"`, `stacks: []`, empty `region`, and prints no advisory; worse,
261
+ such a rig could previously be recovered by `upgrade`, which re-detects the
262
+ install from the files on disk **only when there is no manifest at all**, and
263
+ the one `init` writes takes that route away. On a pre-0.4.0 rig, run
264
+ `upgrade` before `init`.
265
+
266
+ **Recovering a rig whose manifest was already flattened:** delete
267
+ `.claude/.rig-manifest.json` and run `upgrade` — the detection restores
268
+ `kind`, `stacks` and `region` from the files themselves; hand-writing the
269
+ manifest is not needed and `parseManifest` rejects the whole file on any
270
+ malformed field. What that does **not** repair is `CLAUDE.md`: the flattening
271
+ `init` overwrote it with the `init` flavour, so `upgrade` reports it as
272
+ `conflict` ("not a version this rig ever released — treated as yours") and
273
+ the create flavour has to be merged back by hand.
274
+
275
+ - **The `jira` queue adapter was calling an endpoint Atlassian removed.** Both
276
+ selection and the triage dedupe went through `GET /rest/api/3/search`, which
277
+ answers `410 Gone`; the adapter threw on the status line and the loop read
278
+ that as an unreadable queue. It now uses `POST /rest/api/3/search/jql`. If
279
+ your rig is on the `jira` adapter, this is the difference between a loop that
280
+ works and one that reports an empty board. Cursor pagination
281
+ (`nextPageToken`) is **not** implemented yet, so a board with more open issues
282
+ than `limit` (default 100) still loses its tail.
283
+
284
+ ### Changed — action needed if your board uses the `jira` adapter
285
+
286
+ - 🔴 **The elevated-tier marker on Jira is now the `elevated` label, not
287
+ `human-review`.** A board that marked elevated work with `human-review` will,
288
+ after this upgrade, hand every item to the loop as `normal` — the
289
+ elevated-spacing ration silently stops holding anything back. **Relabel those
290
+ issues to `elevated` before running the loop again.** The change is
291
+ deliberate: on a Jira board `human-review` reads as "a human is looking at
292
+ it", which is a different claim from "this change is expensive to reverse".
293
+ The `github-issues` adapter is unaffected and still reads `human-review`,
294
+ where it does mean a human reviewed the diff.
295
+ - **Selection now excludes the `operator-queue` label as well as `triage`.** An
296
+ item in the owner's lane is work a human has taken, so the loop no longer
297
+ picks one up. If you used `operator-queue` for something else, rename it
298
+ first.
299
+
14
300
  ## 0.4.0
15
301
 
16
302
  Upgrading is a command now: **`npx create-agent-rig@0.4.0 upgrade`** (`@latest`
@@ -332,10 +618,27 @@ sometimes earlier (step 6). Everything before that is mechanical:
332
618
  tree, are in the tarball. This is where scaffolders break, and the git path
333
619
  cannot catch it.
334
620
  3. Version in `package.json` (and the private inner package, kept in step).
335
- 4. `node scripts/build-hash-history.mjs` regenerate the released-hash table
336
- from the tags **after** the version bump, so the version now shipping is the
337
- first one it excludes. Forgetting it would leave `upgrade` unable to
338
- recognise the previous release.
621
+ 4. **Record where the previous release was published from, then regenerate
622
+ the released-hash table.** `templates/release-ledger.json` maps each released
623
+ version to the commit it was published from; the entry for the release
624
+ _before_ this one is written now, because a commit cannot carry its own sha:
625
+
626
+ ```sh
627
+ npm view create-agent-rig@<previous> gitHead # → the sha for the ledger
628
+ node scripts/build-hash-history.mjs # rebuilds the table from it
629
+ ```
630
+
631
+ The builder reads every `## X.Y.Z` this file lists below the version in
632
+ `package.json` and **refuses, naming the version and that command**, when
633
+ the ledger has no entry for one — it never drops a release silently, since a
634
+ dropped release is one `upgrade` can no longer recognise. A value of `null`
635
+ is the one other answer: the published bytes are not recoverable from git
636
+ (0.1.0 was published from a commit whose `package.json` already read
637
+ 0.2.0), so that version deliberately gets no row. Pinned in
638
+ `test/template/hash-history.test.ts` › "throws for a released version the
639
+ ledger does not mention, naming the version and the npm command" and ›
640
+ "points at a commit whose package.json carries that version".
641
+
339
642
  5. This file, and `PLAN.md` if the plan's claims changed.
340
643
  6. **`pnpm test` again — this run, not step 1, is the one that can catch a
341
644
  stale hash table.** The check compares the table against the versions this
@@ -343,13 +646,15 @@ sometimes earlier (step 6). Everything before that is mechanical:
343
646
  comparing the _old_ release to the _old_ table and passes either way. A
344
647
  guard that can only fire after the thing it guards has changed has to be run
345
648
  after it.
346
- 7. `git tag v<version> && git push --tags` **first check that the tag does not
347
- already exist** (`git ls-remote --tags origin`). A leftover from an abandoned
348
- attempt is a published ref: deleting or moving it is an **owner** action, and
349
- the release stops here until it is gone. A tag pointing at the wrong commit
350
- is not cosmetic the next release builds its hash table from it, and every
351
- file that changed in between then reads as user-modified and stops being
352
- upgradable.
649
+ 7. **Tagging is not part of this project's release process** standing owner
650
+ decision, recorded at 0.5.0: the owner publishes by hand and does not tag.
651
+ Since 0.6.0 (AR-35) that costs nothing: the table is built from the ledger in
652
+ step 4, not from tags, and a `v*` tag is neither required nor trusted. One
653
+ that exists and points elsewhere than the ledger `v0.4.0` does, at 0.3.2's
654
+ content is printed as a warning by the builder and changes nothing:
655
+ `test/template/hash-history.test.ts` › "builds the table from the ledger
656
+ alone — tags are a warning source, never an input".
657
+
353
658
  8. **Owner:** `npm publish`.
354
659
  9. **Owner:** smoke the published artifact — `npx create-agent-rig@<version>` in
355
660
  an empty directory, then `pnpm install && pnpm check` inside it; and
package/README.md CHANGED
@@ -3,6 +3,12 @@
3
3
  Scaffold a project that ships with an **agent operating system** — rules,
4
4
  gates, and hooks that hold the architecture mechanically, not by prose.
5
5
 
6
+ The same Agent OS is native to both **Claude Code and Codex**. Claude-facing
7
+ files remain the authoring surface; the generator derives Codex's `AGENTS.md`,
8
+ repository skills under `.agents/skills/`, custom agents under `.codex/agents/`,
9
+ and `.codex/hooks.json`. `node scripts/sync-codex-adapter.mjs --check` refuses
10
+ drift between the two projections.
11
+
6
12
  ```sh
7
13
  npx create-agent-rig my-app # choose a target interactively
8
14
  npx create-agent-rig my-app --target node-service # or name it up front
@@ -22,16 +28,24 @@ npx create-agent-rig init --dry-run # print the plan, write nothing
22
28
  ```
23
29
 
24
30
  `init` drops in the autonomy tiers, stop rules, workflow, and the enforcement
25
- hooks — **wired**, in a `.claude/settings.json` that names exactly the hooks it
26
- installed plus a `CLAUDE.md` that describes that rig rather than the generated
27
- monorepo. It refuses to clobber an existing `CLAUDE.md`; if the repo already has
28
- a `.claude/settings.json`, it keeps it and prints the entries to merge, because a
29
- hook nothing calls is not enforcement.
31
+ hooks — **wired** for both harnesses, in `.claude/settings.json` and
32
+ `.codex/hooks.json`, each naming exactly the hooks it installed plus matching
33
+ `CLAUDE.md` and `AGENTS.md` maps that describe that rig rather than the generated
34
+ monorepo. It refuses to clobber either existing map; if the repo already has a
35
+ Claude or Codex hook config, it keeps it and prints the entries to merge,
36
+ because a hook nothing calls is not enforcement.
30
37
 
31
38
  Two things it deliberately leaves to you, and says so in the installed
32
- `CLAUDE.md`: the Definition-of-Done gate has no `dod-checks.json` (it cannot know
39
+ maps: the Definition-of-Done gate has no `dod-checks.json` (it cannot know
33
40
  your commands), and the elevated-path list names only what every repo has.
34
41
 
42
+ After generation or upgrade, review the checked-in `.codex/hooks.json` in Codex's
43
+ `/hooks` view and explicitly trust it if Codex presents a trust prompt. The
44
+ [official Codex hooks documentation](https://learn.chatgpt.com/docs/hooks)
45
+ records trust against the current hook hash, so a changed hook definition may
46
+ require that review again; the adapter does not silently replace user-owned hook
47
+ configuration.
48
+
35
49
  ## Upgrading a rig you already have
36
50
 
37
51
  A release changes files, and `init` only ever _adds_ — so bringing an existing
@@ -54,12 +68,22 @@ How it knows: `create` and `init` write `.claude/.rig-manifest.json` — the rig
54
68
  version plus a hash per installed file. **Commit it**; without it in the
55
69
  repository the command is blind on CI and on a colleague's machine. Rigs
56
70
  installed before 0.4.0 have no manifest, so the package also carries the hashes
57
- of every **tagged** release (0.3.0 onward — 0.1.0 and 0.2.0 shipped untagged,
58
- and a rig from those reports every file as yours) and recognises a file matching
59
- one of them.
60
-
61
- `.claude/settings.json` is never replaced it is where your own hooks live, so
62
- the new wiring is printed for you to merge.
71
+ of every release whose published commit is on record (0.2.0 onward — 0.1.0's
72
+ published bytes are not recoverable, and a rig from it reports every file as
73
+ yours) and recognises a file matching one of them. The record is
74
+ `templates/release-ledger.json`, written at the release _after_ the one it
75
+ describes, so the newest release is never in the table a rig installed from it
76
+ carries — one more reason committing the manifest is the sentence in bold above
77
+ and not an aside.
78
+
79
+ `.claude/settings.json` is replaced only when the manifest's recorded hash
80
+ proves the rig wrote those exact bytes and you have not touched them — the case
81
+ where a release adds a hook and the wiring that calls it. Anything else, and it
82
+ is where your own hooks live: the new wiring is printed for you to merge, never
83
+ written. Unlike every other file, a match against the released hashes is not
84
+ enough for this one, and a replacement that would stop calling a hook the
85
+ current wiring names — while that hook's file is still in `.claude/hooks/` — is
86
+ handed over instead.
63
87
 
64
88
  **A file you deleted stays deleted.** The rules invite you to delete the ones
65
89
  whose invariant your project does not have, so an upgrade that quietly restored
@@ -75,13 +99,26 @@ and `--dry-run` lists it before anything is written.
75
99
  **A system of boundaries, each held by tooling.** An agent (or a human using
76
100
  one) cannot talk its way past them — each guard is a pre-write scan that stops
77
101
  the normal path cold (review and tests back it; the claim is stated exactly,
78
- never inflated). The hooks live in `.claude/hooks/` and are wired in
79
- `.claude/settings.json`:
102
+ never inflated). The hook implementations live once in `.claude/hooks/` and are
103
+ wired by both `.claude/settings.json` and `.codex/hooks.json`:
80
104
 
81
105
  - **`guard-core-purity`** — refuses any edit that puts I/O, clock, randomness,
82
106
  environment access, or a non-allowlisted import into the pure domain core;
83
107
  - **`guard-web-boundary`** — refuses `db`/service imports from the frontend;
84
108
  the web talks to the backend over HTTP only;
109
+ - **`guard-rulebook`** — in an unattended run (a flag file the `loop` skill
110
+ writes at claim time), refuses an edit to the rulebook — hooks, wiring,
111
+ `queue.json`, the queue adapters, the router, the gate sweep, the rules,
112
+ `CLAUDE.md` — outside the current item's allow-list; does nothing in an
113
+ attended session.
114
+ - **`guard-secret-file`** — refuses an edit that writes a credential: either the
115
+ path names one (`jira.env`, `id_rsa`, anything under `secrets/`) or the text
116
+ carries a credential VALUE. Both arms read one vocabulary,
117
+ `.claude/scripts/lib/secrets.mjs`, and a refusal names the pattern and the line
118
+ and **never the matched value** — printing it would leak the secret in the act
119
+ of refusing it. Its four blind spots are in its own header, each naming the
120
+ test that pins it or saying plainly that none does — and those tests live in
121
+ this generator, not in the rig;
85
122
  - **`block-no-verify`** — refuses bypassing pre-commit checks (and knows the
86
123
  difference between using the `--no-verify`/`-n` flag and merely mentioning it
87
124
  in a message);
@@ -96,7 +133,9 @@ never inflated). The hooks live in `.claude/hooks/` and are wired in
96
133
  check is red; it fails open (a missing or corrupt config never makes the
97
134
  session unquittable) and never blocks twice in a row;
98
135
  - **`inject-rules`** — re-injects the autonomy rules at session start, so they
99
- survive compaction and resumes.
136
+ survive compaction and resumes: the whole file, minus the regions the file
137
+ itself marks as reference. What is left out is a decision written in
138
+ `autonomy.md` on the line above it, not one this hook infers.
100
139
 
101
140
  **A brake that is a real file.** `touch ~/.claude/<project>-loop-STOP` and no
102
141
  merge lands until it is removed — enforced at the tool layer, so it holds even if
@@ -125,8 +164,8 @@ never), **stop rules** (three strikes, flaky ≠ retry, session staleness),
125
164
  `prose-reviewer`, and `cdk-diff-reviewer` on the AWS target), **skills** (`pr-ship` pre-merge gate;
126
165
  `loop` queue driver; `worktree-task` for concurrent sessions; `new-invariant`, a
127
166
  generator for the invariant→hook→test pattern; `post-deploy-verify` and
128
- `ro-debug` on the AWS target), and a one-page `CLAUDE.md` map a fresh session
129
- orients by.
167
+ `ro-debug` on the AWS target), and matching one-page `CLAUDE.md` / `AGENTS.md`
168
+ maps a fresh session orients by.
130
169
 
131
170
  **The hooks are examples, not laws.** `.claude/rules/invariants.md` states the
132
171
  pattern behind each one — a stated invariant, a mechanical check, a test for the
@@ -196,8 +235,8 @@ under test, because that is exactly where scaffolders break. A grep-test keeps
196
235
  the universal rules free of any provider mention; the hook-blocking behavior
197
236
  itself is under test; and a weekly lockfile-free run resolves each template's
198
237
  dependencies fresh to catch upstream breakage early. This repo dogfoods its own
199
- rulebook — `CLAUDE.md` and `.claude/` are composed from the templates, and
200
- drift fails the suite.
238
+ rulebook — the Claude and Codex projections are composed from the templates,
239
+ and drift fails the suite.
201
240
 
202
241
  **And the enforcement layer is adversarially reviewed, not just tested.** The
203
242
  Bash guard went through four review rounds with ten reviewers, who executed it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agent-rig",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Scaffold a new project with an agent operating system (rules, gates, hooks) and a runnable code skeleton",
5
5
  "keywords": [
6
6
  "create",
@@ -10,6 +10,7 @@
10
10
  "template",
11
11
  "agent",
12
12
  "claude-code",
13
+ "codex",
13
14
  "agent-os",
14
15
  "aws-serverless",
15
16
  "node-service"
@@ -2,7 +2,7 @@ import { execFile } from 'node:child_process';
2
2
  import { mkdir, readFile, readdir, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { promisify } from 'node:util';
5
- import { copyTree, listTree } from '../lib/copy-tree.js';
5
+ import { copyTree, listTree, mapConcurrent } from '../lib/copy-tree.js';
6
6
  import { ALLOWED_OVERWRITES, detectCollisions } from '../lib/composition.js';
7
7
  import { agentOsLayerDirs } from '../lib/install-set.js';
8
8
  import { sha256, writeManifest } from '../lib/manifest.js';
@@ -82,8 +82,13 @@ export async function createProject(dirArg, options) {
82
82
  async function recordInstall(projectDir, agentOsLayers, transforms, ctx, target) {
83
83
  const files = {};
84
84
  for (const layer of agentOsLayers) {
85
- for (const rel of await listTree(layer.dir, transforms)) {
86
- files[rel] = sha256(await readFile(path.join(projectDir, ...rel.split('/')), 'utf8'));
85
+ const paths = await listTree(layer.dir, transforms);
86
+ const hashes = await mapConcurrent(paths, 16, async (rel) => ({
87
+ rel,
88
+ hash: sha256(await readFile(path.join(projectDir, ...rel.split('/')), 'utf8')),
89
+ }));
90
+ for (const { rel, hash } of hashes) {
91
+ files[rel] = hash;
87
92
  }
88
93
  }
89
94
  await writeManifest(projectDir, {