@haposoft/cafekit 0.16.7 → 0.18.1

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 (231) hide show
  1. package/README.md +14 -22
  2. package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
  3. package/bin/__tests__/codex-hooks.test.js +169 -272
  4. package/bin/__tests__/codex-native.test.js +100 -202
  5. package/bin/__tests__/develop-contract.test.js +122 -1413
  6. package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
  7. package/bin/__tests__/grok-envelope.test.js +0 -56
  8. package/bin/__tests__/hook-payload.test.js +4 -6
  9. package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
  10. package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
  11. package/bin/__tests__/omp-bridge.test.js +20 -17
  12. package/bin/__tests__/omp-hooks.test.js +0 -19
  13. package/bin/__tests__/optional-skill-inventory.test.js +112 -4
  14. package/bin/__tests__/orca-session.test.js +0 -2
  15. package/bin/__tests__/package-inventory.test.js +90 -925
  16. package/bin/__tests__/runtime-dir.test.js +0 -3
  17. package/bin/__tests__/runtime-schema.test.js +3 -2
  18. package/bin/__tests__/skill-routing-source.test.js +27 -15
  19. package/bin/__tests__/spec-narrowing.test.js +30 -161
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
  21. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  22. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  23. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  24. package/bin/__tests__/validator-grounding.test.js +18 -2949
  25. package/bin/lib/managed-writer.js +2 -3
  26. package/bin/phases/codex-runtime.js +27 -0
  27. package/bin/phases/copy-payload.js +13 -28
  28. package/bin/phases/omp-runtime.js +25 -0
  29. package/package.json +2 -3
  30. package/src/claude/CLAUDE.md +3 -10
  31. package/src/claude/agents/brainstormer.md +7 -3
  32. package/src/claude/agents/code-auditor.md +44 -56
  33. package/src/claude/agents/debugger.md +8 -88
  34. package/src/claude/agents/deployer.md +3 -5
  35. package/src/claude/agents/docs-keeper.md +6 -14
  36. package/src/claude/agents/git-ops.md +2 -4
  37. package/src/claude/agents/implementer.md +12 -16
  38. package/src/claude/agents/inspector.md +4 -3
  39. package/src/claude/agents/project-manager.md +9 -17
  40. package/src/claude/agents/researcher.md +9 -3
  41. package/src/claude/agents/spec-maker.md +15 -15
  42. package/src/claude/agents/test-runner.md +8 -11
  43. package/src/claude/agents/ui-ux-designer.md +0 -1
  44. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  45. package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
  46. package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
  47. package/src/claude/hooks/__tests__/state.test.js +68 -0
  48. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  49. package/src/claude/hooks/agent.cjs +2 -1
  50. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  51. package/src/claude/hooks/rules.cjs +2 -1
  52. package/src/claude/hooks/session.cjs +0 -1
  53. package/src/claude/hooks/spec-gate.cjs +17 -83
  54. package/src/claude/hooks/spec-state.cjs +142 -39
  55. package/src/claude/hooks/state.cjs +21 -2
  56. package/src/claude/migration-manifest.json +42 -22
  57. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  58. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  59. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  60. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  61. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  62. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  63. package/src/claude/rules/ai-dev-rules.md +2 -3
  64. package/src/claude/rules/hook-protocols.md +8 -15
  65. package/src/claude/rules/manage-docs.md +9 -23
  66. package/src/claude/rules/orchestrator.md +3 -4
  67. package/src/claude/rules/review-audit-self-decision.md +3 -3
  68. package/src/claude/rules/skill-domain-routing.md +1 -3
  69. package/src/claude/rules/skill-workflow-routing.md +4 -5
  70. package/src/claude/rules/state-sync.md +3 -12
  71. package/src/claude/rules/workflow.md +11 -19
  72. package/src/claude/runtime.schema.json +5 -3
  73. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  74. package/src/claude/scripts/provenance.cjs +23 -5
  75. package/src/claude/scripts/spec-receipt.cjs +24 -121
  76. package/src/claude/scripts/spec-resolver.cjs +41 -458
  77. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  78. package/src/claude/settings/settings.json +1 -32
  79. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  80. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  81. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  82. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  83. package/src/claude/skills/code-review/SKILL.md +29 -81
  84. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
  85. package/src/claude/skills/debug/SKILL.md +29 -149
  86. package/src/claude/skills/develop/SKILL.md +26 -83
  87. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  88. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  89. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  90. package/src/claude/skills/docs/SKILL.md +2 -2
  91. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  92. package/src/claude/skills/fix/SKILL.md +153 -0
  93. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  94. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  95. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  96. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  97. package/src/claude/skills/git/SKILL.md +48 -9
  98. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  99. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  100. package/src/claude/skills/orca/SKILL.md +9 -8
  101. package/src/claude/skills/research/SKILL.md +17 -46
  102. package/src/claude/skills/scout/SKILL.md +61 -0
  103. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  104. package/src/claude/skills/specs/SKILL.md +22 -29
  105. package/src/claude/skills/specs/references/review.md +8 -6
  106. package/src/claude/skills/specs/references/templates.md +34 -41
  107. package/src/claude/skills/sync/SKILL.md +16 -45
  108. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  109. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  110. package/src/claude/skills/test/SKILL.md +39 -104
  111. package/src/claude/skills/test/references/execution-strategy.md +93 -21
  112. package/src/claude/skills/test/references/failure-triage.md +1 -1
  113. package/src/claude/skills/ui-ux/LICENSE +21 -0
  114. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  115. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  116. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  117. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  118. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  119. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  120. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  121. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  122. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  123. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  124. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  125. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  126. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  127. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  128. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  129. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  130. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  131. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  132. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  133. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  134. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  135. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  136. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  137. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  138. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  139. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  140. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  141. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  142. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  143. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  144. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  145. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  146. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  147. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  148. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  149. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  150. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  151. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  152. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  153. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  154. package/src/claude/skills/ui-ux/references/review.md +528 -0
  155. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  156. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  157. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  158. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  159. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  160. package/src/claude/skills/ui-ux/references/system.md +219 -0
  161. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  162. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  163. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  164. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  165. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  166. package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
  167. package/src/claude/skills/web-testing/SKILL.md +2 -20
  168. package/src/claude/status.cjs +149 -144
  169. package/src/codex/AGENTS.md +8 -13
  170. package/src/codex/hooks/agent.cjs +2 -1
  171. package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
  172. package/src/codex/hooks/lib/spec-utils.cjs +6 -52
  173. package/src/codex/hooks/rules.cjs +2 -1
  174. package/src/codex/hooks/session.cjs +0 -2
  175. package/src/codex/hooks/spec-gate.cjs +13 -81
  176. package/src/codex/hooks/spec-state.cjs +37 -41
  177. package/src/codex/hooks.json +0 -20
  178. package/src/codex/rules/state-sync.md +3 -12
  179. package/src/common/AGENTS.md +6 -12
  180. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  181. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  182. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  183. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  184. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  185. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  186. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  187. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  188. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  189. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  190. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  191. package/src/claude/hooks/completion-authority.cjs +0 -163
  192. package/src/claude/hooks/lib/context.cjs +0 -616
  193. package/src/claude/hooks/lib/detect.cjs +0 -474
  194. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  195. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  196. package/src/claude/hooks/usage.cjs +0 -197
  197. package/src/claude/scripts/change-firewall.cjs +0 -1748
  198. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  199. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  200. package/src/claude/scripts/spec-final-state.cjs +0 -290
  201. package/src/claude/scripts/spec-ground.cjs +0 -650
  202. package/src/claude/scripts/spec-readiness.cjs +0 -425
  203. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  204. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  205. package/src/claude/scripts/validate-spec-output.cjs +0 -3941
  206. package/src/claude/skills/code-review/references/verification-gate.md +0 -46
  207. package/src/claude/skills/delegate/SKILL.md +0 -124
  208. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  209. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  210. package/src/claude/skills/hotfix/SKILL.md +0 -289
  211. package/src/claude/skills/inspect/SKILL.md +0 -142
  212. package/src/claude/skills/loop/SKILL.md +0 -114
  213. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  214. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  215. package/src/claude/skills/route/SKILL.md +0 -94
  216. package/src/claude/skills/route/references/agent-timing.md +0 -46
  217. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  218. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  219. package/src/claude/skills/specs/templates/design.md +0 -82
  220. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  221. package/src/claude/skills/specs/templates/requirements.md +0 -42
  222. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  223. package/src/claude/skills/specs/templates/task.md +0 -47
  224. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  225. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  226. package/src/codex/hooks/completion-authority.cjs +0 -109
  227. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  228. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  229. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  230. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  231. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -39,7 +39,7 @@ function boundaryTableHasRequiredRows(table) {
39
39
  }
40
40
 
