@haposoft/cafekit 0.16.7 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) 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 +0 -36
  4. package/bin/__tests__/codex-native.test.js +46 -88
  5. package/bin/__tests__/develop-contract.test.js +26 -57
  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 +159 -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 +100 -541
  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 +0 -27
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
  21. package/bin/__tests__/ui-ux-skill.test.js +101 -0
  22. package/bin/__tests__/usage-hook-retired.test.js +114 -0
  23. package/bin/__tests__/validator-grounding.test.js +9 -28
  24. package/bin/phases/codex-runtime.js +24 -0
  25. package/bin/phases/omp-runtime.js +22 -0
  26. package/package.json +1 -1
  27. package/src/claude/CLAUDE.md +3 -10
  28. package/src/claude/agents/brainstormer.md +7 -3
  29. package/src/claude/agents/code-auditor.md +35 -50
  30. package/src/claude/agents/debugger.md +5 -79
  31. package/src/claude/agents/deployer.md +3 -5
  32. package/src/claude/agents/docs-keeper.md +2 -7
  33. package/src/claude/agents/implementer.md +6 -9
  34. package/src/claude/agents/inspector.md +2 -3
  35. package/src/claude/agents/project-manager.md +7 -15
  36. package/src/claude/agents/researcher.md +9 -3
  37. package/src/claude/agents/spec-maker.md +11 -11
  38. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
  39. package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
  40. package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
  41. package/src/claude/hooks/__tests__/state.test.js +68 -0
  42. package/src/claude/hooks/__tests__/statusline.test.js +140 -11
  43. package/src/claude/hooks/agent.cjs +2 -1
  44. package/src/claude/hooks/lib/context.cjs +0 -89
  45. package/src/claude/hooks/lib/hook-payload.cjs +2 -2
  46. package/src/claude/hooks/rules.cjs +2 -1
  47. package/src/claude/hooks/session.cjs +0 -1
  48. package/src/claude/hooks/spec-gate.cjs +17 -28
  49. package/src/claude/hooks/spec-state.cjs +95 -1
  50. package/src/claude/hooks/state.cjs +21 -2
  51. package/src/claude/migration-manifest.json +30 -15
  52. package/src/claude/rules/ai-dev-rules.md +1 -1
  53. package/src/claude/rules/hook-protocols.md +8 -15
  54. package/src/claude/rules/manage-docs.md +9 -23
  55. package/src/claude/rules/orchestrator.md +3 -4
  56. package/src/claude/rules/review-audit-self-decision.md +3 -3
  57. package/src/claude/rules/skill-domain-routing.md +1 -3
  58. package/src/claude/rules/skill-workflow-routing.md +4 -5
  59. package/src/claude/rules/state-sync.md +3 -12
  60. package/src/claude/rules/workflow.md +10 -18
  61. package/src/claude/runtime.schema.json +5 -3
  62. package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
  63. package/src/claude/scripts/provenance.cjs +23 -5
  64. package/src/claude/scripts/spec-receipt.cjs +24 -7
  65. package/src/claude/scripts/spec-resolver.cjs +1 -2
  66. package/src/claude/scripts/validate-spec-output.cjs +5 -32
  67. package/src/claude/settings/settings.json +1 -32
  68. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  69. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  70. package/src/claude/skills/code-review/SKILL.md +48 -17
  71. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  72. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  73. package/src/claude/skills/debug/SKILL.md +29 -149
  74. package/src/claude/skills/develop/SKILL.md +27 -36
  75. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  76. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  77. package/src/claude/skills/docs/SKILL.md +2 -2
  78. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  79. package/src/claude/skills/fix/SKILL.md +153 -0
  80. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  81. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  83. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  84. package/src/claude/skills/git/SKILL.md +48 -9
  85. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  86. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  87. package/src/claude/skills/orca/SKILL.md +9 -8
  88. package/src/claude/skills/research/SKILL.md +2 -0
  89. package/src/claude/skills/scout/SKILL.md +61 -0
  90. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  91. package/src/claude/skills/specs/SKILL.md +22 -29
  92. package/src/claude/skills/specs/references/review.md +8 -6
  93. package/src/claude/skills/specs/references/templates.md +34 -41
  94. package/src/claude/skills/sync/SKILL.md +8 -15
  95. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  96. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  97. package/src/claude/skills/test/SKILL.md +22 -3
  98. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  99. package/src/claude/skills/test/references/failure-triage.md +1 -1
  100. package/src/claude/skills/ui-ux/LICENSE +21 -0
  101. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  102. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  103. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  104. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  105. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  106. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  107. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  108. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  109. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  110. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  111. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  112. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  113. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  114. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  115. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  116. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  117. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  118. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  119. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  120. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  121. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  122. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  123. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  124. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  125. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  126. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  127. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  128. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  129. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  130. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  131. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  132. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  134. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  135. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  136. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  137. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  138. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  139. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  140. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  141. package/src/claude/skills/ui-ux/references/review.md +528 -0
  142. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  143. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  144. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  145. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  146. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  147. package/src/claude/skills/ui-ux/references/system.md +219 -0
  148. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  149. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  150. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  151. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  152. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  153. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  154. package/src/claude/status.cjs +149 -144
  155. package/src/codex/AGENTS.md +8 -13
  156. package/src/codex/hooks/agent.cjs +2 -1
  157. package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
  158. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  159. package/src/codex/hooks/rules.cjs +2 -1
  160. package/src/codex/hooks/session.cjs +0 -2
  161. package/src/codex/hooks/spec-gate.cjs +7 -18
  162. package/src/codex/hooks.json +0 -20
  163. package/src/codex/rules/state-sync.md +3 -12
  164. package/src/common/AGENTS.md +2 -2
  165. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  166. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  167. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  168. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  169. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  170. package/src/claude/hooks/completion-authority.cjs +0 -163
  171. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  172. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  173. package/src/claude/hooks/usage.cjs +0 -197
  174. package/src/claude/skills/delegate/SKILL.md +0 -124
  175. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  176. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  177. package/src/claude/skills/hotfix/SKILL.md +0 -289
  178. package/src/claude/skills/inspect/SKILL.md +0 -142
  179. package/src/claude/skills/loop/SKILL.md +0 -114
  180. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  181. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  182. package/src/claude/skills/route/SKILL.md +0 -94
  183. package/src/claude/skills/route/references/agent-timing.md +0 -46
  184. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  185. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  186. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  187. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  188. package/src/codex/hooks/completion-authority.cjs +0 -109
  189. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  190. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  191. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  192. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  193. /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
  };
