create-agent-rig 0.7.1 → 0.9.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 (56) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +93 -4
  3. package/package.json +4 -3
  4. package/packages/cli/dist/commands/memory.js +123 -0
  5. package/packages/cli/dist/commands/setup.js +45 -0
  6. package/packages/cli/dist/index.js +107 -3
  7. package/packages/cli/dist/lib/subsystems.js +269 -0
  8. package/packages/cli/dist/lib/version.js +15 -0
  9. package/packages/cli/dist/policy/benchmark/corpus.js +165 -0
  10. package/packages/cli/dist/policy/core/adapter.js +18 -0
  11. package/packages/cli/dist/policy/core/coverage.js +253 -0
  12. package/packages/cli/dist/policy/core/decision-record.js +287 -0
  13. package/packages/cli/dist/policy/core/declaration.js +127 -0
  14. package/packages/cli/dist/policy/core/evidence-matrix.js +94 -0
  15. package/packages/cli/dist/policy/core/probe.js +442 -0
  16. package/packages/cli/dist/policy/core/registry.js +115 -0
  17. package/packages/cli/dist/policy/core/validation.js +275 -0
  18. package/packages/cli/dist/policy/core/vocabulary.js +123 -0
  19. package/packages/cli/dist/policy/harness/claude.js +47 -0
  20. package/packages/cli/dist/policy/harness/codex.js +87 -0
  21. package/packages/cli/dist/policy/harness/index.js +15 -0
  22. package/packages/cli/dist/policy/harness/shared-hooks.js +28 -0
  23. package/packages/cli/dist/policy/index.js +17 -0
  24. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +2 -0
  25. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +2 -0
  26. package/templates/agent-os/subagent-routing.json +32 -0
  27. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +48 -7
  28. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +2 -0
  29. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +2 -0
  30. package/templates/agent-os/universal/.claude/agents/security-scanner.md +2 -0
  31. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -0
  32. package/templates/agent-os/universal/.claude/hooks/guard-subagent-model.mjs +234 -0
  33. package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +75 -32
  34. package/templates/agent-os/universal/.claude/hooks/warn-subagent-routing.mjs +120 -0
  35. package/templates/agent-os/universal/.claude/rules/autonomy.md +17 -7
  36. package/templates/agent-os/universal/.claude/rules/workflow.md +5 -0
  37. package/templates/agent-os/universal/.claude/scripts/preflight.mjs +27 -3
  38. package/templates/agent-os/universal/.claude/scripts/queue/gate-rounds.mjs +70 -2
  39. package/templates/agent-os/universal/.claude/scripts/queue/index.mjs +12 -4
  40. package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +64 -1
  41. package/templates/agent-os/universal/.claude/settings.json +16 -0
  42. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +48 -7
  43. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -0
  44. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -0
  45. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +2 -0
  46. package/templates/agent-os/universal/.codex/agents/test-writer.toml +2 -0
  47. package/templates/agent-os/universal/.codex/config.toml +3 -0
  48. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +31 -5
  49. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +142 -0
  50. package/templates/agent-os/universal/layers.json +4 -0
  51. package/templates/hash-history.json +15 -7
  52. package/templates/release-ledger.json +3 -1
  53. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +3 -4
  54. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +40 -0
  55. package/templates/skeleton/node-service/services/api/test/package-manager.ts +51 -0
  56. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +9 -8
