@haposoft/cafekit 0.16.6 → 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 (194) 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 +112 -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 +18 -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 +52 -6
  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/scripts/workflow-policy.cjs +22 -10
  68. package/src/claude/settings/settings.json +1 -32
  69. package/src/claude/skills/{question → ask}/SKILL.md +9 -75
  70. package/src/claude/skills/brainstorm/SKILL.md +43 -131
  71. package/src/claude/skills/code-review/SKILL.md +48 -17
  72. package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
  73. package/src/claude/skills/code-review/references/verification-gate.md +22 -27
  74. package/src/claude/skills/debug/SKILL.md +29 -149
  75. package/src/claude/skills/develop/SKILL.md +27 -36
  76. package/src/claude/skills/develop/references/parallel-waves.md +0 -8
  77. package/src/claude/skills/develop/references/quality-gate.md +15 -13
  78. package/src/claude/skills/docs/SKILL.md +2 -2
  79. package/src/claude/skills/docs/references/update-workflow.md +1 -1
  80. package/src/claude/skills/fix/SKILL.md +153 -0
  81. package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
  82. package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
  83. package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
  84. package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
  85. package/src/claude/skills/git/SKILL.md +48 -9
  86. package/src/claude/skills/git/references/commit-protocols.md +35 -16
  87. package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
  88. package/src/claude/skills/orca/SKILL.md +9 -8
  89. package/src/claude/skills/research/SKILL.md +2 -0
  90. package/src/claude/skills/scout/SKILL.md +61 -0
  91. package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
  92. package/src/claude/skills/specs/SKILL.md +22 -29
  93. package/src/claude/skills/specs/references/review.md +8 -6
  94. package/src/claude/skills/specs/references/templates.md +38 -44
  95. package/src/claude/skills/sync/SKILL.md +8 -15
  96. package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
  97. package/src/claude/skills/sync/references/sync-protocols.md +4 -13
  98. package/src/claude/skills/test/SKILL.md +22 -3
  99. package/src/claude/skills/test/references/execution-strategy.md +83 -0
  100. package/src/claude/skills/test/references/failure-triage.md +1 -1
  101. package/src/claude/skills/ui-ux/LICENSE +21 -0
  102. package/src/claude/skills/ui-ux/SKILL.md +497 -0
  103. package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
  104. package/src/claude/skills/ui-ux/references/budgets.md +113 -0
  105. package/src/claude/skills/ui-ux/references/checklist.md +271 -0
  106. package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
  107. package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
  108. package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
  109. package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
  110. package/src/claude/skills/ui-ux/references/components/button.md +142 -0
  111. package/src/claude/skills/ui-ux/references/components/card.md +113 -0
  112. package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
  113. package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
  114. package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
  115. package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
  116. package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
  117. package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
  118. package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
  119. package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
  120. package/src/claude/skills/ui-ux/references/components/input.md +161 -0
  121. package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
  122. package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
  123. package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
  124. package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
  125. package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
  126. package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
  127. package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
  128. package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
  129. package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
  130. package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
  131. package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
  132. package/src/claude/skills/ui-ux/references/design-process.md +650 -0
  133. package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
  134. package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
  135. package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
  136. package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
  137. package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
  138. package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
  139. package/src/claude/skills/ui-ux/references/principles.md +408 -0
  140. package/src/claude/skills/ui-ux/references/refactor.md +245 -0
  141. package/src/claude/skills/ui-ux/references/responsive.md +195 -0
  142. package/src/claude/skills/ui-ux/references/review.md +528 -0
  143. package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
  144. package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
  145. package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
  146. package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
  147. package/src/claude/skills/ui-ux/references/styles.md +311 -0
  148. package/src/claude/skills/ui-ux/references/system.md +219 -0
  149. package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
  150. package/src/claude/skills/ui-ux/references/tokens.css +362 -0
  151. package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
  152. package/src/claude/skills/ui-ux/scripts/package.json +9 -0
  153. package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
  154. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  155. package/src/claude/status.cjs +149 -144
  156. package/src/codex/AGENTS.md +8 -13
  157. package/src/codex/hooks/agent.cjs +2 -1
  158. package/src/codex/hooks/lib/spec-receipt.cjs +5 -0
  159. package/src/codex/hooks/lib/spec-utils.cjs +0 -7
  160. package/src/codex/hooks/rules.cjs +2 -1
  161. package/src/codex/hooks/session.cjs +0 -2
  162. package/src/codex/hooks/spec-gate.cjs +8 -18
  163. package/src/codex/hooks.json +0 -20
  164. package/src/codex/rules/state-sync.md +3 -12
  165. package/src/common/AGENTS.md +2 -2
  166. package/src/omp/extensions/cafekit-bridge.mjs +6 -6
  167. package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
  168. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
  169. package/src/claude/hooks/completion-authority-check.cjs +0 -56
  170. package/src/claude/hooks/completion-authority-state.cjs +0 -527
  171. package/src/claude/hooks/completion-authority.cjs +0 -163
  172. package/src/claude/hooks/semantic-review-authority.cjs +0 -154
  173. package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
  174. package/src/claude/hooks/usage.cjs +0 -197
  175. package/src/claude/skills/delegate/SKILL.md +0 -124
  176. package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
  177. package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
  178. package/src/claude/skills/hotfix/SKILL.md +0 -289
  179. package/src/claude/skills/inspect/SKILL.md +0 -142
  180. package/src/claude/skills/loop/SKILL.md +0 -114
  181. package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
  182. package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
  183. package/src/claude/skills/route/SKILL.md +0 -94
  184. package/src/claude/skills/route/references/agent-timing.md +0 -46
  185. package/src/claude/skills/route/references/chaining-patterns.md +0 -53
  186. package/src/claude/skills/route/references/task-taxonomy.md +0 -48
  187. package/src/codex/hooks/completion-authority-check.cjs +0 -67
  188. package/src/codex/hooks/completion-authority-state.cjs +0 -527
  189. package/src/codex/hooks/completion-authority.cjs +0 -109
  190. package/src/codex/hooks/semantic-review-authority.cjs +0 -130
  191. package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
  192. /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
  193. /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
  194. /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