@@ -126,19 +126,12 @@ const REQUIRED_PAYLOAD = [
126
126
  'src/claude/rules/review-audit-self-decision.md',
127
127
  'src/claude/rules/skill-domain-routing.md',
128
128
  '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
129
  'src/claude/skills/orca/SKILL.md',
134
130
  'src/claude/skills/brainstorm/SKILL.md',
135
131
  'src/claude/skills/brainstorm/references/question-framework.md',
136
132
  'src/claude/agents/brainstormer.md',
137
133
  'src/claude/skills/research/SKILL.md',
138
134
  '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
135
  'src/claude/skills/docs/SKILL.md',
143
136
  'src/claude/skills/docx/SKILL.md',
144
137
  'src/claude/skills/pdf/SKILL.md',
@@ -179,20 +172,17 @@ const BRAINSTORM_SOURCE_RELATIVES = {
179
172
  framework: 'src/claude/skills/brainstorm/references/question-framework.md',
180
173
  agent: 'src/claude/agents/brainstormer.md',
181
174
  };
182
- const RESEARCH_LOOP_SOURCE_RELATIVES = {
175
+ const RESEARCH_SOURCE_RELATIVES = {
183
176
  research: 'src/claude/skills/research/SKILL.md',
184
177
  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
178
  workflow: 'src/claude/rules/skill-workflow-routing.md',
189
179
  domain: 'src/claude/rules/skill-domain-routing.md',
190
180
  };
191
181
  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',
182
+ skill: 'src/claude/skills/fix/SKILL.md',
183
+ review: 'src/claude/skills/fix/references/review-cycle.md',
184
+ parallel: 'src/claude/skills/fix/references/parallel-patterns.md',
185
+ specialized: 'src/claude/skills/fix/references/workflow-specialized.md',
196
186
  };
197
187
  const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
198
188
  'adviser-gate-fallback',
@@ -208,7 +198,7 @@ const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
208
198
  ];
209
199
  const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
210
200
  { 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.' },
201
+ { 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
202
  ] },