package/CHANGELOG.md CHANGED
@@ -9,7 +9,178 @@ Versions are published to npm as [`create-agent-rig`](https://www.npmjs.com/pack
9
9
 
10
10
  Numbering is ordinary semver — **additive is a minor, a fix is a patch** — so
11
11
  that "I only take minors" remains a usable policy; 0.3.2 shipped additive
12
- content as a patch by the owner's call and stays recorded as one.
12
+ content as a patch by the owner's call and stays recorded as one. 0.8.0 is the
13
+ second recorded departure; its own entry states the direction and the reason,
14
+ and this paragraph deliberately does not restate them — a numbering rule with
15
+ two copies of its exceptions is the shape 0.8.0 exists to remove.
16
+
17
+ ## 0.9.0
18
+
19
+ **The harness ↔ Memory boundary is executable, and the rig is a consumer of
20
+ Memory rather than a host for it.** The CLI gains a `setup --memory-root
21
+ <checkout>` command that records the Memory executable in a machine-scoped
22
+ subsystem manifest after a `--version --json` handshake, and a `memory
23
+ <doctor|load>` command that runs the registered executable through that
24
+ handshake and refuses a foreign contract major with exit 4 before any verb
25
+ runs; the CLI itself answers `--version --json` with its name, version and
26
+ contract version. These are commands of the tool, not files in a project: the
27
+ manifest is machine-scoped and nothing Memory-related is added to what
28
+ `create`, `init` or `upgrade` write into a project. None of this puts
29
+ Memory code inside the rig: the executable is spawned across a process
30
+ boundary and only its handshake is parsed (`docs/command-contract.md`, "The
31
+ version handshake"; `docs/decisions/memory-rig-boundary.md`).
32
+
33
+ **Deprecated: the application skeletons.** `create <dir> [--target <name>]`
34
+ still scaffolds `aws-serverless` and `node-service` in this release, unchanged, and they are
35
+ **scheduled for removal in 0.10.0**. The product boundary the owner fixed on
36
+ 2026-09-13 is a package manager for repository-scoped Claude Code and Codex
37
+ configuration — `init` into an existing repository, `upgrade`, `doctor`, the
38
+ Memory handshake — and application scaffolding is outside it. Take `init` for
39
+ a new project's rig; a skeleton generated today is yours and stays yours, but
40
+ `upgrade` will not carry skeleton files forward once they are gone.
41
+
42
+ **Numbered a minor by the rule at the top of this file**: two new commands and
43
+ a new payload are additive.
44
+
45
+ ### Added
46
+
47
+ - **`setup --memory-root <checkout> [--memory-ref <sha>] [--dry-run]`** and the
48
+ machine subsystem manifest it writes (`~/.config/create-agent-rig/subsystems.json`,
49
+ `%APPDATA%` on Windows) — RP-147. `upgrade` re-runs the same derivation when a
50
+ manifest exists, so `installedVersion` follows the executable the root holds.
51
+ - **`memory <doctor|load> [args…]`** through the registered executable, with the
52
+ handshake first and Memory's answer passed through unchanged; `--version
53
+ --json` on the rig bin — RP-19. Exit codes on that surface: 0 unsupported /
54
+ absent, 1 integration-failed, 2 invalid invocation, 3 prerequisite unmet, 4
55
+ foreign contract major.
56
+ - **Role-specific subagent routing** in the payload: one routing table
57
+ (`templates/agent-os/subagent-routing.json`) pins each reviewer's model and
58
+ effort for both harnesses; the Claude agent specs carry the pins, the Codex
59
+ profiles are derived from the same table, and two Claude Code hooks —
60
+ `guard-subagent-model` (refuses a call-site `model` override on a pinned
61
+ reviewer) and `warn-subagent-routing` (says so at session start when an
62
+ effort override is in force) — are wired in `.claude/settings.json`
63
+ (RP-166, RP-173; `docs/decisions/subagent-routing.md`).
64
+ - **`preflight` fails on an unreadable configured queue** instead of selecting
65
+ from nothing (RP-56).
66
+ - **Concurrent sessions on one machine** — the ruling on what shared state
67
+ they may touch, and a bounded rename retry so a Windows gate-round counter
68
+ is not lost to a transient lock (RP-120; `docs/decisions/concurrent-sessions.md`).
69
+ - **A mechanical release preflight**, `node scripts/release-preflight.mjs`,
70
+ for the step the owner types by hand (`docs/releasing.md`).
71
+ - **The Rig-side conformance runner** for the Memory boundary,
72
+ `scripts/memory-conformance.mjs --from <checkout> --json`, and the contract
73
+ schemas under `contracts/conformance/v1/` (RP-13) — generator repository
74
+ only, never delivered to a rig; the authoritative cross-repository run lives
75
+ in the private Memory repository and checks this one out at an exact SHA.
76
+ - **The policy benchmark** (`docs/policy-benchmark.md`, RP-111): adapter-process
77
+ evidence for both harnesses on an immutable snapshot of the tree. It remains
78
+ in 0.9.0; whether it stays is a 0.10.0 decision.
79
+
80
+ ### Fixed
81
+
82
+ - **`edit-input`** — an unreadable `tool_input` is a refusal on every edit
83
+ surface, never a clean edit (RP-85); a widening `--allow` entry no longer
84
+ leaves an unattended run with no flag on disk, and the `loop` skill verifies
85
+ the flag armed instead of trusting that it did (RP-103).
86
+ - **Capability coverage** — the policy capability contract names exactly what
87
+ the code covers, and the two silent passes it had are closed (RP-36); policy
88
+ validators read own-and-enumerable record fields (RP-153).
89
+ - **Windows** — the node-service skeleton's static-dir tests use a native file
90
+ URL (RP-168); the e2e harness spawns package managers and file checks on
91
+ Windows (RP-169); the one PowerShell case carries its own measured budget
92
+ (RP-162); the benchmark's guard-spawning cases are classified UNVERIFIABLE
93
+ on the hosted Windows image rather than reported green on nothing (RP-111),
94
+ and the Windows governance fixtures are set up under a bound that names the
95
+ stage that timed out (RP-172).
96
+ - **Repository scans skip sibling worktrees** under `.claude/worktrees/`, so a
97
+ second checkout is no longer reported as this one's drift (RP-155).
98
+
99
+ ### Generator CI (not a rig-facing change)
100
+
101
+ The pull-request path runs on GitHub-hosted runners only: in `ci.yml`, `ci`
102
+ and the two template checks on Linux and `windows-smoke` (the CLI's unit
103
+ project) on Windows; in `e2e.yml`, the Linux `e2e` job — the full suite with
104
+ the e2e installs and the benchmark — when the pull request touches the CLI,
105
+ the templates, the e2e harness or that workflow. The full suite on Windows
106
+ runs in `e2e.yml` on master, nightly and by dispatch only, and a
107
+ `runner_mode` switch there selects a self-hosted fallback that no pull
108
+ request can reach (`docs/runners.md`).
109
+
110
+ ## 0.8.0
111
+
112
+ **Three stale second copies, spread over three payload files a rig obeys,
113
+ described the mechanisms behind them wrongly — and each fix is a deletion.** A
114
+ count and a path list, both in `.claude/rules/autonomy.md`, and a paraphrase of
115
+ that same path list in `loop/SKILL.md`, which ships in both harnesses' copies.
116
+ Every one of them was a second writing of a fact the code owns, and every one
117
+ had drifted from its source while the suite stayed green.
118
+
119
+ **The `templates/agent-os/` payload gains no file and loses none**, measured
120
+ tarball to tarball — `npm pack create-agent-rig@0.7.1` unpacked, against this
121
+ release's own `npm pack`. That payload is the same 95 files either way, and
122
+ exactly three of them differ in content. ⚠ The tarball as a whole is **not**
123
+ unchanged: it grows by eleven files, and the section below names which and why.
124
+
125
+ **Numbered a minor by the owner's call, not by this file's rule.** The rule at
126
+ the top — additive is a minor, a fix is a patch — makes this delta a patch,
127
+ because nothing is added to what a project installs. It ships as `0.8.0`
128
+ because the owner's milestone of that name closes here and the number was fixed
129
+ before the delta was measured. Recorded rather than reconciled, the way `0.3.2`
130
+ is recorded above: a consumer on "I only take minors" receives three corrected
131
+ documents and no new capability.
132
+
133
+ ### Fixed
134
+
135
+ - **`.claude/rules/autonomy.md` stated a blind-spot count `guard-secret-file`
136
+ had outgrown.** The Never bullet said the guard's header "states the four
137
+ blind spots"; the header had been raised past four in `51402e99`, and the
138
+ stale copy shipped in 0.6.1, 0.6.2, 0.7.0 and 0.7.1 — a security rule
139
+ describing its own mechanism wrongly for four releases. The fix is not a
140
+ corrected number, which would only restart the same clock: the prose states
141
+ no count and points at the header, and `README.md` moved with it for the same
142
+ reason. Pinned in the generator's `test/template/guard-secret-file.test.ts`
143
+ (absent in a generated rig) › "no live rulebook document restates the guard’s
144
+ limit count" and › "the rule and the README still send the reader to the
145
+ guard’s own header".
146
+
147
+ - **The same file re-listed the paths `guard-rulebook` protects, and its list
148
+ was incomplete.** The enumeration of trees an unattended run may not edit
149
+ omitted `.claude/doctor-exemptions.json`, which the guard does refuse. It is
150
+ replaced by a pointer to `RULEBOOK_PREFIXES` in
151
+ `.claude/scripts/unattended-flag.mjs` — the set the guard judges an edit
152
+ against — so there is one spelling of it rather than two. One fact that
153
+ pointer cannot carry is stated beside it, because it does not follow from the
154
+ set: the checkout board selector is refused **even when an item's allow-list
155
+ names it**.
156
+
157
+ - **`loop/SKILL.md` paraphrased that set too, in both harnesses' copies.** The
158
+ step where a session composes an allow-list summarised the protected rulebook
159
+ in prose, so the one moment the set is read in anger was the moment it was
160
+ read from a summary that had already drifted. It now names
161
+ `RULEBOOK_PREFIXES` and says to open it. Both copies carry the change — the
162
+ Claude skill at `.claude/skills/loop/SKILL.md` and its Codex projection at
163
+ `.agents/skills/loop/SKILL.md`.
164
+
165
+ ### Inside the generator, and not inside a rig
166
+
167
+ `packages/cli/src/policy/` is new — a typed policy declaration, a registry, a
168
+ decision-record schema and one adapter per harness. Its compiled output adds
169
+ eleven files to the published tarball, 245 → 256. **No command imports it**, so nothing a
170
+ project scaffolded from this release does comes from it. What such a project
171
+ does get from this release is the three corrected documents above — and an
172
+ installed 0.7.1 rig still needs `create-agent-rig upgrade` to receive them —
173
+ which is a necessary condition and not a sufficient one, because a rig whose
174
+ owner has edited one of those three files is handed a conflict to merge rather
175
+ than an overwrite.
176
+ This module is named here only because a reader diffing the two tarballs sees
177
+ eleven new files and is owed the reason they are not part of that answer.
178
+
179
+ `templates/release-ledger.json` carries `0.7.1` at `52e879b6`, the commit it was
180
+ published from, read from `npm view create-agent-rig@0.7.1 gitHead` and verified
181
+ an ancestor of `master`. `templates/hash-history.json` is regenerated from it and
182
+ now covers eleven releases, `0.2.0` through `0.7.1`, so `upgrade` can tell an
183
+ untouched file from an edited one in a rig installed from 0.7.1.
13
184
 
14
185
  ## 0.7.1
15
186
 
@@ -913,7 +1084,18 @@ sometimes earlier (step 6). Everything before that is mechanical:
913
1084
  `test/template/hash-history.test.ts` › "builds the table from the ledger
914
1085
  alone — tags are a warning source, never an input".
915
1086
 
916
- 8. **Owner:** `npm publish`.
1087
+ 8. **Owner:** `npm publish` — and immediately before it, from the checkout you
1088
+ are about to publish from:
1089
+
1090
+ ```sh
1091
+ node scripts/release-preflight.mjs # exit 0, or it names what to fix
1092
+ ```
1093
+
1094
+ It checks the manifests, the ledger, the checkout, and the tarball `npm pack`
1095
+ would actually produce. What it looks at is the code, not this list; what it
1096
+ cannot see is stated in its own header. It is a preflight, not a gate —
1097
+ nothing runs it for you, and exit 0 is not a verdict on the release.
1098
+
917
1099
  9. **Owner:** smoke the published artifact — `npx create-agent-rig@<version>` in
918
1100
  an empty directory, then `pnpm install && pnpm check` inside it; and
919
1101
  `upgrade --dry-run` in a rig installed from the previous version.
package/README.md CHANGED
@@ -85,6 +85,44 @@ enough for this one, and a replacement that would stop calling a hook the
85
85
  current wiring names — while that hook's file is still in `.claude/hooks/` — is
86
86
  handed over instead.
87
87
 
88
+ ### Registering Memory on this machine
89
+
90
+ Memory is a separate subsystem with its own version; the rig never imports it
91
+ and never searches for it. `setup` records where it is, once per machine:
92
+
93
+ ```sh
94
+ npx create-agent-rig@latest setup --memory-root ~/claude-config # the checkout that holds shared-memory/memory.mjs
95
+ npx create-agent-rig@latest setup --memory-root ~/claude-config --dry-run # handshake only, write nothing
96
+ ```
97
+
98
+ It derives the invocation from that one root, runs Memory's `--version --json`
99
+ first, and refuses a foreign contract major with exit 4 before writing anything.
100
+ What it writes is one machine-scoped manifest —
101
+ `~/.config/create-agent-rig/subsystems.json` (`%APPDATA%\create-agent-rig\` on
102
+ Windows) — carrying the invocation, the required contract major, the pinned
103
+ Memory ref (`--memory-ref`) and the version the handshake observed. `upgrade`
104
+ re-runs the same derivation when the manifest exists; it never creates one. The
105
+ behaviour is pinned in `packages/cli/test/setup.test.ts` and
106
+ `packages/cli/test/subsystems.test.ts`; the seam itself is ADR-RP-002 R6
107
+ (`docs/decisions/memory-rig-boundary.md`).
108
+
109
+ Both bins answer the same handshake, and the rig consumes Memory only through it
110
+ (RP-19):
111
+
112
+ ```sh
113
+ npx create-agent-rig@latest --version --json # {"schemaVersion":1,"name":"create-agent-rig","version":"…","contractVersion":"1.0"}
114
+ npx create-agent-rig@latest memory doctor --json # handshake first, then Memory's doctor, answer passed through unchanged
115
+ npx create-agent-rig@latest memory load --json # same, for load; every argument after the verb goes to Memory verbatim
116
+ ```
117
+
118
+ `memory` reads the manifest above, runs Memory's `--version --json`, and only
119
+ then the verb: a foreign contract major exits 4 and the verb never runs; no
120
+ manifest (or an executable that has moved) is `unsupported`/`absent`, exit 0;
121
+ an executable that answers but not as the manifest promised — a broken
122
+ `VERSION`, a malformed handshake — is `integration-failed`, exit 1, never
123
+ "absent". Pinned in `packages/cli/test/memory.test.ts` and
124
+ `packages/cli/test/cli-version.test.ts`.
125
+
88
126
  **A file you deleted stays deleted.** The rules invite you to delete the ones
89
127
  whose invariant your project does not have, so an upgrade that quietly restored
90
128
  them would be undoing your work. With a manifest that is direct — it names the
@@ -94,13 +132,42 @@ it covers was there to be removed. The single case nothing can tell apart is a
94
132
  file a **later** release added, which your rig never had — that one is installed,
95
133
  and `--dry-run` lists it before anything is written.
96
134
 
135
+ ### Conformance runner
136
+
137
+ `contracts/conformance/v1/` holds the JSON schemas of the command contract's
138
+ `--version --json`, `doctor --json` and `load --json` answers, and
139
+ `scripts/memory-conformance.mjs` checks a Memory checkout against them:
140
+
141
+ ```sh
142
+ pnpm build
143
+ node scripts/memory-conformance.mjs --from <claude-config checkout> --json [--out report.json]
144
+ ```
145
+
146
+ It is offline by construction — `--from` is mandatory, nothing is fetched, no
147
+ credential is read — and it never imports Memory code or copies a Memory
148
+ fixture into this repository (`test/template/memory-conformance.test.ts` ›
149
+ "carries no fetch, clone or credential: the checkout is always the caller's"
150
+ and › "the repository carries no Memory fixture"). The contract directory is
151
+ this repository's own, not a rig payload: `create`, `init` and `upgrade` do
152
+ not deliver it (`test/template/conformance-contract.test.ts` › "is not
153
+ delivered to rigs: no template carries a conformance contract"). The report's
154
+ rows, its `rigSha` / `memorySha` / `verifierDigest` fields and the `--out`
155
+ file are pinned by the same test file's › "passes every row against a
156
+ well-formed local fixture root and names both SHAs and the verifier digest"
157
+ and › "derives verifierDigest from the runner, its validator and the contract
158
+ files, in that order, and writes the same report to --out". The authoritative
159
+ cross-repository run lives in the private `claude-config` repository, which
160
+ checks this repository out at an explicit full SHA and runs the command above
161
+ against its own tree; the CI here runs only the offline tests.
162
+
97
163
  ## What you get
98
164
 
99
165
  **A system of boundaries, each held by tooling.** An agent (or a human using
100
166
  one) cannot talk its way past them — each guard is a pre-write scan that stops
101
167
  the normal path cold (review and tests back it; the claim is stated exactly,
102
168
  never inflated). The hook implementations live once in `.claude/hooks/` and are
103
- wired by both `.claude/settings.json` and `.codex/hooks.json`:
169
+ wired by both `.claude/settings.json` and `.codex/hooks.json` — except the two
170
+ marked Claude Code, which only `.claude/settings.json` wires:
104
171
 
105
172
  - **`guard-core-purity`** — refuses any edit that puts I/O, clock, randomness,
106
173
  environment access, or a non-allowlisted import into the pure domain core;
@@ -116,7 +183,7 @@ wired by both `.claude/settings.json` and `.codex/hooks.json`:
116
183
  carries a credential VALUE. Both arms read one vocabulary,
117
184
  `.claude/scripts/lib/secrets.mjs`, and a refusal names the pattern and the line
118
185
  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
186
+ of refusing it. Its blind spots are in its own header, each naming the
120
187
  test that pins it or saying plainly that none does — and those tests live in
121
188
  this generator, not in the rig;
122
189
  - **`block-no-verify`** — refuses bypassing pre-commit checks (and knows the
@@ -135,7 +202,16 @@ wired by both `.claude/settings.json` and `.codex/hooks.json`:
135
202
  - **`inject-rules`** — re-injects the autonomy rules at session start, so they
136
203
  survive compaction and resumes: the whole file, minus the regions the file
137
204
  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.
205
+ `autonomy.md` on the line above it, not one this hook infers;
206
+ - **`guard-subagent-model`** (Claude Code) — refuses an `Agent` dispatch that
207
+ passes a call-site `model` for a subagent whose definition pins one: the
208
+ definition, not the call, decides which model a gate reads with;
209
+ - **`warn-subagent-routing`** (Claude Code) — at session start, warns when
210
+ `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` is set (it replaces every model pin below),
211
+ when `CLAUDE_CODE_EFFORT_LEVEL` is set (it replaces every effort pin), when
212
+ Claude Code is older than 2.1.251 (the unnamed default then replaces the model
213
+ pins), or when its version cannot be read (so the pins cannot be confirmed to
214
+ hold). It warns and never blocks.
139
215
 
140
216
  **A brake that is a real file.** `touch ~/.claude/<project>-loop-STOP` and no
141
217
  merge lands until it is removed — enforced at the tool layer, so it holds even if
@@ -167,6 +243,19 @@ generator for the invariant→hook→test pattern; `post-deploy-verify` and
167
243
  `ro-debug` on the AWS target), and matching one-page `CLAUDE.md` / `AGENTS.md`
168
244
  maps a fresh session orients by.
169
245
 
246
+ **Each gate reads with a pinned model and effort**, so a SHIP does not change
247
+ meaning with whatever model the session was started on. `code-reviewer`,
248
+ `security-scanner` and `cdk-diff-reviewer` pin `claude-opus-5`; `test-writer` and
249
+ `prose-reviewer` pin `claude-sonnet-5`; all pin `high` effort — and their Codex
250
+ profiles pin `gpt-5.6-sol` / `gpt-5.6-terra` from the same role table. A subagent
251
+ with no definition defaults to `claude-sonnet-5` through
252
+ `CLAUDE_CODE_SUBAGENT_MODEL` in `.claude/settings.json`; its effort cannot be
253
+ pinned and follows the session. The driver session's own model and effort stay
254
+ yours. To change a role in a generated project, edit `model:` / `effort:` in its
255
+ `.claude/agents/<role>.md` — and the matching `.codex/agents/<role>.toml` — in a
256
+ reviewed change; `upgrade` then reports the edited file as yours instead of
257
+ replacing it. Why these values, and what voids them: `docs/decisions/subagent-routing.md`.
258
+
170
259
  **The hooks are examples, not laws.** `.claude/rules/invariants.md` states the
171
260
  pattern behind each one — a stated invariant, a mechanical check, a test for the
172
261
  check — so you can delete the ones whose invariant your project does not have and
@@ -249,6 +338,6 @@ that fails open must do provably bounded work, because fail-open turns every lin
249
338
  of its own work into a potential bypass.
250
339
 
251
340
  Development (from a clone — `PLAN.md` and `demo.sh` live in the repository, not
252
- in the published tarball): `pnpm test` (full), `pnpm test:unit` (fast loop),
341
+ in the published tarball): `pnpm test` (full), `pnpm test:unit` (fast loop), `pnpm test:smoke` (the unit project only — the Windows pull-request lane),
253
342
  `pnpm template:check` (templates in place). The plan of record is `PLAN.md`;
254
343
  release notes and the release checklist ship in `CHANGELOG.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agent-rig",
3
- "version": "0.7.1",
3
+ "version": "0.9.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",
@@ -43,7 +43,8 @@
43
43
  "format": "prettier --write .",
44
44
  "typecheck": "tsc -p packages/cli/tsconfig.json && tsc -p tsconfig.json",
45
45
  "test": "pnpm build && vitest run",
46
- "test:unit": "vitest run --project unit --project template",
46
+ "test:unit": "vitest run --project unit --project template --project benchmark",
47
+ "test:smoke": "vitest run --project unit",
47
48
  "test:e2e": "pnpm build && vitest run --project e2e",
48
49
  "template:install": "pnpm --dir templates/skeleton/aws-serverless install",
49
50
  "template:check": "pnpm --dir templates/skeleton/aws-serverless run check"
@@ -59,6 +60,6 @@
59
60
  "prettier": "^3.9.6",
60
61
  "typescript": "~6.0.3",
61
62
  "typescript-eslint": "^8.65.0",
62
- "vitest": "^4.1.10"
63
+ "vitest": "^5.0.0"
63
64
  }
64
65
  }