@@ -17,7 +17,6 @@ const RECEIPTS = require(path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-receip
17
17
  const FINAL_STATE = require(path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-final-state.cjs'));
18
18
  const VALIDATOR = require(path.join(PACKAGE_ROOT, 'src/claude/scripts/validate-spec-output.cjs'));
19
19
  const GROUNDER = require(path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-ground.cjs'));
20
- const SEMANTIC_AUTHORITY = require(path.join(PACKAGE_ROOT, 'src/claude/hooks/semantic-review-authority.cjs'));
21
20
  const READINESS = require(path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-readiness.cjs'));
22
21
  const AUTHORING_VALIDATOR = path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-authoring-validation.cjs');
23
22
  const { copyClaudeTestRuntime } = require('./test-runtime-dependency-closure.cjs');
@@ -80,9 +79,6 @@ function installClaudeRuntimeClosure(fixtureRoot, entry = 'hooks/spec-gate.cjs')
80
79
  const hooksRoot = path.join(PACKAGE_ROOT, 'src', 'claude', 'hooks');
81
80
  for (const hook of [
82
81
  'spec-gate.cjs',
83
- 'completion-authority-check.cjs',
84
- 'completion-authority-state.cjs',
85
- 'semantic-review-authority.cjs',
86
82
  ]) {
87
83
  const target = path.join(destinationRoot, 'hooks', hook);
88
84
  fs.mkdirSync(path.dirname(target), { recursive: true });
@@ -759,10 +755,7 @@ test('CLI exposes a derived lane without making it primary Develop authority', (
759
755
  const regions = markdownLegacyRegions(develop);
760
756
  assert.doesNotMatch(develop, /DO NOT write implementation code until an approved spec exists/i);
761
757
  assert.doesNotMatch(regions.primary, /planning_depth|assurance_level|execution_tier|\blane\b/i);
762
- assert.match(regions.legacy, /planning_depth/);
763
- assert.match(regions.legacy, /assurance_level/);
764
- assert.match(regions.legacy, /execution_tier/);
765
- assert.match(regions.legacy, /derived lane/i);
758
+ assert.doesNotMatch(develop, /## Legacy workflow compatibility|Also supports existing legacy Specs packets/);
766
759
  assert.match(regions.primary, /specs\/<feature>\/plan\.md[\s\S]*task-NN-\*\.md/i);
767
760
  assert.match(regions.primary, /one unblocked task at a time/i);
768
761
  assert.match(regions.primary, /Verification Plan/);
@@ -789,66 +782,63 @@ test('Specs primary output is a flat process-first packet with isolated legacy c
789
782
  const specs = read(SPECS);
790
783
  const regions = markdownLegacyRegions(specs);
791
784
  const legacyHeadings = regions.headings.filter(({ text }) => /legacy/i.test(text));
792
- assert.equal(legacyHeadings.length, 1);
785
+ assert.equal(legacyHeadings.length, 0);
793
786
  assert.match(regions.primary, /specs\/<feature>\/[\s\S]*plan\.md[\s\S]*task-01-<slug>\.md[\s\S]*task-02-<slug>\.md/i);
794
787
  assert.match(regions.primary, /Task files are flat beside `plan\.md`/i);
795
788
  assert.match(regions.primary, /one task at a time/i);
796
789
  assert.match(regions.primary, /inline `## Receipt`/i);
797
- assert.match(regions.primary, /C1 — Scope[\s\S]*C2 — Findings[\s\S]*C3 — Done/i);
798
- assert.match(regions.legacy, /spec\.json/i);
799
- assert.match(regions.legacy, /never requires the legacy kernel/i);
790
+ assert.match(regions.primary, /GATE-SCOPE[\s\S]*GATE-REVIEW[\s\S]*GATE-DONE/i);
791
+ assert.doesNotMatch(specs, /installed legacy adapters?/i);
800
792
  assertVocabularyIsLegacyOnly(SPECS, regions);
801
793
  });
802
794
 
803
- test('core execution agents default to process-first state and isolate legacy packets', () => {
795
+ test('core execution agents default to process-first state and carry no legacy adapter', () => {
804
796
  for (const filePath of PROCESS_FIRST_AGENTS) {
805
797
  const content = read(filePath);
806
798
  const name = path.basename(filePath);
807
799
  assert.match(content, /plan\.md/i, `${name} must read the process-first plan`);
808
800
  assert.match(content, /task-NN-\*\.md|task-NN-<slug>\.md/i, `${name} must understand flat tasks`);
809
- assert.match(content, /legacy/i, `${name} must isolate legacy compatibility`);
801
+ assert.doesNotMatch(content, /legacy|spec\.json|scope_lock/i, `${name} must not teach a legacy adapter`);
810
802
  }
811
803
 
812
804
  const implementer = read(PROCESS_FIRST_AGENTS[0]);
813
805
  assert.match(implementer, /Under every dispatch mode[\s\S]{0,160}do NOT edit[\s\S]{0,80}`plan\.md`[\s\S]{0,80}`Status:`[\s\S]{0,80}`## Receipt`/i);
814
806
  assert.match(implementer, /controller/i);
815
- assert.match(implementer, /process-first work[\s\S]{0,100}Ownership[\s\S]{0,100}`Related Files` only for a valid legacy adapter/i);
807
+ assert.match(implementer, /process-first work[\s\S]{0,100}Ownership\s+boundary\./i);
816
808
 
817
809
  const auditor = read(PROCESS_FIRST_AGENTS[2]);
818
- assert.match(auditor, /accepted C1\/C2 decisions/i);
819
- assert.match(auditor, /Only for a valid legacy adapter[\s\S]*`Related Files`[\s\S]*`## Evidence`/i);
820
- assert.match(auditor, /Never require those legacy artifacts from a[\s\S]*process-first packet/i);
821
- assert.match(auditor, /attestation belongs only to the valid legacy/i);
810
+ assert.match(auditor, /accepted GATE-SCOPE\/GATE-REVIEW decisions/i);
811
+ assert.doesNotMatch(auditor, /CAFEKIT_SEMANTIC_REVIEW_ATTESTATION/);
822
812
 
823
813
  const docsKeeper = read(PROCESS_FIRST_AGENTS[3]);
824
814
  assert.match(docsKeeper, /never[\s\S]{0,80}invent or write Status, Receipt, approval, or execution proof/i);
825
815
 
826
816
  const deployer = read(PROCESS_FIRST_AGENTS[4]);
827
817
  assert.match(deployer, /current final inline Receipt/i);
828
- assert.match(deployer, /C3\/release authorization/i);
818
+ assert.match(deployer, /GATE-DONE\/release authorization/i);
829
819
  assert.match(deployer, /does not write process-first Status\/Receipt/i);
830
820
  });
831
821
 
832
822
  test('R7 Develop and Sync surfaces teach process-v3 and isolate hierarchical Legacy sections', () => {
833
823
  const surfaces = [
834
- ['develop', DEVELOP],
835
- ['parallel waves', PARALLEL_WAVES],
836
- ['sync', SYNC_SKILL],
837
- ['sync protocols', SYNC_PROTOCOLS],
824
+ ['develop', DEVELOP, 0],
825
+ ['parallel waves', PARALLEL_WAVES, 0],
826
+ ['sync', SYNC_SKILL, 0],
827
+ ['sync protocols', SYNC_PROTOCOLS, 0],
838
828
  ];
839
829
  const regionsByName = new Map();
840
- let legacyCorpus = '';
841
830
 
842
- for (const [name, filePath] of surfaces) {
831
+ for (const [name, filePath, expectedLegacy] of surfaces) {
843
832
  const regions = markdownLegacyRegions(read(filePath));
844
833
  const legacyHeadings = regions.headings.filter(({ text }) => /legacy/i.test(text));
845
- assert.equal(legacyHeadings.length, 1, `${name} must have exactly one Legacy heading`);
834
+ assert.equal(legacyHeadings.length, expectedLegacy, `${name} must have exactly ${expectedLegacy} Legacy heading(s)`);
846
835
  assert.match(regions.primary, /inline (?:`## )?Receipts?/i, `${name} must teach inline Receipts`);
847
- assert.match(regions.legacy, /spec\.json/i, `${name} must retain the spec.json adapter`);
848
- assert.match(regions.legacy, /task_registry/i, `${name} must retain task_registry compatibility`);
836
+ if (expectedLegacy === 1) {
837
+ assert.match(regions.legacy, /spec\.json/i, `${name} must retain the spec.json adapter`);
838
+ assert.match(regions.legacy, /task_registry/i, `${name} must retain task_registry compatibility`);
839
+ }
849
840
  assertVocabularyIsLegacyOnly(filePath, regions);
850
841
  regionsByName.set(name, regions);
851
- legacyCorpus += `\n${regions.legacy}`;
852
842
  }
853
843
 
854
844
  const develop = regionsByName.get('develop').primary;
@@ -874,7 +864,7 @@ test('R7 Develop and Sync surfaces teach process-v3 and isolate hierarchical Leg
874
864
  assert.match(protocols, /`plan\.md` and flat `task-\*\.md` files/i);
875
865
  assert.match(protocols, /acceptance IDs/i);
876
866
  assert.match(protocols, /current inline Receipt/i);
877
- assert.match(legacyCorpus, /sync-finalize/i);
867
+ assert.match(regionsByName.get('sync').primary, /sync-finalize/i);
878
868
  });
879
869
 
880
870
  test('Develop process-first source contract preserves selection, recovery, final-Head, parallel, and Flash boundaries', () => {
@@ -1293,7 +1283,7 @@ test('auto authoring reaches readiness only after promotion, validation, groundi
1293
1283
  assert.ok(executableBlocks.every((block) => !/cf:?develop/i.test(block)), 'Specs must never execute Develop');
1294
1284
  assert.match(specs, /An implementation workflow owns execution/i);
1295
1285
  assert.match(specs, /selects one unblocked task[\s\S]*runs\s+the task's verification[\s\S]*inline `## Receipt`/i);
1296
- assert.match(specs, /The user decides\s+at C3 whether the feature is done/i);
1286
+ assert.match(specs, /The user decides\s+at GATE-DONE whether the feature is done/i);
1297
1287
  } finally {
1298
1288
  fs.rmSync(fixture.root, { recursive: true, force: true });
1299
1289
  }
@@ -1369,41 +1359,20 @@ test('atomic readiness preserves exact bytes on review, grounding, decision, and
1369
1359
  blocked.workflow_policy = POLICY.canonicalWorkflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Strict' });
1370
1360
  fs.writeFileSync(fixture.specPath, `${JSON.stringify(blocked, null, 2)}\n`);
1371
1361
  const strictBytes = fs.readFileSync(fixture.specPath);
1372
- assert.throws(() => READINESS.finalizeReadiness({ specDir: fixture.specDir, projectRoot: fixture.root, reviewResult: ordinaryReview() }), /Strict readiness/);
1362
+ assert.throws(() => READINESS.finalizeReadiness({ specDir: fixture.specDir, projectRoot: fixture.root, reviewResult: ordinaryReview() }), /Strict assurance is no longer supported/);
1373
1363
  assert.equal(fs.readFileSync(fixture.specPath).equals(strictBytes), true);
1374
1364
  } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
1375
1365
  });
1376
1366
 
1377
- test('Strict atomic readiness succeeds only after current allowlisted host observation', () => {
1367
+ test('Strict readiness is refused as no longer supported', () => {
1378
1368
  const fixture = createOrdinaryAuthoringFixture();
1379
1369
  try {
1380
1370
  const spec = JSON.parse(fs.readFileSync(fixture.specPath, 'utf8'));
1381
1371
  spec.workflow_policy = POLICY.canonicalWorkflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Strict' });
1382
1372
  fs.writeFileSync(fixture.specPath, `${JSON.stringify(spec, null, 2)}\n`);
1383
1373
  const before = fs.readFileSync(fixture.specPath);
1384
- assert.throws(() => READINESS.finalizeReadiness({ specDir: fixture.specDir, projectRoot: fixture.root, reviewResult: ordinaryReview() }), /Strict readiness/);
1374
+ assert.throws(() => READINESS.finalizeReadiness({ specDir: fixture.specDir, projectRoot: fixture.root, reviewResult: ordinaryReview() }), /Strict assurance is no longer supported/);
1385
1375
  assert.equal(fs.readFileSync(fixture.specPath).equals(before), true);
1386
-
1387
- const digest = VALIDATOR.computeSemanticDigest21(fixture.specDir, spec);
1388
- assert.equal(digest.errors.length, 0, digest.errors.join('\n'));
1389
- const authorityHome = path.join(fixture.root, 'authority-home');
1390
- const claim = `CAFEKIT_SEMANTIC_REVIEW_ATTESTATION ${JSON.stringify({ feature_name: 'ordinary', spec_file: 'specs/ordinary/spec.json', semantic_digest: digest.digest, verdict: 'PASS' })}`;
1391
- const observed = spawnSync(process.execPath, [path.join(PACKAGE_ROOT, 'src/claude/hooks/semantic-review-authority.cjs')], {
1392
- cwd: fixture.root,
1393
- env: { ...process.env, HOME: authorityHome, USERPROFILE: authorityHome, PROJECT_ROOT: fixture.root },
1394
- input: JSON.stringify({ hook_event_name: 'SubagentStop', session_id: 'host-session', agent_id: 'reviewer-1', agent_type: 'code_auditor', last_assistant_message: claim, cwd: fixture.root }),
1395
- encoding: 'utf8',
1396
- });
1397
- assert.equal(observed.status, 0, observed.stderr);
1398
- const reviewFile = path.join(fixture.root, 'review.json');
1399
- fs.writeFileSync(reviewFile, `${JSON.stringify(ordinaryReview())}\n`);
1400
- const finalized = spawnSync(process.execPath, [path.join(PACKAGE_ROOT, 'src/claude/scripts/spec-readiness.cjs'), fixture.specDir, '--review-result', reviewFile], {
1401
- cwd: fixture.root,
1402
- env: { ...process.env, HOME: authorityHome, USERPROFILE: authorityHome, PROJECT_ROOT: fixture.root },
1403
- encoding: 'utf8',
1404
- });
1405
- assert.equal(finalized.status, 0, `${finalized.stdout}\n${finalized.stderr}`);
1406
- assert.equal(JSON.parse(fs.readFileSync(fixture.specPath, 'utf8')).ready_for_implementation, true);
1407
1376
  } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); }
1408
1377
  });
1409
1378
 
@@ -1423,7 +1392,7 @@ test('done is a final-state request bound to current semantic model and digest',
1423
1392
  const dependencies = () => ({
1424
1393
  validator: VALIDATOR,
1425
1394
  grounder: GROUNDER,
1426
- semanticAuthority: SEMANTIC_AUTHORITY,
1395
+ semanticAuthority: {},
1427
1396
  });
1428
1397
  const current = FINAL_STATE.validateCanonicalFinalState({
1429
1398
  policy: POLICY, projectRoot: fixture.root, candidate, dependencies,
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ // docs-sync printed "Docs sync needed" at every Claude SessionStart, and nothing ever
4
+ // cleared it. It now runs only when the docs skill asks for it. The skill text is shared
5
+ // with Codex, whose copy crashes silently on an empty stdin, so the command feeds `{}`.
6
+ // Each case names the failure it prevents.
7
+
8
+ const assert = require('node:assert/strict');
9
+ const fs = require('node:fs');
10
+ const os = require('node:os');
11
+ const path = require('node:path');
12
+ const { spawnSync, execSync } = require('node:child_process');
13
+ const { test } = require('node:test');
14
+
15
+ const PACKAGE_ROOT = path.join(__dirname, '..', '..');
16
+ const INSTALLER = path.join(PACKAGE_ROOT, 'bin/install.js');
17
+ const DOCS_SKILL = path.join(PACKAGE_ROOT, 'src/claude/skills/docs');
18
+ const ON_DEMAND = "echo '{}' | node .claude/hooks/docs-sync.cjs";
19
+
20
+ function inTempProject(run) {
21
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-docs-sync-'));
22
+ try {
23
+ const init = spawnSync('git', ['-C', root, 'init', '-q'], { encoding: 'utf8' });
24
+ assert.equal(init.status, 0, init.stderr);
25
+ return run(root);
26
+ } finally {
27
+ fs.rmSync(root, { recursive: true, force: true });
28
+ }
29
+ }
30
+
31
+ function install(root, platforms) {
32
+ const result = spawnSync(process.execPath, [INSTALLER, '--platform', platforms.join(','), '--yes'],
33
+ { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
34
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
35
+ }
36
+
37
+ function commands(settings, event) {
38
+ return (settings.hooks[event] || []).flatMap((group) => (group.hooks || []).map((hook) => hook.command));
39
+ }
40
+
41
+ /** A git project with code, docs and a `.sync_hash` that no longer matches the source. */
42
+ function makeStale(root) {
43
+ fs.writeFileSync(path.join(root, 'package.json'), '{"name":"fixture"}\n');
44
+ fs.mkdirSync(path.join(root, 'docs'), { recursive: true });
45
+ fs.writeFileSync(path.join(root, 'docs', 'README.md'), '# Docs\n');
46
+ fs.writeFileSync(path.join(root, 'docs', '.sync_hash'), '0000000000000000000000000000000000000000\n');
47
+ execSync('git -c user.email=t@t -c user.name=t add -A && git -c user.email=t@t -c user.name=t commit -qm fixture', { cwd: root });
48
+ }
49
+
50
+ test('fresh claude install runs no docs-sync at SessionStart', () => {
51
+ inTempProject((root) => {
52
+ install(root, ['claude']);
53
+ const settings = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'settings.json'), 'utf8'));
54
+ assert.equal(commands(settings, 'SessionStart').some((c) => c.includes('docs-sync.cjs')), false);
55
+ assert.equal(fs.existsSync(path.join(root, '.claude', 'hooks', 'docs-sync.cjs')), true, 'the hook stays installed for on-demand use');
56
+ });
57
+ });
58
+
59
+ test('upgrade drops the SessionStart docs-sync entry', () => {
60
+ inTempProject((root) => {
61
+ install(root, ['claude']);
62
+ const settingsPath = path.join(root, '.claude', 'settings.json');
63
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
64
+ const kept = commands(settings, 'SessionStart');
65
+ settings.hooks.SessionStart[0].hooks.push({ type: 'command', command: 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/docs-sync.cjs"' });
66
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
67
+ install(root, ['claude']);
68
+ const after = commands(JSON.parse(fs.readFileSync(settingsPath, 'utf8')), 'SessionStart');
69
+ assert.equal(after.some((c) => c.includes('docs-sync.cjs')), false);
70
+ for (const command of kept) assert.ok(after.includes(command), `kept: ${command}`);
71
+ });
72
+ });
73
+
74
+ test('the docs skill runs docs-sync on demand', () => {
75
+ for (const rel of ['SKILL.md', 'references/update-workflow.md']) {
76
+ assert.ok(fs.readFileSync(path.join(DOCS_SKILL, rel), 'utf8').includes(ON_DEMAND), `${rel} names the on-demand command`);
77
+ }
78
+ const files = fs.readdirSync(DOCS_SKILL, { recursive: true }).filter((f) => f.endsWith('.md'));
79
+ for (const rel of files) {
80
+ assert.doesNotMatch(fs.readFileSync(path.join(DOCS_SKILL, rel), 'utf8'), /SessionStart docs-sync/, rel);
81
+ }
82
+ });
83
+
84
+ for (const [runtime, folder] of [['claude', '.claude'], ['codex', '.codex']]) {
85
+ test(`the ${runtime === 'claude' ? 'Claude' : 'Codex'} copy prints its signal on demand`, () => {
86
+ inTempProject((root) => {
87
+ install(root, [runtime]);
88
+ makeStale(root);
89
+ const out = execSync(`echo '{}' | node ${folder}/hooks/docs-sync.cjs`, { cwd: root, encoding: 'utf8' });
90
+ assert.match(out, /Docs sync needed/);
91
+ });
92
+ });
93
+ }
@@ -134,36 +134,6 @@ test('a broad glob is denied with its full reason in JSON', () => {
134
134
  });
135
135
  });
136
136
 
137
- test('a nested task write is denied under the grok write tool', () => {
138
- withInstall((root) => {
139
- const target = path.join(root, 'specs', 'demo', 'tasks', 'task-R0-01-x.md');
140
- fs.mkdirSync(path.dirname(target), { recursive: true });
141
- // grok creates files with `write`; `search_replace` edits an existing one. The guard
142
- // fires on Write only, so the alias has to keep those two apart.
143
- const r = runHook(root, 'task-scaffold-guard.cjs', {
144
- hookEventName: 'pre_tool_use',
145
- cwd: root,
146
- toolName: 'write',
147
- toolInput: { path: target, content: '# x' },
148
- });
149
- const { decision, reason } = decisionOf(r.stdout);
150
- assert.equal(decision, 'deny');
151
- assert.match(reason, /TASK SCAFFOLD REQUIRED/);
152
- assert.match(reason, /spec-scaffold\.cjs/, 'the command the model must run has to reach it');
153
- assert.equal(r.code, 2);
154
-
155
- // The escape route the message names must stay open: an edit is not a create.
156
- const edit = runHook(root, 'task-scaffold-guard.cjs', {
157
- hookEventName: 'pre_tool_use',
158
- cwd: root,
159
- toolName: 'search_replace',
160
- toolInput: { path: target, old_string: 'a', new_string: 'b' },
161
- });
162
- assert.equal(decisionOf(edit.stdout).decision, null, 'search_replace must not be treated as a create');
163
- assert.equal(edit.code, 0);
164
- });
165
- });
166
-
167
137
  /** A packet the Stop gate will block: one done task with no receipt. */
168
138
  function unprovenPacket(root) {
169
139
  const feature = path.join(root, 'specs', 'demo');
@@ -206,32 +176,6 @@ test('the Stop gate still blocks an unproven done task', () => {
206
176
  }, { git: true });
207
177
  });
208
178
 
209
- test('the approval hook takes its approve path on a grok prompt', () => {
210
- withInstall((root) => {
211
- // Registered without an argv mode, this hook picks its mode from the event name, and
212
- // approve() returns immediately unless that name is exactly 'UserPromptSubmit'. An
213
- // approval phrase with no pending request is therefore the oracle: only the approve
214
- // path can print a rejection line, so the line proves the event name was translated.
215
- // Under an unreadable envelope the mode fell back to the stop path on every prompt.
216
- const nonce = 'a'.repeat(24);
217
- const r = runHook(root, 'completion-authority.cjs', {
218
- hookEventName: 'user_prompt_submit',
219
- sessionId: 'grok-1',
220
- cwd: root,
221
- prompt: `APPROVE CAFEKIT COMPLETION ${nonce}`,
222
- });
223
- assert.match(r.stdout, /CafeKit completion approval rejected/,
224
- 'the approve path must run and refuse an approval that matches no pending request');
225
- assert.equal(decisionOf(r.stdout).decision, null, 'and it must not block the prompt');
226
-
227
- // An ordinary prompt stays silent on both counts.
228
- const plain = runHook(root, 'completion-authority.cjs', {
229
- hookEventName: 'user_prompt_submit', sessionId: 'grok-1', cwd: root, prompt: 'please continue',
230
- });
231
- assert.equal(plain.stdout.trim(), '', 'an ordinary prompt produces nothing');
232
- }, { git: true });
233
- });
234
-
235
179
  test('the secret guardrail sees a grok prompt', () => {
236
180
  withInstall((root) => {
237
181
  // grok's spelling for the prompt field is unverified, so the reader accepts the Claude
@@ -49,8 +49,8 @@ test('read_file path becomes file_path', () => {
49
49
  toolInput: { path: '/p/.env' },
50
50
  });
51
51
  assert.equal(out.tool_name, 'Read');
52
- // privacy-block reads file_path and path, but task-scaffold-guard reads file_path first
53
- // and inspect-block's path list is keyed the same way.
52
+ // privacy-block and inspect-block both read file_path first, so the normalized key
53
+ // must be file_path.
54
54
  assert.equal(out.tool_input.file_path, '/p/.env');
55
55
  assert.equal(out.tool_input.path, '/p/.env', 'the original key stays for hooks that read it');
56
56
  });
@@ -83,7 +83,6 @@ test('Stop and subagent fields map', () => {
83
83
  subagentId: 'agent-7',
84
84
  lastAssistantMessage: 'REVIEW: PASS',
85
85
  });
86
- // semantic-review-authority refuses to record an attestation without agent_id.
87
86
  assert.equal(sub.hook_event_name, 'SubagentStop');
88
87
  assert.equal(sub.agent_type, 'code-auditor');
89
88
  assert.equal(sub.agent_id, 'agent-7');
@@ -102,9 +101,8 @@ test('every name reaching a Bash matcher becomes Bash', () => {
102
101
  });
103
102
 
104
103
  test('write and search_replace map to different Claude tools', () => {
105
- // The scaffold guard only fires on Write. Grok creates files with `write` and edits
106
- // them with `search_replace`; conflating them would either disable the guard or make it
107
- // reject the Edit-fill step the guard's own message tells the model to run.
104
+ // Grok creates files with `write` and edits them with `search_replace`; they must stay
105
+ // distinct Claude tools so a hook matcher on Write or on Edit sees the event it names.
108
106
  assert.equal(normalizeToolName('write'), 'Write');
109
107
  assert.equal(normalizeToolName('delete_file'), 'Write');
110
108
  assert.equal(normalizeToolName('search_replace'), 'Edit');
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ // The Stop gates and the session hooks must not depend on the completion-authority hook files,
4
+ // so those files can be deleted without blocking every Stop or crashing SessionStart. Each case
5
+ // runs against a real install (bin/install.js), with and without the five legacy files.
6
+
7
+ const assert = require('node:assert/strict');
8
+ const fs = require('node:fs');
9
+ const os = require('node:os');
10
+ const path = require('node:path');
11
+ const { spawnSync } = require('node:child_process');
12
+ const { test } = require('node:test');
13
+
14
+ const INSTALLER = path.join(__dirname, '..', 'install.js');
15
+ const LEGACY_FILES = [
16
+ 'completion-authority.cjs',
17
+ 'completion-authority-check.cjs',
18
+ 'completion-authority-state.cjs',
19
+ 'semantic-review-authority.cjs',
20
+ 'task-scaffold-guard.cjs',
21
+ ];
22
+ const MIGRATION = /Legacy spec\.json closeout is no longer supported/;
23
+
24
+ function inTempProject(run) {
25
+ const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-decoupled-')));
26
+ try {
27
+ for (const args of [['init', '-q'], ['config', 'user.email', 'test@example.invalid'], ['config', 'user.name', 'Test'], ['commit', '--allow-empty', '-qm', 'base']]) {
28
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
29
+ assert.equal(result.status, 0, result.stderr);
30
+ }
31
+ const installed = spawnSync(process.execPath, [INSTALLER, '--platform', 'claude,codex', '--yes'],
32
+ { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
33
+ assert.equal(installed.status, 0, `${installed.stdout}\n${installed.stderr}`);
34
+ return run(root);
35
+ } finally {
36
+ fs.rmSync(root, { recursive: true, force: true });
37
+ }
38
+ }
39
+
40
+ function removeLegacyFiles(root) {
41
+ for (const runtime of ['.claude', '.codex']) {
42
+ for (const file of LEGACY_FILES) {
43
+ const target = path.join(root, runtime, 'hooks', file);
44
+ fs.rmSync(target, { force: true });
45
+ }
46
+ }
47
+ }
48
+
49
+ function runHook(root, kind, script, event, extra = {}) {
50
+ const hook = path.join(root, kind === 'claude' ? '.claude' : '.codex', 'hooks', script);
51
+ const result = spawnSync(process.execPath, [hook], {
52
+ cwd: root,
53
+ input: JSON.stringify({ hook_event_name: event, session_id: 's', stop_hook_active: false, cwd: root, ...extra }),
54
+ env: { ...process.env, PATH: '/usr/bin:/bin', ...(kind === 'claude' ? { CLAUDE_PROJECT_DIR: root } : {}) },
55
+ encoding: 'utf8',
56
+ });
57
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
58
+ return result.stdout.trim();
59
+ }
60
+
61
+ function stopGate(root, kind) {
62
+ return runHook(root, kind, 'spec-gate.cjs', 'Stop');
63
+ }
64
+
65
+ function isBlocked(stdout) {
66
+ return stdout !== '' && JSON.parse(stdout.split('\n').pop()).decision === 'block';
67
+ }
68
+
69
+ function blockReason(stdout) {
70
+ assert.equal(isBlocked(stdout), true, `expected a block, got: ${stdout}`);
71
+ return JSON.parse(stdout.split('\n').pop()).reason;
72
+ }
73
+
74
+ function provenance(root, taskFile) {
75
+ const result = spawnSync(process.execPath, [
76
+ path.join(root, '.claude', 'scripts', 'provenance.cjs'),
77
+ '--project-root', root, '--specs-root', 'specs', '--spec-file', taskFile,
78
+ '--feature-name', 'demo', '--session', 's', '--json',
79
+ ], { cwd: root, encoding: 'utf8' });
80
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
81
+ const parsed = JSON.parse(result.stdout);
82
+ return { base: parsed.Base, head: parsed.Head };
83
+ }
84
+
85
+ function writeProcessFirst(root, { receipt }) {
86
+ const featureDir = path.join(root, 'specs', 'demo');
87
+ fs.mkdirSync(featureDir, { recursive: true });
88
+ fs.writeFileSync(path.join(featureDir, 'plan.md'), '# Demo plan\n');
89
+ const taskFile = path.join(featureDir, 'task-01-demo.md');
90
+ const body = ['# Task 01: demo', '', 'Status: done', '', '## Dependencies', '', '- none', '', '## Verification Plan', '', '- Command: node --test', ''];
91
+ fs.writeFileSync(taskFile, body.join('\n'));
92
+ if (!receipt) return;
93
+ const { base, head } = provenance(root, taskFile);
94
+ fs.writeFileSync(taskFile, [...body, '## Receipt', '', 'Verification: PASS', 'Command: node --test', 'Exit: 0',
95
+ `Base: ${base}`, `Head: ${head}`, '```text', '$ node --test', 'pass: 1', '```', ''].join('\n'));
96
+ }
97
+
98
+ // A 2.1 packet keeps its machine state in spec.json and its tasks in a nested tasks/ directory.
99
+ function writeLegacy(root, { status, doneTask = false }) {
100
+ const featureDir = path.join(root, 'specs', 'legacy');
101
+ fs.mkdirSync(path.join(featureDir, 'tasks'), { recursive: true });
102
+ const task = 'tasks/task-R1-01-legacy.md';
103
+ const spec = { schema_version: '2.1', feature_name: 'legacy', status, current_phase: 'implementation', task_registry: {} };
104
+ if (doneTask) spec.task_registry[task] = { status: 'done', completed_at: '2026-08-13T00:00:00.000Z', dependencies: [] };
105
+ fs.writeFileSync(path.join(featureDir, 'spec.json'), JSON.stringify(spec));
106
+ if (doneTask) {
107
+ fs.writeFileSync(path.join(featureDir, task), ['# Task R1-01: legacy', '**Status:** done', '## Outcome', 'Legacy work.',
108
+ '## Verification Plan', '- **Command:** `node --test`', '- **Expected:** passes', ''].join('\n'));
109
+ }
110
+ }
111
+
112
+ function checkProcessFirst(kind) {
113
+ inTempProject((root) => {
114
+ writeProcessFirst(root, { receipt: true });
115
+ assert.equal(stopGate(root, kind), '', 'valid receipt passes with the legacy files');
116
+ writeProcessFirst(root, { receipt: false });
117
+ assert.match(blockReason(stopGate(root, kind)), /missing_receipt/, 'no receipt blocks with the legacy files');
118
+ removeLegacyFiles(root);
119
+ assert.match(blockReason(stopGate(root, kind)), /missing_receipt/, 'no receipt blocks without the legacy files');
120
+ writeProcessFirst(root, { receipt: true });
121
+ assert.equal(stopGate(root, kind), '', 'valid receipt passes without the legacy files');
122
+ });
123
+ }
124
+
125
+ function checkLegacyCloseout(kind) {
126
+ for (const status of ['done', 'completed']) {
127
+ inTempProject((root) => {
128
+ writeLegacy(root, { status });
129
+ removeLegacyFiles(root);
130
+ assert.match(blockReason(stopGate(root, kind)), MIGRATION, `${kind} ${status}`);
131
+ });
132
+ }
133
+ }
134
+
135
+ function checkLegacyMidExecution(kind) {
136
+ inTempProject((root) => {
137
+ writeLegacy(root, { status: 'in_progress', doneTask: true });
138
+ removeLegacyFiles(root);
139
+ const reason = blockReason(stopGate(root, kind));
140
+ assert.doesNotMatch(reason, MIGRATION);
141
+ assert.match(reason, /task-R1-01-legacy\.md/);
142
+ });
143
+ }
144
+
145
+ function checkSession(kind) {
146
+ inTempProject((root) => {
147
+ removeLegacyFiles(root);
148
+ runHook(root, kind, 'session.cjs', 'SessionStart', { source: 'startup' });
149
+ });
150
+ }
151
+
152
+ test('claude gate passes and blocks process-first with and without the legacy files', () => checkProcessFirst('claude'));
153
+ test('codex gate passes and blocks process-first with and without the legacy files', () => checkProcessFirst('codex'));
154
+ test('claude gate blocks legacy 2.1 closeout with the migration message', () => checkLegacyCloseout('claude'));
155
+ test('codex gate blocks legacy 2.1 closeout with the migration message', () => checkLegacyCloseout('codex'));
156
+ test('claude gate still receipt-checks a legacy 2.1 packet mid-execution', () => checkLegacyMidExecution('claude'));
157
+ test('codex gate still receipt-checks a legacy 2.1 packet mid-execution', () => checkLegacyMidExecution('codex'));
158
+ test('claude session starts without the legacy files', () => checkSession('claude'));
159
+ test('codex session starts without the legacy files', () => checkSession('codex'));