213
203
  { group: 'ordered-depth', mutations: [
214
204
  { 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 +206,32 @@ const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
216
206
  { group: 'leading-flags', mutations: [
217
207
  { 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
208
  { 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.' },
209
+ { from: '`--` ends the control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
220
210
  { 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
211
  ] },
222
212
  { 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' },
213
+ { 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
214
  ] },
225
215
  { 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`.' },
216
+ { 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
217
  ] },
228
218
  { 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`.' },
219
+ { 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
220
  ] },
231
221
  { 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.' },
222
+ { 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.' },
223
+ { 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.' },
224
+ { 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
225
  ] },
236
226
  { 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.' },
227
+ { 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;' },
228
+ { 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
229
  ] },
240
230
  { 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.' },
231
+ { 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
232
  ] },
243
233
  { 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.' },
234
+ { 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
235
  ] },
246
236
  ];
247
237
 
@@ -342,20 +332,15 @@ function runInstaller(installer, root, platforms, lang, extraArgs = []) {
342
332
  return result;
343
333
  }
344
334
 
345
- function installedRouteFiles(project, platform) {
335
+ function installedRoutingRuleFiles(project, platform) {
346
336
  const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
347
- const root = path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
348
337
  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
338
  workflow: path.join(project, runtimeRoot, 'rules/skill-workflow-routing.md'),
354
339
  domain: path.join(project, runtimeRoot, 'rules/skill-domain-routing.md'),
355
340
  };
356
341
  }
357
342
 
358
- function routeProjectionIssues(files) {
343
+ function routingRuleIssues(files) {
359
344
  const text = Object.fromEntries(Object.entries(files).map(([key, file]) => [
360
345
  key, fs.readFileSync(file, 'utf8').replace(/\s+/g, ' '),
361
346
  ]));
@@ -363,56 +348,10 @@ function routeProjectionIssues(files) {
363
348
  const requires = (issue, clauses) => {
364
349
  if (clauses.some(([source, clause]) => !text[source].includes(clause))) issues.add(issue);
365
350
  };
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
351
  requires('rule-direct-path', [
413
352
  ['workflow', 'If the user names a valid installed skill, use it directly'],
414
353
  ['workflow', 'If one obvious low-risk installed skill covers the intent, use it directly'],
415
- ['workflow', 'do not invoke Route or agents for ceremony'],
354
+ ['workflow', 'do not spawn agents for ceremony'],
416
355
  ]);
417
356
  requires('rule-live-catalog', [
418
357
  ['workflow', 'Resolve every abstract link against the current runtime catalog'],
@@ -774,30 +713,24 @@ function assertPackedBrainstormParity(project, platform) {
774
713
  }
775
714
  }
776
715
 
777
- function packedResearchLoopPaths(project, platform) {
716
+ function packedResearchPaths(project, platform) {
778
717
  return platform === 'codex'
779
718
  ? {
780
719
  research: path.join(project, '.agents/skills/research/SKILL.md'),
781
720
  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
721
  workflow: path.join(project, '.codex/rules/skill-workflow-routing.md'),
786
722
  domain: path.join(project, '.codex/rules/skill-domain-routing.md'),
787
723
  }
788
724
  : {
789
725
  research: path.join(project, '.claude/skills/research/SKILL.md'),
790
726
  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
727
  workflow: path.join(project, '.claude/rules/skill-workflow-routing.md'),
795
728
  domain: path.join(project, '.claude/rules/skill-domain-routing.md'),
796
729
  };
797
730
  }
798
731
 
799
- function readPackedResearchLoop(project, platform) {
800
- const paths = packedResearchLoopPaths(project, platform);
732
+ function readPackedResearch(project, platform) {
733
+ const paths = packedResearchPaths(project, platform);
801
734
  const values = Object.fromEntries(Object.entries(paths).map(([key, target]) => [
802
735
  key, fs.readFileSync(target, 'utf8'),
803
736
  ]));
@@ -807,13 +740,12 @@ function readPackedResearchLoop(project, platform) {
807
740
  return values;
808
741
  }
809
742
 
810
- function packedResearchLoopIssues(project, platform) {
811
- const values = Object.fromEntries(Object.entries(readPackedResearchLoop(project, platform)).map(
743
+ function packedResearchIssues(project, platform) {
744
+ const values = Object.fromEntries(Object.entries(readPackedResearch(project, platform)).map(
812
745
  ([key, value]) => [key, value.replace(/\s+/g, ' ').trim()]
813
746
  ));
814
747
  const issues = new Set();
815
748
  const publicResearch = platform === 'codex' ? 'name: cf-research' : 'name: cf:research';
816
- const publicLoop = platform === 'codex' ? 'name: cf-loop' : 'name: cf:loop';
817
749
  if (!values.research.includes(publicResearch)
818
750
  || !values.research.includes('Choose the smallest depth that can support the decision:')
819
751
  || !values.research.includes('Use delegated researchers only as optional acceleration')
@@ -826,35 +758,15 @@ function packedResearchLoopIssues(project, platform) {
826
758
  || !values.agent.includes('owns any authorized persistence.')) {
827
759
  issues.add('research-agent-boundary');
828
760
  }
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');
761
+ if (/numeric optimization capability/.test(`${values.workflow}\n${values.domain}`)) {
762
+ issues.add('loop-removed');
851
763
  }
852
764
  return [...issues].sort();
853
765
  }
854
766
 
855
- function assertPackedResearchLoopParity(project, platform) {
856
- const installed = packedResearchLoopPaths(project, platform);
857
- for (const [key, relative] of Object.entries(RESEARCH_LOOP_SOURCE_RELATIVES)) {
767
+ function assertPackedResearchParity(project, platform) {
768
+ const installed = packedResearchPaths(project, platform);
769
+ for (const [key, relative] of Object.entries(RESEARCH_SOURCE_RELATIVES)) {
858
770
  const sourcePath = path.join(PACKAGE_ROOT, relative);
859
771
  const source = fs.readFileSync(sourcePath, 'utf8');
860
772
  const expected = platform === 'codex'
@@ -866,12 +778,15 @@ function assertPackedResearchLoopParity(project, platform) {
866
778
  }
867
779
  const manifest = JSON.parse(fs.readFileSync(path.join(project, RUNTIMES[platform].manifest), 'utf8'));
868
780
  const prefixes = platform === 'codex'
869
- ? ['.agents/skills/research/', '.agents/skills/loop/']
870
- : ['skills/research/', 'skills/loop/'];
781
+ ? ['.agents/skills/research/']
782
+ : ['skills/research/'];
871
783
  for (const prefix of prefixes) {
872
784
  assert.ok(Object.keys(manifest.files).some((entry) => entry.startsWith(prefix)), `${platform} manifest missing ${prefix}`);
873
785
  }
874
- assert.deepEqual(packedResearchLoopIssues(project, platform), []);
786
+ const removedLoop = platform === 'codex' ? '.agents/skills/loop/' : 'skills/loop/';
787
+ assert.equal(Object.keys(manifest.files).some((entry) => entry.startsWith(removedLoop)), false, `${platform} manifest still lists ${removedLoop}`);
788
+ assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/loop' : '.claude/skills/loop')), false, `${platform} still installs Loop`);
789
+ assert.deepEqual(packedResearchIssues(project, platform), []);
875
790
  }
876
791
 
877
792
  function packedBrainstormInstalledPaths(project, platform) {
@@ -1316,8 +1231,8 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
1316
1231
  },
1317
1232
  {
1318
1233
  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:',
1234
+ 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:',
1235
+ to: 'After applying an accepted GATE-REVIEW finding, record the repair under this review-log header:',
1321
1236
  },
1322
1237
  {
1323
1238
  name: 'distinct-repair-and-proof-evidence', issue: 'repair-closure', source: 'review',
@@ -1423,7 +1338,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
1423
1338
  ['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
1424
1339
  'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
1425
1340
  ['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']],
1341
+ '`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
1342
  ['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
1428
1343
  'Source proof may promote installed and live proof.', ['proof-lifecycle']],
1429
1344
  ['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
@@ -1437,7 +1352,7 @@ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
1437
1352
  ['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
1438
1353
  'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
1439
1354
  ['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']],
1355
+ 'Apply the risk-first route before GATE-SCOPE and stop only for direct work.', ['spec-maker-authority']],
1441
1356
  ['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
1442
1357
  'Source/static checks prove live-model adherence.', ['proof-lifecycle']],
1443
1358
  ];
@@ -1613,63 +1528,11 @@ function assertInstalledProvenance(root, platform, fixture) {
1613
1528
  root,
1614
1529
  platform === 'claude' ? '.claude/hooks/spec-gate.cjs' : '.codex/hooks/spec-gate.cjs'
1615
1530
  );
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
1531
  for (const file of [helper, policy, resolver, receipt, finalState, readiness]) {
1622
1532
  assert.equal(fs.existsSync(file), true, `${platform} installed file missing: ${file}`);
1623
1533
  }
1624
1534
  assert.equal(fs.existsSync(gate), true, `${platform} installed gate missing: ${gate}`);
1625
1535
 
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
1536
  const helperRun = spawnSync(process.execPath, provenanceCliArgs(helper, root, fixture), {
1674
1537
  cwd: root,
1675
1538
  encoding: 'utf8',
@@ -1843,8 +1706,6 @@ function installedSemanticPaths(root, platform) {
1843
1706
  semanticModel: path.join(runtimeRoot, 'scripts', 'spec-semantic-model.cjs'),
1844
1707
  resolver: path.join(runtimeRoot, 'scripts', 'spec-resolver.cjs'),
1845
1708
  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
1709
  stateTemplate: path.join(skillRoot, 'specs', 'templates', 'spec-state.json'),
1849
1710
  };
1850
1711
  for (const [name, target] of Object.entries(paths)) {
@@ -2133,125 +1994,16 @@ function initializeGit(root) {
2133
1994
  }
2134
1995
  }
2135
1996
 
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');
1997
+ function assertStrictRefusedAsUnsupported(paths, root, fixture) {
1998
+ const specFile = path.join(fixture.featureDir, 'spec.json');
1999
+ const before = fs.readFileSync(specFile);
2000
+ const reviewFile = path.join(root, 'strict-review.json');
2001
+ fs.writeFileSync(reviewFile, JSON.stringify(fixture.reviewResult));
2002
+ const result = spawnSync(process.execPath, [paths.readiness, fixture.featureDir, '--review-result', reviewFile], { cwd: root, encoding: 'utf8' });
2003
+ fs.rmSync(reviewFile, { force: true });
2004
+ assert.notEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);
2005
+ assert.match(`${result.stdout}\n${result.stderr}`, /Strict assurance is no longer supported/);
2006
+ assert.equal(fs.readFileSync(specFile).equals(before), true, 'a refused Strict readiness must leave spec.json unchanged');
2255
2007
  }
2256
2008
 
2257
2009
  function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
@@ -2285,8 +2037,8 @@ function assertTransforms(root, platform) {
2285
2037
  );
2286
2038
  const skill = path.join(skillRoot, 'develop', 'SKILL.md');
2287
2039
  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');
2040
+ const scout = fs.readFileSync(path.join(skillRoot, 'scout', 'SKILL.md'), 'utf8');
2041
+ const ask = fs.readFileSync(path.join(skillRoot, 'ask', 'SKILL.md'), 'utf8');
2290
2042
  if (platform === 'codex') {
2291
2043
  assert.match(content, /\$cf-develop/);
2292
2044
  assert.doesNotMatch(content, /\/cf:develop/);
@@ -2297,8 +2049,8 @@ function assertTransforms(root, platform) {
2297
2049
  assert.match(scout, /^name:\s*cf:scout$/m);
2298
2050
  assert.match(ask, /^name:\s*cf:ask$/m);
2299
2051
  }
2300
- assert.equal(fs.existsSync(path.join(skillRoot, 'scout')), false);
2301
- assert.equal(fs.existsSync(path.join(skillRoot, 'ask')), false);
2052
+ assert.equal(fs.existsSync(path.join(skillRoot, 'inspect')), false);
2053
+ assert.equal(fs.existsSync(path.join(skillRoot, 'question')), false);
2302
2054
  assert.ok(fs.existsSync(path.join(root, RUNTIMES[platform].root, 'agents')) || platform === 'codex');
2303
2055
  }
2304
2056
 
@@ -2320,12 +2072,7 @@ test('packed core-only installs reject optional capability routing', () => {
2320
2072
  const tarball = path.join(destination, packed.filename);
2321
2073
  const inventory = packedInventory(tarball);
2322
2074
  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);
2075
+ assert.equal(inventory.some((entry) => entry.startsWith('src/claude/skills/route/')), false, 'Route is not packed');
2329
2076
 
2330
2077
  const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2331
2078
  const project = path.join(root, 'project');
@@ -2339,9 +2086,10 @@ test('packed core-only installs reject optional capability routing', () => {
2339
2086
  });
2340
2087
  assert.equal(catalogResult.status, 0, catalogResult.stderr);
2341
2088
  const catalog = JSON.parse(catalogResult.stdout);
2342
- assert.ok(catalog.skills.some((skill) => skill.public_id === 'cf:route'));
2089
+ assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:route'), false);
2090
+ assert.equal(fs.existsSync(path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route')), false);
2343
2091
  assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
2344
- assert.deepEqual(routeProjectionIssues(installedRouteFiles(project, platform)), []);
2092
+ assert.deepEqual(routingRuleIssues(installedRoutingRuleFiles(project, platform)), []);
2345
2093
  }
2346
2094
  const domain = fs.readFileSync(path.join(project, '.claude/rules/skill-domain-routing.md'), 'utf8');
2347
2095
  assert.doesNotMatch(domain, /\/cf:(?:docs|docx|pdf|pptx|xlsx|ai-multimodal)/);
@@ -2350,7 +2098,7 @@ test('packed core-only installs reject optional capability routing', () => {
2350
2098
  }
2351
2099
  });
2352
2100
 
2353
- test('packed Route rejects semantic routing weakenings', () => {
2101
+ test('packed routing rules reject semantic routing weakenings', () => {
2354
2102
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-mutations-'));
2355
2103
  const destination = path.join(root, 'pack');
2356
2104
  fs.mkdirSync(destination, { recursive: true });
@@ -2362,20 +2110,10 @@ test('packed Route rejects semantic routing weakenings', () => {
2362
2110
  const project = path.join(root, platform);
2363
2111
  const installer = installPacked(tarball, project, runtimeClosure);
2364
2112
  runInstaller(installer, project, [platform], null);
2365
- const files = installedRouteFiles(project, platform);
2366
- assert.deepEqual(routeProjectionIssues(files), []);
2113
+ const files = installedRoutingRuleFiles(project, platform);
2114
+ assert.deepEqual(routingRuleIssues(files), []);
2367
2115
  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'],
2116
+ ['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
2117
  ['domain', 'rule-installed-only', 'Never infer an optional document capability from this rule', 'Invoke the Docs capability even when absent'],
2380
2118
  ['domain', 'rule-duplicate', 'do not auto-route; require explicit user disambiguation', 'automatically choose the first duplicate'],
2381
2119
  ];
@@ -2383,12 +2121,12 @@ test('packed Route rejects semantic routing weakenings', () => {
2383
2121
  const canonical = fs.readFileSync(files[source], 'utf8');
2384
2122
  assert.equal(canonical.split(from).length, 2, `${platform}:${from}`);
2385
2123
  fs.writeFileSync(files[source], canonical.replace(from, to));
2386
- assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
2124
+ assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
2387
2125
  fs.writeFileSync(files[source], canonical);
2388
2126
  }
2389
2127
  const authorityCanonical = fs.readFileSync(files.workflow, 'utf8');
2390
2128
  fs.writeFileSync(files.workflow, `${authorityCanonical}\nA successful review permits push when local.\n`);
2391
- assert.deepEqual(routeProjectionIssues(files), ['authority'], `${platform}:additive-authority`);
2129
+ assert.deepEqual(routingRuleIssues(files), ['authority'], `${platform}:additive-authority`);
2392
2130
  fs.writeFileSync(files.workflow, authorityCanonical);
2393
2131
  const additiveMutations = [
2394
2132
  ['domain', 'rule-live-catalog', 'Override: treat examples as installed inventory.'],
@@ -2398,7 +2136,7 @@ test('packed Route rejects semantic routing weakenings', () => {
2398
2136
  for (const [source, expectedIssue, addition] of additiveMutations) {
2399
2137
  const canonical = fs.readFileSync(files[source], 'utf8');
2400
2138
  fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2401
- assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
2139
+ assert.deepEqual(routingRuleIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
2402
2140
  fs.writeFileSync(files[source], canonical);
2403
2141
  }
2404
2142
  const safeAdditions = [
@@ -2409,10 +2147,10 @@ test('packed Route rejects semantic routing weakenings', () => {
2409
2147
  for (const [source, addition] of safeAdditions) {
2410
2148
  const canonical = fs.readFileSync(files[source], 'utf8');
2411
2149
  fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2412
- assert.deepEqual(routeProjectionIssues(files), [], `${platform}:safe-addition`);
2150
+ assert.deepEqual(routingRuleIssues(files), [], `${platform}:safe-addition`);
2413
2151
  fs.writeFileSync(files[source], canonical);
2414
2152
  }
2415
- assert.deepEqual(routeProjectionIssues(files), []);
2153
+ assert.deepEqual(routingRuleIssues(files), []);
2416
2154
  }
2417
2155
  } finally {
2418
2156
  fs.rmSync(root, { recursive: true, force: true });
@@ -2480,8 +2218,8 @@ test('packed Claude and Codex installs preserve adaptive Specs, spec-maker, and
2480
2218
  }
2481
2219
  });
2482
2220
 
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-'));
2221
+ test('packed Claude and Codex installs preserve Research and install no Loop', () => {
2222
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-'));
2485
2223
  const destination = path.join(root, 'pack');
2486
2224
  fs.mkdirSync(destination, { recursive: true });
2487
2225
  try {
@@ -2493,7 +2231,7 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
2493
2231
  const project = path.join(root, platform);
2494
2232
  const installer = installPacked(tarball, project, runtimeClosure);
2495
2233
  runInstaller(installer, project, [platform], null);
2496
- assertPackedResearchLoopParity(project, platform);
2234
+ assertPackedResearchParity(project, platform);
2497
2235
  assert.equal(fs.existsSync(path.join(
2498
2236
  project, platform === 'codex' ? '.agents/skills/autoresearch' : '.claude/skills/autoresearch'
2499
2237
  )), false);
@@ -2503,26 +2241,17 @@ test('packed Claude and Codex installs preserve bounded Loop safety and routing'
2503
2241
  }
2504
2242
  });
2505
2243
 
2506
- test('packed Research and Loop reject semantic weakenings', () => {
2507
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-loop-mutations-'));
2244
+ test('packed Research rejects semantic weakenings', () => {
2245
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-mutations-'));
2508
2246
  const destination = path.join(root, 'pack');
2509
2247
  fs.mkdirSync(destination, { recursive: true });
2510
- const canonicalBytes = new Map(Object.values(RESEARCH_LOOP_SOURCE_RELATIVES).map((relative) => {
2248
+ const canonicalBytes = new Map(Object.values(RESEARCH_SOURCE_RELATIVES).map((relative) => {
2511
2249
  const sourcePath = path.join(PACKAGE_ROOT, relative);
2512
2250
  return [sourcePath, fs.readFileSync(sourcePath)];
2513
2251
  }));
2514
2252
  const mutations = [
2515
2253
  ['research', 'research-adaptive-evidence', 'Use delegated researchers only as optional acceleration', 'Delegation is required for every research request'],
2516
2254
  ['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
2255
  ];
2527
2256
  try {
2528
2257
  const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
@@ -2532,8 +2261,8 @@ test('packed Research and Loop reject semantic weakenings', () => {
2532
2261
  const project = path.join(root, platform);
2533
2262
  const installer = installPacked(tarball, project, runtimeClosure);
2534
2263
  runInstaller(installer, project, [platform], null);
2535
- assertPackedResearchLoopParity(project, platform);
2536
- const paths = packedResearchLoopPaths(project, platform);
2264
+ assertPackedResearchParity(project, platform);
2265
+ const paths = packedResearchPaths(project, platform);
2537
2266
  for (const [source, expectedIssue, from, to] of mutations) {
2538
2267
  const target = fs.realpathSync(paths[source]);
2539
2268
  const relative = path.relative(fs.realpathSync(project), target);
@@ -2549,12 +2278,12 @@ test('packed Research and Loop reject semantic weakenings', () => {
2549
2278
  assert.equal(content.indexOf(from, anchor + from.length), -1, `${platform}/${source} duplicate mutation anchor`);
2550
2279
  try {
2551
2280
  fs.writeFileSync(target, `${content.slice(0, anchor)}${to}${content.slice(anchor + from.length)}`);
2552
- assert.deepEqual(packedResearchLoopIssues(project, platform), [expectedIssue]);
2281
+ assert.deepEqual(packedResearchIssues(project, platform), [expectedIssue]);
2553
2282
  for (const [sourcePath, bytes] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), bytes);
2554
2283
  } finally {
2555
2284
  fs.writeFileSync(target, original);
2556
2285
  }
2557
- assert.deepEqual(packedResearchLoopIssues(project, platform), []);
2286
+ assert.deepEqual(packedResearchIssues(project, platform), []);
2558
2287
  }
2559
2288
  }
2560
2289
  } finally {
@@ -2562,40 +2291,24 @@ test('packed Research and Loop reject semantic weakenings', () => {
2562
2291
  }
2563
2292
  });
2564
2293
 
2565
- test('repository and package guides document adaptive Research and bounded Loop', () => {
2294
+ test('repository and package guides document adaptive Research and no Loop', () => {
2566
2295
  const guides = {
2567
2296
  repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
2568
2297
  package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2569
2298
  };
2570
2299
  for (const [name, guide] of Object.entries(guides)) {
2571
2300
  assert.match(guide, /cf:research/, `${name} names Claude Research`);
2572
- assert.match(guide, /cf:loop/, `${name} names Claude Loop`);
2573
2301
  assert.match(guide, /\$cf-research/, `${name} names Codex Research`);
2574
- assert.match(guide, /\$cf-loop/, `${name} names Codex Loop`);
2575
2302
  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`);
2303
+ assert.doesNotMatch(guide, /cf[:-]loop/, `${name} does not advertise Loop`);
2584
2304
  assert.doesNotMatch(guide, /cf[:-]autoresearch/, `${name} does not advertise Autoresearch`);
2585
2305
  }
2586
2306
 
2587
2307
  const catalog = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/catalog-visuals.tsx'), 'utf8');
2588
2308
  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
2309
  assert.match(catalog, /proportional, traceable evidence/);
2591
2310
  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/);
2311
+ assert.doesNotMatch(`${catalog}\n${overview}`, /cf:loop|'loop'/);
2599
2312
  assert.doesNotMatch(`${catalog}\n${overview}`, /autoresearch/i);
2600
2313
  });
2601
2314
 
@@ -2623,11 +2336,13 @@ test('packed Claude and Codex reject adaptive Brainstorm semantic weakenings', (
2623
2336
  const project = path.join(root, platform);
2624
2337
  const installer = installPacked(tarball, project, runtimeClosure);
2625
2338
  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`);
2339
+ const skillsRoot = platform === 'claude'
2340
+ ? path.join(project, '.claude/skills')
2341
+ : path.join(project, '.agents/skills');
2342
+ assert.equal(fs.existsSync(path.join(skillsRoot, 'fix', 'SKILL.md')), true,
2343
+ `${platform} installs Fix from the fix directory`);
2344
+ assert.equal(fs.existsSync(path.join(skillsRoot, 'hotfix')), false,
2345
+ `${platform} no longer installs the retired hotfix directory`);
2631
2346
  assertPackedBrainstormParity(project, platform);
2632
2347
  for (const entry of assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes)) exercised.add(entry);
2633
2348
  assertPackedBrainstormParity(project, platform);
@@ -2723,7 +2438,7 @@ function packedHotfixIssues(files, refPrefix) {
2723
2438
  }
2724
2439
  if (!parallel.includes('Diagnosis still starts only')
2725
2440
  || !parallel.includes('after the required scout outputs are synthesized')
2726
- || !parallel.includes('Research begins only after Step 2 diagnosis')
2441
+ || !parallel.includes('Research begins only after diagnosis')
2727
2442
  || parallel.includes('scout + diagnose + research together')
2728
2443
  || parallel.includes("You don't need to wait for scouting")) {
2729
2444
  issues.add('scout-before-diagnosis');
@@ -2745,8 +2460,8 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
2745
2460
  const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2746
2461
  assertCleanInventory(packedInventory(tarball));
2747
2462
  const layouts = {
2748
- claude: { skillsRoot: '.claude/skills/hotfix', refPrefix: 'cf:' },
2749
- codex: { skillsRoot: '.agents/skills/hotfix', refPrefix: 'cf-' },
2463
+ claude: { skillsRoot: '.claude/skills/fix', refPrefix: 'cf:' },
2464
+ codex: { skillsRoot: '.agents/skills/fix', refPrefix: 'cf-' },
2750
2465
  };
2751
2466
  const exercised = new Set();
2752
2467
  for (const [platform, layout] of Object.entries(layouts)) {
@@ -2773,7 +2488,7 @@ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings',
2773
2488
  { group: 'deep-decision-route', file: 'SKILL.md', from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis' },
2774
2489
  { group: 'specialized-proof-overlays', file: 'references/workflow-specialized.md', from: 'Load only the matching section', to: 'Load every section' },
2775
2490
  { 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' },
2491
+ { group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Research begins only after diagnosis', to: 'Research may begin before diagnosis' },
2777
2492
  ];
2778
2493
  for (const mutation of mutations) {
2779
2494
  const target = path.join(project, layout.skillsRoot, mutation.file);
@@ -2849,8 +2564,7 @@ test('website keeps process-first docs, canonical public names, and historical l
2849
2564
  const relative = path.relative(docsRoot, file).split(path.sep).join('/');
2850
2565
  return file.endsWith('.mdx')
2851
2566
  && !relative.endsWith('/reference.mdx')
2852
- && !relative.endsWith('/platforms/opencode.mdx')
2853
- && !relative.endsWith('/spec-lifecycle.mdx');
2567
+ && !relative.endsWith('/platforms/opencode.mdx');
2854
2568
  });
2855
2569
  const currentSources = walk(sourceRoot).filter((file) => /\.(?:ts|tsx)$/.test(file));
2856
2570
  const currentCorpus = [...currentDocs, ...currentSources]
@@ -2898,10 +2612,7 @@ test('website keeps process-first docs, canonical public names, and historical l
2898
2612
 
2899
2613
  for (const locale of ['en', 'vi', 'ja']) {
2900
2614
  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/);
2615
+ assert.doesNotMatch(lifecycle, /^## Legacy compatibility$/m, `${locale} spec lifecycle must not promise a legacy adapter`);
2905
2616
 
2906
2617
  const opencode = fs.readFileSync(path.join(docsRoot, locale, 'platforms/opencode.mdx'), 'utf8');
2907
2618
  assert.match(opencode, /0\.17/);
@@ -3055,12 +2766,10 @@ test('packed Claude and Codex installs execute semantic kernel behavior without
3055
2766
  const paths = installedSemanticPaths(project, platform);
3056
2767
  const taskless = createTasklessFixture(paths, project, 'compact-installed');
3057
2768
  initializeGit(project);
3058
- assertInstalledCompletion(paths, project, platform, taskless);
3059
2769
 
3060
2770
  const strict = createTasklessFixture(paths, project, 'strict-installed', { strict: true });
3061
2771
  const taskBearing = createTaskFixture(paths, project, 'tasks-installed');
3062
- assertInstalledResolution(paths, project, platform, 'strict-installed');
3063
- assertStrictSimulatedHandlerGuardrail(paths, project, strict);
2772
+ assertStrictRefusedAsUnsupported(paths, project, strict);
3064
2773
  assertStaleDigestMutations(paths, project, taskless, taskBearing);
3065
2774
  runInstalled(paths.validator, [taskless.featureDir], project);
3066
2775
  runInstalled(paths.validator, [taskBearing.featureDir], project);
@@ -3097,153 +2806,3 @@ test('packed Claude and Codex installs self-contain runtime provenance and fail
3097
2806
  fs.rmSync(root, { recursive: true, force: true });
3098
2807
  }
3099
2808
  });
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
- });