create-agent-rig 0.9.1 → 0.10.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 (234) hide show
  1. package/CHANGELOG.md +419 -3
  2. package/README.md +267 -305
  3. package/package.json +8 -14
  4. package/packages/cli/dist/commands/create.js +56 -88
  5. package/packages/cli/dist/commands/doctor.js +213 -0
  6. package/packages/cli/dist/commands/init.js +111 -50
  7. package/packages/cli/dist/commands/integrations.js +468 -0
  8. package/packages/cli/dist/commands/setup-wizard.js +61 -0
  9. package/packages/cli/dist/commands/uninstall.js +1223 -0
  10. package/packages/cli/dist/commands/upgrade.js +472 -90
  11. package/packages/cli/dist/index.js +651 -46
  12. package/packages/cli/dist/integrations/declaration.js +158 -0
  13. package/packages/cli/dist/integrations/doctor-guards.js +126 -0
  14. package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
  15. package/packages/cli/dist/integrations/mcp-json.js +107 -0
  16. package/packages/cli/dist/integrations/memory-doctor.js +124 -0
  17. package/packages/cli/dist/integrations/registry.js +22 -0
  18. package/packages/cli/dist/integrations/spawn.js +228 -0
  19. package/packages/cli/dist/integrations/spec-kit.js +280 -0
  20. package/packages/cli/dist/integrations/verify.js +193 -0
  21. package/packages/cli/dist/integrations/windows-job.js +275 -0
  22. package/packages/cli/dist/lib/elevated-paths.js +74 -0
  23. package/packages/cli/dist/lib/install-set.js +1 -44
  24. package/packages/cli/dist/lib/manifest.js +64 -5
  25. package/packages/cli/dist/lib/prompts.js +0 -23
  26. package/packages/cli/dist/lib/safe-path.js +193 -2
  27. package/packages/cli/dist/lib/safe-text.js +78 -0
  28. package/packages/cli/dist/lib/substitute.js +1 -52
  29. package/packages/cli/dist/lib/summary.js +5 -5
  30. package/packages/cli/dist/templates.js +4 -14
  31. package/scripts/prepare.mjs +28 -12
  32. package/templates/agent-os/subagent-routing.json +4 -4
  33. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
  34. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
  35. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
  36. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  37. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
  38. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
  39. package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
  40. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
  41. package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
  42. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
  43. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
  44. package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
  45. package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
  46. package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
  47. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
  48. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
  49. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
  50. package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
  51. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
  52. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +4 -3
  53. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
  54. package/templates/agent-os/universal/.claude/settings.json +0 -8
  55. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
  56. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
  57. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
  58. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  59. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
  60. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  61. package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
  62. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
  63. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  64. package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
  65. package/templates/agent-os/universal/.codex/hooks.json +0 -10
  66. package/templates/agent-os/universal/AGENTS.md +208 -107
  67. package/templates/agent-os/universal/CLAUDE.md +15 -162
  68. package/templates/agent-os/universal/PLAN.md +17 -10
  69. package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
  70. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
  71. package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
  72. package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
  73. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
  74. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
  75. package/templates/agent-os/universal/layers.json +25 -30
  76. package/templates/hash-history.json +8 -4
  77. package/templates/release-ledger.json +2 -1
  78. package/packages/cli/dist/lib/composition.js +0 -20
  79. package/packages/cli/dist/lib/targets.js +0 -28
  80. package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
  81. package/packages/cli/dist/policy/core/adapter.js +0 -18
  82. package/packages/cli/dist/policy/core/coverage.js +0 -253
  83. package/packages/cli/dist/policy/core/decision-record.js +0 -287
  84. package/packages/cli/dist/policy/core/declaration.js +0 -127
  85. package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
  86. package/packages/cli/dist/policy/core/probe.js +0 -442
  87. package/packages/cli/dist/policy/core/registry.js +0 -115
  88. package/packages/cli/dist/policy/core/validation.js +0 -275
  89. package/packages/cli/dist/policy/core/vocabulary.js +0 -123
  90. package/packages/cli/dist/policy/harness/claude.js +0 -47
  91. package/packages/cli/dist/policy/harness/codex.js +0 -87
  92. package/packages/cli/dist/policy/harness/index.js +0 -15
  93. package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
  94. package/packages/cli/dist/policy/index.js +0 -17
  95. package/templates/agent-os/init/AGENTS.md +0 -201
  96. package/templates/agent-os/init/CLAUDE.md +0 -201
  97. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
  98. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
  99. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
  100. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
  101. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
  102. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
  103. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
  104. package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
  105. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
  106. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
  107. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
  108. package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
  109. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
  110. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
  111. package/templates/skeleton/aws-serverless/README.md +0 -181
  112. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
  113. package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
  114. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
  115. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
  116. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
  117. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
  118. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
  119. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
  120. package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
  121. package/templates/skeleton/aws-serverless/gitignore +0 -56
  122. package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
  123. package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
  124. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
  125. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
  126. package/templates/skeleton/aws-serverless/infra/package.json +0 -18
  127. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
  128. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
  129. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
  130. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
  131. package/templates/skeleton/aws-serverless/package.json +0 -30
  132. package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
  133. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
  134. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
  135. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
  136. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
  137. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
  138. package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
  139. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
  140. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
  141. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
  142. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
  143. package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
  144. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
  145. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
  146. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
  147. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
  148. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
  149. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
  150. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
  151. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
  152. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
  153. package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
  154. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
  155. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
  156. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
  157. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
  158. package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
  159. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
  160. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
  161. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
  162. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
  163. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
  164. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
  165. package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
  166. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
  167. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
  168. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
  169. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
  170. package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
  171. package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
  172. package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
  173. package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
  174. package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
  175. package/templates/skeleton/node-service/README.md +0 -104
  176. package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
  177. package/templates/skeleton/node-service/apps/web/package.json +0 -19
  178. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
  179. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
  180. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
  181. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
  182. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
  183. package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
  184. package/templates/skeleton/node-service/eslint.config.mjs +0 -20
  185. package/templates/skeleton/node-service/gitignore +0 -54
  186. package/templates/skeleton/node-service/package.json +0 -30
  187. package/templates/skeleton/node-service/packages/core/package.json +0 -11
  188. package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
  189. package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
  190. package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
  191. package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
  192. package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
  193. package/templates/skeleton/node-service/packages/db/package.json +0 -12
  194. package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
  195. package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
  196. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
  197. package/templates/skeleton/node-service/packages/shared/package.json +0 -11
  198. package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
  199. package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
  200. package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
  201. package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
  202. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
  203. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
  204. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
  205. package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
  206. package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
  207. package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
  208. package/templates/skeleton/node-service/services/api/package.json +0 -17
  209. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
  210. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
  211. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
  212. package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
  213. package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
  214. package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
  215. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
  216. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
  217. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
  218. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
  219. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
  220. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
  221. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
  222. package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
  223. package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
  224. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
  225. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
  226. package/templates/skeleton/node-service/services/worker/package.json +0 -16
  227. package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
  228. package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
  229. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
  230. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
  231. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
  232. package/templates/skeleton/node-service/tsconfig.base.json +0 -15
  233. package/templates/skeleton/node-service/tsconfig.json +0 -13
  234. package/templates/skeleton/node-service/vitest.config.ts +0 -12