41
41
  const IMPLEMENTATION_READINESS_CLAUSES = {
42
- noInvention: 'Before implementation handoff, apply the **no-invention gate**: if two implementations conform to the packet text yet can produce different externally observable output, state, error, security, or compatibility behavior, surface the missing choice as an explicit C1 or C2 question and block handoff.',
42
+ noInvention: 'Before implementation handoff, apply the **no-invention gate**: if two implementations conform to the packet text yet can produce different externally observable output, state, error, security, or compatibility behavior, surface the missing choice as an explicit GATE-SCOPE or GATE-REVIEW question and block handoff.',
43
43
  materialDefinition: 'A boundary is material when the task creates, changes, or depends on it and a different choice changes an external observation, security, durable data, compatibility, or proof reachability. Require only the matching material row; omit nonmaterial categories.',
44
44
  exactBoundaryChoices: 'For every required row, name each listed choice exactly; labels such as “JSON”, “local path”, “locked”, or “timestamped” alone remain unresolved.',
45
45
  proofPlanLines: [
@@ -58,11 +58,11 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
58
58
  disposableReviewControls: 'Run mutation or destructive negative controls only on disposable copies below a verified temporary root, never tracked worktree or canonical source bytes.',
59
59
  failureSemantics: '`Crash` means abrupt unhandled termination before the claimed catch point; a catchable failure returns/raises an error or exits nonzero. Never use them interchangeably.',
60
60
  privacyIdentifiers: 'Any privacy/security claim names the exact identifier surface at risk, such as an env var, header, path, token class, or field name; generic “sensitive data” is insufficient.',
61
- freshReplay: 'After applying an accepted C2 finding, a fresh-context closure pass records and freshly replays its original counterexample after the repair under this exact review-log header:',
61
+ freshReplay: 'After applying an accepted GATE-REVIEW finding, a fresh-context closure pass records and freshly replays its original counterexample after the repair under this exact review-log header:',
62
62
  distinctRepairProof: '`Repaired at` cites the repair edit; `Proved at` must cite distinct evidence from the fresh replay, never the repair-edit citation.',
63
63
  closureTransition: 'An accepted finding transitions `accepted → repaired → PASS|FAIL|UNKNOWN`.',
64
64
  unknownBlocks: 'Only `PASS` closes it; `FAIL` remains open for the remaining paper-review round; `UNKNOWN` blocks implementation handoff.',
65
- scopeReturnsToC1: 'A repair that adds user semantics or scope returns to C1.',
65
+ scopeReturnsToC1: 'A repair that adds user semantics or scope returns to GATE-SCOPE.',
66
66
  };
67
67
 
68
68
  const ADAPTIVE_COVERAGE_PROFILE_HEADER = [
@@ -77,7 +77,7 @@ const ADAPTIVE_COVERAGE_AMBIGUITY_ROWS = [
77
77
  ['State', 'Required action'],
78
78
  ['`none`', 'proceed'],
79
79
  ['`examples-needed`', 'add two or three examples only for an already decided rule; promote to `decision-needed` if an example changes observable behavior'],
80
- ['`decision-needed`', 'ask the user at C1/C2 and keep affected tasks blocked'],
80
+ ['`decision-needed`', 'ask the user at GATE-SCOPE/GATE-REVIEW and keep affected tasks blocked'],
81
81
  ['`design-needed`', 'after user-owned decisions settle, route material competing technical designs through Brainstorm'],
82
82
  ];
83
83
  const ADAPTIVE_REVIEWER_ROWS = [
@@ -98,12 +98,12 @@ const ADAPTIVE_COVERAGE_CLAUSES = {
98
98
  openKinds: 'Change kinds are multi-valued (`add`, `modify`, `fix`, `refactor`, `remove`, `migrate`, `integrate`), and unfamiliar kinds or surfaces use `other:<verbatim>` rather than disappearing.',
99
99
  scopedUnion: 'Each task references its CP IDs; authoring, review, edge, and proof obligations union only inside affected rows/tasks.',
100
100
  profileRederivation: 'Rederive affected CP rows after any accepted scope, outcome, criteria, ownership, dependency, risk, or proof delta before task status.',
101
- plannedProof: '`Required proof` is a planned level set, not execution\nevidence: known but unrun proof may be `pending`; `UNKNOWN` reachability blocks\n`pending`; missing, failed, or unavailable required evidence blocks `done`/C3.',
101
+ plannedProof: '`Required proof` is a planned level set, not execution\nevidence: known but unrun proof may be `pending`; `UNKNOWN` reachability blocks\n`pending`; missing, failed, or unavailable required evidence blocks `done`/GATE-DONE.',
102
102
  proofSeparation: 'Levels stay separate and never promote one another.',
103
103
  liveLimit: 'Source/static checks prove the written contract, not live-model adherence.',
104
104
  specMakerAuthority: 'they are the canonical risk and coverage authority. Do not duplicate\ntheir taxonomy here.',
105
105
  specMakerAmbiguity: 'Apply the canonical ambiguity action; examples never decide observable behavior.',
106
- specMakerRoute: 'Apply their risk-first route before C1 and stop when the\nrequest qualifies for direct work; hand off when it requires Brainstorm-only exploration.',
106
+ specMakerRoute: 'Apply their risk-first route before GATE-SCOPE and stop when the\nrequest qualifies for direct work; hand off when it requires Brainstorm-only exploration.',
107
107
  reviewRisk: 'Keep Fact Checker as the baseline. Assign every remaining material CP risk to a\nnamed reviewer lens; a critical row includes both relevant security-adversary and\nfailure-mode coverage, and nonmaterial lenses are not added.',
108
108
  reviewCapacity: 'Reviewer count is fixed by the table, not lens count. Give each reviewer a distinct primary lens; when material lenses exceed reviewers, combine related named lenses on one reviewer and keep every material lens assigned.',
109
109
  };
@@ -114,31 +114,17 @@ const REQUIRED_PAYLOAD = [
114
114
  'src/claude/scripts/workflow-policy.cjs',
115
115
  'src/claude/scripts/provenance.cjs',
116
116
  'src/claude/scripts/spec-receipt.cjs',
117
- 'src/claude/scripts/spec-ground.cjs',
118
- 'src/claude/scripts/spec-final-state.cjs',
119
- 'src/claude/scripts/spec-readiness.cjs',
120
- 'src/claude/scripts/spec-semantic-model.cjs',
121
- 'src/claude/scripts/validate-spec-output.cjs',
122
- 'src/claude/scripts/spec-authoring-validation.cjs',
123
- 'src/claude/scripts/spec-authoring-digest.cjs',
124
117
  'src/claude/scripts/generate-skill-catalog.cjs',
125
118
  'src/claude/rules/process-management.md',
126
119
  'src/claude/rules/review-audit-self-decision.md',
127
120
  'src/claude/rules/skill-domain-routing.md',
128
121
  'src/claude/rules/skill-workflow-routing.md',
129
- 'src/claude/skills/route/SKILL.md',
130
- 'src/claude/skills/route/references/task-taxonomy.md',
131
- 'src/claude/skills/route/references/chaining-patterns.md',
132
- 'src/claude/skills/route/references/agent-timing.md',
133
122
  'src/claude/skills/orca/SKILL.md',
134
123
  'src/claude/skills/brainstorm/SKILL.md',
135
124
  'src/claude/skills/brainstorm/references/question-framework.md',
136
125
  'src/claude/agents/brainstormer.md',
137
126
  'src/claude/skills/research/SKILL.md',
138
127
  'src/claude/agents/researcher.md',
139
- 'src/claude/skills/loop/SKILL.md',
140
- 'src/claude/skills/loop/references/bounded-loop-protocol.md',
141
- 'src/claude/skills/loop/references/metric-and-guard-contract.md',
142
128
  'src/claude/skills/docs/SKILL.md',
143
129
  'src/claude/skills/docx/SKILL.md',
144
130
  'src/claude/skills/pdf/SKILL.md',
@@ -179,20 +165,17 @@ const BRAINSTORM_SOURCE_RELATIVES = {
179
165
  framework: 'src/claude/skills/brainstorm/references/question-framework.md',
180
166
  agent: 'src/claude/agents/brainstormer.md',
181
167
  };
182
- const RESEARCH_LOOP_SOURCE_RELATIVES = {
168
+ const RESEARCH_SOURCE_RELATIVES = {
183
169
  research: 'src/claude/skills/research/SKILL.md',
184
170
  agent: 'src/claude/agents/researcher.md',
185
- loop: 'src/claude/skills/loop/SKILL.md',
186
- protocol: 'src/claude/skills/loop/references/bounded-loop-protocol.md',
187
- metric: 'src/claude/skills/loop/references/metric-and-guard-contract.md',
188
171
  workflow: 'src/claude/rules/skill-workflow-routing.md',
189
172
  domain: 'src/claude/rules/skill-domain-routing.md',
190
173
  };
191
174
  const HOTFIX_SOURCE_RELATIVES = {
192
- skill: 'src/claude/skills/hotfix/SKILL.md',
193
- review: 'src/claude/skills/hotfix/references/review-cycle.md',
194
- parallel: 'src/claude/skills/hotfix/references/parallel-patterns.md',
195
- specialized: 'src/claude/skills/hotfix/references/workflow-specialized.md',
175
+ skill: 'src/claude/skills/fix/SKILL.md',
176
+ review: 'src/claude/skills/fix/references/review-cycle.md',
177
+ parallel: 'src/claude/skills/fix/references/parallel-patterns.md',
178
+ specialized: 'src/claude/skills/fix/references/workflow-specialized.md',
196
179
  };
197
180
  const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
198
181
  'adviser-gate-fallback',
@@ -208,7 +191,7 @@ const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
208
191
  ];
209
192
  const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
210
193
  { group: 'direct-precedence', mutations: [
211
- { from: 'Route Direct first, then\napply controls only to requests that remain in Brainstorm.', to: 'Apply controls before Direct classification.', clause: 'Route Direct first, then apply controls only to requests that remain in Brainstorm.' },
194
+ { from: 'Route Direct first, then apply controls only to requests that remain in Brainstorm.', to: 'Apply controls before Direct classification.', clause: 'Route Direct first, then apply controls only to requests that remain in Brainstorm.' },
212
195
  ] },
213
196
  { group: 'ordered-depth', mutations: [
214
197
  { from: 'With no Deep signal, use Standard.', to: 'Deep is always the default.', clause: 'With no Deep signal, use Standard. `--deep` raises Standard to Deep.' },
@@ -216,32 +199,32 @@ const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
216
199
  { group: 'leading-flags', mutations: [
217
200
  { from: 'Parse controls only from the leading consecutive token segment.', to: 'Parse flag-like tokens anywhere.', clause: 'Parse controls only from the leading consecutive token segment.' },
218
201
  { from: '`--deep`, `--visual`, and `--advice` in any order, each at most once.', to: '`--deep`, `--visual`, and `--advice` may repeat.', clause: '`--deep`, `--visual`, and `--advice` in any order, each at most once.' },
219
- { from: '`--` ends\nthe control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
202
+ { from: '`--` ends the control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
220
203
  { from: 'An unknown or duplicate `--*` inside the leading segment returns usage', to: 'An unknown or duplicate `--*` is ignored', clause: 'An unknown or duplicate `--*` inside the leading segment returns usage and performs no scout, question, tool call, write, or workflow action.' },
221
204
  ] },
222
205
  { group: 'lens-trigger-skip', mutations: [
223
- { from: 'failure isolation for partial or cascading failure\nacross boundaries', to: 'generic failure notes', clause: 'failure isolation for partial or cascading failure across boundaries' },
206
+ { from: 'failure isolation for partial or cascading failure across boundaries', to: 'generic failure notes', clause: 'failure isolation for partial or cascading failure across boundaries' },
224
207
  ] },
225
208
  { group: 'evidence-semantics', mutations: [
226
- { from: 'Missing evidence\nforces feasibility `unknown` and confidence `low`.', to: 'Missing evidence permits a confident guess.', clause: 'Missing evidence forces feasibility `unknown` and confidence `low`.' },
209
+ { from: 'Missing evidence forces feasibility `unknown` and confidence `low`.', to: 'Missing evidence permits a confident guess.', clause: 'Missing evidence forces feasibility `unknown` and confidence `low`.' },
227
210
  ] },
228
211
  { group: 'numeric-estimates', mutations: [
229
- { from: 'A numeric estimate requires\nrange, unit, basis, evidence, and assumptions; otherwise report `unknown`.', to: 'A numeric estimate may be a best-effort number.', clause: 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.' },
212
+ { from: 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.', to: 'A numeric estimate may be a best-effort number.', clause: 'A numeric estimate requires range, unit, basis, evidence, and assumptions; otherwise report `unknown`.' },
230
213
  ] },
231
214
  { group: 'pre-tool-authority-redaction', mutations: [
232
- { from: 'Before an\nexternal visual tool or adviser handoff, minimize context and redact secrets,\ncredentials, private keys, access tokens, and unnecessary PII.', to: 'Forward full context to every external tool and adviser.', clause: 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.' },
233
- { from: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and\nfalls back to equivalent text when rendering is unavailable.', to: '`--visual` fails when rendering is unavailable.', clause: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and falls back to equivalent text when rendering is unavailable.' },
234
- { from: 'Durable or external\nrendering requires explicit user authority before invocation.', to: 'Durable or external rendering may run without consent.', clause: 'Durable or external rendering requires explicit user authority before invocation.' },
215
+ { from: 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.', to: 'Forward full context to every external tool and adviser.', clause: 'Before an external visual tool or adviser handoff, minimize context and redact secrets, credentials, private keys, access tokens, and unnecessary PII.' },
216
+ { from: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and falls back to equivalent text when rendering is unavailable.', to: '`--visual` fails when rendering is unavailable.', clause: '`--visual` may present inline Mermaid or ASCII for any non-direct analysis and falls back to equivalent text when rendering is unavailable.' },
217
+ { from: 'Durable or external rendering requires explicit user authority before invocation.', to: 'Durable or external rendering may run without consent.', clause: 'Durable or external rendering requires explicit user authority before invocation.' },
235
218
  ] },
236
219
  { group: 'adviser-gate-fallback', mutations: [
237
- { from: '`--advice` invokes\n`brainstormer` only after the material-choice gate;', to: '`--advice` invokes `brainstormer` before routing;', clause: '`--advice` invokes `brainstormer` only after the material-choice gate;' },
238
- { from: 'if advice is unavailable or\nfails, label it unavailable and continue with controller analysis.', to: 'if advice is unavailable, stop the workflow.', clause: 'if advice is unavailable or fails, label it unavailable and continue with controller analysis.' },
220
+ { from: '`--advice` invokes `brainstormer` only after the material-choice gate;', to: '`--advice` invokes `brainstormer` before routing;', clause: '`--advice` invokes `brainstormer` only after the material-choice gate;' },
221
+ { from: 'if advice is unavailable or fails, label it unavailable and continue with controller analysis.', to: 'if advice is unavailable, stop the workflow.', clause: 'if advice is unavailable or fails, label it unavailable and continue with controller analysis.' },
239
222
  ] },
240
223
  { group: 'decision-brief', mutations: [
241
- { from: 'The first section records target\nidentity, current source revision and worktree state or `[UNVERIFIED]`, an\nevidence-as-of value, and what change invalidates the brief.', to: 'The handoff has no revision or freshness binding.', clause: 'The first section records target identity, current source revision and worktree state or `[UNVERIFIED]`, an evidence-as-of value, and what change invalidates the brief.' },
224
+ { from: 'The first section records target identity, current source revision and worktree state or `[UNVERIFIED]`, an evidence-as-of value, and what change invalidates the brief.', to: 'The handoff has no revision or freshness binding.', clause: 'The first section records target identity, current source revision and worktree state or `[UNVERIFIED]`, an evidence-as-of value, and what change invalidates the brief.' },
242
225
  ] },
243
226
  { group: 'no-persistence-dispatch', mutations: [
244
- { from: 'Neither overlay\nwrites, approves, persists, dispatches, or completes work.', to: 'Overlays may persist, approve, dispatch, and complete work.', clause: 'Neither overlay writes, approves, persists, dispatches, or completes work.' },
227
+ { from: 'Neither overlay writes, approves, persists, dispatches, or completes work.', to: 'Overlays may persist, approve, dispatch, and complete work.', clause: 'Neither overlay writes, approves, persists, dispatches, or completes work.' },
245
228
  ] },
246
229
  ];
247
230
 
@@ -342,20 +325,15 @@ function runInstaller(installer, root, platforms, lang, extraArgs = []) {
342
325
  return result;
343
326
  }
344
327
 
345
- function installedRouteFiles(project, platform) {
328
+ function installedRoutingRuleFiles(project, platform) {
346
329
  const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
347
- const root = path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
348
330
  return {
349
- skill: path.join(root, 'SKILL.md'),
350
- taxonomy: path.join(root, 'references/task-taxonomy.md'),
351
- chaining: path.join(root, 'references/chaining-patterns.md'),
352
- timing: path.join(root, 'references/agent-timing.md'),
353
331
  workflow: path.join(project, runtimeRoot, 'rules/skill-workflow-routing.md'),
354
332
  domain: path.join(project, runtimeRoot, 'rules/skill-domain-routing.md'),
355
333
  };
356
334
  }
357
335
 
358
- function routeProjectionIssues(files) {
336
+ function routingRuleIssues(files) {
359
337
  const text = Object.fromEntries(Object.entries(files).map(([key, file]) => [
360
338
  key, fs.readFileSync(file, 'utf8').replace(/\s+/g, ' '),
361
339
  ]));
@@ -363,56 +341,10 @@ function routeProjectionIssues(files) {
363
341
  const requires = (issue, clauses) => {
364
342
  if (clauses.some(([source, clause]) => !text[source].includes(clause))) issues.add(issue);
365
343
  };
366
- requires('direct-path', [
367
- ['skill', 'invoke that skill directly'],
368
- ['skill', 'use it directly without constructing a chain'],
369
- ['skill', 'Do not create a route chain or spawn agents merely to answer it'],
370
- ]);
371
- requires('classification', [
372
- ['skill', 'final deliverable'], ['skill', 'highest-link risk'],
373
- ['skill', 'number of material domains'],
374
- ]);
375
- requires('link-contract', [
376
- ['chaining', '**Entry:**'], ['chaining', '**Exit:**'], ['chaining', '**Owner:** exactly one'],
377
- ]);
378
- requires('failure-stop', [
379
- ['chaining', '(link, owner, normalized cause)'],
380
- ['chaining', 'Two failures with the same failure key stop the chain'],
381
- ['chaining', 'Different normalized causes do not share a key'],
382
- ]);
383
- requires('collapse-and-detour', [
384
- ['skill', 'remove every link whose output is already evidenced'],
385
- ['chaining', 'preserve its valid exit evidence, normalize the root cause, and choose one bounded detour'],
386
- ]);
387
- requires('delegation-contract', [
388
- ['timing', '**Outcome** — observable result'],
389
- ['timing', '**Scope** — owned files, systems, or questions'],
390
- ['timing', '**Inputs** — current evidence and prerequisite artifacts'],
391
- ['timing', '**Constraints** — safety, compatibility, authority, and non-goals'],
392
- ['timing', '**Acceptance** — proof required for completion'],
393
- ['timing', '**Handoff** — expected returned artifact and destination'],
394
- ['timing', '**Status vocabulary** — `DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT`'],
395
- ]);
396
- requires('agent-fallback', [
397
- ['timing', 'If the preferred agent is absent'],
398
- ['timing', 'never synthesize a role'],
399
- ['timing', 'DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT'],
400
- ]);
401
- requires('authority', [
402
- ['skill', 'never expand it'],
403
- ['skill', 'diagnosis does not authorize repair'],
404
- ['skill', 'does not authorize commit, push, deploy, publish, or release'],
405
- ]);
406
- requires('risk-gates', [
407
- ['skill', 'require independent review and user confirmation before advancing past the high-risk gate'],
408
- ]);
409
- requires('proof-boundary', [
410
- ['skill', 'remains `[UNPROVEN]` until a separate host run observes it'],
411
- ]);
412
344
  requires('rule-direct-path', [
413
345
  ['workflow', 'If the user names a valid installed skill, use it directly'],
414
346
  ['workflow', 'If one obvious low-risk installed skill covers the intent, use it directly'],
415
- ['workflow', 'do not invoke Route or agents for ceremony'],
347
+ ['workflow', 'do not spawn agents for ceremony'],
416
348
  ]);
417
349
  requires('rule-live-catalog', [
418
350
  ['workflow', 'Resolve every abstract link against the current runtime catalog'],
@@ -774,30 +706,24 @@ function assertPackedBrainstormParity(project, platform) {
774
706
  }
775
707
  }
776
708
 
777
- function packedResearchLoopPaths(project, platform) {
709
+ function packedResearchPaths(project, platform) {
778
710
  return platform === 'codex'
779
711
  ? {
780
712
  research: path.join(project, '.agents/skills/research/SKILL.md'),
781
713
  agent: path.join(project, '.codex/agents/researcher.toml'),
782
- loop: path.join(project, '.agents/skills/loop/SKILL.md'),
783
- protocol: path.join(project, '.agents/skills/loop/references/bounded-loop-protocol.md'),
784
- metric: path.join(project, '.agents/skills/loop/references/metric-and-guard-contract.md'),
785
714
  workflow: path.join(project, '.codex/rules/skill-workflow-routing.md'),
786
715
  domain: path.join(project, '.codex/rules/skill-domain-routing.md'),
787
716
  }
788
717
  : {
789
718
  research: path.join(project, '.claude/skills/research/SKILL.md'),
790
719
  agent: path.join(project, '.claude/agents/researcher.md'),
791
- loop: path.join(project, '.claude/skills/loop/SKILL.md'),
792
- protocol: path.join(project, '.claude/skills/loop/references/bounded-loop-protocol.md'),
793
- metric: path.join(project, '.claude/skills/loop/references/metric-and-guard-contract.md'),
794
720
  workflow: path.join(project, '.claude/rules/skill-workflow-routing.md'),
795
721
  domain: path.join(project, '.claude/rules/skill-domain-routing.md'),
796
722
  };
797
723
  }
798
724
 
799
- function readPackedResearchLoop(project, platform) {
800
- const paths = packedResearchLoopPaths(project, platform);
725
+ function readPackedResearch(project, platform) {
726
+ const paths = packedResearchPaths(project, platform);
801
727
  const values = Object.fromEntries(Object.entries(paths).map(([key, target]) => [
802
728
  key, fs.readFileSync(target, 'utf8'),
803
729
  ]));
@@ -807,13 +733,12 @@ function readPackedResearchLoop(project, platform) {
807
733
  return values;
808
734
  }
809
735
 
810
- function packedResearchLoopIssues(project, platform) {
811
- const values = Object.fromEntries(Object.entries(readPackedResearchLoop(project, platform)).map(
736
+ function packedResearchIssues(project, platform) {
737
+ const values = Object.fromEntries(Object.entries(readPackedResearch(project, platform)).map(
812
738
  ([key, value]) => [key, value.replace(/\s+/g, ' ').trim()]
813
739
  ));
814
740
  const issues = new Set();
815
741
  const publicResearch = platform === 'codex' ? 'name: cf-research' : 'name: cf:research';
816
- const publicLoop = platform === 'codex' ? 'name: cf-loop' : 'name: cf:loop';
817
742
  if (!values.research.includes(publicResearch)
818
743
  || !values.research.includes('Choose the smallest depth that can support the decision:')
819
744
  || !values.research.includes('Use delegated researchers only as optional acceleration')
@@ -826,35 +751,15 @@ function packedResearchLoopIssues(project, platform) {
826
751
  || !values.agent.includes('owns any authorized persistence.')) {
827
752
  issues.add('research-agent-boundary');
828
753
  }
829
- if (!values.loop.includes(publicLoop)
830
- || !values.loop.includes('Loop is explicit-only. Never auto-route ordinary implementation, debugging, or research into Loop.')
831
- || !values.loop.includes('Reject dirty in-scope state; record but never import or clean out-of-scope dirt.')
832
- || !values.loop.includes('separately approved external realpath')
833
- || !values.loop.includes('complete detached-worktree tracked/untracked manifest')
834
- || !values.loop.includes('Live-agent adherence to this written contract is `[UNPROVEN]` without a host run.')) {
835
- issues.add('loop-safety');
836
- }
837
- if (!values.metric.includes('Guard is mandatory, fixed before baseline, and distinct from Metric.')
838
- || !values.metric.includes('`numeric_format`: exactly IEEE-754 binary64')
839
- || !values.metric.includes('nonzero exit')
840
- || !values.metric.includes('require median, noise, improvement, and required delta to remain finite.')) {
841
- issues.add('metric-guard');
842
- }
843
- if (!values.protocol.includes('do not clean. Preserve the exact path, PID/process evidence, and ownership marker; return `BLOCKED`')
844
- || !values.protocol.includes('`base_oid`;')
845
- || !values.protocol.includes('patch byte length and lowercase SHA-256')) {
846
- issues.add('failure-handoff');
847
- }
848
- if (!values.workflow.includes('numeric optimization capability remains explicit-only and must never be auto-routed.')
849
- || !values.domain.includes('explicit-only numeric optimization capability unless the user invokes it with its required bounded metric/guard contract.')) {
850
- issues.add('explicit-routing');
754
+ if (/numeric optimization capability/.test(`${values.workflow}\n${values.domain}`)) {
755
+ issues.add('loop-removed');
851
756
  }
852
757
  return [...issues].sort();
853
758
  }
854
759
 
855
- function assertPackedResearchLoopParity(project, platform) {
856
- const installed = packedResearchLoopPaths(project, platform);
857
- for (const [key, relative] of Object.entries(RESEARCH_LOOP_SOURCE_RELATIVES)) {
760
+ function assertPackedResearchParity(project, platform) {
761
+ const installed = packedResearchPaths(project, platform);
762
+ for (const [key, relative] of Object.entries(RESEARCH_SOURCE_RELATIVES)) {
858
763
  const sourcePath = path.join(PACKAGE_ROOT, relative);
859
764
  const source = fs.readFileSync(sourcePath, 'utf8');
860
765
  const expected = platform === 'codex'
@@ -866,12 +771,15 @@ function assertPackedResearchLoopParity(project, platform) {
866
771
  }
867
772
  const manifest = JSON.parse(fs.readFileSync(path.join(project, RUNTIMES[platform].manifest), 'utf8'));
868
773
  const prefixes = platform === 'codex'
869
- ? ['.agents/skills/research/', '.agents/skills/loop/']
870
- : ['skills/research/', 'skills/loop/'];
774
+ ? ['.agents/skills/research/']
775
+ : ['skills/research/'];
871
776
  for (const prefix of prefixes) {
872
777
  assert.ok(Object.keys(manifest.files).some((entry) => entry.startsWith(prefix)), `${platform} manifest missing ${prefix}`);
873
778
  }
874
- assert.deepEqual(packedResearchLoopIssues(project, platform), []);
779
+ const removedLoop = platform === 'codex' ? '.agents/skills/loop/' : 'skills/loop/';
780
+ assert.equal(Object.keys(manifest.files).some((entry) => entry.startsWith(removedLoop)), false, `${platform} manifest still lists ${removedLoop}`);
781
+ assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/loop' : '.claude/skills/loop')), false, `${platform} still installs Loop`);
782
+ assert.deepEqual(packedResearchIssues(project, platform), []);
875
783
  }
876
784
 
877
785
  function packedBrainstormInstalledPaths(project, platform) {
@@ -1316,8 +1224,8 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
1316
1224
  },
1317
1225
  {
1318
1226
  name: 'fresh-original-counterexample-replay', issue: 'repair-closure', source: 'review',
1319
- from: 'After applying an accepted C2 finding, a fresh-context closure pass records and\nfreshly replays its original counterexample after the repair under this exact review-log header:',
1320
- to: 'After applying an accepted C2 finding, record the repair under this review-log header:',
1227
+ from: 'After applying an accepted GATE-REVIEW finding, a fresh-context closure pass records and\nfreshly replays its original counterexample after the repair under this exact review-log header:',
1228
+ to: 'After applying an accepted GATE-REVIEW finding, record the repair under this review-log header:',
1321
1229
  },
1322
1230
  {
1323
1231
  name: 'distinct-repair-and-proof-evidence', issue: 'repair-closure', source: 'review',
@@ -1423,7 +1331,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
1423
1331
  ['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
1424
1332
  'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
1425
1333
  ['planned-proof-blocks-start', 'templates', ADAPTIVE_COVERAGE_CLAUSES.plannedProof,
1426
- '`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/C3.', ['proof-lifecycle']],
1334
+ '`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/GATE-DONE.', ['proof-lifecycle']],
1427
1335
  ['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
1428
1336
  'Source proof may promote installed and live proof.', ['proof-lifecycle']],
1429
1337
  ['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
@@ -1437,7 +1345,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
1437
1345
  ['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
1438
1346
  'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
1439
1347
  ['spec-maker-skips-brainstorm', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute,
1440
- 'Apply the risk-first route before C1 and stop only for direct work.', ['spec-maker-authority']],
1348
+ 'Apply the risk-first route before GATE-SCOPE and stop only for direct work.', ['spec-maker-authority']],
1441
1349
  ['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
1442
1350
  'Source/static checks prove live-model adherence.', ['proof-lifecycle']],
1443
1351
  ];
@@ -1607,69 +1515,15 @@ function assertInstalledProvenance(root, platform, fixture) {
1607
1515
  const policy = path.join(scripts, 'workflow-policy.cjs');
1608
1516
  const resolver = path.join(scripts, 'spec-resolver.cjs');
1609
1517
  const receipt = path.join(scripts, 'spec-receipt.cjs');
1610
- const finalState = path.join(scripts, 'spec-final-state.cjs');
1611
- const readiness = path.join(scripts, 'spec-readiness.cjs');
1612
1518
  const gate = path.join(
1613
1519
  root,
1614
1520
  platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
1615
1521
  );
1616
- const authorityDir = path.join(root, platform === 'claude' ? '.claude/hooks' : '.codex/hooks');
1617
- const authority = path.join(authorityDir, 'completion-authority.cjs');
1618
- const authorityCheck = path.join(authorityDir, 'completion-authority-check.cjs');
1619
- const authorityState = path.join(authorityDir, 'completion-authority-state.cjs');
1620
- const semanticAuthority = path.join(authorityDir, 'semantic-review-authority.cjs');
1621
- for (const file of [helper, policy, resolver, receipt, finalState, readiness]) {
1522
+ for (const file of [helper, policy, resolver, receipt]) {
1622
1523
  assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
1623
1524
  }
1624
1525
  assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
1625
1526
 
1626
- for (const file of [authority, authorityCheck, authorityState, semanticAuthority]) {
1627
- assert.equal(fs.existsSync(file), true, `${platform} installed completion authority missing: ${file}`);
1628
- }
1629
- const finalStateBytes = fs.readFileSync(finalState);
1630
- fs.rmSync(finalState);
1631
- const missingFinalState = spawnSync(process.execPath, [authority, '--stop'], {
1632
- cwd: root,
1633
- env: { ...process.env, PROJECT_ROOT: root },
1634
- input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
1635
- encoding: 'utf8',
1636
- });
1637
- assert.equal(JSON.parse(missingFinalState.stdout).decision, 'block', `${platform} missing final-state closure must fail closed`);
1638
- const escapedFinalState = path.join(root, 'escaped-spec-final-state.cjs');
1639
- fs.writeFileSync(escapedFinalState, finalStateBytes);
1640
- fs.symlinkSync(escapedFinalState, finalState);
1641
- const symlinkFinalState = spawnSync(process.execPath, [authority, '--stop'], {
1642
- cwd: root,
1643
- env: { ...process.env, PROJECT_ROOT: root },
1644
- input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
1645
- encoding: 'utf8',
1646
- });
1647
- assert.equal(JSON.parse(symlinkFinalState.stdout).decision, 'block', `${platform} symlinked final-state closure must fail closed`);
1648
- fs.rmSync(finalState);
1649
- fs.writeFileSync(finalState, finalStateBytes);
1650
- fs.rmSync(escapedFinalState);
1651
- const authorityCheckBytes = fs.readFileSync(authorityCheck);
1652
- fs.writeFileSync(authorityCheck, 'module.exports = {};\n');
1653
- const malformedAuthority = spawnSync(process.execPath, [authority, '--stop'], {
1654
- cwd: root,
1655
- env: { ...process.env, PROJECT_ROOT: root },
1656
- input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
1657
- encoding: 'utf8',
1658
- });
1659
- assert.equal(malformedAuthority.status, 0, `${platform} malformed authority must fail closed`);
1660
- assert.equal(JSON.parse(malformedAuthority.stdout).decision, 'block');
1661
- fs.writeFileSync(authorityCheck, authorityCheckBytes);
1662
- fs.rmSync(authorityCheck);
1663
- const missingAuthority = spawnSync(process.execPath, [authority, '--stop'], {
1664
- cwd: root,
1665
- env: { ...process.env, PROJECT_ROOT: root },
1666
- input: JSON.stringify({ cwd: root, session_id: fixture.session, hook_event_name: 'Stop' }),
1667
- encoding: 'utf8',
1668
- });
1669
- assert.equal(missingAuthority.status, 0, `${platform} missing authority dependency must fail closed`);
1670
- assert.equal(JSON.parse(missingAuthority.stdout).decision, 'block');
1671
- fs.writeFileSync(authorityCheck, authorityCheckBytes);
1672
-
1673
1527
  const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
1674
1528
  cwd: root,
1675
1529
  encoding: 'utf8',
@@ -1797,26 +1651,13 @@ function assertInstalledScripts(root, platform) {
1797
1651
  const scripts = path.join(root, RUNTIMES[platform].root, 'scripts');
1798
1652
  const policy = path.join(scripts, 'workflow-policy.cjs');
1799
1653
  const scanner = path.join(scripts, 'scan-staged-secrets.cjs');
1800
- const grounder = path.join(scripts, 'spec-ground.cjs');
1801
- const finalState = path.join(scripts, 'spec-final-state.cjs');
1802
- const readiness = path.join(scripts, 'spec-readiness.cjs');
1803
- const validator = path.join(scripts, 'validate-spec-output.cjs');
1804
1654
  assert.ok(fs.existsSync(policy), `installed policy missing: ${policy}`);
1805
1655
  assert.ok(fs.existsSync(scanner), `installed scanner missing: ${scanner}`);
1806
- assert.ok(fs.existsSync(grounder), `installed validator dependency missing: ${grounder}`);
1807
- assert.ok(fs.existsSync(finalState), `installed final-state dependency missing: ${finalState}`);
1808
- assert.ok(fs.existsSync(readiness), `installed readiness finalizer missing: ${readiness}`);
1809
- assert.ok(fs.existsSync(validator), `installed validator missing: ${validator}`);
1810
1656
 
1811
1657
  const policyRun = spawnSync(process.execPath, [policy, '--json'], { cwd: root, encoding: 'utf8' });
1812
1658
  assert.equal(policyRun.status, 0, `${policyRun.stdout}\n${policyRun.stderr}`);
1813
1659
  assert.equal(JSON.parse(policyRun.stdout).contract, 'execution-policy');
1814
1660
 
1815
- const validatorRun = spawnSync(process.execPath, [validator], { cwd: root, encoding: 'utf8' });
1816
- assert.equal(validatorRun.status, 2, `${validatorRun.stdout}\n${validatorRun.stderr}`);
1817
- assert.match(validatorRun.stderr, /Usage: .*validate-spec-output\.cjs/);
1818
- assert.doesNotMatch(validatorRun.stderr, /MODULE_NOT_FOUND|Cannot find module/);
1819
-
1820
1661
  const safe = path.join(root, 'safe.txt');
1821
1662
  fs.writeFileSync(safe, 'mode=safe\n');
1822
1663
  spawnSync('git', ['init', '-q'], { cwd: root });
@@ -1826,458 +1667,10 @@ function assertInstalledScripts(root, platform) {
1826
1667
  assert.match(scannerRun.stdout, /No staged secrets found/);
1827
1668
  }
1828
1669
 
1829
- function installedSemanticPaths(root, platform) {
1830
- const runtimeRoot = path.join(root, RUNTIMES[platform].root);
1831
- const skillRoot = platform === 'codex'
1832
- ? path.join(root, '.agents', 'skills')
1833
- : path.join(runtimeRoot, 'skills');
1834
- const paths = {
1835
- policy: path.join(runtimeRoot, 'scripts', 'workflow-policy.cjs'),
1836
- scaffold: path.join(runtimeRoot, 'scripts', 'spec-scaffold.cjs'),
1837
- validator: path.join(runtimeRoot, 'scripts', 'validate-spec-output.cjs'),
1838
- authoringValidator: path.join(runtimeRoot, 'scripts', 'spec-authoring-validation.cjs'),
1839
- authoringDigest: path.join(runtimeRoot, 'scripts', 'spec-authoring-digest.cjs'),
1840
- grounder: path.join(runtimeRoot, 'scripts', 'spec-ground.cjs'),
1841
- finalState: path.join(runtimeRoot, 'scripts', 'spec-final-state.cjs'),
1842
- readiness: path.join(runtimeRoot, 'scripts', 'spec-readiness.cjs'),
1843
- semanticModel: path.join(runtimeRoot, 'scripts', 'spec-semantic-model.cjs'),
1844
- resolver: path.join(runtimeRoot, 'scripts', 'spec-resolver.cjs'),
1845
- provenance: path.join(runtimeRoot, 'scripts', 'provenance.cjs'),
1846
- completion: path.join(runtimeRoot, 'hooks', 'completion-authority-check.cjs'),
1847
- semanticAuthority: path.join(runtimeRoot, 'hooks', 'semantic-review-authority.cjs'),
1848
- stateTemplate: path.join(skillRoot, 'specs', 'templates', 'spec-state.json'),
1849
- };
1850
- for (const [name, target] of Object.entries(paths)) {
1851
- assert.equal(fs.existsSync(target), true, `${platform} installed ${name} missing: ${target}`);
1852
- assert.equal(path.relative(root, target).startsWith('..'), false, `${platform} ${name} escaped install root`);
1853
- }
1854
- return paths;
1855
- }
1856
-
1857
- function runInstalled(script, args, root, expected = 0) {
1858
- const result = spawnSync(process.execPath, [script, ...args], { cwd: root, encoding: 'utf8' });
1859
- assert.equal(result.status, expected, `${script}\n${result.stdout}\n${result.stderr}`);
1860
- return result;
1861
- }
1862
-
1863
1670
  function writeJson(target, value) {
1864
1671
  fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
1865
1672
  }
1866
1673
 
1867
- function materializeSpecState(paths, root, feature, { strict = false } = {}) {
1868
- const featureDir = path.join(root, 'specs', feature);
1869
- runInstalled(paths.scaffold, [feature], root);
1870
- const state = JSON.parse(fs.readFileSync(path.join(featureDir, 'spec.json'), 'utf8'));
1871
- state.scope_lock.in_scope = [`${feature} behavior`];
1872
- state.scope_lock.out_of_scope = ['unrelated behavior'];
1873
- if (strict) {
1874
- state.workflow_policy.assurance_level = 'Strict';
1875
- state.workflow_policy.classified_minimum.assurance_level = 'Strict';
1876
- state.workflow_policy.risks = ['auth'];
1877
- }
1878
- writeJson(path.join(featureDir, 'spec.json'), state);
1879
- return { featureDir, specFile: path.join(featureDir, 'spec.json'), state };
1880
- }
1881
-
1882
- function requirementsProjection({ proof = false } = {}) {
1883
- return `# Requirements
1884
-
1885
- ## Requirements
1886
-
1887
- ### Requirement 1: Installed behavior
1888
-
1889
- - **R1.1**: The CommonJS entrypoint \`src/entry.js\` shall return an object containing only status \`enabled\` when its sole \`valid\` argument is the boolean \`true\`; it shall return an object containing only status \`rejected\` for \`false\`, a missing argument, or any non-boolean value.${proof ? '\n- **R1.2**: The installed verifier shall prove the observed entry behavior through its grounded evidence boundary.' : ''}
1890
- `;
1891
- }
1892
-
1893
- const TYPED_ANCHOR_COLUMNS = ['ID', 'Type', 'Target', 'Role', 'Access', 'Action'];
1894
-
1895
- function typedAnchorTable(rows) {
1896
- assert.ok(Array.isArray(rows) && rows.length > 0, 'typed anchor projection requires rows');
1897
- const header = `| ${TYPED_ANCHOR_COLUMNS.join(' | ')} |`;
1898
- const separator = `|${TYPED_ANCHOR_COLUMNS.map(() => '---').join('|')}|`;
1899
- const body = rows.map((row) => {
1900
- assert.deepEqual(Object.keys(row), ['id', 'type', 'target', 'role', 'access', 'action']);
1901
- assert.ok(Object.values(row).every((value) => typeof value === 'string' && value.trim() !== ''));
1902
- return `| ${row.id} | ${row.type} | \`${row.target}\` | ${row.role} | ${row.access} | ${row.action} |`;
1903
- });
1904
- return [header, separator, ...body].join('\n');
1905
- }
1906
-
1907
- function designProjection({
1908
- expected = 'exit 0; boolean true returns the exact enabled object; false, undefined, null, `"true"`, 1, and an object return the exact rejected object',
1909
- taskful = false,
1910
- } = {}) {
1911
- const anchors = typedAnchorTable([{
1912
- id: 'A-D-01', type: 'file', target: taskful ? 'src/design-boundary.js' : 'src/entry.js',
1913
- role: taskful ? 'existing design boundary for the entrypoint' : 'existing runtime entrypoint and contract owner',
1914
- access: 'read', action: 'read',
1915
- }]);
1916
- const reachabilityAnchors = taskful ? 'A-R1-01-01, A-R1-02-02' : 'A-D-01';
1917
- return `# Design
1918
-
1919
- ## Boundary
1920
-
1921
- - **Owns:** The exact return contract of the CommonJS function exported by \`src/entry.js\`.
1922
- - **Reads:** One \`valid\` argument; only the boolean \`true\` is valid.
1923
- - **Writes/exposes:** Exactly one status field whose value is \`enabled\` or \`rejected\`.
1924
- - **Outside boundary:** Unrelated runtime behavior.
1925
-
1926
- ## Typed Anchors
1927
-
1928
- ${anchors}
1929
-
1930
- ## Decisions and Invariants
1931
-
1932
- ### D1 — Installed result decision
1933
-
1934
- - **Decision:** \`valid === true\` returns an object containing only status \`enabled\`; every other value returns an object containing only status \`rejected\`.
1935
- - **Rejects ambiguity:** Truthy non-booleans such as \`"true"\` never count as valid.
1936
- - **Negative path:** \`false\`, \`undefined\`, \`null\`, strings, numbers, and objects return rejected.
1937
- - **Anchors:** A-D-01
1938
-
1939
- ### I1 — Rejection invariant
1940
-
1941
- Any input other than the boolean \`true\` never returns enabled.
1942
-
1943
- ### C1 — Result contract
1944
-
1945
- - **Owner:** A-D-01
1946
- - **Consumers:** \`test/entry.test.js\` and callers of the CommonJS export.
1947
- - **Shape/behavior:** The result is an object with exactly one \`status\` field set to \`enabled\` or \`rejected\`.
1948
- - **Compatibility:** The strict boolean discriminator, exact object shape, and two status values remain stable.
1949
-
1950
- ## Verification Definitions
1951
-
1952
- - **V1**: Criteria R1.1; Owner ${taskful ? 'R1-01' : 'A-D-01'}; ${taskful ? 'Proof criteria R1.2; Proof owner R1-02; Evidence anchor A-R1-02-02; ' : ''}Decision refs D1, I1, C1; Method command \`node --test test/entry.test.js\`; Expected ${expected}; Negative/failure \`false\`, missing input, \`null\`, and truthy non-booleans all return the exact rejected object; Reachability/grounding entrypoint \`src/entry.js\` via ${reachabilityAnchors}.
1953
- `;
1954
- }
1955
-
1956
- function completeSemanticReview(paths, root, fixture, counterexamples) {
1957
- // C16/D13: only the installed spec-authoring-validation.cjs coordinator may
1958
- // flip authoring.* to validated and write the matching receipt (I21/R3.9) —
1959
- // run it over the fixture's current bytes instead of hand-writing the enum.
1960
- // Lifecycle order (I15/R3.7): authoring -> coordinator -> semantic review ->
1961
- // readiness, never the reverse.
1962
- runInstalled(paths.scaffold, [fixture.state.feature_name, '--sync-semantic-model'], root);
1963
- runInstalled(paths.authoringValidator, [fixture.featureDir], root);
1964
- const state = JSON.parse(fs.readFileSync(fixture.specFile, 'utf8'));
1965
- fixture.reviewResult = {
1966
- verdict: 'PASS',
1967
- findings: [],
1968
- unresolved_decisions: [],
1969
- graph_coverage: [
1970
- 'criterion_local', 'cross_criterion', 'runtime_path',
1971
- 'assumption_provenance', 'compatibility_migration',
1972
- ].map((surface) => ({
1973
- surface, covered: true,
1974
- notes: 'The review covers this semantic surface against the canonical model.',
1975
- })),
1976
- reviewed_criteria: counterexamples.map(({ criterion }) => criterion),
1977
- counterexamples,
1978
- reviewer_evidence: null,
1979
- };
1980
- if (state.workflow_policy.assurance_level === 'Strict') {
1981
- const digest = runInstalled(paths.validator, [fixture.featureDir, '--semantic-digest'], root).stdout.trim();
1982
- assert.match(digest, /^sha256:[a-f0-9]{64}$/);
1983
- return digest;
1984
- }
1985
- const reviewFile = path.join(fixture.featureDir, '.review-result.json');
1986
- writeJson(reviewFile, fixture.reviewResult);
1987
- runInstalled(paths.readiness, [fixture.featureDir, '--review-result', reviewFile], root);
1988
- fs.unlinkSync(reviewFile);
1989
- runInstalled(paths.validator, [fixture.featureDir], root);
1990
- runInstalled(paths.grounder, [fixture.featureDir, '--root', root], root);
1991
- return JSON.parse(fs.readFileSync(fixture.specFile, 'utf8')).validation.semantic_review.semantic_digest;
1992
- }
1993
-
1994
- function createTasklessFixture(paths, root, feature, options = {}) {
1995
- fs.mkdirSync(path.join(root, 'src'), { recursive: true });
1996
- fs.mkdirSync(path.join(root, 'test'), { recursive: true });
1997
- fs.writeFileSync(path.join(root, 'src', 'entry.js'), 'module.exports = (valid) => ({ status: valid === true ? "enabled" : "rejected" });\n');
1998
- fs.writeFileSync(path.join(root, 'src', 'design-boundary.js'), 'module.exports = { entry: "src/entry.js" };\n');
1999
- fs.writeFileSync(path.join(root, 'test', 'entry.test.js'), `const test = require('node:test');
2000
- const assert = require('node:assert/strict');
2001
- const entry = require('../src/entry.js');
2002
-
2003
- test('entry accepts only the boolean true discriminator', () => {
2004
- assert.deepEqual(entry(true), { status: 'enabled' });
2005
- for (const invalid of [false, undefined, null, 'true', 1, {}]) {
2006
- assert.deepEqual(entry(invalid), { status: 'rejected' });
2007
- }
2008
- });
2009
- `);
2010
- fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ scripts: { test: 'node --test' } }));
2011
- const fixture = materializeSpecState(paths, root, feature, options);
2012
- fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection());
2013
- fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection());
2014
- fixture.digest = completeSemanticReview(paths, root, fixture, [{
2015
- criterion: 'R1.1', case_kind: 'failure',
2016
- scenario: 'The installed entry receives input without the required discriminator.',
2017
- expected: 'The installed entry returns rejected and never returns enabled.',
2018
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
2019
- }]);
2020
- return fixture;
2021
- }
2022
-
2023
- function taskProjection({ id, title, ownedPath, criterion, verificationRef, role, artifact = false }) {
2024
- const artifactPath = `artifacts/${id}.json`;
2025
- const anchors = typedAnchorTable([
2026
- { id: `A-${id}-01`, type: 'file', target: ownedPath, role: 'owner', access: 'write', action: 'modify' },
2027
- ...(artifact ? [{
2028
- id: `A-${id}-02`, type: 'artifact', target: artifactPath,
2029
- role: 'verifier', access: 'write', action: 'create',
2030
- }] : []),
2031
- {
2032
- id: `A-${id}-${artifact ? '03' : '02'}`, type: 'command',
2033
- target: 'node --test test/entry.test.js', role: 'verifier', access: 'read', action: 'read',
2034
- },
2035
- ]);
2036
- return `# Task ${id}: ${title}
2037
- **Status:** pending
2038
-
2039
- ## Outcome
2040
-
2041
- Deliver observable ${title.toLowerCase()} behavior through the installed entrypoint.
2042
-
2043
- ## Scope
2044
-
2045
- - **In scope:** Exact behavior owned at ${ownedPath}.
2046
- - **Out of scope:** Unrelated runtime behavior.
2047
-
2048
- ## Anchors and Ownership
2049
-
2050
- ${anchors}
2051
-
2052
- ## Changes
2053
-
2054
- - [ ] Implement the exact owned behavior. _Requirements: ${criterion.slice(1)}_
2055
-
2056
- ## Acceptance
2057
-
2058
- - **${criterion}:** The installed command returns the criterion-specific observable state.
2059
-
2060
- ## Dependencies
2061
-
2062
- - none
2063
-
2064
- ## Verification Plan
2065
-
2066
- - **Verification ref:** ${verificationRef}
2067
- - **Task role:** ${role}
2068
- - **Command:** \`node --test test/entry.test.js\`
2069
- - **Expected:** Exit code 0 and the criterion-specific state is observed.${artifact ? ` The artifact \`${artifactPath}\` must exist, and SHA-256 over its current bytes must match the recorded digest.` : ''}
2070
- - **Negative path:** Invalid input returns the named rejected state.
2071
- - **Reachability:** \`src/entry.js\` is reached by the installed test command.
2072
- `;
2073
- }
2074
-
2075
- function createTaskFixture(paths, root, feature) {
2076
- fs.mkdirSync(path.join(root, 'artifacts'), { recursive: true });
2077
- const fixture = materializeSpecState(paths, root, feature);
2078
- fs.writeFileSync(path.join(fixture.featureDir, 'requirements.md'), requirementsProjection({ proof: true }));
2079
- fs.writeFileSync(path.join(fixture.featureDir, 'design.md'), designProjection({
2080
- expected: 'exit 0, status enabled, and verifier artifact `artifacts/R1-02.json` whose SHA-256 over current bytes matches the recorded digest',
2081
- taskful: true,
2082
- }));
2083
- const taskOne = 'tasks/task-R1-01-implement.md';
2084
- const taskTwo = 'tasks/task-R1-02-verify.md';
2085
- fs.mkdirSync(path.join(fixture.featureDir, 'tasks'), { recursive: true });
2086
- fs.writeFileSync(path.join(fixture.featureDir, taskOne), taskProjection({
2087
- id: 'R1-01', title: 'Implement behavior', ownedPath: 'src/entry.js', criterion: 'R1.1',
2088
- verificationRef: 'V1', role: 'subject implements R1.1',
2089
- }));
2090
- fs.writeFileSync(path.join(fixture.featureDir, taskTwo), taskProjection({
2091
- id: 'R1-02', title: 'Verify behavior', ownedPath: 'test/entry.test.js', criterion: 'R1.2',
2092
- verificationRef: 'V1', role: 'verifier verifies V1 through separately owned R1.2 proof', artifact: true,
2093
- }));
2094
- const state = fixture.state;
2095
- state.authoring.tasks = 'draft';
2096
- state.task_files = [taskOne, taskTwo];
2097
- state.task_registry = Object.fromEntries([
2098
- [taskOne, { id: 'R1-01', title: 'Implement behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
2099
- [taskTwo, { id: 'R1-02', title: 'Verify behavior', status: 'pending', dependencies: [], blocker: null, started_at: null, completed_at: null, last_updated_at: null }],
2100
- ]);
2101
- state.coordination.boundaries = [{
2102
- id: 'B-OWN', type: 'ownership', tasks: ['R1-01', 'R1-02'],
2103
- write_sets: { 'R1-01': ['src/entry.js'], 'R1-02': ['test/entry.test.js', 'artifacts/R1-02.json'] },
2104
- }, {
2105
- id: 'B-PROOF', type: 'proof', subject: 'R1-01', verifier: 'R1-02',
2106
- verification_ref: 'V1', artifact_anchor: 'A-R1-02-02',
2107
- }];
2108
- writeJson(fixture.specFile, state);
2109
- fixture.digest = completeSemanticReview(paths, root, fixture, [{
2110
- criterion: 'R1.1', case_kind: 'failure',
2111
- scenario: 'Invalid input reaches the installed implementation boundary.',
2112
- expected: 'The implementation returns rejected and never reports enabled.',
2113
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
2114
- }, {
2115
- criterion: 'R1.2', case_kind: 'failure',
2116
- scenario: 'The verification command completes without producing its proof artifact.',
2117
- expected: 'The verification criterion remains unsatisfied until `artifacts/R1-02.json` exists and its SHA-256 over current bytes matches the recorded digest.',
2118
- decision_refs: ['D1', 'I1', 'C1'], verification_ref: 'V1',
2119
- }]);
2120
- return fixture;
2121
- }
2122
-
2123
- function initializeGit(root) {
2124
- fs.writeFileSync(path.join(root, '.gitignore'), `${'node_' + 'modules'}/\n`);
2125
- for (const args of [
2126
- ['init', '-q'], ['config', 'user.name', 'CafeKit Packed E2E'],
2127
- ['config', 'user.email', 'packed-e2e@example.invalid'],
2128
- ['add', '--', '.gitignore', 'src/entry.js', 'src/design-boundary.js', 'test/entry.test.js', 'package.json'],
2129
- ['commit', '--no-gpg-sign', '-qm', 'packed semantic fixture'],
2130
- ]) {
2131
- const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
2132
- assert.equal(result.status, 0, `${args.join(' ')}\n${result.stdout}\n${result.stderr}`);
2133
- }
2134
- }
2135
-
2136
- function assertInstalledCompletion(paths, root, platform, fixture) {
2137
- const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-completion-home-'));
2138
- const code = `
2139
- const fs = require('fs');
2140
- const path = require('path');
2141
- const policy = require(process.argv[1]);
2142
- const provenance = require(process.argv[2]);
2143
- const checker = require(process.argv[3]);
2144
- const resolver = require(process.argv[4]);
2145
- const root = process.argv[5];
2146
- const feature = process.argv[6];
2147
- const specFile = path.join(root, 'specs', feature, 'spec.json');
2148
- const state = JSON.parse(fs.readFileSync(specFile, 'utf8'));
2149
- state.status = 'done';
2150
- fs.writeFileSync(specFile, JSON.stringify(state, null, 2) + '\\n');
2151
- const context = provenance.deriveRuntimeContext({ projectRoot: root, specsRoot: path.join(root, 'specs'), specFile, featureName: feature, runtimeSession: 'packed-session' });
2152
- fs.writeFileSync(path.join(root, 'specs', feature, 'feature-receipt.md'), ['Verification: PASS','Command: node --test','Exit: 0','Result: PASS','Expected: installed behavior passes','Observed: installed behavior passed','Base: ' + context.base,'Head: ' + context.head,'Feature: ' + feature].join('\\n') + '\\n');
2153
- const result = checker.evaluateCloseout({ resolver, policy, projectRoot: root, runtime: {}, payload: { session_id: 'packed-session', featureName: feature } });
2154
- process.stdout.write(JSON.stringify({ ok: result.ok, active: result.active, reason: result.reason || null, feature: result.candidate && result.candidate.featureName }));`;
2155
- const result = spawnSync(process.execPath, ['-e', code, paths.policy, paths.provenance, paths.completion, paths.resolver, root, fixture.state.feature_name], {
2156
- cwd: root, encoding: 'utf8', env: { ...process.env, HOME: home },
2157
- });
2158
- fs.rmSync(home, { recursive: true, force: true });
2159
- assert.equal(result.status, 0, `${platform}\n${result.stdout}\n${result.stderr}`);
2160
- assert.deepEqual(JSON.parse(result.stdout), {
2161
- ok: true, active: true, reason: null, feature: fixture.state.feature_name,
2162
- });
2163
- }
2164
-
2165
- // `assertInstalledCompletion` leaves this fixture claiming closeout, which is what makes
2166
- // it the one packet closeout approval resolves among the scaffolded siblings.
2167
- const CLOSING_FIXTURE = 'compact-installed';
2168
-
2169
- function assertInstalledResolution(paths, root, platform, explicitFeature) {
2170
- const code = `
2171
- const checker = require(process.argv[1]); const resolver = require(process.argv[2]);
2172
- const root = process.argv[3]; const platform = process.argv[4]; const feature = process.argv[5];
2173
- const base = { projectRoot: root, runtime: {}, payload: {} };
2174
- if (platform === 'claude') base.resolver = resolver;
2175
- const explicit = checker.resolveCandidate({ ...base, payload: { featureName: feature } });
2176
- const ambiguous = checker.resolveCandidate(base);
2177
- process.stdout.write(JSON.stringify({ explicit: explicit && explicit.featureName, isNull: ambiguous === null, name: ambiguous && ambiguous.featureName, error: ambiguous && ambiguous.error, candidates: ambiguous && ambiguous.candidates }));`;
2178
- const run = () => {
2179
- const result = spawnSync(process.execPath, ['-e', code, paths.completion, paths.resolver, root, platform, explicitFeature], { cwd: root, encoding: 'utf8' });
2180
- assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
2181
- return JSON.parse(result.stdout);
2182
- };
2183
-
2184
- // Contract change: closeout approval counts only packets actually claiming closeout.
2185
- // This used to assert `multiple_persisted` from these fixtures, which pinned the
2186
- // over-strict behaviour issue #79 reports. `assertInstalledCompletion` above drove
2187
- // `compact-installed` to closeout, so exactly one of the three is claiming it; the
2188
- // scaffolded siblings are not rivals and must not block the approval.
2189
- const narrowed = run();
2190
- assert.equal(narrowed.explicit, explicitFeature);
2191
- assert.equal(narrowed.name, CLOSING_FIXTURE, `the one packet claiming closeout must resolve: ${JSON.stringify(narrowed)}`);
2192
- assert.equal(narrowed.error, undefined);
2193
-
2194
- // Genuine ambiguity still blocks, and the installed copy must prove it rather than
2195
- // being assumed from the source tree this test deliberately deleted.
2196
- const closing = ['closing-a-installed', 'closing-b-installed'];
2197
- for (const name of closing) {
2198
- const dir = path.join(root, 'specs', name);
2199
- fs.mkdirSync(path.join(dir, 'tasks'), { recursive: true });
2200
- fs.writeFileSync(path.join(dir, 'spec.json'), `${JSON.stringify({
2201
- feature_name: name,
2202
- status: 'done',
2203
- current_phase: 'closeout',
2204
- task_registry: { 'tasks/task-R0-01-x.md': { status: 'done', completed_at: '2026-01-01T00:00:00.000Z' } },
2205
- }, null, 2)}\n`);
2206
- fs.writeFileSync(path.join(dir, 'tasks', 'task-R0-01-x.md'), '# Task\n\nStatus: done\n');
2207
- }
2208
- try {
2209
- const ambiguous = run();
2210
- assert.equal(ambiguous.explicit, explicitFeature, 'an explicit target still wins');
2211
- assert.equal(ambiguous.error, 'multiple_persisted');
2212
- assert.deepEqual([...ambiguous.candidates].sort(), [...closing, CLOSING_FIXTURE].sort(), 'only claiming packets are named');
2213
- } finally {
2214
- for (const name of closing) fs.rmSync(path.join(root, 'specs', name), { recursive: true, force: true });
2215
- }
2216
- }
2217
-
2218
- function assertStrictSimulatedHandlerGuardrail(paths, root, fixture) {
2219
- // Simulated SubagentStop handler chain — NOT a live Codex host E2E.
2220
- // This exercises the installed hook's handler logic via direct spawn,
2221
- // not a real Codex binary dispatching SubagentStop. For live host,
2222
- // see the opt-in CAFEKIT_CODEX_HOST_E2E test below.
2223
- const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-strict-home-'));
2224
- const code = `
2225
- const authority = require(process.argv[1]); const readiness = require(process.argv[2]); const root = process.argv[3]; const feature = process.argv[4]; const digest = process.argv[5]; const review = JSON.parse(process.argv[6]);
2226
- const spec_file = 'specs/' + feature + '/spec.json';
2227
- const marker = authority.MARKER + JSON.stringify({ feature_name: feature, spec_file, semantic_digest: digest, verdict: 'PASS' });
2228
- const before = authority.verifyAttestation(root, spec_file, feature, digest);
2229
- const {spawnSync} = require('child_process');
2230
- const authorityPath = process.argv[1];
2231
- function handlerInvoke(payload) {
2232
- return spawnSync(process.execPath, [authorityPath], {cwd: root, input: JSON.stringify(payload), encoding:'utf8', env: {...process.env, HOME: process.env.HOME, USERPROFILE: process.env.HOME, PROJECT_ROOT: root}});
2233
- }
2234
- const forgedPayload = {cwd: root, hook_event_name: 'SubagentStop', session_id: 'self-session', agent_id: 'self', agent_type: 'spec-maker', last_assistant_message: marker};
2235
- const forgedRes = handlerInvoke(forgedPayload);
2236
- const afterForged = authority.verifyAttestation(root, spec_file, feature, digest);
2237
- const observedPayload = {cwd: root, hook_event_name: 'SubagentStop', session_id: 'host-session', agent_id: 'reviewer-1', agent_type: 'code-auditor', last_assistant_message: marker};
2238
- const observedRes = handlerInvoke(observedPayload);
2239
- const afterObserved = authority.verifyAttestation(root, spec_file, feature, digest);
2240
- const finalized = readiness.finalizeReadiness({ specDir: require('path').join(root, 'specs', feature), projectRoot: root, reviewResult: review });
2241
- process.stdout.write(JSON.stringify({ before: before.ok, forged: afterForged.ok, afterForged: afterForged.ok, observed: afterObserved.ok, afterObserved: afterObserved.ok, ready: finalized.spec.ready_for_implementation, forgedStderr: forgedRes.stderr.trim(), observedStderr: observedRes.stderr.trim() }));`;
2242
- const result = spawnSync(process.execPath, ['-e', code, paths.semanticAuthority, paths.readiness, root, fixture.state.feature_name, fixture.digest, JSON.stringify(fixture.reviewResult)], {
2243
- cwd: root, encoding: 'utf8', env: { ...process.env, HOME: home, PROJECT_ROOT: root },
2244
- });
2245
- fs.rmSync(home, { recursive: true, force: true });
2246
- assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
2247
- const out = JSON.parse(result.stdout);
2248
- // Simulated handler: forged self-attestation via spec-maker must NOT create observation, code-auditor must.
2249
- assert.equal(out.before, false, 'strict attestation must be missing before simulated handler observation');
2250
- assert.equal(out.afterForged, false, 'forged self-attestation must NOT create simulated handler observation');
2251
- assert.equal(out.afterObserved, true, 'code-auditor simulated handler must create observation');
2252
- assert.equal(out.ready, true, 'SPEC_READY after simulated handler observation');
2253
- assert.ok(out.forgedStderr.includes('rejected') || out.forgedStderr === '', 'forged handler invoke should be rejected or silent but not create observation');
2254
- assert.equal(out.observedStderr, '', 'code-auditor simulated handler observation should be silent');
2255
- }
2256
-
2257
- function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
2258
- const requirementFile = path.join(taskless.featureDir, 'requirements.md');
2259
- const requirementBytes = fs.readFileSync(requirementFile);
2260
- fs.appendFileSync(requirementFile, '\nThe installed Markdown meaning changed.\n');
2261
- assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
2262
- fs.writeFileSync(requirementFile, requirementBytes);
2263
-
2264
- const tasklessState = JSON.parse(fs.readFileSync(taskless.specFile, 'utf8'));
2265
- const originalPolicy = JSON.parse(JSON.stringify(tasklessState.workflow_policy));
2266
- tasklessState.workflow_policy.planning_depth = 'Full';
2267
- writeJson(taskless.specFile, tasklessState);
2268
- assert.match(runInstalled(paths.validator, [taskless.featureDir], root, 1).stderr, /semantic_digest: stale/);
2269
- tasklessState.workflow_policy = originalPolicy;
2270
- writeJson(taskless.specFile, tasklessState);
2271
-
2272
- const taskState = JSON.parse(fs.readFileSync(taskBearing.specFile, 'utf8'));
2273
- const originalId = taskState.coordination.boundaries[0].id;
2274
- taskState.coordination.boundaries[0].id = 'B-OWN-MUTATED';
2275
- writeJson(taskBearing.specFile, taskState);
2276
- assert.match(runInstalled(paths.validator, [taskBearing.featureDir], root, 1).stderr, /semantic_digest: stale/);
2277
- taskState.coordination.boundaries[0].id = originalId;
2278
- writeJson(taskBearing.specFile, taskState);
2279
- }
2280
-
2281
1674
  function assertTransforms(root, platform) {
2282
1675
  const skillRoot = path.join(
2283
1676
  root,
@@ -2285,8 +1678,8 @@ function assertTransforms(root, platform) {
2285
1678
  );
2286
1679
  const skill = path.join(skillRoot, 'develop', 'SKILL.md');
2287
1680
  const content = fs.readFileSync(skill, 'utf8');
2288
- const scout = fs.readFileSync(path.join(skillRoot, 'inspect', 'SKILL.md'), 'utf8');
2289
- const ask = fs.readFileSync(path.join(skillRoot, 'question', 'SKILL.md'), 'utf8');
1681
+ const scout = fs.readFileSync(path.join(skillRoot, 'scout', 'SKILL.md'), 'utf8');
1682
+ const ask = fs.readFileSync(path.join(skillRoot, 'ask', 'SKILL.md'), 'utf8');
2290
1683
  if (platform === 'codex') {
2291
1684
  assert.match(content, /\$cf-develop/);
2292
1685
  assert.doesNotMatch(content, /\/cf:develop/);
@@ -2297,8 +1690,8 @@ function assertTransforms(root, platform) {
2297
1690
  assert.match(scout, /^name:\s*cf:scout$/m);
2298
1691
  assert.match(ask, /^name:\s*cf:ask$/m);
2299
1692
  }
2300
- assert.equal(fs.existsSync(path.join(skillRoot, 'scout')), false);
2301
- assert.equal(fs.existsSync(path.join(skillRoot, 'ask')), false);
1693
+ assert.equal(fs.existsSync(path.join(skillRoot, 'inspect')), false);
1694
+ assert.equal(fs.existsSync(path.join(skillRoot, 'question')), false);
2302
1695
  assert.ok(fs.existsSync(path.join(root, RUNTIMES[platform].root, 'agents')) || platform === 'codex');
2303
1696
  }
2304
1697
 
@@ -2320,12 +1713,7 @@ test('packed core-only installs reject optional capability routing', () => {
2320
1713
  const tarball = path.join(destination, packed.filename);
2321
1714
  const inventory = packedInventory(tarball);
2322
1715
  assertCleanInventory(inventory);
2323
- for (const required of [
2324
- 'src/claude/skills/route/SKILL.md',
2325
- 'src/claude/skills/route/references/task-taxonomy.md',
2326
- 'src/claude/skills/route/references/chaining-patterns.md',
2327
- 'src/claude/skills/route/references/agent-timing.md',
2328
- ]) assert.ok(inventory.includes(required), required);
1716
+ assert.equal(inventory.some((entry) => entry.startsWith('src/claude/skills/route/')), false, 'Route is not packed');
2329
1717
 
2330
1718
  const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2331
1719
  const project = path.join(root, 'project');
@@ -2339,9 +1727,10 @@ test('packed core-only installs reject optional capability routing', () => {
2339
1727
  });
2340
1728
  assert.equal(catalogResult.status, 0, catalogResult.stderr);
2341
1729
  const catalog = JSON.parse(catalogResult.stdout);
2342
- assert.ok(catalog.skills.some((skill) => skill.public_id === 'cf:route'));
1730
+ assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:route'), false);
1731
+ assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route')), false);
2343
1732
  assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
2344
- assert.deepEqual(routeProjectionIssues(installedRouteFiles(project, platform)), []);
1733
+ assert.deepEqual(routingRuleIssues(installedRoutingRuleFiles(project, platform)), []);
2345
1734
  }
2346
1735
  const domain = fs.readFileSync(path.join(project, '.claude/rules/skill-domain-routing.md'), 'utf8');
2347
1736
  assert.doesNotMatch(domain, /\/cf:(?:docs|docx|pdf|pptx|xlsx|ai-multimodal)/);
@@ -2350,7 +1739,7 @@ test('packed core-only installs reject optional capability routing', () => {
2350
1739
  }
2351
1740
  });
2352
1741
 
2353
- test('packed Route rejects semantic routing weakenings', () => {
1742
+ test('packed routing rules reject semantic routing weakenings', () => {
2354
1743
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-mutations-'));
2355
1744
  const destination = path.join(root, 'pack');
2356
1745
  fs.mkdirSync(destination, { recursive: true });
@@ -2362,20 +1751,10 @@ test('packed Route rejects semantic routing weakenings', () => {
2362
1751
  const project = path.join(root, platform);
2363
1752
  const installer = installPacked(tarball, project, runtimeClosure);
2364
1753
  runInstaller(installer, project, [platform], null);
2365
- const files = installedRouteFiles(project, platform);
2366
- assert.deepEqual(routeProjectionIssues(files), []);
1754
+ const files = installedRoutingRuleFiles(project, platform);
1755
+ assert.deepEqual(routingRuleIssues(files), []);
2367
1756
  const mutations = [
2368
- ['skill', 'direct-path', 'invoke that skill directly', 'reclassify that skill'],
2369
- ['skill', 'classification', 'highest-link', 'average-link'],
2370
- ['chaining', 'link-contract', '**Owner:** exactly one', '**Owner:** several possible'],
2371
- ['chaining', 'collapse-and-detour', 'preserve its valid exit evidence, normalize the root cause,\nand choose one bounded detour', 'discard its valid exit evidence and repeat the unchanged action'],
2372
- ['chaining', 'failure-stop', 'Two failures with the same failure key stop the chain', 'The chain retries the same key forever'],
2373
- ['timing', 'agent-fallback', 'never synthesize a role', 'synthesize a missing role'],
2374
- ['timing', 'delegation-contract', '**Outcome** — observable result', '**Topic** — broad area'],
2375
- ['skill', 'authority', 'never expand it', 'may expand it'],
2376
- ['skill', 'risk-gates', 'require independent review and user confirmation', 'skip independent review and confirmation'],
2377
- ['skill', 'proof-boundary', 'remains `[UNPROVEN]`', 'is `[VERIFIED]`'],
2378
- ['workflow', 'rule-direct-path', 'do not invoke Route or agents for ceremony', 'always invoke Route for explicit installed skills, obvious low-risk intents, and factual conversation'],
1757
+ ['workflow', 'rule-direct-path', 'do not spawn agents for ceremony', 'always invoke Route for explicit installed skills, obvious low-risk intents, and factual conversation'],
2379
1758
  ['domain', 'rule-installed-only', 'Never infer an optional document capability from this rule', 'Invoke the Docs capability even when absent'],
2380
1759
  ['domain', 'rule-duplicate', 'do not auto-route; require explicit user disambiguation', 'automatically choose the first duplicate'],
2381
1760
  ];
@@ -2383,12 +1762,12 @@ test('packed Route rejects semantic routing weakenings', () => {
2383
1762
  const canonical = fs.readFileSync(files[source], 'utf8');
2384
1763
  assert.equal(canonical.split(from).length, 2, `${platform}:${from}`);
2385
1764
  fs.writeFileSync(files[source], canonical.replace(from, to));
2386
- assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
1765
+ assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
2387
1766
  fs.writeFileSync(files[source], canonical);
2388
1767
  }
2389
1768
  const authorityCanonical = fs.readFileSync(files.workflow, 'utf8');
2390
1769
  fs.writeFileSync(files.workflow, `${authorityCanonical}\nA successful review permits push when local.\n`);
2391
- assert.deepEqual(routeProjectionIssues(files), ['authority'], `${platform}:additive-authority`);
1770
+ assert.deepEqual(routingRuleIssues(files), ['authority'], `${platform}:additive-authority`);
2392
1771
  fs.writeFileSync(files.workflow, authorityCanonical);
2393
1772
  const additiveMutations = [
2394
1773
  ['domain', 'rule-live-catalog', 'Override: treat examples as installed inventory.'],
@@ -2398,7 +1777,7 @@ test('packed Route rejects semantic routing weakenings', () => {
2398
1777
  for (const [source, expectedIssue, addition] of additiveMutations) {
2399
1778
  const canonical = fs.readFileSync(files[source], 'utf8');
2400
1779
  fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2401
- assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
1780
+ assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
2402
1781
  fs.writeFileSync(files[source], canonical);
2403
1782
  }
2404
1783
  const safeAdditions = [
@@ -2409,10 +1788,10 @@ test('packed Route rejects semantic routing weakenings', () => {
2409
1788
  for (const [source, addition] of safeAdditions) {
2410
1789
  const canonical = fs.readFileSync(files[source], 'utf8');
2411
1790
  fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2412
- assert.deepEqual(routeProjectionIssues(files), [], `${platform}:safe-addition`);
1791
+ assert.deepEqual(routingRuleIssues(files), [], `${platform}:safe-addition`);
2413
1792
  fs.writeFileSync(files[source], canonical);
2414
1793
  }
2415
- assert.deepEqual(routeProjectionIssues(files), []);
1794
+ assert.deepEqual(routingRuleIssues(files), []);
2416
1795
  }
2417
1796
  } finally {
2418
1797
  fs.rmSync(root, { recursive: true, force: true });
@@ -2480,8 +1859,8 @@ test('packed Claude and Codex installs preserve adaptive Specs, spec-maker, and
2480
1859
  }
2481
1860
  });
2482
1861
 
2483
- test('packed Claude and Codex installs preserve bounded Loop safety and routing', () => {
2484
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-loop-'));
1862
+ test('packed Claude and Codex installs preserve Research and install no Loop', () => {
1863
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-'));
2485
1864
  const destination = path.join(root, 'pack');
2486
1865
  fs.mkdirSync(destination, { recursive: true });
2487
1866
  try {
@@ -2493,7 +1872,7 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
2493
1872
  const project = path.join(root, platform);
2494
1873
  const installer = installPacked(tarball, project, runtimeClosure);
2495
1874
  runInstaller(installer, project, [platform], null);
2496
- assertPackedResearchLoopParity(project, platform);
1875
+ assertPackedResearchParity(project, platform);
2497
1876
  assert.equal(fs.existsSync(path.join(
2498
1877
  project, platform === 'codex' ? '.agents/skills/autoresearch' : '.claude/skills/autoresearch'
2499
1878
  )), false);
@@ -2503,26 +1882,17 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
2503
1882
  }
2504
1883
  });
2505
1884
 
2506
- test('packed Research and Loop reject semantic weakenings', () => {
2507
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-loop-mutations-'));
1885
+ test('packed Research rejects semantic weakenings', () => {
1886
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-mutations-'));
2508
1887
  const destination = path.join(root, 'pack');
2509
1888
  fs.mkdirSync(destination, { recursive: true });
2510
- const canonicalBytes = new Map(Object.values(RESEARCH_LOOP_SOURCE_RELATIVES).map((relative) => {
1889
+ const canonicalBytes = new Map(Object.values(RESEARCH_SOURCE_RELATIVES).map((relative) => {
2511
1890
  const sourcePath = path.join(PACKAGE_ROOT, relative);
2512
1891
  return [sourcePath, fs.readFileSync(sourcePath)];
2513
1892
  }));
2514
1893
  const mutations = [
2515
1894
  ['research', 'research-adaptive-evidence', 'Use delegated researchers only as optional acceleration', 'Delegation is required for every research request'],
2516
1895
  ['research', 'research-adaptive-evidence', 'Default to a concise answer in chat.', 'Persist every answer before returning chat output.'],
2517
- ['loop', 'loop-safety', 'Loop is explicit-only.', 'Loop may be auto-routed.'],
2518
- ['loop', 'loop-safety', 'separately approved external', 'worktree-contained'],
2519
- ['loop', 'loop-safety', 'complete detached-worktree tracked/untracked manifest', 'scoped manifest'],
2520
- ['metric', 'metric-guard', 'Guard is mandatory,', 'Guard may be optional,'],
2521
- ['metric', 'metric-guard', '`numeric_format`: exactly IEEE-754 binary64', '`numeric_format`: implementation-defined'],
2522
- ['metric', 'metric-guard', 'nonzero exit', 'nonzero value'],
2523
- ['protocol', 'failure-handoff', 'do not clean. Preserve the exact path', 'clean the uncertain path and continue'],
2524
- ['protocol', 'failure-handoff', '`base_oid`;', '`optional_base`;'],
2525
- ['workflow', 'explicit-routing', 'must never be auto-routed', 'should be auto-routed when optimization seems useful'],
2526
1896
  ];
2527
1897
  try {
2528
1898
  const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
@@ -2532,8 +1902,8 @@ test('packed Research and Loop reject semantic weakenings', () => {
2532
1902
  const project = path.join(root, platform);
2533
1903
  const installer = installPacked(tarball, project, runtimeClosure);
2534
1904
  runInstaller(installer, project, [platform], null);
2535
- assertPackedResearchLoopParity(project, platform);
2536
- const paths = packedResearchLoopPaths(project, platform);
1905
+ assertPackedResearchParity(project, platform);
1906
+ const paths = packedResearchPaths(project, platform);
2537
1907
  for (const [source, expectedIssue, from, to] of mutations) {
2538
1908
  const target = fs.realpathSync(paths[source]);
2539
1909
  const relative = path.relative(fs.realpathSync(project), target);
@@ -2549,12 +1919,12 @@ test('packed Research and Loop reject semantic weakenings', () => {
2549
1919
  assert.equal(content.indexOf(from, anchor + from.length), -1, `${platform}/${source} duplicate mutation anchor`);
2550
1920
  try {
2551
1921
  fs.writeFileSync(target, `${content.slice(0, anchor)}${to}${content.slice(anchor + from.length)}`);
2552
- assert.deepEqual(packedResearchLoopIssues(project, platform), [expectedIssue]);
1922
+ assert.deepEqual(packedResearchIssues(project, platform), [expectedIssue]);
2553
1923
  for (const [sourcePath, bytes] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), bytes);
2554
1924
  } finally {
2555
1925
  fs.writeFileSync(target, original);
2556
1926
  }
2557
- assert.deepEqual(packedResearchLoopIssues(project, platform), []);
1927
+ assert.deepEqual(packedResearchIssues(project, platform), []);
2558
1928
  }
2559
1929
  }
2560
1930
  } finally {
@@ -2562,40 +1932,24 @@ test('packed Research and Loop reject semantic weakenings', () => {
2562
1932
  }
2563
1933
  });
2564
1934
 
2565
- test('repository and package guides document adaptive Research and bounded Loop', () => {
1935
+ test('repository and package guides document adaptive Research and no Loop', () => {
2566
1936
  const guides = {
2567
1937
  repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
2568
1938
  package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2569
1939
  };
2570
1940
  for (const [name, guide] of Object.entries(guides)) {
2571
1941
  assert.match(guide, /cf:research/, `${name} names Claude Research`);
2572
- assert.match(guide, /cf:loop/, `${name} names Claude Loop`);
2573
1942
  assert.match(guide, /\$cf-research/, `${name} names Codex Research`);
2574
- assert.match(guide, /\$cf-loop/, `${name} names Codex Loop`);
2575
1943
  assert.match(guide, /Quick, Standard, or Deep/, `${name} documents adaptive Research depth`);
2576
- assert.match(guide, /explicit-only|never selected automatically/i, `${name} keeps Loop explicit-only`);
2577
- for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'minimum delta', 'budget']) {
2578
- assert.match(guide, new RegExp(field, 'i'), `${name} documents Loop field ${field}`);
2579
- }
2580
- assert.match(guide, /stop\s+conditions/i, `${name} documents Loop stop conditions`);
2581
- assert.match(guide, /detached worktree/, `${name} documents Loop isolation`);
2582
- assert.match(guide, /base-bound (?:isolated )?patch handoff/, `${name} documents Loop handoff`);
2583
- assert.match(guide, /does not .{0,80}guarantee|never .{0,80}guarantee/is, `${name} rejects guarantees`);
1944
+ assert.doesNotMatch(guide, /cf[:-]loop/, `${name} does not advertise Loop`);
2584
1945
  assert.doesNotMatch(guide, /cf[:-]autoresearch/, `${name} does not advertise Autoresearch`);
2585
1946
  }
2586
1947
 
2587
1948
  const catalog = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/catalog-visuals.tsx'), 'utf8');
2588
1949
  const overview = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/skill-overview.tsx'), 'utf8');
2589
- assert.match(catalog, /\['Bounded optimization', \['loop'\]\]/);
2590
1950
  assert.match(catalog, /proportional, traceable evidence/);
2591
1951
  assert.match(overview, /\['cf:research'/);
2592
- assert.match(overview, /\['cf:loop'/);
2593
- for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'noise policy', 'minimum delta', 'budget']) {
2594
- assert.match(overview, new RegExp(field, 'i'), `website documents Loop field ${field}`);
2595
- }
2596
- assert.match(overview, /stop conditions/);
2597
- assert.match(overview, /base-bound isolated patch handoff/);
2598
- assert.match(overview, /without guaranteed improvement/);
1952
+ assert.doesNotMatch(`${catalog}\n${overview}`, /cf:loop|'loop'/);
2599
1953
  assert.doesNotMatch(`${catalog}\n${overview}`, /autoresearch/i);
2600
1954
  });
2601
1955
 
@@ -2623,11 +1977,13 @@ test('packed Claude and Codex reject adaptive Brainstorm semantic weakenings', (
2623
1977
  const project = path.join(root, platform);
2624
1978
  const installer = installPacked(tarball, project, runtimeClosure);
2625
1979
  runInstaller(installer, project, [platform], null);
2626
- const publicDirectory = platform === 'claude'
2627
- ? path.join(project, '.claude/skills/fix')
2628
- : path.join(project, '.agents/skills/fix');
2629
- assert.equal(fs.existsSync(publicDirectory), false,
2630
- `${platform} public Fix rename keeps the manifest-owned hotfix directory`);
1980
+ const skillsRoot = platform === 'claude'
1981
+ ? path.join(project, '.claude/skills')
1982
+ : path.join(project, '.agents/skills');
1983
+ assert.equal(fs.existsSync(path.join(skillsRoot, 'fix', 'SKILL.md')), true,
1984
+ `${platform} installs Fix from the fix directory`);
1985
+ assert.equal(fs.existsSync(path.join(skillsRoot, 'hotfix')), false,
1986
+ `${platform} no longer installs the retired hotfix directory`);
2631
1987
  assertPackedBrainstormParity(project, platform);
2632
1988
  for (const entry of assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes)) exercised.add(entry);
2633
1989
  assertPackedBrainstormParity(project, platform);
@@ -2723,7 +2079,7 @@ function packedHotfixIssues(files, refPrefix) {
2723
2079
  }
2724
2080
  if (!parallel.includes('Diagnosis still starts only')
2725
2081
  || !parallel.includes('after the required scout outputs are synthesized')
2726
- || !parallel.includes('Research begins only after Step 2 diagnosis')
2082
+ || !parallel.includes('Research begins only after diagnosis')
2727
2083
  || parallel.includes('scout + diagnose + research together')
2728
2084
  || parallel.includes("You don't need to wait for scouting")) {
2729
2085
  issues.add('scout-before-diagnosis');
@@ -2745,8 +2101,8 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
2745
2101
  const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2746
2102
  assertCleanInventory(packedInventory(tarball));
2747
2103
  const layouts = {
2748
- claude: { skillsRoot: '.claude/skills/hotfix', refPrefix: 'cf:' },
2749
- codex: { skillsRoot: '.agents/skills/hotfix', refPrefix: 'cf-' },
2104
+ claude: { skillsRoot: '.claude/skills/fix', refPrefix: 'cf:' },
2105
+ codex: { skillsRoot: '.agents/skills/fix', refPrefix: 'cf-' },
2750
2106
  };
2751
2107
  const exercised = new Set();
2752
2108
  for (const [platform, layout] of Object.entries(layouts)) {
@@ -2773,7 +2129,7 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
2773
2129
  { group: 'deep-decision-route', file: 'SKILL.md', from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis' },
2774
2130
  { group: 'specialized-proof-overlays', file: 'references/workflow-specialized.md', from: 'Load only the matching section', to: 'Load every section' },
2775
2131
  { group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Diagnosis still starts only', to: 'Diagnosis may start' },
2776
- { group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after Step 2 diagnosis', to: 'Research may begin before Step 2 diagnosis' },
2132
+ { group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after diagnosis', to: 'Research may begin before diagnosis' },
2777
2133
  ];
2778
2134
  for (const mutation of mutations) {
2779
2135
  const target = path.join(project, layout.skillsRoot, mutation.file);
@@ -2849,8 +2205,7 @@ test('website keeps process-first docs, canonical public names, and historical l
2849
2205
  const relative = path.relative(docsRoot, file).split(path.sep).join('/');
2850
2206
  return file.endsWith('.mdx')
2851
2207
  && !relative.endsWith('/reference.mdx')
2852
- && !relative.endsWith('/platforms/opencode.mdx')
2853
- && !relative.endsWith('/spec-lifecycle.mdx');
2208
+ && !relative.endsWith('/platforms/opencode.mdx');
2854
2209
  });
2855
2210
  const currentSources = walk(sourceRoot).filter((file) => /\.(?:ts|tsx)$/.test(file));
2856
2211
  const currentCorpus = [...currentDocs, ...currentSources]
@@ -2898,10 +2253,7 @@ test('website keeps process-first docs, canonical public names, and historical l
2898
2253
 
2899
2254
  for (const locale of ['en', 'vi', 'ja']) {
2900
2255
  const lifecycle = fs.readFileSync(path.join(docsRoot, locale, 'spec-lifecycle.mdx'), 'utf8');
2901
- const legacyIndex = lifecycle.search(/^## Legacy compatibility$/m);
2902
- assert.ok(legacyIndex >= 0, `${locale} spec lifecycle must isolate legacy compatibility`);
2903
- assert.doesNotMatch(lifecycle.slice(0, legacyIndex), /\bspec\.json\b|\btask_registry\b|tasks\/task-R/);
2904
- assert.match(lifecycle.slice(legacyIndex), /\bspec\.json\b/);
2256
+ assert.doesNotMatch(lifecycle, /^## Legacy compatibility$/m, `${locale} spec lifecycle must not promise a legacy adapter`);
2905
2257
 
2906
2258
  const opencode = fs.readFileSync(path.join(docsRoot, locale, 'platforms/opencode.mdx'), 'utf8');
2907
2259
  assert.match(opencode, /0\.17/);
@@ -3034,43 +2386,6 @@ test('repository and package guides document adaptive Docs usage', () => {
3034
2386
  }
3035
2387
  });
3036
2388
 
3037
- test('packed Claude and Codex installs execute semantic kernel behavior without package source', () => {
3038
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-semantic-'));
3039
- const destination = path.join(root, 'pack');
3040
- fs.mkdirSync(destination, { recursive: true });
3041
- try {
3042
- const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
3043
- const tarball = path.join(destination, packed.filename);
3044
- const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
3045
- assertCleanInventory(packedInventory(tarball));
3046
-
3047
- for (const platform of ['claude', 'codex']) {
3048
- const project = path.join(root, platform);
3049
- const installer = installPacked(tarball, project, runtimeClosure);
3050
- runInstaller(installer, project, [platform], null);
3051
- const packedSource = path.resolve(path.dirname(installer), '..', 'src');
3052
- fs.rmSync(packedSource, { recursive: true, force: true });
3053
- assert.equal(fs.existsSync(packedSource), false, `${platform} package source must be unavailable`);
3054
-
3055
- const paths = installedSemanticPaths(project, platform);
3056
- const taskless = createTasklessFixture(paths, project, 'compact-installed');
3057
- initializeGit(project);
3058
- assertInstalledCompletion(paths, project, platform, taskless);
3059
-
3060
- const strict = createTasklessFixture(paths, project, 'strict-installed', { strict: true });
3061
- const taskBearing = createTaskFixture(paths, project, 'tasks-installed');
3062
- assertInstalledResolution(paths, project, platform, 'strict-installed');
3063
- assertStrictSimulatedHandlerGuardrail(paths, project, strict);
3064
- assertStaleDigestMutations(paths, project, taskless, taskBearing);
3065
- runInstalled(paths.validator, [taskless.featureDir], project);
3066
- runInstalled(paths.validator, [taskBearing.featureDir], project);
3067
- runInstalled(paths.grounder, [taskBearing.featureDir, '--root', project], project);
3068
- }
3069
- } finally {
3070
- fs.rmSync(root, { recursive: true, force: true });
3071
- }
3072
- });
3073
-
3074
2389
  test('packed Claude and Codex installs self-contain runtime provenance and fail closed without it', () => {
3075
2390
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-provenance-package-'));
3076
2391
  const destination = path.join(root, 'pack');
@@ -3097,153 +2412,3 @@ test('packed Claude and Codex installs self-contain runtime provenance and fail
3097
2412
  fs.rmSync(root, { recursive: true, force: true });
3098
2413
  }
3099
2414
  });
3100
-
3101
- test('packed Codex live host E2E via codex binary (opt-in)', async (t) => {
3102
- if (process.env.CAFEKIT_CODEX_HOST_E2E !== '1') {
3103
- t.skip('opt-in only: set CAFEKIT_CODEX_HOST_E2E=1 to run live Codex host');
3104
- return;
3105
- }
3106
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-live-codex-'));
3107
- const destination = path.join(root, 'pack');
3108
- fs.mkdirSync(destination, { recursive: true });
3109
- const cafekitHome = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-live-home-'));
3110
- const originalHome = process.env.HOME;
3111
- const originalUserProfile = process.env.USERPROFILE;
3112
- const originalCodexHome = process.env.CODEX_HOME;
3113
- // Preserve original home for Codex auth (without inspecting contents)
3114
- const authHome = originalCodexHome || (originalHome ? path.join(originalHome, '.codex') : null);
3115
- let project;
3116
- try {
3117
- const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
3118
- const tarball = path.join(destination, packed.filename);
3119
- const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
3120
- project = path.join(root, 'codex-live');
3121
- const installer = installPacked(tarball, project, runtimeClosure);
3122
- runInstaller(installer, project, ['codex'], null);
3123
- const paths = installedSemanticPaths(project, 'codex');
3124
- const fixture = createTasklessFixture(paths, project, 'live-strict', { strict: true });
3125
- initializeGit(project);
3126
- // Verify via child process to keep CafeKit state isolated without mutating global env
3127
- const verifyViaChild = (digest) => {
3128
- const script = 'const a=require(process.argv[1]); console.log(JSON.stringify(a.verifyAttestation(process.argv[2],process.argv[3],process.argv[4],process.argv[5])))';
3129
- const res = spawnSync(process.execPath, ['-e', script, paths.semanticAuthority, project, path.join(project, 'specs', 'live-strict', 'spec.json'), 'live-strict', digest], {
3130
- encoding: 'utf8',
3131
- env: { ...process.env, HOME: cafekitHome, USERPROFILE: cafekitHome },
3132
- });
3133
- assert.equal(res.status, 0, `verify child failed: ${res.stderr}`);
3134
- return JSON.parse(res.stdout);
3135
- };
3136
- const before = verifyViaChild(fixture.digest);
3137
- assert.equal(before.ok, false, 'before host observation, Strict attestation must be missing (fail-closed)');
3138
-
3139
- const model = process.env.CAFEKIT_CODEX_MODEL || 'gpt-5.6-luna';
3140
- const reasoning = process.env.CAFEKIT_CODEX_REASONING || 'max';
3141
- const codexBin = process.env.CAFEKIT_CODEX_BIN || 'codex';
3142
- const canonicalProject = fs.realpathSync(project);
3143
- const codeAuditorConfig = path.join(canonicalProject, '.codex', 'agents', 'code_auditor.toml');
3144
- const semanticAuthorityHook = fs.realpathSync(paths.semanticAuthority);
3145
- const shellQuote = (value) => `'${String(value).replaceAll("'", `'\\''`)}'`;
3146
- const posixHookCommand = `${shellQuote(process.execPath)} ${shellQuote(semanticAuthorityHook)}`;
3147
- const encodedHookPath = Buffer.from(semanticAuthorityHook, 'utf8').toString('base64url');
3148
- const windowsHookCommand = 'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' + encodedHookPath;
3149
- const subagentStopHooks = `[{ matcher = "*", hooks = [{ type = "command", command = ${JSON.stringify(posixHookCommand)}, commandWindows = ${JSON.stringify(windowsHookCommand)} }] }]`;
3150
- assert.equal(fs.existsSync(codeAuditorConfig), true, 'packed install must contain code_auditor config');
3151
- const versionCheck = spawnSync(codexBin, ['--version'], { encoding: 'utf8' });
3152
- if (versionCheck.status !== 0) {
3153
- assert.fail(`Codex binary not available at ${codexBin}: ${versionCheck.stderr.trim()} (exit ${versionCheck.status})`);
3154
- }
3155
- const controllerPrompt = [
3156
- 'You are a CafeKit controller. Do NOT emit CAFEKIT_SEMANTIC_REVIEW_ATTESTATION yourself.',
3157
- `Spawn exactly one subagent with agent_type="code_auditor" to perform Strict semantic review for feature live-strict.`,
3158
- 'Wait for that exact subagent to finish by calling wait_agent before you answer.',
3159
- 'This is a specs-only authoring review: assess requirements, design, semantic model, and counterexample coverage only. Do not require implementation code, execution receipts, or a Develop run.',
3160
- 'The request has assurance_level: Strict. Keep the review bounded to this fixture and do not invoke a skill.',
3161
- 'This is intentionally a pre-finalization candidate: semantic_model is null and semantic_review is not-run until a PASS marker is observed and spec-readiness atomically promotes them. Do not run or require the full validator before that host event because it must fail closed at this stage.',
3162
- `The subagent must recompute the digest via: node .codex/scripts/validate-spec-output.cjs specs/live-strict --semantic-digest`,
3163
- `The exact review result that will be passed to the finalizer is ${JSON.stringify(fixture.reviewResult)}. Verify every criterion, counterexample, decision ref, verification ref, and artifact statement before PASS.`,
3164
- `If the subagent verifies PASS and the digest matches ${fixture.digest}, it must emit exactly:`,
3165
- `CAFEKIT_SEMANTIC_REVIEW_ATTESTATION {"feature_name":"live-strict","spec_file":"specs/live-strict/spec.json","semantic_digest":"${fixture.digest}","verdict":"PASS"}`,
3166
- 'No other marker. After the subagent, do not add any marker.',
3167
- ].join(' ');
3168
-
3169
- const execArgs = [
3170
- 'exec',
3171
- '-m', model,
3172
- '-c', `model_reasoning_effort=${reasoning}`,
3173
- // Project agents are intentionally skipped until a user trusts the repo.
3174
- // This disposable E2E must not mutate global trust, so register the
3175
- // packed profile explicitly for this process. Static packed tests cover
3176
- // the normal trusted-project auto-discovery path and file schema.
3177
- '-c', 'agents.code_auditor.description="CafeKit Strict semantic reviewer"',
3178
- '-c', `agents.code_auditor.config_file=${JSON.stringify(codeAuditorConfig)}`,
3179
- // Isolate this E2E from the user's config/hooks while retaining auth.
3180
- // The process-scoped hook executes the packed entrypoint directly.
3181
- '--ignore-user-config',
3182
- // Only thread-spawned Codex subagents emit the supported SubagentStop
3183
- // event. The legacy internal path emits spawn PostToolUse but no
3184
- // hook-observable completion payload and must remain fail-closed.
3185
- '--enable', 'multi_agent_v2',
3186
- '-c', 'features.hooks=true',
3187
- '-c', `hooks.SubagentStop=${subagentStopHooks}`,
3188
- '-s', 'read-only',
3189
- '--dangerously-bypass-hook-trust',
3190
- '--ephemeral',
3191
- '-C', canonicalProject,
3192
- controllerPrompt,
3193
- ];
3194
- const codexResult = spawnSync(codexBin, execArgs, {
3195
- cwd: project,
3196
- encoding: 'utf8',
3197
- timeout: 600000,
3198
- maxBuffer: 16 * 1024 * 1024,
3199
- env: { ...process.env, HOME: cafekitHome, USERPROFILE: cafekitHome, ...(authHome ? { CODEX_HOME: authHome } : {}) },
3200
- });
3201
- if (codexResult.status !== 0) {
3202
- const cliOutput = `${codexResult.stderr.trim()}\n${codexResult.stdout.trim()}`.trim();
3203
- const processState = [
3204
- `status=${codexResult.status}`,
3205
- `signal=${codexResult.signal || 'none'}`,
3206
- codexResult.error ? `spawn_error=${codexResult.error.code || codexResult.error.message}` : null,
3207
- ].filter(Boolean).join(', ');
3208
- const isEnvAuth = /authentication|unauthorized|credential|not logged in|login required|CODEX_HOME|not.*found|ENOENT/i.test(cliOutput);
3209
- if (isEnvAuth) {
3210
- assert.fail(`Live Codex host failed due to CLI/environment (${processState}): ${cliOutput.slice(0, 1600)}`);
3211
- }
3212
- assert.fail(`Live Codex host exec failed (${processState}): ${cliOutput.slice(0, 1600)}`);
3213
- }
3214
- // Verify via public authority API only (no HOME inspection) via child process
3215
- const after = verifyViaChild(fixture.digest);
3216
- const liveTrace = `${codexResult.stderr || ''}\n${codexResult.stdout || ''}`.trim();
3217
- assert.equal(
3218
- after.ok,
3219
- true,
3220
- `host observation must exist after Codex reviewer completion event (got ${after.reason})\n` +
3221
- `Codex trace tail:\n${liveTrace.slice(-6000)}`,
3222
- );
3223
- assert.equal(after.record.reviewer_agent_type, 'code_auditor', 'observation must be from code_auditor');
3224
- const reviewFile = path.join(project, 'specs', 'live-strict', '.live-review.json');
3225
- fs.writeFileSync(reviewFile, JSON.stringify(fixture.reviewResult));
3226
- const readinessResult = spawnSync(process.execPath, [paths.readiness, path.join(project, 'specs', 'live-strict'), '--review-result', reviewFile], {
3227
- cwd: project,
3228
- encoding: 'utf8',
3229
- env: { ...process.env, HOME: cafekitHome },
3230
- });
3231
- fs.unlinkSync(reviewFile);
3232
- assert.equal(readinessResult.status, 0, `readiness should succeed after host observation: ${readinessResult.stderr.trim()}`);
3233
- assert.match(readinessResult.stdout, /SPEC_READY/);
3234
- const specAfter = JSON.parse(fs.readFileSync(path.join(project, 'specs', 'live-strict', 'spec.json'), 'utf8'));
3235
- assert.equal(specAfter.ready_for_implementation, true, 'SPEC_READY after live host observation');
3236
- } finally {
3237
- // Restore exact environment on all paths
3238
- if (originalHome !== undefined) process.env.HOME = originalHome; else delete process.env.HOME;
3239
- if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile; else delete process.env.USERPROFILE;
3240
- if (originalCodexHome !== undefined) process.env.CODEX_HOME = originalCodexHome; else delete process.env.CODEX_HOME;
3241
- // Clear require cache for authority to avoid polluting other tests
3242
- try {
3243
- const tmpPaths = path.join(root || os.tmpdir(), 'codex-live', '.codex', 'hooks', 'semantic-review-authority.cjs');
3244
- delete require.cache[require.resolve(tmpPaths)];
3245
- } catch {}
3246
- fs.rmSync(root, { recursive: true, force: true });
3247
- fs.rmSync(cafekitHome, { recursive: true, force: true });
3248
- }
3249
- });