@@ -0,0 +1,123 @@
1
+ // `create-agent-rig memory <doctor|load> [args…]`: the Rig side of the
2
+ // RP-19 version handshake. The Rig is a CONSUMER of the Memory subsystem: it
3
+ // resolves the executable from the machine subsystem manifest `setup` wrote
4
+ // (RP-147), performs `--version --json` first, refuses a foreign contract
5
+ // major with exit 4 before doctor/load ever run, and otherwise hands the verb
6
+ // and its arguments to Memory verbatim and returns Memory's answer unchanged.
7
+ //
8
+ // What it never does, by construction: import Memory code, read Memory's
9
+ // storage tree, or reinterpret a doctor/load payload — the only Memory bytes
10
+ // it parses are the handshake's (`docs/command-contract.md`, "The version
11
+ // handshake"; "Storage-tree ownership"). Pinned in packages/cli/test/memory.test.ts.
12
+ //
13
+ // Exit codes on this surface follow the contract: 0 for UNSUPPORTED/absent
14
+ // (Memory is not registered here, or its executable is gone — the relocation
15
+ // case), 1 for INTEGRATION-FAILED (the executable answered, but not with a
16
+ // handshake the manifest promised — a broken VERSION is `manifest-stale`,
17
+ // never `absent`), 3 for an unmet prerequisite (no configuration root: APPDATA
18
+ // or HOME unset), 4 for a foreign contract major. For 0, 1, 3 and 4 the stdout
19
+ // is exactly one JSON object with no file path in it; an invalid invocation
20
+ // (exit 2 — no verb, or one outside doctor/load) writes nothing to stdout and
21
+ // names the verbs on stderr; human hints always go to stderr.
22
+ import { execFileRunner } from './setup.js';
23
+ import { MEMORY_CONTRACT_MAJOR, SubsystemsError, handshake, readSubsystemsManifest, subsystemsManifestPath, } from '../lib/subsystems.js';
24
+ const MEMORY_VERBS = ['doctor', 'load'];
25
+ /** doctor/load do real work; the caller-supplied `--timeout-ms` is Memory's own bound. */
26
+ const VERB_TIMEOUT_MS = 60_000;
27
+ const jsonLine = (payload) => `${JSON.stringify(payload)}\n`;
28
+ const isVerb = (verb) => MEMORY_VERBS.includes(verb);
29
+ export async function runMemory(options) {
30
+ if (!isVerb(options.verb)) {
31
+ return {
32
+ exitCode: 2,
33
+ stdout: '',
34
+ stderr: `memory needs a verb: create-agent-rig memory <${MEMORY_VERBS.join('|')}> [args…]\n`,
35
+ };
36
+ }
37
+ const env = options.env ?? process.env;
38
+ const platform = options.platform ?? process.platform;
39
+ const run = options.run ?? execFileRunner;
40
+ let file;
41
+ try {
42
+ file = subsystemsManifestPath(env, platform);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof SubsystemsError && error.code === 'config-root-unavailable') {
46
+ // Nothing was attempted: without a configuration root there is no place
47
+ // the manifest could be. The contract's first exit-3 occasion — a
48
+ // variable that is not set — with the variable named, never a path.
49
+ const variable = platform === 'win32' ? 'APPDATA' : 'HOME';
50
+ return {
51
+ exitCode: 3,
52
+ stdout: jsonLine({
53
+ schemaVersion: 1,
54
+ result: 'prerequisites-unmet',
55
+ missing: [{ kind: 'environment', name: variable, detail: 'not set' }],
56
+ }),
57
+ stderr: `memory: ${variable} is not set, so there is no configuration root to read the subsystem manifest from\n`,
58
+ };
59
+ }
60
+ throw error;
61
+ }
62
+ let manifest;
63
+ try {
64
+ manifest = await readSubsystemsManifest(file);
65
+ }
66
+ catch (error) {
67
+ if (error instanceof SubsystemsError)
68
+ return {
69
+ exitCode: 1,
70
+ stdout: jsonLine({ schemaVersion: 1, result: 'integration-failed', reason: 'unreadable' }),
71
+ stderr: `memory: the subsystem manifest is not readable (${error.code}); run setup --memory-root again\n`,
72
+ };
73
+ throw error;
74
+ }
75
+ if (manifest === null)
76
+ return {
77
+ exitCode: 0,
78
+ stdout: jsonLine({ schemaVersion: 1, result: 'unsupported', reason: 'absent' }),
79
+ stderr: 'memory: Memory is not registered on this machine; run setup --memory-root <checkout>\n',
80
+ };
81
+ const entry = manifest.entries.memory;
82
+ const result = await handshake(entry, run);
83
+ if (result.status === 'foreign-major')
84
+ return {
85
+ exitCode: 4,
86
+ stdout: jsonLine({
87
+ schemaVersion: 1,
88
+ result: 'foreign-major',
89
+ contractVersion: result.contractVersion,
90
+ requiredMajor: MEMORY_CONTRACT_MAJOR,
91
+ }),
92
+ stderr: `memory: the registered Memory implements contract ${result.contractVersion}, ` +
93
+ `this rig requires major ${MEMORY_CONTRACT_MAJOR}; ${options.verb} was not run\n`,
94
+ };
95
+ if (result.status === 'unsupported')
96
+ return {
97
+ exitCode: 0,
98
+ stdout: jsonLine({ schemaVersion: 1, result: 'unsupported', reason: 'absent' }),
99
+ stderr: 'memory: the registered Memory executable is no longer there; ' +
100
+ 'run setup --memory-root <checkout> again\n',
101
+ };
102
+ if (result.status === 'integration-failed')
103
+ return {
104
+ exitCode: 1,
105
+ stdout: jsonLine({ schemaVersion: 1, result: 'integration-failed', reason: result.reason }),
106
+ stderr: `memory: the handshake did not answer as the manifest promised (${result.reason}); ${options.verb} was not run\n`,
107
+ };
108
+ const [command, script] = entry.invocation;
109
+ const answer = await run(command, [script, options.verb, ...options.args], {
110
+ timeoutMs: VERB_TIMEOUT_MS,
111
+ });
112
+ if (answer.spawnError)
113
+ return {
114
+ exitCode: 1,
115
+ stdout: jsonLine({
116
+ schemaVersion: 1,
117
+ result: 'integration-failed',
118
+ reason: 'backend-failed',
119
+ }),
120
+ stderr: `memory: ${options.verb} could not be started (${answer.spawnError.code ?? 'spawn error'})\n`,
121
+ };
122
+ return { exitCode: answer.code, stdout: answer.stdout, stderr: answer.stderr };
123
+ }
@@ -0,0 +1,45 @@
1
+ // `create-agent-rig setup --memory-root <checkout>`: the sole writer of the
2
+ // machine subsystem manifest for 0.9.0 (ADR-RP-002 R6, RP-147). Order is the
3
+ // contract: derive the entry from the one declared root, perform the
4
+ // `--version --json` handshake, refuse a foreign contract major with exit 4
5
+ // before anything is written, then write the manifest atomically.
6
+ import { execFile } from 'node:child_process';
7
+ import { SUBSYSTEMS_SCHEMA_VERSION, SubsystemsError, deriveMemoryEntry, handshake, subsystemsManifestPath, writeSubsystemsManifest, } from '../lib/subsystems.js';
8
+ /** The default runner: one child, one JSON line, a bounded wait. */
9
+ export const execFileRunner = (file, args, { timeoutMs }) => new Promise((resolve) => {
10
+ execFile(file, args, { timeout: timeoutMs, maxBuffer: 64 * 1024, windowsHide: true }, (error, stdout, stderr) => {
11
+ if (error && error.code === 'ENOENT')
12
+ resolve({ code: 1, stdout, stderr, spawnError: error });
13
+ else if (error && typeof error.code === 'number')
14
+ resolve({ code: error.code, stdout, stderr });
15
+ else if (error)
16
+ resolve({ code: 1, stdout, stderr, spawnError: error });
17
+ else
18
+ resolve({ code: 0, stdout, stderr });
19
+ });
20
+ });
21
+ export async function setupSubsystems(options) {
22
+ const env = options.env ?? process.env;
23
+ const platform = options.platform ?? process.platform;
24
+ const run = options.run ?? execFileRunner;
25
+ const file = subsystemsManifestPath(env, platform);
26
+ const entry = deriveMemoryEntry({
27
+ memoryRoot: options.memoryRoot,
28
+ nodeExecutable: options.nodeExecutable ?? process.execPath,
29
+ memoryRef: options.memoryRef ?? null,
30
+ installedVersion: '',
31
+ }, platform);
32
+ const result = await handshake(entry, run);
33
+ if (result.status === 'foreign-major')
34
+ return { outcome: 'refused', exitCode: 4, handshake: result };
35
+ if (result.status !== 'ok')
36
+ return { outcome: 'refused', exitCode: 1, handshake: result };
37
+ if (options.dryRun === true)
38
+ return { outcome: 'dry-run', file, handshake: result };
39
+ await writeSubsystemsManifest(file, {
40
+ schemaVersion: SUBSYSTEMS_SCHEMA_VERSION,
41
+ entries: { memory: { ...entry, installedVersion: result.version } },
42
+ });
43
+ return { outcome: 'written', file, handshake: result };
44
+ }
45
+ export { SubsystemsError };