@@ -0,0 +1,235 @@
1
+ # Why the workflow layer is opt-in, and where the line falls
2
+
3
+ The rule lives in `CLAUDE.md`, under "The opt-in workflow layer (experimental)".
4
+ This file explains why `layers.json` (RP-180) draws the line where it does,
5
+ and why a few files that look like PR-lifecycle automation stayed in Lean Core
6
+ instead of moving with the rest of it.
7
+
8
+ ## What moved, and why
9
+
10
+ Before this split, `templates/agent-os/universal/layers.json` had one array,
11
+ and `init`/`create` installed all of it unconditionally — including the queue
12
+ adapter, the `loop` and `pr-ship` skills, run-state, the run journal,
13
+ revalidation, claim-records and the PR-lifecycle helpers named below. None of
14
+ that is required by Lean Core: a thin CRUD service or a solo-maintained
15
+ library gets the same review gates and stop rules with none of the
16
+ autonomous, cooperative machinery, and shipping it anyway made every fresh
17
+ rig carry queue-selection semantics it never asked for.
18
+
19
+ The workflow layer is what a project opts into with `init --layer workflow`:
20
+ the queue adapter (`.claude/scripts/queue/`, `.claude/queue.json`), the
21
+ `loop` and `pr-ship` skills, `run-state.mjs`, the run journal
22
+ (`run-journal.mjs` — see the one exception below —, `journal/README.md`),
23
+ revalidation and claim-records, and the PR-lifecycle helpers
24
+ `decision-router.mjs`, `detect-missed-gate.mjs` and
25
+ `reconcile-external-prs.mjs`.
26
+
27
+ ## The exception: why `verdict.mjs`, `lib/verdict.mjs`, `lib/gate-coverage.mjs`
28
+ ## and `run-journal.mjs` stayed in Lean Core
29
+
30
+ These four read as PR-lifecycle automation — the ticket's own wording groups
31
+ "PR-lifecycle helpers" with the workflow layer — and the first draft of this
32
+ split moved them too. It does not survive contact with what Lean Core
33
+ actually installs, for one mechanical reason: `check-premises` is a Lean Core
34
+ skill (a session runs it standalone, with no queue and no loop, "before the
35
+ Red step" and again "before the gate"), and its own second entry point tells
36
+ the reader to run `node .claude/scripts/verdict.mjs check <report>
37
+ check-premises` on its own output. `.claude/scripts/verdict.mjs` is a single
38
+ module with **static, top-level imports** of `./lib/gate-coverage.mjs` and
39
+ `./run-journal.mjs` — Node resolves the whole file before any subcommand
40
+ runs, so a core-only install that shipped `verdict.mjs` without those two
41
+ would break `check-premises`'s own documented command on the very first
42
+ `check` subcommand, not only on `coverage`.
43
+
44
+ The three reviewer agent specs (`code-reviewer.md`, `security-scanner.md`,
45
+ `prose-reviewer.md`) are Lean Core for the same reason `check-premises` is —
46
+ review applies to any PR, loop-driven or not — and all three cite
47
+ `.claude/scripts/verdict.mjs` as the shape their own report is checked
48
+ against.
49
+
50
+ So the dependency runs from Lean Core outward: `check-premises` (core) needs
51
+ `verdict.mjs` (core) to load at all, `verdict.mjs`'s own module graph needs
52
+ `lib/gate-coverage.mjs` and `run-journal.mjs`, and neither of those two
53
+ imports anything outside itself (`node:fs`, `node:path`, and in
54
+ `gate-coverage.mjs`'s case nothing at all — the mention of
55
+ `decision-router.mjs` in its header is a comment, not an import). Moving the
56
+ leaves and leaving the root behind was the option that did not exist;
57
+ `invariants.md`'s "a file a core guard imports must stay core" generalises
58
+ past hooks to this case on the same reasoning.
59
+
60
+ `decision-router.mjs` itself has no such pull: `lib/gate-coverage.mjs`
61
+ mentions it only in a comment, and the one real static import of it
62
+ (`.claude/hooks/lib/edit-input.mjs`) is also a comment, not code. It moved
63
+ with the rest of the workflow layer, and `workflow.md`'s "PR flow" section
64
+ states the Lean Core fallback when it is not installed: every change still
65
+ reaches `code-reviewer`, and a human or the session decides which additional
66
+ reviewers apply, by the same triggers `decision-router` would have read.
67
+
68
+ `docs/decisions/gate-coverage.md` stayed with `pr-ship` (workflow) rather
69
+ than `lib/gate-coverage.mjs` (core) for a different reason: nothing in Lean
70
+ Core cites the record, only `pr-ship`'s own skill file does. The module and
71
+ the rationale for *why it exists* travel separately on purpose — the module
72
+ is a dependency of a core skill's documented command, the essay about why a
73
+ hook version of the same check was rejected is not.
74
+
75
+ ## The freeze this split does not touch
76
+
77
+ RP-53 holds revalidation and claim-records' *behavior* frozen through the
78
+ RP-26 gate (2026-10-27): no feature expansion, no experiment restart, no bias
79
+ introduced by this or any other 0.10 change. This split moves their
80
+ *install-time layer* only — which `layers.json` array they sit in — and
81
+ changes not one line of `revalidate.mjs`, `revalidation-report.mjs`,
82
+ `lib/revalidation-evidence.mjs`, `lib/revalidation-points.mjs` or
83
+ `lib/claim-records.mjs` beyond that relocation. After the gate: GO retains
84
+ them in the optional workflow layer as they are today; NO-GO removes them in
85
+ a bounded follow-up. Missing evidence is not a NO-GO.
86
+
87
+ ## What the manifest records, and the direction that matters
88
+
89
+ `RigManifest.layers` records which layer(s) a rig installed, and `upgrade`
90
+ refreshes only what it names. The one detail worth restating here because
91
+ getting it backwards is silent and destructive: a manifest written **before**
92
+ this field existed recorded nothing about layers because there was only one
93
+ payload to record — so `parseManifest` reads that absence as `['process',
94
+ 'workflow']`, never as `['process']`. A fresh `init` with no
95
+ `--layer workflow` flag writes `['process']` explicitly. The two defaults
96
+ point in opposite directions on purpose, and swapping them would make the
97
+ very next `upgrade` on an existing dogfood repository report every workflow
98
+ file it already has as `retired` and stop managing it.
99
+
100
+ ## Interaction with `uninstall` (RP-181, merged after this split was written)
101
+
102
+ `uninstall` needs no separate awareness of `layers` at all, and gains none:
103
+ it walks `manifest.files` and `manifest.kept` directly (`commands/uninstall.ts`),
104
+ and both of those already name exactly the paths a given install wrote or
105
+ found, regardless of which `layers.json` array a path happens to live in. Three
106
+ cases, all covered by the same unmodified byte-hash check:
107
+
108
+ - **A Core-only rig** (no `--layer workflow`) never has a workflow-layer path in
109
+ `manifest.files` in the first place — `initManifest` only reads the layers
110
+ `effectiveLayers` resolved — so `uninstall` has nothing workflow-shaped to
111
+ remove and reports nothing about it.
112
+ - **A rig that opted into the workflow layer** has those paths in
113
+ `manifest.files` exactly like any process-layer path, and `uninstall` removes
114
+ or preserves them by the same pristine-bytes check as everything else it
115
+ owns.
116
+ - **An inherited pre-0.10 rig** (a manifest with no `layers` key at all) had its
117
+ workflow files recorded in `files` the only way any release before this one
118
+ ever wrote a manifest — there was no `kept`/`files` split by layer to begin
119
+ with — so `uninstall` already treats them as owned, the same as it always
120
+ has. Nothing here widens what `uninstall` is willing to remove, and nothing
121
+ narrows it: a manifest either names a path in `files`/`kept` with the right
122
+ hash, or `uninstall` leaves it alone, and that rule does not read `layers`.
123
+
124
+ Pinned in `packages/cli/test/uninstall.test.ts` (absent in a generated rig —
125
+ this is the generator's own test suite, not a payload path) › "removes an
126
+ installed workflow-layer file exactly like any Core file, when the rig opted
127
+ in", › "a workflow-layer file never installed here (Core-only rig) is simply
128
+ absent from the manifest, never reported \"not a path this release installs\"",
129
+ and › "an inherited pre-layers manifest (no `layers` key) still owns its
130
+ workflow files, exactly like any other installed path".
131
+
132
+ ## There is no opt-out short of `uninstall`
133
+
134
+ Once a rig has the workflow layer — `init --layer workflow`, or inherited
135
+ from before RP-180 — nothing in `init` or `upgrade` ever drops a layer a
136
+ READABLE manifest already recorded; `effectiveLayers` (`init.ts`) is
137
+ additive by construction, full stop. `detectLayersOnDisk` (`upgrade.ts`) is
138
+ a narrower claim: it only ever runs when there is NO readable manifest at
139
+ all, and even then it does not treat "a layer's files exist" as license to
140
+ keep them regardless of how many — it is additive in the same direction
141
+ (never narrows what a READABLE manifest said), but on the bootstrapped path
142
+ it decides per layer by quorum (`LAYER_ADOPTION_QUORUM`, more than half of
143
+ the layer's own files present), and can legitimately decide a layer is NOT
144
+ this rig's: a Core-only rig with one or a few stray files that happen to
145
+ share a workflow-layer path is left at Core, its stray files untouched and
146
+ unrecorded (round 4, blocker A — round 3's fix asked only "does at least one
147
+ file exist", which re-adopted the whole layer from a single stray path).
148
+ The only supported way back to Core-only for a rig the manifest genuinely
149
+ still names as having the workflow layer is `uninstall` (removing the
150
+ workflow files this rig owns) followed by a fresh `init` with no `--layer`
151
+ flag — see "The exact opt-out procedure, measured" below for what that
152
+ takes when `uninstall` had to preserve something.
153
+
154
+ **Hand-editing `layers` in `.claude/.rig-manifest.json` down to `["process"]`
155
+ is not that opt-out, and it does not do what it looks like it does.** The
156
+ files themselves are untouched by the edit itself — nothing deletes them —
157
+ but the next `upgrade` reads the manifest's `layers` as authoritative
158
+ (`upgrade.ts`'s `initInstallSet(repoDir, project, layers)`), so every
159
+ workflow path drops out of that plan's own install set and is reported
160
+ `retired`, reason `"no longer shipped by this release — the rig no longer
161
+ manages it; it is now yours"` — the same verdict RP-177 gave a deleted stack
162
+ overlay, applied here to files that are simply no longer read as this rig's
163
+ layer. `applyUpgrade` then writes a manifest whose `files` map has no entry
164
+ for any of the roughly three dozen workflow paths at all — measured, on a
165
+ clean `--layer workflow` install, at exactly 89 file entries down to 56
166
+ (`packages/cli/test/upgrade.test.ts`, absent in a generated rig, pins "a
167
+ clean workflow-layer install hand-edited down to a core-only layers array
168
+ goes from 89 manifest entries to 56"; the figure moved by one from an
169
+ earlier 86/53 when RP-186 added `docs/decisions/agents-md-canonical.md` to
170
+ the process layer, and by two more when `implementation-agent` joined it).
171
+ The files stay on disk, silently un-hashed and unowned —
172
+ and a LATER `uninstall`, reading the same manifest, has nothing there to
173
+ recognise them by: they read as an ordinary foreign/untracked path, not as
174
+ something this release ever installed, and are left alone. The manifest
175
+ itself does not warn about this because it has no opinion on why `layers`
176
+ changed; the tool trusts its own evidence file. This is measured behaviour,
177
+ not a guess: `packages/cli/test/upgrade.test.ts` (absent in a generated rig)
178
+ pins the `retired` verdict and the orphaning it causes in "hand-editing
179
+ `layers` down to `["process"]` on a rig that already has the workflow
180
+ layer retires every workflow file — on disk, unowned, never deleted".
181
+
182
+ **The mirror hand-edit — `layers: ["workflow"]` alone, dropping `process` —
183
+ does the identical thing in the other direction, on a still-readable
184
+ manifest.** `initInstallSet` reads exactly the array it is given; a `layers`
185
+ that never names `process` un-owns every Core file the same way dropping
186
+ `workflow` un-owns every workflow file: the next `upgrade` retires them from
187
+ the plan and drops them from `files`, silently, files untouched on disk.
188
+ (This is not the bootstrapped path's own normalisation — `detectLayersOnDisk`
189
+ always forces `process` into a layer set it INFERS from disk; a READABLE
190
+ manifest is trusted as written, `process` included, and nothing here
191
+ prevents an operator from writing one that omits it.)
192
+
193
+ ## The exact opt-out procedure, measured (RP-180 round 4, blocker B)
194
+
195
+ `uninstall --yes` followed by a fresh `init` (no `--layer` flag) reaches
196
+ Core-only in exactly ONE of the two cases that matter, and round 3 stated it
197
+ as though it always worked. Measured on the built CLI, both ways:
198
+
199
+ - **Nothing on the rig was ever edited.** `uninstall --yes` removes every
200
+ file it owns — Core and workflow alike — and, because nothing was left to
201
+ preserve, deletes the manifest too. A fresh `init` then finds no manifest
202
+ and no workflow files: Core-only, as documented. A fresh `init` then
203
+ installs Core only.
204
+ - **Anything was edited — one file is enough.** `uninstall --yes` preserves
205
+ that one file (and reports it) and, because something was preserved,
206
+ **keeps the manifest** — still recording `layers: ["process","
207
+ workflow"]`. The next plain `init` reads that surviving manifest
208
+ (`effectiveLayers` in `init.ts`: `previous?.layers.includes('workflow')`)
209
+ and reinstalls the entire workflow layer right back. `uninstall --yes` leaves
210
+ the one preserved file plus the manifest, and a plain `init` afterward
211
+ brings the whole workflow layer back, `layers` still both. The
212
+ "fresh `init`" instruction alone does NOT reach Core-only here — round 3
213
+ said it did.
214
+
215
+ **The procedure that actually reaches Core-only when something was
216
+ preserved: pass `--detach`.** `uninstall --yes --detach` performs the
217
+ identical safe removal and then deletes the manifest regardless of what is
218
+ left behind, handing the preserved file(s) over to the operator outright
219
+ (they stay on disk, no longer named by anything). The next plain `init`
220
+ then finds no manifest at all and installs Core only. A fresh `init` then installs
221
+ Core, leaves the preserved file untouched, and records `layers: ["process"]`. No new flag
222
+ was added for this — `--detach` already existed (RP-181) for the identical
223
+ reason: a manifest kept alive only by something it has no business
224
+ prescribing further action over.
225
+
226
+ Equivalently, without `--detach`: remove the file(s) `uninstall --yes`
227
+ reported as preserved, then remove `.claude/.rig-manifest.json` by hand,
228
+ then run a plain `init`. Both procedures were measured to reach the same
229
+ end state; `--detach` is the one to recommend because it is one command
230
+ instead of a report a human has to read and act on by hand.
231
+
232
+ Pinned end-to-end on the built CLI in `test/e2e/uninstall.test.ts` (absent in
233
+ a generated rig) › `describe('the opt-out procedure to Core-only, measured
234
+ (RP-180 round 4, blocker B)')`: both cases end in `layers: ["process"]` and
235
+ no un-preserved workflow file left on disk.
@@ -7,6 +7,7 @@
7
7
  ".claude/skills/new-invariant/guard-invariant.example.mjs",
8
8
  ".claude/skills/new-invariant/guard-invariant.example.test.mjs",
9
9
  ".claude/agents/test-writer.md",
10
+ ".claude/agents/implementation-agent.md",
10
11
  ".claude/agents/code-reviewer.md",
11
12
  ".claude/agents/security-scanner.md",
12
13
  ".claude/agents/prose-reviewer.md",
@@ -20,41 +21,52 @@
20
21
  ".claude/hooks/warn-subagent-routing.mjs",
21
22
  ".claude/hooks/lib/edit-input.mjs",
22
23
  ".claude/hooks/lib/hook-input.mjs",
23
- ".claude/skills/pr-ship/SKILL.md",
24
- ".claude/skills/loop/SKILL.md",
25
24
  ".claude/skills/check-premises/SKILL.md",
26
25
  ".claude/skills/worktree-task/SKILL.md",
27
26
  ".agents/skills/new-invariant/SKILL.md",
28
27
  ".agents/skills/new-invariant/guard-invariant.example.mjs",
29
28
  ".agents/skills/new-invariant/guard-invariant.example.test.mjs",
30
- ".agents/skills/pr-ship/SKILL.md",
31
- ".agents/skills/loop/SKILL.md",
32
29
  ".agents/skills/check-premises/SKILL.md",
33
30
  ".agents/skills/worktree-task/SKILL.md",
34
31
  ".codex/agents/test-writer.toml",
32
+ ".codex/agents/implementation-agent.toml",
35
33
  ".codex/agents/code-reviewer.toml",
36
34
  ".codex/agents/security-scanner.toml",
37
35
  ".codex/agents/prose-reviewer.toml",
38
- ".claude/scripts/detect-missed-gate.mjs",
39
- ".claude/scripts/decision-router.mjs",
40
- ".claude/scripts/reconcile-external-prs.mjs",
41
36
  ".claude/scripts/stop-flag.mjs",
42
37
  ".claude/scripts/unattended-flag.mjs",
43
38
  ".claude/scripts/git-env.mjs",
44
- ".claude/scripts/preflight.mjs",
45
39
  ".claude/scripts/doctor.mjs",
46
40
  ".claude/scripts/run-journal.mjs",
47
- ".claude/scripts/run-state.mjs",
48
41
  ".claude/scripts/verdict.mjs",
42
+ ".claude/scripts/lib/gate-coverage.mjs",
43
+ ".claude/scripts/lib/verdict.mjs",
44
+ ".claude/scripts/lib/secrets.mjs",
45
+ ".claude/scripts/lib/shell-tools.mjs",
46
+ "PLAN.md",
47
+ "docs/decisions/fail-open-guards.md",
48
+ "docs/decisions/codex-adapter.md",
49
+ "docs/decisions/subagent-routing.md",
50
+ "docs/decisions/review-lanes.md",
51
+ "docs/decisions/workflow-layer-split.md",
52
+ "docs/decisions/session-start-wire-format.md",
53
+ "docs/decisions/agents-md-canonical.md"
54
+ ],
55
+ "workflow": [
56
+ ".claude/skills/pr-ship/SKILL.md",
57
+ ".claude/skills/loop/SKILL.md",
58
+ ".agents/skills/pr-ship/SKILL.md",
59
+ ".agents/skills/loop/SKILL.md",
60
+ ".claude/scripts/detect-missed-gate.mjs",
61
+ ".claude/scripts/decision-router.mjs",
62
+ ".claude/scripts/reconcile-external-prs.mjs",
63
+ ".claude/scripts/preflight.mjs",
64
+ ".claude/scripts/run-state.mjs",
49
65
  ".claude/scripts/revalidate.mjs",
50
66
  ".claude/scripts/revalidation-report.mjs",
51
- ".claude/scripts/lib/gate-coverage.mjs",
52
67
  ".claude/scripts/lib/claim-records.mjs",
53
68
  ".claude/scripts/lib/revalidation-evidence.mjs",
54
69
  ".claude/scripts/lib/revalidation-points.mjs",
55
- ".claude/scripts/lib/verdict.mjs",
56
- ".claude/scripts/lib/secrets.mjs",
57
- ".claude/scripts/lib/shell-tools.mjs",
58
70
  ".claude/scripts/queue/core.mjs",
59
71
  ".claude/scripts/queue/plan-md.mjs",
60
72
  ".claude/scripts/queue/github-issues.mjs",
@@ -66,30 +78,13 @@
66
78
  ".claude/scripts/queue/gate-rounds.mjs",
67
79
  ".claude/queue.json",
68
80
  ".rig/revalidation.json",
69
- "PLAN.md",
70
81
  "journal/README.md",
71
82
  "docs/decisions/gate-coverage.md",
72
- "docs/decisions/fail-open-guards.md",
73
- "docs/decisions/codex-adapter.md",
74
- "docs/decisions/subagent-routing.md",
75
83
  "docs/decisions/closing-a-task.md",
76
84
  "docs/decisions/content-blind-revalidation.md",
77
- "docs/decisions/review-lanes.md",
78
85
  "docs/decisions/run-directory.md",
79
86
  "docs/decisions/spacing-rations-mechanisms.md",
80
87
  "docs/decisions/stop-conditions-in-a-file.md",
81
88
  "docs/decisions/two-empty-endings.md"
82
- ],
83
- "architecture": [
84
- ".claude/rules/architecture.md",
85
- ".claude/hooks/guard-core-purity.mjs",
86
- ".claude/hooks/guard-web-boundary.mjs"
87
- ],
88
- "meta": [
89
- ".claude/settings.json",
90
- ".codex/config.toml",
91
- ".codex/hooks.json",
92
- "CLAUDE.md",
93
- "AGENTS.md"
94
89
  ]
95
90
  }
@@ -12,7 +12,8 @@
12
12
  "0.7.0",
13
13
  "0.7.1",
14
14
  "0.8.0",
15
- "0.9.0"
15
+ "0.9.0",
16
+ "0.9.1"
16
17
  ],
17
18
  "files": {
18
19
  ".agents/skills/check-premises/SKILL.md": {
@@ -397,7 +398,8 @@
397
398
  "d8a5916bae71efcc6a21978af713112c9ec33a7554fd07f5978bdf2c3633b560",
398
399
  "1804c2351a4f3042dc6a3fb4203f96e98ae12bec32569b99bda77d002d6ba5f2",
399
400
  "b81ae3181e02b9c1c3f0e954688490ab3db3961381c2dc397c47c651c9d07f88",
400
- "c9902da15d06de3e952352115db0288433520eae2caac1f70ee6e06753d1f444"
401
+ "c9902da15d06de3e952352115db0288433520eae2caac1f70ee6e06753d1f444",
402
+ "e31f9899d6edad127db7a2f43dafdfff2671c5c780209cccd3107e56b09c6042"
401
403
  ]
402
404
  },
403
405
  ".claude/scripts/queue/gate-rounds.mjs": {
@@ -437,7 +439,8 @@
437
439
  "b8be5855809c0d44ece94858456098663296c17c5ad9abb1ea2cf1a8f48ca491",
438
440
  "3fc8ff9a06a2e22355a660a31e2943a97f50869f7e5726f768a7931d17b64b5b",
439
441
  "f44203af187e855b9f738f8dbedfd313abf9d6a5cc29182fb46cd58d70698d7a",
440
- "c2f2af8ac5eadd8d35a15bcdeffe585e32c6f99b4e6ab9ace0f9d7fd9d694943"
442
+ "c2f2af8ac5eadd8d35a15bcdeffe585e32c6f99b4e6ab9ace0f9d7fd9d694943",
443
+ "add45ea2bf2f96fbda0399afc1903bab75c251001f4d05fcbaa0b5958c8c1702"
441
444
  ]
442
445
  },
443
446
  ".claude/scripts/queue/plan-md.mjs": {
@@ -459,7 +462,8 @@
459
462
  ".claude/scripts/reconcile-external-prs.mjs": {
460
463
  "since": "0.3.0",
461
464
  "hashes": [
462
- "a326acd64ea19a50d093c91e711ad0234eb05c76d00066db5657c22f4355c190"
465
+ "a326acd64ea19a50d093c91e711ad0234eb05c76d00066db5657c22f4355c190",
466
+ "1b14ee0302932a40611011a57410d7f45d6c2d54799847b94f01f448de7c1827"
463
467
  ]
464
468
  },
465
469
  ".claude/scripts/revalidate.mjs": {
@@ -12,5 +12,6 @@
12
12
  "0.7.0": "6589db36e1daa63a99ec595191db1cccf7373196",
13
13
  "0.7.1": "52e879b6c103f6ba70493007b6a6466c57ea9824",
14
14
  "0.8.0": "870f9a3ecae2881908ece8ec3e2ac13f84f505f5",
15
- "0.9.0": "c27f391e7395accaf09354f255a07e1b9e4710c1"
15
+ "0.9.0": "c27f391e7395accaf09354f255a07e1b9e4710c1",
16
+ "0.9.1": "872f7f67f11761c17aad3cbbe26540862581795b"
16
17
  }
@@ -1,20 +0,0 @@
1
- /**
2
- * Layer-composition policy (PLAN phase 9): layers must claim disjoint paths.
3
- * A collision is refused, never resolved silently by copy order. Intended
4
- * overwrites — if one ever becomes necessary — are declared here, visibly,
5
- * not implied by ordering.
6
- */
7
- export const ALLOWED_OVERWRITES = new Set([]);
8
- export function detectCollisions(layers, allowed) {
9
- const claims = new Map();
10
- for (const layer of layers) {
11
- for (const file of layer.files) {
12
- const owners = claims.get(file) ?? [];
13
- owners.push(layer.name);
14
- claims.set(file, owners);
15
- }
16
- }
17
- return [...claims]
18
- .filter(([file, owners]) => owners.length > 1 && !allowed.has(file))
19
- .map(([file, owners]) => ({ path: file, layers: owners }));
20
- }
@@ -1,28 +0,0 @@
1
- export const TARGETS = {
2
- 'aws-serverless': {
3
- skeletonDir: 'aws-serverless',
4
- stacks: ['node-ts', 'aws-cdk'],
5
- defaultRegion: 'eu-central-1',
6
- },
7
- 'node-service': {
8
- skeletonDir: 'node-service',
9
- stacks: ['node-ts'],
10
- },
11
- };
12
- export const TARGET_NAMES = Object.keys(TARGETS);
13
- /**
14
- * The pre-selected entry in the interactive menu, and the API-level default.
15
- *
16
- * 🔴 **Not the fallback for a non-interactive run.** A run with no TTY and no
17
- * `--target` is *refused* (`index.ts`) — never prompt into a pipe, and never
18
- * guess a whole project shape for a script that did not say. This constant is
19
- * what `Enter`, an out-of-range number or an unrecognised name resolve to at the
20
- * prompt (`prompts.ts`), plus what `createProject` uses when called as a library
21
- * with no target.
22
- *
23
- * The comment this replaces said "one implicit target", which was stale from
24
- * when there was one. Its first correction claimed the CLI defaults silently in
25
- * CI — the opposite of what it does, in the file a maintainer would open to
26
- * check exactly that.
27
- */
28
- export const DEFAULT_TARGET = 'aws-serverless';
@@ -1,165 +0,0 @@
1
- import { CAPABILITY_STATES } from '../core/vocabulary.js';
2
- export const BENCHMARK_CLASSIFICATIONS = Object.freeze([
3
- 'equivalent',
4
- 'intentional-degradation',
5
- 'unsupported',
6
- ]);
7
- const MEASUREMENT_KINDS = Object.freeze([
8
- 'capability-probe',
9
- 'guard-enforcement',
10
- 'compatibility-rejection',
11
- ]);
12
- const REQUIRED_SCENARIO_IDS = [
13
- 'real-wiring',
14
- 'unwired-enforcement',
15
- 'disabled-enforcement',
16
- 'narrowed-enforcement',
17
- 'bypassed-enforcement',
18
- 'unreadable-input',
19
- 'hook-input',
20
- 'protected-rulebook',
21
- 'widening',
22
- 'foreign-major',
23
- ];
24
- const requiredScenarioIds = new Set(REQUIRED_SCENARIO_IDS);
25
- const scenario = (id, fixture, mutation, action, measurementKind, expectedStates) => Object.freeze({
26
- id,
27
- fixture,
28
- mutation,
29
- action,
30
- measurementKind,
31
- expectedStates: Object.freeze([...expectedStates]),
32
- });
33
- export const BENCHMARK_CORPUS = Object.freeze({
34
- contractMajor: 1,
35
- scenarios: Object.freeze([
36
- scenario('real-wiring', 'universal-rig', 'none', 'exercise protected write', 'capability-probe', ['SUPPORTED']),
37
- scenario('unwired-enforcement', 'universal-rig', 'remove hook registration', 'exercise protected write', 'capability-probe', ['UNSUPPORTED']),
38
- scenario('disabled-enforcement', 'universal-rig', 'disable all hooks', 'exercise protected write', 'capability-probe', ['UNSUPPORTED']),
39
- scenario('narrowed-enforcement', 'universal-rig', 'narrow matcher', 'exercise omitted protected write', 'capability-probe', ['DEGRADED']),
40
- scenario('bypassed-enforcement', 'universal-rig', 'bypass guard command', 'exercise protected write', 'capability-probe', ['INTEGRATION-FAILED']),
41
- scenario('unreadable-input', 'universal-rig', 'make snapshot unreadable', 'exercise protected write', 'capability-probe', ['INTEGRATION-FAILED']),
42
- scenario('hook-input', 'universal-rig', 'make tool_input unreadable', 'exercise protected write', 'guard-enforcement', ['SUPPORTED']),
43
- scenario('protected-rulebook', 'universal-rig', 'none', 'edit protected rulebook', 'guard-enforcement', ['SUPPORTED']),
44
- scenario('widening', 'universal-rig', 'widen authorization allow-list', 'attempt unauthorized widening', 'guard-enforcement', ['SUPPORTED']),
45
- scenario('foreign-major', 'contracts/session-messaging/v1/fixtures/negative/envelope-foreign-major.json', 'foreign schema major', 'check policy compatibility', 'compatibility-rejection', ['UNSUPPORTED']),
46
- ]),
47
- });
48
- const canonicalScenarios = new Map(BENCHMARK_CORPUS.scenarios.map((entry) => [entry.id, entry]));
49
- export const classifyCapability = (state) => {
50
- switch (state) {
51
- case 'SUPPORTED':
52
- return { classification: 'equivalent', integrationFailed: false };
53
- case 'DEGRADED':
54
- return { classification: 'intentional-degradation', integrationFailed: false };
55
- case 'UNSUPPORTED':
56
- return { classification: 'unsupported', integrationFailed: false };
57
- case 'INTEGRATION-FAILED':
58
- return { classification: 'unsupported', integrationFailed: true };
59
- default:
60
- throw new Error(`Unknown capability state: ${String(state)}`);
61
- }
62
- };
63
- const isOwnDataRecord = (value) => {
64
- if (typeof value !== 'object' || value === null || Array.isArray(value))
65
- return false;
66
- const prototype = Object.getPrototypeOf(value);
67
- if (prototype !== Object.prototype && prototype !== null)
68
- return false;
69
- return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => descriptor.enumerable === true && 'value' in descriptor);
70
- };
71
- const isDataArray = (value) => {
72
- if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype)
73
- return false;
74
- for (let index = 0; index < value.length; index += 1) {
75
- const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
76
- if (descriptor === undefined || descriptor.enumerable !== true || !('value' in descriptor))
77
- return false;
78
- }
79
- return true;
80
- };
81
- const hasOnlyKeys = (value, keys) => {
82
- const actual = Object.keys(value);
83
- return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
84
- };
85
- const nonBlank = (value) => typeof value === 'string' && value.trim() !== '';
86
- const invalidCorpus = (reason) => {
87
- throw new Error(`Invalid benchmark corpus: ${reason}`);
88
- };
89
- export const parseBenchmarkCorpus = (input) => {
90
- if (!isOwnDataRecord(input) || !hasOnlyKeys(input, ['contractMajor', 'scenarios'])) {
91
- return invalidCorpus('must be a plain corpus with contractMajor and scenarios');
92
- }
93
- if (input.contractMajor !== 1)
94
- return invalidCorpus('unsupported contract major; expected 1');
95
- if (!isDataArray(input.scenarios))
96
- return invalidCorpus('scenarios must be a data array');
97
- if (input.scenarios.length !== REQUIRED_SCENARIO_IDS.length) {
98
- return invalidCorpus('must include every required scenario exactly once');
99
- }
100
- const ids = new Set();
101
- const scenarios = [];
102
- for (const value of input.scenarios) {
103
- if (!isOwnDataRecord(value) ||
104
- !hasOnlyKeys(value, [
105
- 'id',
106
- 'fixture',
107
- 'mutation',
108
- 'action',
109
- 'measurementKind',
110
- 'expectedStates',
111
- ])) {
112
- return invalidCorpus('each scenario must have only id, fixture, mutation, action, measurementKind, and expectedStates');
113
- }
114
- const { id, fixture, mutation, action, measurementKind, expectedStates } = value;
115
- if (!nonBlank(id) || !requiredScenarioIds.has(id))
116
- return invalidCorpus(`unknown scenario ${String(id)}`);
117
- if (ids.has(id))
118
- return invalidCorpus(`scenario ${id} is duplicated`);
119
- if (!nonBlank(fixture) || !nonBlank(mutation) || !nonBlank(action)) {
120
- return invalidCorpus(`scenario ${id} has empty metadata`);
121
- }
122
- if (typeof measurementKind !== 'string' ||
123
- !MEASUREMENT_KINDS.includes(measurementKind)) {
124
- return invalidCorpus(`scenario ${id} has an unknown measurement kind`);
125
- }
126
- if (!isDataArray(expectedStates) || expectedStates.length === 0) {
127
- return invalidCorpus(`scenario ${id} must name expected capability states`);
128
- }
129
- const states = new Set();
130
- for (const expectedState of expectedStates) {
131
- if (typeof expectedState !== 'string' ||
132
- !CAPABILITY_STATES.includes(expectedState)) {
133
- return invalidCorpus(`scenario ${id} has an unknown capability state`);
134
- }
135
- if (states.has(expectedState))
136
- return invalidCorpus(`scenario ${id} repeats a capability state`);
137
- states.add(expectedState);
138
- }
139
- const canonical = canonicalScenarios.get(id);
140
- if (fixture !== canonical.fixture ||
141
- mutation !== canonical.mutation ||
142
- action !== canonical.action ||
143
- measurementKind !== canonical.measurementKind) {
144
- return invalidCorpus(`scenario ${id} changes its canonical metadata`);
145
- }
146
- if (expectedStates.length !== canonical.expectedStates.length ||
147
- expectedStates.some((state, index) => state !== canonical.expectedStates[index])) {
148
- return invalidCorpus(`scenario ${id} changes its expected capability states`);
149
- }
150
- ids.add(id);
151
- scenarios.push(Object.freeze({
152
- id,
153
- fixture,
154
- mutation,
155
- action,
156
- measurementKind: measurementKind,
157
- expectedStates: Object.freeze([...states]),
158
- }));
159
- }
160
- for (const id of REQUIRED_SCENARIO_IDS) {
161
- if (!ids.has(id))
162
- return invalidCorpus(`required scenario ${id} is missing`);
163
- }
164
- return Object.freeze({ contractMajor: 1, scenarios: Object.freeze(scenarios) });
165
- };
@@ -1,18 +0,0 @@
1
- /**
2
- * The contract a harness adapter satisfies (RP-76).
3
- *
4
- * The core knows that a harness exists and has a hook-wiring surface; it does
5
- * not know any harness's name, event vocabulary, tool names or paths. Those
6
- * belong to the adapter, one module per harness under `../harness/`, and
7
- * adding a harness means adding one such module and registering it — pinned in
8
- * `test/template/policy-declaration.test.ts` under "adding a harness touches
9
- * adapters only", whose two tests name exactly the files that may mention each
10
- * harness. Their names carry the harness words this file may not, which is why
11
- * the describe is cited here rather than either test.
12
- *
13
- * An adapter is a mapping, not a compiler: given a declaration it names the
14
- * event, matcher and hook path the harness wires for it. The correspondence
15
- * between that answer and the snapshot the rig actually ships is a test, in
16
- * both directions, in the same file.
17
- */
18
- export {};