@haposoft/cafekit 0.16.0-rc.8 → 0.16.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 (297) hide show
  1. package/README.md +116 -27
  2. package/bin/__tests__/codex-hooks-ownership.test.js +239 -0
  3. package/bin/__tests__/codex-hooks.test.js +50 -0
  4. package/bin/__tests__/codex-native.test.js +1882 -70
  5. package/bin/__tests__/develop-contract.test.js +400 -23
  6. package/bin/__tests__/installer-safety.test.js +3 -3
  7. package/bin/__tests__/optional-skill-inventory.test.js +249 -0
  8. package/bin/__tests__/output-styles.test.js +214 -0
  9. package/bin/__tests__/package-inventory.test.js +1735 -98
  10. package/bin/__tests__/runtime-schema.test.js +111 -0
  11. package/bin/__tests__/secret-output-guardrail.test.js +122 -0
  12. package/bin/__tests__/skill-routing-source.test.js +172 -0
  13. package/bin/__tests__/specs-v2-execution-closeout.test.js +1 -1
  14. package/bin/install.js +10 -0
  15. package/bin/lib/codex-install.js +170 -5
  16. package/bin/lib/context.js +14 -0
  17. package/bin/lib/i18n.js +12 -6
  18. package/bin/lib/skill-deps.js +5 -5
  19. package/bin/phases/claude-runtime.js +23 -0
  20. package/bin/phases/claude-settings.js +8 -0
  21. package/bin/phases/codex-hooks.js +156 -0
  22. package/bin/phases/codex-runtime.js +29 -38
  23. package/bin/phases/copy-payload.js +8 -2
  24. package/bin/phases/select-skill-bundles.js +96 -0
  25. package/bin/phases/skill-inventory.js +71 -0
  26. package/bin/phases/skills-setup.js +9 -2
  27. package/bin/phases/write-metadata.js +8 -5
  28. package/package.json +8 -2
  29. package/src/claude/CLAUDE.md +3 -3
  30. package/src/claude/agents/brainstormer.md +52 -69
  31. package/src/claude/agents/code-auditor.md +32 -14
  32. package/src/claude/agents/debugger.md +35 -20
  33. package/src/claude/agents/deployer.md +12 -5
  34. package/src/claude/agents/docs-keeper.md +11 -1
  35. package/src/claude/agents/implementer.md +25 -13
  36. package/src/claude/agents/inspector.md +6 -3
  37. package/src/claude/agents/project-manager.md +1 -1
  38. package/src/claude/agents/researcher.md +26 -55
  39. package/src/claude/agents/spec-maker.md +12 -7
  40. package/src/claude/agents/strategist.md +52 -0
  41. package/src/claude/agents/test-runner.md +117 -132
  42. package/src/claude/agents/ui-ux-designer.md +1 -1
  43. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -0
  44. package/src/claude/hooks/__tests__/precompact.test.js +158 -0
  45. package/src/claude/hooks/__tests__/spec-gate.test.js +10 -3
  46. package/src/claude/hooks/__tests__/state.test.js +5 -0
  47. package/src/claude/hooks/__tests__/statusline.test.js +196 -0
  48. package/src/claude/hooks/agent.cjs +1 -1
  49. package/src/claude/hooks/docs-sync.cjs +12 -10
  50. package/src/claude/hooks/inspect-block.cjs +1 -1
  51. package/src/claude/hooks/lib/config.cjs +3 -0
  52. package/src/claude/hooks/lib/hook-state-dir.cjs +39 -0
  53. package/src/claude/hooks/lib/secret-keywords.cjs +42 -0
  54. package/src/claude/hooks/precompact.cjs +105 -0
  55. package/src/claude/hooks/rules.cjs +1 -1
  56. package/src/claude/hooks/secret-output-guardrail.cjs +43 -0
  57. package/src/claude/hooks/session.cjs +27 -1
  58. package/src/claude/hooks/spec-gate.cjs +3 -3
  59. package/src/claude/hooks/spec-state.cjs +2 -2
  60. package/src/claude/hooks/state.cjs +1 -1
  61. package/src/claude/hooks/task-scaffold-guard.cjs +1 -1
  62. package/src/claude/hooks/usage.cjs +1 -1
  63. package/src/claude/migration-manifest.json +29 -15
  64. package/src/claude/output-styles/coding-level-0-eli5.md +103 -0
  65. package/src/claude/output-styles/coding-level-1-junior.md +124 -0
  66. package/src/claude/output-styles/coding-level-2-mid.md +146 -0
  67. package/src/claude/output-styles/coding-level-3-senior.md +148 -0
  68. package/src/claude/output-styles/coding-level-4-lead.md +159 -0
  69. package/src/claude/output-styles/coding-level-5-god.md +91 -0
  70. package/src/claude/references/debugger/core-philosophy.md +5 -3
  71. package/src/claude/references/debugger/frontend-verification.md +2 -2
  72. package/src/claude/references/debugger/log-ci-analysis.md +13 -1
  73. package/src/claude/references/debugger/parallel-agent-hydration.md +19 -34
  74. package/src/claude/references/debugger/performance-diagnostics.md +2 -2
  75. package/src/claude/references/debugger/root-cause-tracing.md +37 -6
  76. package/src/claude/references/debugger/side-effect-gate.md +3 -1
  77. package/src/claude/references/debugger/verification-protocol.md +13 -12
  78. package/src/claude/rules/orchestrator.md +7 -1
  79. package/src/claude/rules/process-management.md +39 -0
  80. package/src/claude/rules/review-audit-self-decision.md +48 -0
  81. package/src/claude/rules/skill-domain-routing.md +26 -24
  82. package/src/claude/rules/skill-workflow-routing.md +22 -14
  83. package/src/claude/rules/workflow.md +3 -3
  84. package/src/claude/runtime.json +1 -0
  85. package/src/claude/runtime.schema.json +205 -0
  86. package/src/claude/scripts/generate-skill-catalog.cjs +156 -122
  87. package/src/claude/scripts/provenance.cjs +18 -5
  88. package/src/claude/scripts/workflow-policy.cjs +1 -1
  89. package/src/claude/settings/settings.json +16 -0
  90. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  91. package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
  92. package/src/claude/skills/brainstorm/SKILL.md +193 -172
  93. package/src/claude/skills/brainstorm/references/question-framework.md +105 -167
  94. package/src/claude/skills/chrome-devtools/SKILL.md +1 -1
  95. package/src/claude/skills/code-review/SKILL.md +43 -25
  96. package/src/claude/skills/code-review/references/spec-compliance-review.md +3 -4
  97. package/src/claude/skills/code-review/references/verification-gate.md +1 -1
  98. package/src/claude/skills/debug/SKILL.md +48 -19
  99. package/src/claude/skills/delegate/SKILL.md +1 -1
  100. package/src/claude/skills/develop/SKILL.md +92 -65
  101. package/src/claude/skills/develop/references/parallel-waves.md +36 -63
  102. package/src/claude/skills/develop/references/quality-gate.md +31 -4
  103. package/src/claude/skills/develop/references/subagent-patterns.md +21 -7
  104. package/src/claude/skills/docs/SKILL.md +42 -23
  105. package/src/claude/skills/docs/references/init-workflow.md +4 -4
  106. package/src/claude/skills/docs/references/reconstruct-workflow.md +6 -6
  107. package/src/claude/skills/docs/references/standard-docs-workflow.md +6 -2
  108. package/src/claude/skills/docs/references/summarize-workflow.md +2 -2
  109. package/src/claude/skills/docs/references/update-workflow.md +9 -7
  110. package/src/claude/skills/docs/templates/requirements-as-is.md +1 -1
  111. package/src/claude/skills/docx/SKILL.md +1 -1
  112. package/src/claude/skills/git/SKILL.md +1 -1
  113. package/src/claude/skills/git/references/commit-protocols.md +1 -1
  114. package/src/claude/skills/git/references/finish-branch.md +1 -1
  115. package/src/claude/skills/hotfix/SKILL.md +159 -170
  116. package/src/claude/skills/hotfix/references/diagnosis-protocol.md +14 -9
  117. package/src/claude/skills/hotfix/references/parallel-patterns.md +27 -13
  118. package/src/claude/skills/hotfix/references/prevention-gate.md +5 -0
  119. package/src/claude/skills/hotfix/references/review-cycle.md +21 -15
  120. package/src/claude/skills/hotfix/references/workflow-specialized.md +53 -21
  121. package/src/claude/skills/inspect/SKILL.md +55 -46
  122. package/src/claude/skills/inspect/references/internal-inspection.md +21 -5
  123. package/src/claude/skills/loop/SKILL.md +114 -0
  124. package/src/claude/skills/loop/references/bounded-loop-protocol.md +104 -0
  125. package/src/claude/skills/loop/references/metric-and-guard-contract.md +76 -0
  126. package/src/claude/skills/pdf/SKILL.md +1 -1
  127. package/src/claude/skills/pptx/SKILL.md +1 -1
  128. package/src/claude/skills/question/SKILL.md +16 -13
  129. package/src/claude/skills/research/SKILL.md +70 -43
  130. package/src/claude/skills/route/SKILL.md +94 -0
  131. package/src/claude/skills/route/references/agent-timing.md +46 -0
  132. package/src/claude/skills/route/references/chaining-patterns.md +53 -0
  133. package/src/claude/skills/route/references/task-taxonomy.md +48 -0
  134. package/src/claude/skills/specs/SKILL.md +41 -60
  135. package/src/claude/skills/specs/references/review.md +11 -8
  136. package/src/claude/skills/specs/references/templates.md +34 -18
  137. package/src/claude/skills/specs/templates/requirements-init.md +1 -1
  138. package/src/claude/skills/specs/templates/research.md +2 -2
  139. package/src/claude/skills/sync/SKILL.md +6 -6
  140. package/src/claude/skills/sync/references/sync-protocols.md +1 -1
  141. package/src/claude/skills/test/SKILL.md +120 -120
  142. package/src/claude/skills/test/references/execution-strategy.md +208 -361
  143. package/src/claude/skills/test/references/failure-triage.md +55 -121
  144. package/src/claude/skills/test/references/test-memory.md +20 -34
  145. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  146. package/src/claude/skills/web-testing/SKILL.md +1 -1
  147. package/src/claude/skills/xlsx/SKILL.md +1 -1
  148. package/src/claude/status.cjs +135 -33
  149. package/src/codex/AGENTS.md +14 -4
  150. package/src/codex/hooks/docs-sync.cjs +8 -3
  151. package/src/codex/hooks/lib/hook-context.cjs +18 -4
  152. package/src/codex/hooks/rules.cjs +29 -1
  153. package/src/codex/hooks/secret-output-guardrail.cjs +30 -0
  154. package/src/codex/hooks/spec-gate.cjs +3 -8
  155. package/src/codex/hooks/spec-state.cjs +2 -1
  156. package/src/codex/hooks.json +5 -0
  157. package/src/codex/runtime.json +2 -0
  158. package/src/common/.env.example +1 -1
  159. package/src/claude/skills/backend-development/SKILL.md +0 -101
  160. package/src/claude/skills/backend-development/references/backend-api-design.md +0 -495
  161. package/src/claude/skills/backend-development/references/backend-architecture.md +0 -454
  162. package/src/claude/skills/backend-development/references/backend-authentication.md +0 -338
  163. package/src/claude/skills/backend-development/references/backend-code-quality.md +0 -659
  164. package/src/claude/skills/backend-development/references/backend-debugging.md +0 -904
  165. package/src/claude/skills/backend-development/references/backend-devops.md +0 -491
  166. package/src/claude/skills/backend-development/references/backend-mindset.md +0 -387
  167. package/src/claude/skills/backend-development/references/backend-performance.md +0 -397
  168. package/src/claude/skills/backend-development/references/backend-security.md +0 -342
  169. package/src/claude/skills/backend-development/references/backend-technologies.md +0 -256
  170. package/src/claude/skills/backend-development/references/backend-testing.md +0 -429
  171. package/src/claude/skills/devops/.env.example +0 -76
  172. package/src/claude/skills/devops/SKILL.md +0 -102
  173. package/src/claude/skills/devops/references/browser-rendering.md +0 -305
  174. package/src/claude/skills/devops/references/cloudflare-d1-kv.md +0 -123
  175. package/src/claude/skills/devops/references/cloudflare-platform.md +0 -271
  176. package/src/claude/skills/devops/references/cloudflare-r2-storage.md +0 -280
  177. package/src/claude/skills/devops/references/cloudflare-workers-advanced.md +0 -312
  178. package/src/claude/skills/devops/references/cloudflare-workers-apis.md +0 -309
  179. package/src/claude/skills/devops/references/cloudflare-workers-basics.md +0 -418
  180. package/src/claude/skills/devops/references/docker-basics.md +0 -297
  181. package/src/claude/skills/devops/references/docker-compose.md +0 -292
  182. package/src/claude/skills/devops/references/gcloud-platform.md +0 -297
  183. package/src/claude/skills/devops/references/gcloud-services.md +0 -304
  184. package/src/claude/skills/devops/references/kubernetes-basics.md +0 -95
  185. package/src/claude/skills/devops/references/kubernetes-helm-advanced.md +0 -70
  186. package/src/claude/skills/devops/references/kubernetes-helm.md +0 -81
  187. package/src/claude/skills/devops/references/kubernetes-kubectl.md +0 -74
  188. package/src/claude/skills/devops/references/kubernetes-security-advanced.md +0 -94
  189. package/src/claude/skills/devops/references/kubernetes-security.md +0 -89
  190. package/src/claude/skills/devops/references/kubernetes-troubleshooting-advanced.md +0 -74
  191. package/src/claude/skills/devops/references/kubernetes-troubleshooting.md +0 -49
  192. package/src/claude/skills/devops/references/kubernetes-workflows-advanced.md +0 -75
  193. package/src/claude/skills/devops/references/kubernetes-workflows.md +0 -76
  194. package/src/claude/skills/devops/scripts/cloudflare_deploy.py +0 -269
  195. package/src/claude/skills/devops/scripts/docker_optimize.py +0 -332
  196. package/src/claude/skills/devops/scripts/requirements.txt +0 -20
  197. package/src/claude/skills/devops/scripts/tests/requirements.txt +0 -3
  198. package/src/claude/skills/devops/scripts/tests/test_cloudflare_deploy.py +0 -285
  199. package/src/claude/skills/devops/scripts/tests/test_docker_optimize.py +0 -436
  200. package/src/claude/skills/frontend-design/SKILL.md +0 -135
  201. package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +0 -165
  202. package/src/claude/skills/frontend-design/references/analysis-best-practices.md +0 -80
  203. package/src/claude/skills/frontend-design/references/analysis-prompts.md +0 -141
  204. package/src/claude/skills/frontend-design/references/analysis-techniques.md +0 -118
  205. package/src/claude/skills/frontend-design/references/animejs.md +0 -396
  206. package/src/claude/skills/frontend-design/references/anti-slop-rules.md +0 -103
  207. package/src/claude/skills/frontend-design/references/asset-generation.md +0 -337
  208. package/src/claude/skills/frontend-design/references/bento-motion-engine.md +0 -142
  209. package/src/claude/skills/frontend-design/references/design-extraction-overview.md +0 -71
  210. package/src/claude/skills/frontend-design/references/extraction-best-practices.md +0 -141
  211. package/src/claude/skills/frontend-design/references/extraction-output-templates.md +0 -162
  212. package/src/claude/skills/frontend-design/references/extraction-prompts.md +0 -127
  213. package/src/claude/skills/frontend-design/references/magicui-components.md +0 -129
  214. package/src/claude/skills/frontend-design/references/performance-guardrails.md +0 -169
  215. package/src/claude/skills/frontend-design/references/premium-design-patterns.md +0 -93
  216. package/src/claude/skills/frontend-design/references/redesign-audit-checklist.md +0 -114
  217. package/src/claude/skills/frontend-design/references/technical-accessibility.md +0 -119
  218. package/src/claude/skills/frontend-design/references/technical-best-practices.md +0 -97
  219. package/src/claude/skills/frontend-design/references/technical-optimization.md +0 -44
  220. package/src/claude/skills/frontend-design/references/technical-overview.md +0 -90
  221. package/src/claude/skills/frontend-design/references/technical-workflows.md +0 -150
  222. package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +0 -95
  223. package/src/claude/skills/frontend-design/references/workflow-3d.md +0 -102
  224. package/src/claude/skills/frontend-design/references/workflow-describe.md +0 -87
  225. package/src/claude/skills/frontend-design/references/workflow-immersive.md +0 -87
  226. package/src/claude/skills/frontend-design/references/workflow-quick.md +0 -57
  227. package/src/claude/skills/frontend-design/references/workflow-screenshot.md +0 -63
  228. package/src/claude/skills/frontend-design/references/workflow-video.md +0 -74
  229. package/src/claude/skills/frontend-development/SKILL.md +0 -406
  230. package/src/claude/skills/frontend-development/resources/common-patterns.md +0 -331
  231. package/src/claude/skills/frontend-development/resources/complete-examples.md +0 -872
  232. package/src/claude/skills/frontend-development/resources/component-patterns.md +0 -502
  233. package/src/claude/skills/frontend-development/resources/data-fetching.md +0 -767
  234. package/src/claude/skills/frontend-development/resources/file-organization.md +0 -502
  235. package/src/claude/skills/frontend-development/resources/loading-and-error-states.md +0 -501
  236. package/src/claude/skills/frontend-development/resources/performance.md +0 -406
  237. package/src/claude/skills/frontend-development/resources/routing-guide.md +0 -364
  238. package/src/claude/skills/frontend-development/resources/styling-guide.md +0 -428
  239. package/src/claude/skills/frontend-development/resources/typescript-standards.md +0 -418
  240. package/src/claude/skills/mobile-development/SKILL.md +0 -218
  241. package/src/claude/skills/mobile-development/references/mobile-android.md +0 -604
  242. package/src/claude/skills/mobile-development/references/mobile-best-practices.md +0 -544
  243. package/src/claude/skills/mobile-development/references/mobile-debugging.md +0 -1089
  244. package/src/claude/skills/mobile-development/references/mobile-frameworks.md +0 -465
  245. package/src/claude/skills/mobile-development/references/mobile-ios.md +0 -496
  246. package/src/claude/skills/mobile-development/references/mobile-mindset.md +0 -542
  247. package/src/claude/skills/react-best-practices/AGENTS.md +0 -2249
  248. package/src/claude/skills/react-best-practices/README.md +0 -123
  249. package/src/claude/skills/react-best-practices/SKILL.md +0 -128
  250. package/src/claude/skills/react-best-practices/metadata.json +0 -15
  251. package/src/claude/skills/react-best-practices/rules/_sections.md +0 -46
  252. package/src/claude/skills/react-best-practices/rules/_template.md +0 -28
  253. package/src/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  254. package/src/claude/skills/react-best-practices/rules/advanced-use-latest.md +0 -49
  255. package/src/claude/skills/react-best-practices/rules/async-api-routes.md +0 -38
  256. package/src/claude/skills/react-best-practices/rules/async-defer-await.md +0 -80
  257. package/src/claude/skills/react-best-practices/rules/async-dependencies.md +0 -36
  258. package/src/claude/skills/react-best-practices/rules/async-parallel.md +0 -28
  259. package/src/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -99
  260. package/src/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -59
  261. package/src/claude/skills/react-best-practices/rules/bundle-conditional.md +0 -31
  262. package/src/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +0 -49
  263. package/src/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  264. package/src/claude/skills/react-best-practices/rules/bundle-preload.md +0 -50
  265. package/src/claude/skills/react-best-practices/rules/client-event-listeners.md +0 -74
  266. package/src/claude/skills/react-best-practices/rules/client-swr-dedup.md +0 -56
  267. package/src/claude/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
  268. package/src/claude/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
  269. package/src/claude/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
  270. package/src/claude/skills/react-best-practices/rules/js-cache-storage.md +0 -70
  271. package/src/claude/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
  272. package/src/claude/skills/react-best-practices/rules/js-early-exit.md +0 -50
  273. package/src/claude/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
  274. package/src/claude/skills/react-best-practices/rules/js-index-maps.md +0 -37
  275. package/src/claude/skills/react-best-practices/rules/js-length-check-first.md +0 -49
  276. package/src/claude/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
  277. package/src/claude/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
  278. package/src/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
  279. package/src/claude/skills/react-best-practices/rules/rendering-activity.md +0 -26
  280. package/src/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  281. package/src/claude/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
  282. package/src/claude/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
  283. package/src/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  284. package/src/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  285. package/src/claude/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
  286. package/src/claude/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
  287. package/src/claude/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
  288. package/src/claude/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
  289. package/src/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
  290. package/src/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  291. package/src/claude/skills/react-best-practices/rules/rerender-memo.md +0 -44
  292. package/src/claude/skills/react-best-practices/rules/rerender-transitions.md +0 -40
  293. package/src/claude/skills/react-best-practices/rules/server-after-nonblocking.md +0 -73
  294. package/src/claude/skills/react-best-practices/rules/server-cache-lru.md +0 -41
  295. package/src/claude/skills/react-best-practices/rules/server-cache-react.md +0 -26
  296. package/src/claude/skills/react-best-practices/rules/server-parallel-fetching.md +0 -79
  297. package/src/claude/skills/react-best-practices/rules/server-serialization.md +0 -38
@@ -8,18 +8,36 @@ const { createHash } = require('node:crypto');
8
8
  const { spawnSync } = require('node:child_process');
9
9
  const { test } = require('node:test');
10
10
 
11
+ const {
12
+ convertCodexAgentContent,
13
+ normalizeCodexBody,
14
+ } = require('../lib/codex-install');
15
+
11
16
  const PACKAGE_ROOT = path.resolve(__dirname, '../..');
12
17
  const PACKAGE_VERSION = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
13
18
  const OLD_FIXTURE_VERSION = '0.14.1';
14
19
  const NO_INVENTION_ANCHOR = 'Before implementation handoff, apply the **no-invention gate**:';
15
20
  const IMPLEMENTATION_READINESS_BOUNDARY_ROWS = [
21
+ ['Interaction/UI', 'entry journey; visible/loading/empty/error states; input/focus/keyboard; accessibility; responsive/native/device behavior'],
16
22
  ['API/CLI', 'entrypoint/route or command grammar; identity/auth; input/default/normalization; success output; error/status/exit; duplicate/retry/idempotency; compatibility'],
17
- ['Schema', 'version; exact keys/nesting/types; required/optional; enum/format/bounds/cardinality; unknown-field behavior; compatibility/migration'],
18
- ['State/concurrency', 'initial/terminal states; event + guard + effect + next + error; ordering; duplicate/retry; writer/lock acquire/contention/release; rollback/recovery'],
23
+ ['Data/schema', 'authority/storage/transaction; version; exact keys/nesting/types; required/optional; enum/format/bounds/cardinality; unknown-field behavior; compatibility/migration'],
24
+ ['Async/state', 'initial/terminal states; event + guard + effect + next + error; ordering/concurrency; duplicate/retry; writer/lock acquire/contention/release; cancellation; rollback/recovery'],
19
25
  ['Filesystem/security', 'authoritative root; trusted/untrusted segment grammar; lexical + canonical containment and symlink policy; flags/mode; temp/rename/fsync; lock/stale reclaim; crash cleanup'],
26
+ ['Runtime/deploy', 'config/env/flags; registration/packaging; OS/arch; rollout/rollback; health/logging; operator recovery'],
20
27
  ['Time/retention', 'clock source; unit/precision/timezone; endpoints and inclusion/comparator; anomaly behavior; expiry/purge/recovery'],
28
+ ['AI/model', 'provider/model/prompt/tool schema; nondeterminism/bounds; safety/privacy; fallback; cost/token limit; eval oracle'],
21
29
  ['Integration/proof', 'caller; export/registration; packaging/config; native path/consumer; proof level (`source`/`installed`/`live`); observable failure oracle'],
22
30
  ];
31
+
32
+ function boundaryTableHasRequiredRows(table) {
33
+ const [header, ...rows] = table;
34
+ const labels = rows.map((row) => row[0]);
35
+ return JSON.stringify(header || []) === JSON.stringify(['Boundary', 'Required contract when material'])
36
+ && new Set(labels).size === labels.length
37
+ && IMPLEMENTATION_READINESS_BOUNDARY_ROWS.every((expected) =>
38
+ rows.some((row) => JSON.stringify(row) === JSON.stringify(expected)));
39
+ }
40
+
23
41
  const IMPLEMENTATION_READINESS_CLAUSES = {
24
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.',
25
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.',
@@ -27,15 +45,16 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
27
45
  proofPlanLines: [
28
46
  '- Command: `<exact runnable command>`',
29
47
  '- Named probe: <existing concrete probe/test/hook ID; never only a suite label>',
30
- '- Reachability: <real entrypoint/caller at each `source`/`installed`/`live` level; `UNKNOWN` if unexecuted>',
48
+ '- Reachability: <known command/caller/environment per required level; `UNKNOWN` only when the path cannot yet be established>',
31
49
  '- Oracle: <externally observable success or failure>',
32
50
  '- Counterexample: <material alternative behavior that must make this proof fail>',
33
51
  '- Artifacts: <required artifact path plus digest algorithm/comparison rule, or explicitly ephemeral with cleanup rule>',
34
52
  ],
35
53
  proofTrace: 'Trace `Command → Named probe → Reachability → Oracle`.',
36
- namedProbeOwnership: 'Aggregate suites\nname the owning concrete probe.',
37
- proofLevelSeparation: 'Proof at `source`/`installed`/`live` stays\nseparate; one level never promotes another.',
54
+ namedProbeOwnership: 'Aggregate suites name the\nowning concrete probe.',
55
+ proofLevelSeparation: 'Levels stay separate and never promote one another.',
38
56
  disposableTemplateControls: 'Run mutation or destructive\nnegative controls only on disposable copies under a verified temporary root,\nnever tracked worktree or canonical source bytes.',
57
+ proofLevelMapping: 'For every required level in each referenced CP row, map its named probe and\nreachability here; one command may own several explicitly named level probes.',
39
58
  disposableReviewControls: 'Run mutation or destructive negative controls only on disposable copies below a verified temporary root, never tracked worktree or canonical source bytes.',
40
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.',
41
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.',
@@ -45,6 +64,49 @@ const IMPLEMENTATION_READINESS_CLAUSES = {
45
64
  unknownBlocks: 'Only `PASS` closes it; `FAIL` remains open for the remaining paper-review round; `UNKNOWN` blocks implementation handoff.',
46
65
  scopeReturnsToC1: 'A repair that adds user semantics or scope returns to C1.',
47
66
  };
67
+
68
+ const ADAPTIVE_COVERAGE_PROFILE_HEADER = [
69
+ 'ID', 'Outcome', 'Change kinds', 'Material surfaces', 'Ambiguity/action',
70
+ 'Risk/evidence', 'Required proof',
71
+ ];
72
+ const ADAPTIVE_COVERAGE_PROFILE_ROW = [
73
+ 'CP-01', '<externally observable outcome>', '<all kinds>', '<all material surfaces>',
74
+ '<state + action>', '<level + evidence>', '<source/installed/live set>',
75
+ ];
76
+ const ADAPTIVE_COVERAGE_AMBIGUITY_ROWS = [
77
+ ['State', 'Required action'],
78
+ ['`none`', 'proceed'],
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'],
81
+ ['`design-needed`', 'after user-owned decisions settle, route material competing technical designs through Brainstorm'],
82
+ ];
83
+ const ADAPTIVE_REVIEWER_ROWS = [
84
+ ['Groups', 'Reviewers', 'Roles', 'Claim budget'],
85
+ ['1-2', '2', 'Fact Checker plus all matching material lenses', 'about 5 per group'],
86
+ ['3-5', '3', 'Fact Checker plus all matching material lenses', 'about 10 per group'],
87
+ ['6+', '4', 'Fact Checker plus all matching material lenses', 'at least 15 total'],
88
+ ];
89
+ const ADAPTIVE_COVERAGE_CLAUSES = {
90
+ riskFirst: 'Classify material risk before choosing a workflow; user wording never lowers an observed floor.',
91
+ criticalFloor: '`critical`: auth/secrets/privacy; destructive/irreversible work or possible data loss/corruption; money/privilege/safety; production-state mutation.',
92
+ elevatedFloor: '`elevated`: cross-component contracts, compatibility, concurrency, external integration, or installed/runtime behavior.',
93
+ frontmatterGate: 'skip only when a change is clear, isolated, reversible, routine, and likely limited to one or two files.',
94
+ directGate: 'Work directly only when the cause and change are clear, isolated, reversible,\n`routine`, and likely limited to one or two files.',
95
+ splitRoute: 'Split three or more independent\nsubsystems; otherwise use one Specs packet for any material work that does not qualify for direct work or Brainstorm-only exploration.',
96
+ independentSubsystem: 'A subsystem is independent only when its outcome, boundary, and verification/deployment path can move through the lifecycle separately.',
97
+ profileAuthority: 'For a Specs route, `plan.md` owns one `## Coverage profile` row per externally observable outcome; direct and Brainstorm-only routes do not persist it.',
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
+ scopedUnion: 'Each task references its CP IDs; authoring, review, edge, and proof obligations union only inside affected rows/tasks.',
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.',
102
+ proofSeparation: 'Levels stay separate and never promote one another.',
103
+ liveLimit: 'Source/static checks prove the written contract, not live-model adherence.',
104
+ specMakerAuthority: 'they are the canonical risk and coverage authority. Do not duplicate\ntheir taxonomy here.',
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.',
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
+ 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
+ };
48
110
  const REQUIRED_PAYLOAD = [
49
111
  'bin/install.js',
50
112
  'src/claude/migration-manifest.json',
@@ -59,6 +121,29 @@ const REQUIRED_PAYLOAD = [
59
121
  'src/claude/scripts/validate-spec-output.cjs',
60
122
  'src/claude/scripts/spec-authoring-validation.cjs',
61
123
  'src/claude/scripts/spec-authoring-digest.cjs',
124
+ 'src/claude/scripts/generate-skill-catalog.cjs',
125
+ 'src/claude/rules/process-management.md',
126
+ 'src/claude/rules/review-audit-self-decision.md',
127
+ 'src/claude/rules/skill-domain-routing.md',
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
+ 'src/claude/skills/brainstorm/SKILL.md',
134
+ 'src/claude/skills/brainstorm/references/question-framework.md',
135
+ 'src/claude/agents/brainstormer.md',
136
+ 'src/claude/skills/research/SKILL.md',
137
+ 'src/claude/agents/researcher.md',
138
+ 'src/claude/skills/loop/SKILL.md',
139
+ 'src/claude/skills/loop/references/bounded-loop-protocol.md',
140
+ 'src/claude/skills/loop/references/metric-and-guard-contract.md',
141
+ 'src/claude/skills/docs/SKILL.md',
142
+ 'src/claude/skills/docx/SKILL.md',
143
+ 'src/claude/skills/pdf/SKILL.md',
144
+ 'src/claude/skills/pptx/SKILL.md',
145
+ 'src/claude/skills/xlsx/SKILL.md',
146
+ 'src/claude/skills/ai-multimodal/SKILL.md',
62
147
  ];
63
148
  const FORBIDDEN_PAYLOAD = [
64
149
  /(^|\/)\.logs(\/|$)/,
@@ -68,17 +153,96 @@ const FORBIDDEN_PAYLOAD = [
68
153
  /\.pyc$/,
69
154
  /(^|\/)\.(?:cache|state|tmp)(\/|$)/,
70
155
  /^src\/\.codex(?:\/|$)/,
156
+ /^src\/claude\/skills\/(?:backend-development|frontend-development|frontend-design|mobile-development|devops|react-best-practices)(?:\/|$)/,
71
157
  ];
72
158
  const RUNTIMES = {
73
159
  claude: {
74
160
  root: '.claude', rules: '.claude/hooks/rules.cjs', specs: '.claude/skills/specs',
161
+ specMaker: '.claude/agents/spec-maker.md',
75
162
  manifest: '.claude/cafekit-manifest.json', templatesManifestPath: 'skills/specs/references/templates.md'
76
163
  },
77
164
  codex: {
78
165
  root: '.codex', rules: '.codex/hooks/rules.cjs', specs: '.agents/skills/specs',
166
+ specMaker: '.codex/agents/spec_maker.toml',
79
167
  manifest: '.codex/cafekit-manifest.json', templatesManifestPath: '.agents/skills/specs/references/templates.md'
80
168
  },
81
169
  };
170
+ const ADAPTIVE_SOURCE_RELATIVES = {
171
+ skill: 'src/claude/skills/specs/SKILL.md',
172
+ specMaker: 'src/claude/agents/spec-maker.md',
173
+ templates: 'src/claude/skills/specs/references/templates.md',
174
+ review: 'src/claude/skills/specs/references/review.md',
175
+ };
176
+ const BRAINSTORM_SOURCE_RELATIVES = {
177
+ skill: 'src/claude/skills/brainstorm/SKILL.md',
178
+ framework: 'src/claude/skills/brainstorm/references/question-framework.md',
179
+ agent: 'src/claude/agents/brainstormer.md',
180
+ };
181
+ const RESEARCH_LOOP_SOURCE_RELATIVES = {
182
+ research: 'src/claude/skills/research/SKILL.md',
183
+ agent: 'src/claude/agents/researcher.md',
184
+ loop: 'src/claude/skills/loop/SKILL.md',
185
+ protocol: 'src/claude/skills/loop/references/bounded-loop-protocol.md',
186
+ metric: 'src/claude/skills/loop/references/metric-and-guard-contract.md',
187
+ workflow: 'src/claude/rules/skill-workflow-routing.md',
188
+ domain: 'src/claude/rules/skill-domain-routing.md',
189
+ };
190
+ const HOTFIX_SOURCE_RELATIVES = {
191
+ skill: 'src/claude/skills/hotfix/SKILL.md',
192
+ review: 'src/claude/skills/hotfix/references/review-cycle.md',
193
+ parallel: 'src/claude/skills/hotfix/references/parallel-patterns.md',
194
+ specialized: 'src/claude/skills/hotfix/references/workflow-specialized.md',
195
+ };
196
+ const REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS = [
197
+ 'adviser-gate-fallback',
198
+ 'decision-brief',
199
+ 'direct-precedence',
200
+ 'evidence-semantics',
201
+ 'leading-flags',
202
+ 'lens-trigger-skip',
203
+ 'no-persistence-dispatch',
204
+ 'numeric-estimates',
205
+ 'ordered-depth',
206
+ 'pre-tool-authority-redaction',
207
+ ];
208
+ const ADAPTIVE_BRAINSTORM_INSTALLED_RULES = [
209
+ { group: 'direct-precedence', mutations: [
210
+ { 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.' },
211
+ ] },
212
+ { group: 'ordered-depth', mutations: [
213
+ { 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.' },
214
+ ] },
215
+ { group: 'leading-flags', mutations: [
216
+ { 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.' },
217
+ { 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.' },
218
+ { from: '`--` ends\nthe control segment.', to: '`--` is treated as another control.', clause: '`--` ends the control segment.' },
219
+ { 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.' },
220
+ ] },
221
+ { group: 'lens-trigger-skip', mutations: [
222
+ { from: 'failure isolation for partial or cascading failure\nacross boundaries', to: 'generic failure notes', clause: 'failure isolation for partial or cascading failure across boundaries' },
223
+ ] },
224
+ { group: 'evidence-semantics', mutations: [
225
+ { 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`.' },
226
+ ] },
227
+ { group: 'numeric-estimates', mutations: [
228
+ { 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`.' },
229
+ ] },
230
+ { group: 'pre-tool-authority-redaction', mutations: [
231
+ { 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.' },
232
+ { 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.' },
233
+ { 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.' },
234
+ ] },
235
+ { group: 'adviser-gate-fallback', mutations: [
236
+ { 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;' },
237
+ { 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.' },
238
+ ] },
239
+ { group: 'decision-brief', mutations: [
240
+ { 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.' },
241
+ ] },
242
+ { group: 'no-persistence-dispatch', mutations: [
243
+ { 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.' },
244
+ ] },
245
+ ];
82
246
 
83
247
  function npmPack(args, cwd) {
84
248
  const cache = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-npm-pack-cache-'));
@@ -165,8 +329,8 @@ function installPacked(tarball, root, runtimeClosure) {
165
329
  return installer;
166
330
  }
167
331
 
168
- function runInstaller(installer, root, platforms, lang) {
169
- const args = ['--platform', platforms.join(','), '--yes'];
332
+ function runInstaller(installer, root, platforms, lang, extraArgs = []) {
333
+ const args = ['--platform', platforms.join(','), '--yes', ...extraArgs];
170
334
  if (lang) args.push('--lang', lang);
171
335
  const result = spawnSync(process.execPath, [installer, ...args], {
172
336
  cwd: root,
@@ -177,6 +341,106 @@ function runInstaller(installer, root, platforms, lang) {
177
341
  return result;
178
342
  }
179
343
 
344
+ function installedRouteFiles(project, platform) {
345
+ const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
346
+ const root = path.join(project, platform === 'codex' ? '.agents/skills/route' : '.claude/skills/route');
347
+ return {
348
+ skill: path.join(root, 'SKILL.md'),
349
+ taxonomy: path.join(root, 'references/task-taxonomy.md'),
350
+ chaining: path.join(root, 'references/chaining-patterns.md'),
351
+ timing: path.join(root, 'references/agent-timing.md'),
352
+ workflow: path.join(project, runtimeRoot, 'rules/skill-workflow-routing.md'),
353
+ domain: path.join(project, runtimeRoot, 'rules/skill-domain-routing.md'),
354
+ };
355
+ }
356
+
357
+ function routeProjectionIssues(files) {
358
+ const text = Object.fromEntries(Object.entries(files).map(([key, file]) => [
359
+ key, fs.readFileSync(file, 'utf8').replace(/\s+/g, ' '),
360
+ ]));
361
+ const issues = new Set();
362
+ const requires = (issue, clauses) => {
363
+ if (clauses.some(([source, clause]) => !text[source].includes(clause))) issues.add(issue);
364
+ };
365
+ requires('direct-path', [
366
+ ['skill', 'invoke that skill directly'],
367
+ ['skill', 'use it directly without constructing a chain'],
368
+ ['skill', 'Do not create a route chain or spawn agents merely to answer it'],
369
+ ]);
370
+ requires('classification', [
371
+ ['skill', 'final deliverable'], ['skill', 'highest-link risk'],
372
+ ['skill', 'number of material domains'],
373
+ ]);
374
+ requires('link-contract', [
375
+ ['chaining', '**Entry:**'], ['chaining', '**Exit:**'], ['chaining', '**Owner:** exactly one'],
376
+ ]);
377
+ requires('failure-stop', [
378
+ ['chaining', '(link, owner, normalized cause)'],
379
+ ['chaining', 'Two failures with the same failure key stop the chain'],
380
+ ['chaining', 'Different normalized causes do not share a key'],
381
+ ]);
382
+ requires('collapse-and-detour', [
383
+ ['skill', 'remove every link whose output is already evidenced'],
384
+ ['chaining', 'preserve its valid exit evidence, normalize the root cause, and choose one bounded detour'],
385
+ ]);
386
+ requires('delegation-contract', [
387
+ ['timing', '**Outcome** — observable result'],
388
+ ['timing', '**Scope** — owned files, systems, or questions'],
389
+ ['timing', '**Inputs** — current evidence and prerequisite artifacts'],
390
+ ['timing', '**Constraints** — safety, compatibility, authority, and non-goals'],
391
+ ['timing', '**Acceptance** — proof required for completion'],
392
+ ['timing', '**Handoff** — expected returned artifact and destination'],
393
+ ['timing', '**Status vocabulary** — `DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT`'],
394
+ ]);
395
+ requires('agent-fallback', [
396
+ ['timing', 'If the preferred agent is absent'],
397
+ ['timing', 'never synthesize a role'],
398
+ ['timing', 'DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT'],
399
+ ]);
400
+ requires('authority', [
401
+ ['skill', 'never expand it'],
402
+ ['skill', 'diagnosis does not authorize repair'],
403
+ ['skill', 'does not authorize commit, push, deploy, publish, or release'],
404
+ ]);
405
+ requires('risk-gates', [
406
+ ['skill', 'require independent review and user confirmation before advancing past the high-risk gate'],
407
+ ]);
408
+ requires('proof-boundary', [
409
+ ['skill', 'remains `[UNPROVEN]` until a separate host run observes it'],
410
+ ]);
411
+ requires('rule-direct-path', [
412
+ ['workflow', 'If the user names a valid installed skill, use it directly'],
413
+ ['workflow', 'If one obvious low-risk installed skill covers the intent, use it directly'],
414
+ ['workflow', 'do not invoke Route or agents for ceremony'],
415
+ ]);
416
+ requires('rule-live-catalog', [
417
+ ['workflow', 'Resolve every abstract link against the current runtime catalog'],
418
+ ['domain', 'examples below are intent hints, not a copied installed inventory'],
419
+ ]);
420
+ requires('rule-installed-only', [
421
+ ['domain', 'document/artifact work | use only a matching installed optional capability'],
422
+ ['domain', 'Never infer an optional document capability from this rule'],
423
+ ]);
424
+ requires('rule-duplicate', [
425
+ ['domain', 'do not auto-route; require explicit user disambiguation'],
426
+ ]);
427
+ requires('authority', [
428
+ ['workflow', 'Diagnosis does not authorize repair'],
429
+ ['workflow', 'implementation does not authorize commit, push, deploy, publish, or release'],
430
+ ['workflow', "no route expands the user's existing authority"],
431
+ ]);
432
+
433
+ const corpus = Object.values(text).join(' ').toLowerCase();
434
+ const issueIf = (issue, pattern) => { if (pattern.test(corpus)) issues.add(issue); };
435
+ issueIf('authority', /(?:successful review permits push|implementation authorizes (?:push|deploy)|diagnosis authorizes repair)/);
436
+ issueIf('authority', /(?<!no )(?:live catalog|route)(?:(?!\b(?:never|not|cannot|can't)\b).){0,40}(?:grants|expands)(?:(?!\b(?:no|never|not|cannot|can't)\b).){0,30}(?:mutation|delivery|user )?authority/);
437
+ issueIf('rule-direct-path', /always invoke route for explicit installed skills[^.]*obvious low-risk[^.]*factual/);
438
+ issueIf('rule-live-catalog', /(?<!never )treat examples as installed inventory/);
439
+ issueIf('rule-installed-only', /(?<!never )invoke (?:the )?(?:docs|document) capability even when (?:absent|not installed|unavailable)/);
440
+ issueIf('rule-duplicate', /(?<!never )automatically (?:choose|select|route to) the first duplicate/);
441
+ return [...issues].sort();
442
+ }
443
+
180
444
  function markdownTableUnderHeading(content, heading) {
181
445
  const lines = markdownSectionUnderHeading(content, heading).split('\n');
182
446
  const tableStart = lines.findIndex((line) => line.trim().startsWith('|'));
@@ -214,6 +478,18 @@ function markdownBetweenHeadings(content, startHeading, endHeading) {
214
478
  return value.slice(startIndex + startMarker.length, endIndex);
215
479
  }
216
480
 
481
+ function parseGeneratedTomlString(content, key) {
482
+ const matches = String(content).match(new RegExp(`^${key} = (.+)$`, 'gm')) || [];
483
+ assert.equal(matches.length, 1, `expected one TOML key: ${key}`);
484
+ return JSON.parse(matches[0].slice(`${key} = `.length));
485
+ }
486
+
487
+ function rewriteGeneratedTomlString(content, key, value) {
488
+ const matches = String(content).match(new RegExp(`^${key} = (.+)$`, 'gm')) || [];
489
+ assert.equal(matches.length, 1, `expected one TOML key to rewrite: ${key}`);
490
+ return String(content).replace(matches[0], `${key} = ${JSON.stringify(value)}`);
491
+ }
492
+
217
493
  function implementationReadinessContractIssues(input) {
218
494
  const keys = input && typeof input === 'object' && !Array.isArray(input)
219
495
  ? Object.keys(input).sort()
@@ -231,20 +507,19 @@ function implementationReadinessContractIssues(input) {
231
507
  if (!authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.noInvention)) {
232
508
  issues.add('no-invention');
233
509
  }
510
+ const contradictoryNoInvention = /\b(?:exception|however)\b.{0,160}\bimplementation handoff\b.{0,80}\b(?:may|can)\s+(?:proceed|continue)\b.{0,160}\bunresolved\b/i;
511
+ if (contradictoryNoInvention.test(normalizeMarkdownWhitespace(authoring))) {
512
+ issues.add('no-invention');
513
+ }
234
514
 
235
515
  const boundaryTable = markdownTableUnderHeading(
236
516
  input.templates,
237
517
  'No-invention and conditional boundary contracts'
238
518
  );
239
- const expectedBoundaryTable = [
240
- ['Boundary', 'Required contract when material'],
241
- ...IMPLEMENTATION_READINESS_BOUNDARY_ROWS,
242
- ];
243
519
  if (!authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.materialDefinition)
244
520
  || !authoring.includes(IMPLEMENTATION_READINESS_CLAUSES.exactBoundaryChoices)
245
- || IMPLEMENTATION_READINESS_BOUNDARY_ROWS.length !== 6
246
- || boundaryTable.length !== expectedBoundaryTable.length
247
- || JSON.stringify(boundaryTable) !== JSON.stringify(expectedBoundaryTable)) {
521
+ || IMPLEMENTATION_READINESS_BOUNDARY_ROWS.length !== 9
522
+ || !boundaryTableHasRequiredRows(boundaryTable)) {
248
523
  issues.add('boundary-contract');
249
524
  }
250
525
 
@@ -260,6 +535,7 @@ function implementationReadinessContractIssues(input) {
260
535
  IMPLEMENTATION_READINESS_CLAUSES.namedProbeOwnership,
261
536
  IMPLEMENTATION_READINESS_CLAUSES.proofLevelSeparation,
262
537
  IMPLEMENTATION_READINESS_CLAUSES.disposableTemplateControls,
538
+ IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
263
539
  ].map(normalizeMarkdownWhitespace);
264
540
  const reviewNegativeControls = normalizeMarkdownWhitespace(
265
541
  markdownSectionUnderHeading(input.review, 'B2 — fresh-context red team')
@@ -299,6 +575,101 @@ function implementationReadinessContractIssues(input) {
299
575
  return [...issues].sort();
300
576
  }
301
577
 
578
+ function adaptiveCoverageContractIssues(input) {
579
+ const expectedKeys = ['review', 'skill', 'specMaker', 'templates'];
580
+ const keys = input && typeof input === 'object' && !Array.isArray(input)
581
+ ? Object.keys(input).sort()
582
+ : [];
583
+ if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)
584
+ || expectedKeys.some((key) => typeof input[key] !== 'string')) {
585
+ throw new TypeError('adaptive-coverage checker expects skill, specMaker, templates, and review UTF-8 strings');
586
+ }
587
+
588
+ const issues = new Set();
589
+ const skill = normalizeMarkdownWhitespace(input.skill);
590
+ const templates = normalizeMarkdownWhitespace(input.templates);
591
+ const review = normalizeMarkdownWhitespace(input.review);
592
+ const specMaker = normalizeMarkdownWhitespace(input.specMaker);
593
+ const has = (source, clause) => source.includes(normalizeMarkdownWhitespace(clause));
594
+
595
+ const riskClauses = [
596
+ ADAPTIVE_COVERAGE_CLAUSES.riskFirst,
597
+ ADAPTIVE_COVERAGE_CLAUSES.criticalFloor,
598
+ ADAPTIVE_COVERAGE_CLAUSES.elevatedFloor,
599
+ ADAPTIVE_COVERAGE_CLAUSES.frontmatterGate,
600
+ ADAPTIVE_COVERAGE_CLAUSES.directGate,
601
+ ADAPTIVE_COVERAGE_CLAUSES.splitRoute,
602
+ ADAPTIVE_COVERAGE_CLAUSES.independentSubsystem,
603
+ ];
604
+ const riskDowngrade = /\buser\b.{0,80}\b(?:may|can)\b.{0,80}\blower\b.{0,40}\brisk\b/i;
605
+ const riskyDirectOverride = /\b(?:exception|even if|regardless)\b.{0,160}\b(?:auth|secret|privacy|destructive|irreversible|data loss|corruption|production[- ]state|critical)\b.{0,160}\b(?:direct|work directly|go direct)\b/i;
606
+ if (riskClauses.some((clause) => !has(skill, clause))
607
+ || riskDowngrade.test(skill) || riskyDirectOverride.test(skill)) {
608
+ issues.add('risk-first-routing');
609
+ }
610
+
611
+ const profileTable = markdownTableUnderHeading(input.templates, 'Coverage profile');
612
+ const profileHeadingCount = (input.templates.match(/^## Coverage profile\s*$/gm) || []).length;
613
+ const taskReferencesCoverage = /## Coverage\s*\n- <exact `CP-NN` IDs owned by this task>/.test(input.templates);
614
+ if (JSON.stringify(profileTable[0] || []) !== JSON.stringify(ADAPTIVE_COVERAGE_PROFILE_HEADER)
615
+ || profileHeadingCount !== 1 || profileTable.length !== 2
616
+ || JSON.stringify(profileTable[1]) !== JSON.stringify(ADAPTIVE_COVERAGE_PROFILE_ROW)
617
+ || !taskReferencesCoverage
618
+ || !has(templates, ADAPTIVE_COVERAGE_CLAUSES.profileAuthority)
619
+ || !has(templates, ADAPTIVE_COVERAGE_CLAUSES.openKinds)) {
620
+ issues.add('coverage-profile-shape');
621
+ }
622
+
623
+ const ambiguityTable = markdownTableUnderHeading(input.templates, 'Example Mapping rule');
624
+ if (JSON.stringify(ambiguityTable) !== JSON.stringify(ADAPTIVE_COVERAGE_AMBIGUITY_ROWS)
625
+ || !templates.includes('retention of 30 versus 90 days is `decision-needed`')) {
626
+ issues.add('ambiguity-actions');
627
+ }
628
+
629
+ const globalCeremony = /\b(?:critical|security|failure|proof|review|edge|obligations?|lenses?)\b[^.!?\n]{0,120}\b(?:union|apply|spread|require)\w*\b[^.!?\n]{0,120}\b(?:all|every)\s+(?:cp\s+)?(?:rows?|outcomes?|tasks?)\b|\b(?:union|apply|spread)\w*\b[^.!?\n]{0,120}\b(?:all|every)\s+(?:cp\s+)?(?:rows?|outcomes?|tasks?)\b/i;
630
+ if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.scopedUnion)
631
+ || globalCeremony.test(templates)
632
+ || !review.includes('nonmaterial lenses are not added')) {
633
+ issues.add('scoped-coverage');
634
+ }
635
+
636
+ const rederiveSources = [templates, skill, specMaker, review];
637
+ if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.profileRederivation)
638
+ || rederiveSources.some((source) => !/rederive affected cp rows?/.test(source.toLowerCase()))) {
639
+ issues.add('profile-lifecycle');
640
+ }
641
+
642
+ const statusMatrix = markdownTableUnderHeading(input.templates, 'Status matrix');
643
+ if (!has(templates, ADAPTIVE_COVERAGE_CLAUSES.plannedProof)
644
+ || !has(templates, ADAPTIVE_COVERAGE_CLAUSES.proofSeparation)
645
+ || !has(templates, IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping)
646
+ || !has(skill, ADAPTIVE_COVERAGE_CLAUSES.liveLimit)
647
+ || !statusMatrix.some((row) => row[0] === 'accepted finding open or `UNKNOWN` reachability' && row[1] === '`blocked`')) {
648
+ issues.add('proof-lifecycle');
649
+ }
650
+
651
+ const reviewerRowsPresent = ADAPTIVE_REVIEWER_ROWS.every((row) =>
652
+ input.review.includes(`| ${row.join(' | ')} |`));
653
+ if (!has(review, ADAPTIVE_COVERAGE_CLAUSES.reviewRisk)
654
+ || !has(review, ADAPTIVE_COVERAGE_CLAUSES.reviewCapacity)
655
+ || !reviewerRowsPresent) {
656
+ issues.add('reviewer-routing');
657
+ }
658
+ if (!specMaker.includes('skills/specs/SKILL.md')
659
+ || !specMaker.includes('skills/specs/references/templates.md')
660
+ || !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerAuthority)
661
+ || !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity)
662
+ || !has(specMaker, ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute)) {
663
+ issues.add('spec-maker-authority');
664
+ }
665
+
666
+ const boundaryTable = markdownTableUnderHeading(input.templates, 'No-invention and conditional boundary contracts');
667
+ if (!boundaryTableHasRequiredRows(boundaryTable)) {
668
+ issues.add('adaptive-boundary-lenses');
669
+ }
670
+ return [...issues].sort();
671
+ }
672
+
302
673
  function installedSpecsReadinessIssues(project, installedRoot, expectedRelative) {
303
674
  assert.equal(path.isAbsolute(project), true, 'project root must be absolute');
304
675
  assert.equal(path.isAbsolute(installedRoot), true, 'installed Specs root must be absolute');
@@ -322,6 +693,263 @@ function assertInstalledSpecsReadiness(project, platform, expected = []) {
322
693
  return issues;
323
694
  }
324
695
 
696
+ function adaptiveInstalledPaths(project, platform) {
697
+ const runtime = RUNTIMES[platform];
698
+ assert.ok(runtime, `unknown platform: ${platform}`);
699
+ return {
700
+ skill: path.join(project, runtime.specs, 'SKILL.md'),
701
+ specMaker: path.join(project, runtime.specMaker),
702
+ templates: path.join(project, runtime.specs, 'references/templates.md'),
703
+ review: path.join(project, runtime.specs, 'references/review.md'),
704
+ };
705
+ }
706
+
707
+ function installedAdaptiveCoverageSources(project, platform) {
708
+ const installedPaths = adaptiveInstalledPaths(project, platform);
709
+ const sources = Object.fromEntries(
710
+ Object.entries(installedPaths).map(([source, target]) => [source, fs.readFileSync(target, 'utf8')])
711
+ );
712
+ if (platform === 'codex') {
713
+ sources.specMaker = parseGeneratedTomlString(sources.specMaker, 'developer_instructions');
714
+ }
715
+ return sources;
716
+ }
717
+
718
+ function assertInstalledAdaptiveCoverage(project, platform, expected = []) {
719
+ const issues = adaptiveCoverageContractIssues(installedAdaptiveCoverageSources(project, platform));
720
+ assert.deepEqual(issues, expected, `${platform} installed adaptive Specs issues`);
721
+ return issues;
722
+ }
723
+
724
+ function assertPackedAdaptiveParity(project, platform) {
725
+ const installedPaths = adaptiveInstalledPaths(project, platform);
726
+ for (const [source, sourceRelative] of Object.entries(ADAPTIVE_SOURCE_RELATIVES)) {
727
+ const sourcePath = path.join(PACKAGE_ROOT, sourceRelative);
728
+ const sourceBytes = fs.readFileSync(sourcePath);
729
+ let expectedBytes = sourceBytes;
730
+ if (platform === 'codex') {
731
+ const sourceText = sourceBytes.toString('utf8');
732
+ expectedBytes = Buffer.from(source === 'specMaker'
733
+ ? convertCodexAgentContent(sourceText, path.basename(sourcePath))
734
+ : normalizeCodexBody(sourceText, sourcePath));
735
+ }
736
+ assert.deepEqual(
737
+ fs.readFileSync(installedPaths[source]),
738
+ expectedBytes,
739
+ `${platform} installed ${source} must equal its exact production projection`
740
+ );
741
+ }
742
+ assertInstalledSpecsReadiness(project, platform);
743
+ assertInstalledAdaptiveCoverage(project, platform);
744
+ }
745
+
746
+ function assertPackedBrainstormParity(project, platform) {
747
+ const installedRelatives = platform === 'codex'
748
+ ? {
749
+ skill: '.agents/skills/brainstorm/SKILL.md',
750
+ framework: '.agents/skills/brainstorm/references/question-framework.md',
751
+ agent: '.codex/agents/brainstormer.toml',
752
+ }
753
+ : {
754
+ skill: '.claude/skills/brainstorm/SKILL.md',
755
+ framework: '.claude/skills/brainstorm/references/question-framework.md',
756
+ agent: '.claude/agents/brainstormer.md',
757
+ };
758
+
759
+ for (const [source, sourceRelative] of Object.entries(BRAINSTORM_SOURCE_RELATIVES)) {
760
+ const sourcePath = path.join(PACKAGE_ROOT, sourceRelative);
761
+ const sourceText = fs.readFileSync(sourcePath, 'utf8');
762
+ const expected = platform === 'codex'
763
+ ? (source === 'agent'
764
+ ? convertCodexAgentContent(sourceText, path.basename(sourcePath))
765
+ : normalizeCodexBody(sourceText, sourcePath))
766
+ : sourceText;
767
+ const installedPath = path.join(project, installedRelatives[source]);
768
+ assert.equal(
769
+ fs.readFileSync(installedPath, 'utf8'),
770
+ expected,
771
+ `${platform} packed Brainstorm ${source} must equal its exact production projection`
772
+ );
773
+ }
774
+ }
775
+
776
+ function packedResearchLoopPaths(project, platform) {
777
+ return platform === 'codex'
778
+ ? {
779
+ research: path.join(project, '.agents/skills/research/SKILL.md'),
780
+ agent: path.join(project, '.codex/agents/researcher.toml'),
781
+ loop: path.join(project, '.agents/skills/loop/SKILL.md'),
782
+ protocol: path.join(project, '.agents/skills/loop/references/bounded-loop-protocol.md'),
783
+ metric: path.join(project, '.agents/skills/loop/references/metric-and-guard-contract.md'),
784
+ workflow: path.join(project, '.codex/rules/skill-workflow-routing.md'),
785
+ domain: path.join(project, '.codex/rules/skill-domain-routing.md'),
786
+ }
787
+ : {
788
+ research: path.join(project, '.claude/skills/research/SKILL.md'),
789
+ agent: path.join(project, '.claude/agents/researcher.md'),
790
+ loop: path.join(project, '.claude/skills/loop/SKILL.md'),
791
+ protocol: path.join(project, '.claude/skills/loop/references/bounded-loop-protocol.md'),
792
+ metric: path.join(project, '.claude/skills/loop/references/metric-and-guard-contract.md'),
793
+ workflow: path.join(project, '.claude/rules/skill-workflow-routing.md'),
794
+ domain: path.join(project, '.claude/rules/skill-domain-routing.md'),
795
+ };
796
+ }
797
+
798
+ function readPackedResearchLoop(project, platform) {
799
+ const paths = packedResearchLoopPaths(project, platform);
800
+ const values = Object.fromEntries(Object.entries(paths).map(([key, target]) => [
801
+ key, fs.readFileSync(target, 'utf8'),
802
+ ]));
803
+ if (platform === 'codex') {
804
+ values.agent = parseGeneratedTomlString(values.agent, 'developer_instructions');
805
+ }
806
+ return values;
807
+ }
808
+
809
+ function packedResearchLoopIssues(project, platform) {
810
+ const values = Object.fromEntries(Object.entries(readPackedResearchLoop(project, platform)).map(
811
+ ([key, value]) => [key, value.replace(/\s+/g, ' ').trim()]
812
+ ));
813
+ const issues = new Set();
814
+ const publicResearch = platform === 'codex' ? 'name: cf-research' : 'name: cf:research';
815
+ const publicLoop = platform === 'codex' ? 'name: cf-loop' : 'name: cf:loop';
816
+ if (!values.research.includes(publicResearch)
817
+ || !values.research.includes('Choose the smallest depth that can support the decision:')
818
+ || !values.research.includes('Use delegated researchers only as optional acceleration')
819
+ || !values.research.includes('research sequentially with the same evidence bar.')
820
+ || !values.research.includes('Default to a concise answer in chat.')
821
+ || !values.research.includes('claim, URL or repository anchor, authority, date/version, applicability to this project')) {
822
+ issues.add('research-adaptive-evidence');
823
+ }
824
+ if (!values.agent.includes('do not implement code, write files, mutate task state, ask the user directly, or launch another workflow.')
825
+ || !values.agent.includes('owns any authorized persistence.')) {
826
+ issues.add('research-agent-boundary');
827
+ }
828
+ if (!values.loop.includes(publicLoop)
829
+ || !values.loop.includes('Loop is explicit-only. Never auto-route ordinary implementation, debugging, or research into Loop.')
830
+ || !values.loop.includes('Reject dirty in-scope state; record but never import or clean out-of-scope dirt.')
831
+ || !values.loop.includes('separately approved external realpath')
832
+ || !values.loop.includes('complete detached-worktree tracked/untracked manifest')
833
+ || !values.loop.includes('Live-agent adherence to this written contract is `[UNPROVEN]` without a host run.')) {
834
+ issues.add('loop-safety');
835
+ }
836
+ if (!values.metric.includes('Guard is mandatory, fixed before baseline, and distinct from Metric.')
837
+ || !values.metric.includes('`numeric_format`: exactly IEEE-754 binary64')
838
+ || !values.metric.includes('nonzero exit')
839
+ || !values.metric.includes('require median, noise, improvement, and required delta to remain finite.')) {
840
+ issues.add('metric-guard');
841
+ }
842
+ if (!values.protocol.includes('do not clean. Preserve the exact path, PID/process evidence, and ownership marker; return `BLOCKED`')
843
+ || !values.protocol.includes('`base_oid`;')
844
+ || !values.protocol.includes('patch byte length and lowercase SHA-256')) {
845
+ issues.add('failure-handoff');
846
+ }
847
+ if (!values.workflow.includes('numeric optimization capability remains explicit-only and must never be auto-routed.')
848
+ || !values.domain.includes('explicit-only numeric optimization capability unless the user invokes it with its required bounded metric/guard contract.')) {
849
+ issues.add('explicit-routing');
850
+ }
851
+ return [...issues].sort();
852
+ }
853
+
854
+ function assertPackedResearchLoopParity(project, platform) {
855
+ const installed = packedResearchLoopPaths(project, platform);
856
+ for (const [key, relative] of Object.entries(RESEARCH_LOOP_SOURCE_RELATIVES)) {
857
+ const sourcePath = path.join(PACKAGE_ROOT, relative);
858
+ const source = fs.readFileSync(sourcePath, 'utf8');
859
+ const expected = platform === 'codex'
860
+ ? (key === 'agent'
861
+ ? convertCodexAgentContent(source, path.basename(sourcePath))
862
+ : normalizeCodexBody(source, sourcePath))
863
+ : source;
864
+ assert.equal(fs.readFileSync(installed[key], 'utf8'), expected, `${platform} ${key} projection drifted`);
865
+ }
866
+ const manifest = JSON.parse(fs.readFileSync(path.join(project, RUNTIMES[platform].manifest), 'utf8'));
867
+ const prefixes = platform === 'codex'
868
+ ? ['.agents/skills/research/', '.agents/skills/loop/']
869
+ : ['skills/research/', 'skills/loop/'];
870
+ for (const prefix of prefixes) {
871
+ assert.ok(Object.keys(manifest.files).some((entry) => entry.startsWith(prefix)), `${platform} manifest missing ${prefix}`);
872
+ }
873
+ assert.deepEqual(packedResearchLoopIssues(project, platform), []);
874
+ }
875
+
876
+ function packedBrainstormInstalledPaths(project, platform) {
877
+ return platform === 'codex'
878
+ ? {
879
+ skill: path.join(project, '.agents/skills/brainstorm/SKILL.md'),
880
+ framework: path.join(project, '.agents/skills/brainstorm/references/question-framework.md'),
881
+ agent: path.join(project, '.codex/agents/brainstormer.toml'),
882
+ }
883
+ : {
884
+ skill: path.join(project, '.claude/skills/brainstorm/SKILL.md'),
885
+ framework: path.join(project, '.claude/skills/brainstorm/references/question-framework.md'),
886
+ agent: path.join(project, '.claude/agents/brainstormer.md'),
887
+ };
888
+ }
889
+
890
+ function packedAdaptiveBrainstormIssues(project, platform) {
891
+ const skill = fs.readFileSync(packedBrainstormInstalledPaths(project, platform).skill, 'utf8')
892
+ .replace(/\s+/g, ' ').trim();
893
+ return ADAPTIVE_BRAINSTORM_INSTALLED_RULES
894
+ .filter(({ mutations }) => mutations.some(({ clause }) => !skill.includes(clause)))
895
+ .map(({ group }) => group)
896
+ .sort();
897
+ }
898
+
899
+ function assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes) {
900
+ const installedScope = platform === 'codex'
901
+ ? path.join(project, '.agents/skills/brainstorm')
902
+ : path.join(project, '.claude/skills/brainstorm');
903
+ const target = packedBrainstormInstalledPaths(project, platform).skill;
904
+ assertContainedRealpath(project, installedScope, `${platform} Brainstorm installed scope`);
905
+ const targetLstat = fs.lstatSync(target);
906
+ assert.equal(targetLstat.isSymbolicLink(), false, `${platform} mutation target must not be a symlink`);
907
+ assert.equal(targetLstat.isFile(), true, `${platform} mutation target must be a regular file`);
908
+ const canonicalTarget = assertContainedRealpath(installedScope, target, `${platform} Brainstorm mutation target`);
909
+ assert.equal(
910
+ canonicalTarget,
911
+ assertContainedRealpath(project, target, `${platform} Brainstorm project mutation target`),
912
+ `${platform} scope and project containment must resolve to the same target`
913
+ );
914
+ const targetStat = fs.statSync(canonicalTarget);
915
+ assert.equal(targetStat.nlink, 1, `${platform} mutation target must have exactly one hard link`);
916
+ for (const sourcePath of canonicalBytes.keys()) {
917
+ const canonicalSource = fs.realpathSync(sourcePath);
918
+ const sourceStat = fs.statSync(canonicalSource);
919
+ assert.notEqual(canonicalTarget, canonicalSource, `${platform} mutation target must not resolve to canonical source`);
920
+ assert.notDeepEqual(
921
+ [targetStat.dev, targetStat.ino],
922
+ [sourceStat.dev, sourceStat.ino],
923
+ `${platform} mutation target must not share a canonical source inode`
924
+ );
925
+ }
926
+ const original = fs.readFileSync(target);
927
+ const exercised = new Set();
928
+ assert.deepEqual(packedAdaptiveBrainstormIssues(project, platform), []);
929
+ for (const rule of ADAPTIVE_BRAINSTORM_INSTALLED_RULES) {
930
+ assert.ok(rule.mutations.length > 0, `${platform}/${rule.group} must contain at least one mutation`);
931
+ let completedMutations = 0;
932
+ for (const mutation of rule.mutations) {
933
+ const content = original.toString('utf8');
934
+ const anchor = content.indexOf(mutation.from);
935
+ assert.ok(anchor >= 0, `${platform}/${rule.group} anchor must exist`);
936
+ assert.equal(content.indexOf(mutation.from, anchor + mutation.from.length), -1, `${platform}/${rule.group} anchor must be unique`);
937
+ try {
938
+ fs.writeFileSync(target, `${content.slice(0, anchor)}${mutation.to}${content.slice(anchor + mutation.from.length)}`);
939
+ assert.deepEqual(packedAdaptiveBrainstormIssues(project, platform), [rule.group]);
940
+ for (const [sourcePath, expected] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), expected);
941
+ } finally {
942
+ fs.writeFileSync(target, original);
943
+ }
944
+ assert.deepEqual(fs.readFileSync(target), original, `${platform}/${rule.group} installed bytes restored`);
945
+ completedMutations += 1;
946
+ }
947
+ assert.ok(completedMutations > 0, `${platform}/${rule.group} must execute at least one mutation`);
948
+ exercised.add(`${platform}:${rule.group}`);
949
+ }
950
+ return exercised;
951
+ }
952
+
325
953
  function sha256(content) {
326
954
  return createHash('sha256').update(content).digest('hex');
327
955
  }
@@ -343,24 +971,21 @@ function installedMutationContext(tempRoot, project, platform, packageRoot = PAC
343
971
  const canonicalTempRoot = assertContainedRealpath(os.tmpdir(), tempRoot, `${platform} mkdtemp root`);
344
972
  assert.match(path.basename(canonicalTempRoot), /^cafekit-package-matrix-/, `${platform} mutation root must be the verified matrix mkdtemp`);
345
973
  const canonicalProject = assertContainedRealpath(canonicalTempRoot, project, `${platform} packed project`);
346
- const nativeInstalledRoot = path.join(canonicalProject, runtime.specs);
347
- const canonicalInstalledRoot = assertContainedRealpath(
348
- canonicalProject,
349
- nativeInstalledRoot,
350
- `${platform} native installed Specs root`
351
- );
352
974
  const installedRelatives = {
975
+ skill: path.join(runtime.specs, 'SKILL.md'),
976
+ specMaker: runtime.specMaker,
353
977
  templates: 'references/templates.md',
354
978
  review: 'references/review.md',
355
979
  };
356
- const installedPaths = {
357
- templates: path.join(nativeInstalledRoot, installedRelatives.templates),
358
- review: path.join(nativeInstalledRoot, installedRelatives.review),
359
- };
360
- const sourcePaths = {
361
- templates: path.join(packageRoot, 'src/claude/skills/specs/references/templates.md'),
362
- review: path.join(packageRoot, 'src/claude/skills/specs/references/review.md'),
363
- };
980
+ installedRelatives.templates = path.join(runtime.specs, installedRelatives.templates);
981
+ installedRelatives.review = path.join(runtime.specs, installedRelatives.review);
982
+ const installedPaths = Object.fromEntries(
983
+ Object.entries(installedRelatives).map(([source, relative]) => [source, path.join(canonicalProject, relative)])
984
+ );
985
+ const sourcePaths = Object.fromEntries(
986
+ Object.entries(ADAPTIVE_SOURCE_RELATIVES)
987
+ .map(([source, relative]) => [source, path.join(packageRoot, relative)])
988
+ );
364
989
  for (const source of Object.keys(sourcePaths)) sourcePaths[source] = fs.realpathSync(sourcePaths[source]);
365
990
  const sourceHashes = Object.fromEntries(
366
991
  Object.entries(sourcePaths).map(([source, sourcePath]) => [source, sha256(fs.readFileSync(sourcePath))])
@@ -382,12 +1007,6 @@ function installedMutationContext(tempRoot, project, platform, packageRoot = PAC
382
1007
  assert.equal(currentTempRoot, canonicalTempRoot, `${platform} ${stage} mutation root changed`);
383
1008
  const currentProject = assertContainedRealpath(currentTempRoot, project, `${platform} ${stage} packed project`);
384
1009
  assert.equal(currentProject, canonicalProject, `${platform} ${stage} packed project changed`);
385
- const currentInstalledRoot = assertContainedRealpath(
386
- currentProject,
387
- nativeInstalledRoot,
388
- `${platform} ${stage} native installed Specs root`
389
- );
390
- assert.equal(currentInstalledRoot, canonicalInstalledRoot, `${platform} ${stage} native installed Specs root changed`);
391
1010
 
392
1011
  const target = installedPaths[source];
393
1012
  const targetMetadata = fs.lstatSync(target);
@@ -398,26 +1017,29 @@ function installedMutationContext(tempRoot, project, platform, packageRoot = PAC
398
1017
  );
399
1018
  assert.equal(targetMetadata.isFile(), true, `${platform} ${stage} target must be a regular non-symlink file`);
400
1019
  const canonicalTarget = assertContainedRealpath(
401
- currentInstalledRoot,
1020
+ currentProject,
402
1021
  target,
403
1022
  `${platform} ${stage} installed ${source} mutation target`
404
1023
  );
405
- assert.equal(
406
- assertContainedRealpath(currentProject, target, `${platform} ${stage} disposable project target`),
407
- canonicalTarget
408
- );
409
1024
  assert.equal(
410
1025
  canonicalTarget,
411
- path.join(currentInstalledRoot, installedRelatives[source]),
1026
+ path.join(currentProject, installedRelatives[source]),
412
1027
  `${platform} ${stage} target must remain at its exact native installed path`
413
1028
  );
414
1029
  assert.notEqual(canonicalTarget, sourcePaths[source], `${platform} ${stage} target must not be package source`);
415
1030
 
416
1031
  const installedIdentity = fs.statSync(target);
417
- const sourceIdentity = fs.statSync(sourcePaths[source]);
418
- assert.ok(
419
- installedIdentity.dev !== sourceIdentity.dev || installedIdentity.ino !== sourceIdentity.ino,
420
- `${platform} ${stage} target must not share (dev, ino) with package source`
1032
+ for (const [protectedSource, protectedSourcePath] of Object.entries(sourcePaths)) {
1033
+ const sourceIdentity = fs.statSync(protectedSourcePath);
1034
+ assert.ok(
1035
+ installedIdentity.dev !== sourceIdentity.dev || installedIdentity.ino !== sourceIdentity.ino,
1036
+ `${platform} ${stage} target must not share (dev, ino) with any package source: ${protectedSource}`
1037
+ );
1038
+ }
1039
+ assert.equal(
1040
+ installedIdentity.nlink,
1041
+ 1,
1042
+ `${platform} ${stage} target must have exactly one hard link`
421
1043
  );
422
1044
  return target;
423
1045
  };
@@ -429,59 +1051,122 @@ function installedMutationContext(tempRoot, project, platform, packageRoot = PAC
429
1051
  for (const source of Object.keys(installedPaths)) {
430
1052
  assertSafeInstalledTarget(source, `${source} context creation`);
431
1053
  }
432
- return { installedPaths, assertSafeInstalledTarget, writeInstalled, assertSourceHashes };
1054
+ return { installedPaths, sourcePaths, assertSafeInstalledTarget, writeInstalled, assertSourceHashes };
433
1055
  }
434
1056
 
435
1057
  test('installed mutation guard rejects post-context symlink and hardlink before source bytes change', () => {
436
1058
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-package-matrix-'));
437
- const trackedSources = {
438
- templates: path.join(PACKAGE_ROOT, 'src/claude/skills/specs/references/templates.md'),
439
- review: path.join(PACKAGE_ROOT, 'src/claude/skills/specs/references/review.md'),
440
- };
1059
+ const trackedSources = Object.fromEntries(
1060
+ Object.entries(ADAPTIVE_SOURCE_RELATIVES)
1061
+ .map(([source, relative]) => [source, path.join(PACKAGE_ROOT, relative)])
1062
+ );
441
1063
  const trackedHashes = Object.fromEntries(
442
1064
  Object.entries(trackedSources).map(([source, sourcePath]) => [source, sha256(fs.readFileSync(sourcePath))])
443
1065
  );
444
1066
  try {
445
1067
  for (const topology of ['symlink', 'hardlink']) {
446
- const packageRoot = path.join(root, `${topology}-package-source`);
447
- const project = path.join(root, `${topology}-project`);
448
- const sourceDirectory = path.join(packageRoot, 'src/claude/skills/specs/references');
449
- const installedDirectory = path.join(project, RUNTIMES.claude.specs, 'references');
450
- fs.mkdirSync(sourceDirectory, { recursive: true });
451
- fs.mkdirSync(installedDirectory, { recursive: true });
452
- for (const file of ['templates.md', 'review.md']) {
453
- fs.writeFileSync(path.join(sourceDirectory, file), `${topology} disposable source ${file}\n`);
454
- fs.writeFileSync(path.join(installedDirectory, file), `${topology} disposable installed ${file}\n`);
455
- }
1068
+ for (const source of Object.keys(ADAPTIVE_SOURCE_RELATIVES)) {
1069
+ const packageRoot = path.join(root, `${topology}-${source}-package-source`);
1070
+ const project = path.join(root, `${topology}-${source}-project`);
1071
+ for (const [fixtureSource, relative] of Object.entries(ADAPTIVE_SOURCE_RELATIVES)) {
1072
+ const sourcePath = path.join(packageRoot, relative);
1073
+ fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1074
+ fs.writeFileSync(sourcePath, `${topology} disposable source ${fixtureSource}\n`);
1075
+ }
1076
+ for (const [fixtureSource, installedPath] of Object.entries(adaptiveInstalledPaths(project, 'claude'))) {
1077
+ fs.mkdirSync(path.dirname(installedPath), { recursive: true });
1078
+ fs.writeFileSync(installedPath, `${topology} disposable installed ${fixtureSource}\n`);
1079
+ }
456
1080
 
457
- const context = installedMutationContext(root, project, 'claude', packageRoot);
458
- const sourcePath = path.join(sourceDirectory, 'templates.md');
459
- const target = context.installedPaths.templates;
460
- const sourceBytes = fs.readFileSync(sourcePath);
461
- const sourceHash = sha256(sourceBytes);
462
- fs.unlinkSync(target);
463
- if (topology === 'symlink') {
464
- fs.symlinkSync(sourcePath, target);
465
- assert.equal(fs.lstatSync(target).isSymbolicLink(), true, 'symlink control must change target topology');
466
- } else {
467
- fs.linkSync(sourcePath, target);
468
- const sourceIdentity = fs.statSync(sourcePath);
469
- const installedIdentity = fs.statSync(target);
470
- assert.deepEqual(
471
- [installedIdentity.dev, installedIdentity.ino],
472
- [sourceIdentity.dev, sourceIdentity.ino],
473
- 'hardlink control must share source identity'
1081
+ const context = installedMutationContext(root, project, 'claude', packageRoot);
1082
+ const sourcePath = context.sourcePaths[source];
1083
+ const target = context.installedPaths[source];
1084
+ const sourceBytes = fs.readFileSync(sourcePath);
1085
+ const sourceHash = sha256(sourceBytes);
1086
+ fs.unlinkSync(target);
1087
+ if (topology === 'symlink') {
1088
+ fs.symlinkSync(sourcePath, target);
1089
+ assert.equal(fs.lstatSync(target).isSymbolicLink(), true, 'symlink control must change target topology');
1090
+ } else {
1091
+ fs.linkSync(sourcePath, target);
1092
+ const sourceIdentity = fs.statSync(sourcePath);
1093
+ const installedIdentity = fs.statSync(target);
1094
+ assert.deepEqual(
1095
+ [installedIdentity.dev, installedIdentity.ino],
1096
+ [sourceIdentity.dev, sourceIdentity.ino],
1097
+ 'hardlink control must share source identity'
1098
+ );
1099
+ }
1100
+
1101
+ assert.throws(
1102
+ () => context.writeInstalled(source, 'forbidden installed mutation\n', `${topology} regression`),
1103
+ topology === 'symlink' ? /regular non-symlink file/ : /must not share \(dev, ino\)/
474
1104
  );
1105
+ assert.deepEqual(fs.readFileSync(sourcePath), sourceBytes, `${topology} rejection must preserve source bytes`);
1106
+ assert.equal(sha256(fs.readFileSync(sourcePath)), sourceHash, `${topology} rejection must preserve source SHA`);
1107
+ context.assertSourceHashes(`after rejected ${topology} write`);
475
1108
  }
1109
+ }
476
1110
 
1111
+ const crossSourcePackageRoot = path.join(root, 'cross-source-package-source');
1112
+ const crossSourceProject = path.join(root, 'cross-source-project');
1113
+ for (const [source, relative] of Object.entries(ADAPTIVE_SOURCE_RELATIVES)) {
1114
+ const sourcePath = path.join(crossSourcePackageRoot, relative);
1115
+ fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
1116
+ fs.writeFileSync(sourcePath, `cross-source disposable source ${source}\n`);
1117
+ }
1118
+ for (const [source, installedPath] of Object.entries(adaptiveInstalledPaths(crossSourceProject, 'claude'))) {
1119
+ fs.mkdirSync(path.dirname(installedPath), { recursive: true });
1120
+ fs.writeFileSync(installedPath, `cross-source disposable installed ${source}\n`);
1121
+ }
1122
+ const crossSourceContext = installedMutationContext(
1123
+ root,
1124
+ crossSourceProject,
1125
+ 'claude',
1126
+ crossSourcePackageRoot
1127
+ );
1128
+ const targetSource = 'skill';
1129
+ const linkedSource = 'specMaker';
1130
+ assert.notEqual(targetSource, linkedSource, 'cross-source control requires A != B');
1131
+ const crossSourceTarget = crossSourceContext.installedPaths[targetSource];
1132
+ const crossSourceTargetBytes = fs.readFileSync(crossSourceTarget);
1133
+ const crossSourceState = Object.fromEntries(
1134
+ Object.entries(crossSourceContext.sourcePaths).map(([source, sourcePath]) => {
1135
+ const bytes = fs.readFileSync(sourcePath);
1136
+ return [source, { bytes, sha: sha256(bytes) }];
1137
+ })
1138
+ );
1139
+ fs.unlinkSync(crossSourceTarget);
1140
+ fs.linkSync(crossSourceContext.sourcePaths[linkedSource], crossSourceTarget);
1141
+ try {
477
1142
  assert.throws(
478
- () => context.writeInstalled('templates', 'forbidden installed mutation\n', `${topology} regression`),
479
- topology === 'symlink' ? /regular non-symlink file/ : /must not share \(dev, ino\)/
1143
+ () => crossSourceContext.writeInstalled(
1144
+ targetSource,
1145
+ 'forbidden packed cross-source mutation\n',
1146
+ 'cross-source hardlink regression'
1147
+ ),
1148
+ /must not share \(dev, ino\) with any package source: specMaker/
480
1149
  );
481
- assert.deepEqual(fs.readFileSync(sourcePath), sourceBytes, `${topology} rejection must preserve source bytes`);
482
- assert.equal(sha256(fs.readFileSync(sourcePath)), sourceHash, `${topology} rejection must preserve source SHA`);
483
- context.assertSourceHashes(`after rejected ${topology} write`);
1150
+ } finally {
1151
+ fs.unlinkSync(crossSourceTarget);
1152
+ fs.writeFileSync(crossSourceTarget, crossSourceTargetBytes);
1153
+ fs.writeFileSync(
1154
+ crossSourceContext.sourcePaths[linkedSource],
1155
+ crossSourceState[linkedSource].bytes
1156
+ );
1157
+ }
1158
+ assert.deepEqual(
1159
+ fs.readFileSync(crossSourceTarget),
1160
+ crossSourceTargetBytes,
1161
+ 'cross-source control must restore exact installed bytes'
1162
+ );
1163
+ for (const [source, sourcePath] of Object.entries(crossSourceContext.sourcePaths)) {
1164
+ const actual = fs.readFileSync(sourcePath);
1165
+ assert.deepEqual(actual, crossSourceState[source].bytes, `cross-source rejection must preserve ${source} bytes`);
1166
+ assert.equal(sha256(actual), crossSourceState[source].sha, `cross-source rejection must preserve ${source} SHA`);
484
1167
  }
1168
+ crossSourceContext.assertSourceHashes('after rejected cross-source hardlink write');
1169
+
485
1170
  for (const [source, sourcePath] of Object.entries(trackedSources)) {
486
1171
  assert.equal(
487
1172
  sha256(fs.readFileSync(sourcePath)),
@@ -496,11 +1181,27 @@ test('installed mutation guard rejects post-context symlink and hardlink before
496
1181
 
497
1182
  function assertInstalledReadinessMutationsDetected(tempRoot, project, platform) {
498
1183
  const context = installedMutationContext(tempRoot, project, platform);
1184
+ const baselineBytes = Object.fromEntries(
1185
+ Object.entries(context.installedPaths).map(([source, target]) => [source, fs.readFileSync(target)])
1186
+ );
499
1187
  const baseline = Object.fromEntries(
500
- Object.entries(context.installedPaths).map(([source, target]) => [source, fs.readFileSync(target, 'utf8')])
1188
+ Object.entries(baselineBytes).map(([source, bytes]) => [source, bytes.toString('utf8')])
501
1189
  );
502
- const apiBoundary = IMPLEMENTATION_READINESS_BOUNDARY_ROWS.find(([label]) => label === 'API/CLI');
503
- assert.ok(apiBoundary, 'API/CLI boundary contract must exist');
1190
+ const boundaryMutation = (name, boundary, replacements) => {
1191
+ const row = IMPLEMENTATION_READINESS_BOUNDARY_ROWS.find(([label]) => label === boundary);
1192
+ assert.ok(row, `${name} references unknown boundary ${boundary}`);
1193
+ let weakened = row[1];
1194
+ for (const [from, to] of replacements) {
1195
+ const next = weakened.replace(from, to);
1196
+ assert.notEqual(next, weakened, `${name} weakening anchor must exist in ${boundary}`);
1197
+ weakened = next;
1198
+ }
1199
+ return {
1200
+ name, issue: 'boundary-contract', source: 'templates',
1201
+ from: `| ${row[0]} | ${row[1]} |`,
1202
+ to: `| ${row[0]} | ${weakened} |`,
1203
+ };
1204
+ };
504
1205
  const mutations = [
505
1206
  {
506
1207
  name: 'no-invention-blocking', issue: 'no-invention', source: 'templates',
@@ -508,25 +1209,125 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
508
1209
  to: 'Before implementation handoff, note ambiguous choices without blocking handoff.',
509
1210
  },
510
1211
  {
511
- name: 'api-success-and-error-semantics', issue: 'boundary-contract', source: 'templates',
512
- from: `| ${apiBoundary[0]} | ${apiBoundary[1]} |`,
513
- to: `| ${apiBoundary[0]} | ${apiBoundary[1].replace('success output; ', '').replace('error/status/exit; ', '')} |`,
1212
+ name: 'no-invention-contradictory-override', issue: 'no-invention', source: 'templates',
1213
+ from: IMPLEMENTATION_READINESS_CLAUSES.noInvention,
1214
+ to: `${IMPLEMENTATION_READINESS_CLAUSES.noInvention}\n\nException: implementation handoff may proceed with an unresolved material choice.`,
1215
+ },
1216
+ {
1217
+ name: 'material-boundary-definition', issue: 'boundary-contract', source: 'templates',
1218
+ from: IMPLEMENTATION_READINESS_CLAUSES.materialDefinition,
1219
+ to: 'A boundary is material when it seems relevant to the task.',
1220
+ },
1221
+ {
1222
+ name: 'exact-boundary-choices', issue: 'boundary-contract', source: 'templates',
1223
+ from: IMPLEMENTATION_READINESS_CLAUSES.exactBoundaryChoices,
1224
+ to: 'For every required row, describe the listed choices generally.',
514
1225
  },
1226
+ boundaryMutation('interaction-accessibility', 'Interaction/UI', [
1227
+ ['input/focus/keyboard; ', ''],
1228
+ ['accessibility; ', ''],
1229
+ ]),
1230
+ boundaryMutation('api-success-and-error-semantics', 'API/CLI', [
1231
+ ['success output; ', ''],
1232
+ ['error/status/exit; ', ''],
1233
+ ]),
1234
+ boundaryMutation('schema-shape-and-unknown-fields', 'Data/schema', [
1235
+ ['exact keys/nesting/types; ', ''],
1236
+ ['unknown-field behavior; ', ''],
1237
+ ]),
1238
+ boundaryMutation('schema-enum-and-format', 'Data/schema', [
1239
+ ['enum/format/', ''],
1240
+ ]),
1241
+ boundaryMutation('state-lock-lifecycle', 'Async/state', [
1242
+ ['writer/lock acquire/contention/release; ', 'writer/lock; '],
1243
+ ]),
1244
+ boundaryMutation('filesystem-segment-grammar', 'Filesystem/security', [
1245
+ ['trusted/untrusted segment grammar; ', ''],
1246
+ ]),
1247
+ boundaryMutation('filesystem-stale-lock-reclaim', 'Filesystem/security', [
1248
+ ['lock/stale reclaim; ', ''],
1249
+ ]),
1250
+ boundaryMutation('runtime-rollout-and-recovery', 'Runtime/deploy', [
1251
+ ['rollout/rollback; ', ''],
1252
+ ['operator recovery', ''],
1253
+ ]),
1254
+ boundaryMutation('retention-clock-and-endpoints', 'Time/retention', [
1255
+ ['clock source; ', ''],
1256
+ ['unit/precision/timezone; ', ''],
1257
+ ['endpoints and inclusion/comparator; ', ''],
1258
+ ]),
1259
+ boundaryMutation('ai-model-safety-and-eval', 'AI/model', [
1260
+ ['safety/privacy; ', ''],
1261
+ ['eval oracle', ''],
1262
+ ]),
1263
+ boundaryMutation('proof-level-partition', 'Integration/proof', [
1264
+ ['proof level (`source`/`installed`/`live`)', 'proof level'],
1265
+ ]),
515
1266
  {
516
1267
  name: 'concrete-named-probe', issue: 'proof-chain', source: 'templates',
517
1268
  from: IMPLEMENTATION_READINESS_CLAUSES.proofPlanLines[1],
518
1269
  to: '- Named probe: <suite label>',
519
1270
  },
1271
+ {
1272
+ name: 'aggregate-suite-probe-owner', issue: 'proof-chain', source: 'templates',
1273
+ from: IMPLEMENTATION_READINESS_CLAUSES.namedProbeOwnership,
1274
+ to: 'Aggregate suites may cite only the suite label.',
1275
+ },
1276
+ {
1277
+ name: 'reachability-levels', issue: 'proof-chain', source: 'templates',
1278
+ from: IMPLEMENTATION_READINESS_CLAUSES.proofPlanLines[2],
1279
+ to: '- Reachability: <entrypoint or consumer>',
1280
+ },
1281
+ {
1282
+ name: 'proof-level-separation', issue: 'proof-chain', source: 'templates',
1283
+ from: IMPLEMENTATION_READINESS_CLAUSES.proofLevelSeparation,
1284
+ to: 'Proof may be promoted between source, installed, and live levels.',
1285
+ },
1286
+ {
1287
+ name: 'disposable-template-negative-controls', issue: 'proof-chain', source: 'templates',
1288
+ from: IMPLEMENTATION_READINESS_CLAUSES.disposableTemplateControls,
1289
+ to: 'Run mutation or destructive negative controls against the available project copy.',
1290
+ },
1291
+ {
1292
+ name: 'disposable-review-negative-controls', issue: 'proof-chain', source: 'review',
1293
+ from: IMPLEMENTATION_READINESS_CLAUSES.disposableReviewControls,
1294
+ to: 'Run mutation or destructive negative controls against the available project copy.',
1295
+ },
1296
+ {
1297
+ name: 'required-proof-level-mapping', issue: 'proof-chain', source: 'templates',
1298
+ from: IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
1299
+ to: 'Map one required proof level to one probe; other levels may remain implicit.',
1300
+ },
520
1301
  {
521
1302
  name: 'artifact-path-and-digest-or-ephemeral', issue: 'proof-chain', source: 'templates',
522
1303
  from: IMPLEMENTATION_READINESS_CLAUSES.proofPlanLines[5],
523
1304
  to: '- Artifacts: <required artifact path, or none>',
524
1305
  },
1306
+ {
1307
+ name: 'proof-counterexample', issue: 'proof-chain', source: 'templates',
1308
+ from: IMPLEMENTATION_READINESS_CLAUSES.proofPlanLines[4],
1309
+ to: '- Counterexample: <example>',
1310
+ },
1311
+ {
1312
+ name: 'repair-and-proof-columns', issue: 'repair-closure', source: 'review',
1313
+ from: '| ID | Decision | Original counterexample | Repaired at | Proved at | Replay | Closure |',
1314
+ to: '| ID | Decision | Original counterexample | Repaired at | Evidence | Replay | Closure |',
1315
+ },
1316
+ {
1317
+ name: 'fresh-original-counterexample-replay', issue: 'repair-closure', source: 'review',
1318
+ 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:',
1319
+ to: 'After applying an accepted C2 finding, record the repair under this review-log header:',
1320
+ },
525
1321
  {
526
1322
  name: 'distinct-repair-and-proof-evidence', issue: 'repair-closure', source: 'review',
527
1323
  from: IMPLEMENTATION_READINESS_CLAUSES.distinctRepairProof,
528
1324
  to: '`Repaired at` and `Proved at` may cite the same repair edit.',
529
1325
  },
1326
+ {
1327
+ name: 'unknown-blocks-handoff', issue: 'repair-closure', source: 'review',
1328
+ from: IMPLEMENTATION_READINESS_CLAUSES.unknownBlocks,
1329
+ to: '`PASS` closes it; `FAIL` and `UNKNOWN` may continue to implementation handoff.',
1330
+ },
530
1331
  {
531
1332
  name: 'crash-versus-catchable-failure', issue: 'failure-semantics', source: 'templates',
532
1333
  from: IMPLEMENTATION_READINESS_CLAUSES.failureSemantics,
@@ -538,6 +1339,7 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
538
1339
  to: 'Any privacy/security claim names the sensitive data at risk.',
539
1340
  },
540
1341
  ];
1342
+ assert.equal(mutations.length, 30, `${platform} readiness mutation inventory drifted`);
541
1343
  assertInstalledSpecsReadiness(project, platform);
542
1344
  for (const mutation of mutations) {
543
1345
  const anchorIndex = baseline[mutation.source].indexOf(mutation.from);
@@ -552,10 +1354,165 @@ function assertInstalledReadinessMutationsDetected(tempRoot, project, platform)
552
1354
  context.writeInstalled(mutation.source, weakened, `${mutation.name} mutation`);
553
1355
  assertInstalledSpecsReadiness(project, platform, [mutation.issue]);
554
1356
  } finally {
555
- context.writeInstalled(mutation.source, baseline[mutation.source], `${mutation.name} restoration`);
1357
+ context.writeInstalled(mutation.source, baselineBytes[mutation.source], `${mutation.name} restoration`);
556
1358
  }
1359
+ assert.deepEqual(
1360
+ fs.readFileSync(context.installedPaths[mutation.source]),
1361
+ baselineBytes[mutation.source],
1362
+ `${platform} ${mutation.name} must restore exact installed bytes`
1363
+ );
557
1364
  assertInstalledSpecsReadiness(project, platform);
558
1365
  }
1366
+
1367
+ const integrationRow = IMPLEMENTATION_READINESS_BOUNDARY_ROWS
1368
+ .find(([label]) => label === 'Integration/proof');
1369
+ const integrationLine = `| ${integrationRow.join(' | ')} |`;
1370
+ const openBoundary = baseline.templates.replace(
1371
+ integrationLine,
1372
+ `${integrationLine}\n| other:domain-specific | task-specific material choices and observable oracle |`
1373
+ );
1374
+ assert.notEqual(openBoundary, baseline.templates, `${platform} readiness positive control must apply`);
1375
+ try {
1376
+ context.writeInstalled('templates', openBoundary, 'readiness open-boundary positive control');
1377
+ assertInstalledSpecsReadiness(project, platform);
1378
+ } finally {
1379
+ context.writeInstalled('templates', baselineBytes.templates, 'readiness open-boundary restoration');
1380
+ }
1381
+ assert.deepEqual(
1382
+ fs.readFileSync(context.installedPaths.templates),
1383
+ baselineBytes.templates,
1384
+ `${platform} readiness positive control must restore exact installed bytes`
1385
+ );
1386
+ context.assertSourceHashes('after readiness mutations and positive control');
1387
+ }
1388
+
1389
+ function assertInstalledAdaptiveMutationsDetected(tempRoot, project, platform) {
1390
+ const context = installedMutationContext(tempRoot, project, platform);
1391
+ const baselineBytes = Object.fromEntries(
1392
+ Object.entries(context.installedPaths).map(([source, target]) => [source, fs.readFileSync(target)])
1393
+ );
1394
+ const baseline = installedAdaptiveCoverageSources(project, platform);
1395
+ const mutations = [
1396
+ ['frontmatter-risk-bypass', 'skill', ADAPTIVE_COVERAGE_CLAUSES.frontmatterGate,
1397
+ 'skip for any clear one-file or two-file change.', ['risk-first-routing']],
1398
+ ['destructive-routine-direct', 'skill', ADAPTIVE_COVERAGE_CLAUSES.directGate,
1399
+ 'Work directly when the change is routine and likely limited to one or two files.', ['risk-first-routing']],
1400
+ ['destructive-direct-exception', 'skill', ADAPTIVE_COVERAGE_CLAUSES.directGate,
1401
+ `${ADAPTIVE_COVERAGE_CLAUSES.directGate} Exception: destructive one-file work labeled routine may go direct.`, ['risk-first-routing']],
1402
+ ['user-risk-downgrade', 'skill', ADAPTIVE_COVERAGE_CLAUSES.riskFirst,
1403
+ `${ADAPTIVE_COVERAGE_CLAUSES.riskFirst} A user may lower critical risk to routine.`, ['risk-first-routing']],
1404
+ ['four-subsystem-split', 'skill', ADAPTIVE_COVERAGE_CLAUSES.splitRoute,
1405
+ 'Split four or more independent subsystems; otherwise use one Specs packet for substantial work.', ['risk-first-routing']],
1406
+ ['forced-single-kind', 'templates', ADAPTIVE_COVERAGE_CLAUSES.openKinds,
1407
+ 'Choose one primary change kind and ignore unfamiliar kinds or surfaces.', ['coverage-profile-shape']],
1408
+ ['missing-profile-column', 'templates', '| ID | Outcome | Change kinds | Material surfaces | Ambiguity/action | Risk/evidence | Required proof |',
1409
+ '| ID | Outcome | Change kinds | Material surfaces | Risk/evidence | Required proof |', ['coverage-profile-shape']],
1410
+ ['duplicate-profile-heading', 'templates', '## Coverage profile\n',
1411
+ '## Coverage profile\n\n## Coverage profile\n', ['coverage-profile-shape']],
1412
+ ['truncated-profile-row', 'templates', `| ${ADAPTIVE_COVERAGE_PROFILE_ROW.join(' | ')} |`,
1413
+ '| CP-01 | <externally observable outcome> |', ['coverage-profile-shape']],
1414
+ ['examples-promote-to-design', 'templates', `| ${ADAPTIVE_COVERAGE_AMBIGUITY_ROWS[2].join(' | ')} |`,
1415
+ '| `examples-needed` | add examples and promote to `design-needed` if behavior changes |', ['ambiguity-actions']],
1416
+ ['examples-select-retention', 'templates', 'retention of 30 versus 90 days is\n`decision-needed`',
1417
+ 'retention of 30 versus 90 days may remain\n`examples-needed`', ['ambiguity-actions']],
1418
+ ['global-critical-ceremony', 'templates', ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
1419
+ 'Each task copies its CP values; authoring, review, edge, and proof obligations union across every task.', ['scoped-coverage']],
1420
+ ['scoped-union-contradiction', 'templates', ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
1421
+ `${ADAPTIVE_COVERAGE_CLAUSES.scopedUnion} Exception: critical proof obligations apply across every CP row.`, ['scoped-coverage']],
1422
+ ['stale-profile-after-c2', 'templates', ADAPTIVE_COVERAGE_CLAUSES.profileRederivation,
1423
+ 'Keep existing coverage rows after accepted plan changes.', ['profile-lifecycle']],
1424
+ ['planned-proof-blocks-start', 'templates', ADAPTIVE_COVERAGE_CLAUSES.plannedProof,
1425
+ '`Required proof` is execution evidence: known but unrun proof blocks `pending`; `UNKNOWN` may proceed; missing evidence may still reach `done`/C3.', ['proof-lifecycle']],
1426
+ ['source-promotes-live', 'templates', ADAPTIVE_COVERAGE_CLAUSES.proofSeparation,
1427
+ 'Source proof may promote installed and live proof.', ['proof-lifecycle']],
1428
+ ['unmapped-proof-level', 'templates', IMPLEMENTATION_READINESS_CLAUSES.proofLevelMapping,
1429
+ 'A task may map only one required proof level to its probe.', ['proof-lifecycle']],
1430
+ ['critical-reviewer-omitted', 'review', ADAPTIVE_COVERAGE_CLAUSES.reviewRisk,
1431
+ 'Keep Fact Checker as the baseline and choose any remaining reviewer; critical rows need no matching risk role.', ['reviewer-routing', 'scoped-coverage']],
1432
+ ['reviewer-lens-overflow', 'review', ADAPTIVE_COVERAGE_CLAUSES.reviewCapacity,
1433
+ 'Each reviewer owns exactly one lens; skip excess material lenses when the fixed reviewer count is full.', ['reviewer-routing']],
1434
+ ['spec-maker-local-taxonomy', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAuthority,
1435
+ 'this agent owns a separate risk and coverage taxonomy.', ['spec-maker-authority']],
1436
+ ['spec-maker-examples-decide', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerAmbiguity,
1437
+ 'Use examples to settle every ambiguous observable behavior.', ['spec-maker-authority']],
1438
+ ['spec-maker-skips-brainstorm', 'specMaker', ADAPTIVE_COVERAGE_CLAUSES.specMakerRoute,
1439
+ 'Apply the risk-first route before C1 and stop only for direct work.', ['spec-maker-authority']],
1440
+ ['static-proves-live', 'skill', ADAPTIVE_COVERAGE_CLAUSES.liveLimit,
1441
+ 'Source/static checks prove live-model adherence.', ['proof-lifecycle']],
1442
+ ];
1443
+ assert.equal(mutations.length, 23, `${platform} adaptive mutation inventory drifted`);
1444
+ assertInstalledAdaptiveCoverage(project, platform);
1445
+
1446
+ const renderMutation = (source, value) => {
1447
+ if (platform === 'codex' && source === 'specMaker') {
1448
+ return Buffer.from(rewriteGeneratedTomlString(
1449
+ baselineBytes.specMaker.toString('utf8'),
1450
+ 'developer_instructions',
1451
+ value
1452
+ ));
1453
+ }
1454
+ return Buffer.from(value);
1455
+ };
1456
+ for (const [name, source, from, to, expected] of mutations) {
1457
+ const anchorIndex = baseline[source].indexOf(from);
1458
+ assert.notEqual(anchorIndex, -1, `${platform} ${name} mutation anchor must exist`);
1459
+ assert.equal(
1460
+ baseline[source].indexOf(from, anchorIndex + from.length),
1461
+ -1,
1462
+ `${platform} ${name} mutation anchor must be unique`
1463
+ );
1464
+ const weakened = `${baseline[source].slice(0, anchorIndex)}${to}${baseline[source].slice(anchorIndex + from.length)}`;
1465
+ const weakenedBytes = renderMutation(source, weakened);
1466
+ try {
1467
+ context.writeInstalled(source, weakenedBytes, `${name} adaptive mutation`);
1468
+ if (platform === 'codex' && source === 'specMaker') {
1469
+ assert.equal(
1470
+ parseGeneratedTomlString(fs.readFileSync(context.installedPaths.specMaker, 'utf8'), 'developer_instructions'),
1471
+ weakened,
1472
+ `${platform} ${name} must rewrite only parsed developer_instructions`
1473
+ );
1474
+ }
1475
+ assertInstalledAdaptiveCoverage(project, platform, [...expected].sort());
1476
+ } finally {
1477
+ context.writeInstalled(source, baselineBytes[source], `${name} adaptive restoration`);
1478
+ }
1479
+ assert.deepEqual(
1480
+ fs.readFileSync(context.installedPaths[source]),
1481
+ baselineBytes[source],
1482
+ `${platform} ${name} must restore exact installed bytes`
1483
+ );
1484
+ assertInstalledAdaptiveCoverage(project, platform);
1485
+ }
1486
+
1487
+ const integrationRow = IMPLEMENTATION_READINESS_BOUNDARY_ROWS
1488
+ .find(([label]) => label === 'Integration/proof');
1489
+ const integrationLine = `| ${integrationRow.join(' | ')} |`;
1490
+ const validVariants = [
1491
+ ['open material surface', baseline.templates.replace(
1492
+ integrationLine,
1493
+ `${integrationLine}\n| other:domain-specific | task-specific material choices and observable oracle |`
1494
+ )],
1495
+ ['task CP reference requirement', baseline.templates.replace(
1496
+ ADAPTIVE_COVERAGE_CLAUSES.scopedUnion,
1497
+ `${ADAPTIVE_COVERAGE_CLAUSES.scopedUnion} Require every task to reference a CP row.`
1498
+ )],
1499
+ ];
1500
+ for (const [name, templates] of validVariants) {
1501
+ assert.notEqual(templates, baseline.templates, `${platform} ${name} positive control must apply`);
1502
+ try {
1503
+ context.writeInstalled('templates', Buffer.from(templates), `${name} adaptive positive control`);
1504
+ assertInstalledAdaptiveCoverage(project, platform);
1505
+ } finally {
1506
+ context.writeInstalled('templates', baselineBytes.templates, `${name} adaptive restoration`);
1507
+ }
1508
+ assert.deepEqual(
1509
+ fs.readFileSync(context.installedPaths.templates),
1510
+ baselineBytes.templates,
1511
+ `${platform} ${name} must restore exact installed bytes`
1512
+ );
1513
+ }
1514
+ assertInstalledSpecsReadiness(project, platform);
1515
+ context.assertSourceHashes('after adaptive mutations and positive controls');
559
1516
  }
560
1517
 
561
1518
  function assertPristineUpgradeAndUserPreservation(installer, tempRoot, project, platform) {
@@ -780,8 +1737,8 @@ function assertCombinedInstructionIsolation(root) {
780
1737
  [claude, '<!-- CAFEKIT CLAUDE START -->']
781
1738
  ]) assert.equal((content.match(new RegExp(marker, 'g')) || []).length, 1);
782
1739
 
783
- assert.doesNotMatch(core, /Claude|Codex|\.claude|\.codex|\/hapo:|\$hapo-/i);
784
- assert.doesNotMatch(claudeBlock, /Codex|\.codex|\$hapo-/i);
1740
+ assert.doesNotMatch(core, /Claude|Codex|\.claude|\.codex|\/cf:|\$cf-/i);
1741
+ assert.doesNotMatch(claudeBlock, /Codex|\.codex|\$cf-/i);
785
1742
  assert.match(codexBlock, /native project instruction surface is root `AGENTS\.md`/);
786
1743
  assert.match(agents, /shared-root trade-off is intentional/);
787
1744
  // H5 ownership/ignore contract
@@ -790,7 +1747,7 @@ function assertCombinedInstructionIsolation(root) {
790
1747
  assert.match(codexBlock, /owned by Codex/i);
791
1748
  assert.match(codexBlock, /If you are Claude Code or any other runtime, ignore this entire Codex block/i);
792
1749
  assert.doesNotMatch(codexBlock, /If you are Codex CLI or any other runtime, ignore this entire Codex block/i);
793
- assert.doesNotMatch(core, /\$hapo-|hapo:/i);
1750
+ assert.doesNotMatch(core, /\$cf-|cf:/i);
794
1751
  assert.doesNotMatch(claudeBlock, /<!-- CAFEKIT CODEX /);
795
1752
  }
796
1753
  function stableInstallSnapshot(root, platforms) {
@@ -1285,14 +2242,26 @@ function assertStaleDigestMutations(paths, root, taskless, taskBearing) {
1285
2242
  }
1286
2243
 
1287
2244
  function assertTransforms(root, platform) {
1288
- const skill = path.join(root, RUNTIMES[platform].root === '.codex' ? '.agents/skills' : `${RUNTIMES[platform].root}/skills`, 'develop', 'SKILL.md');
2245
+ const skillRoot = path.join(
2246
+ root,
2247
+ RUNTIMES[platform].root === '.codex' ? '.agents/skills' : `${RUNTIMES[platform].root}/skills`
2248
+ );
2249
+ const skill = path.join(skillRoot, 'develop', 'SKILL.md');
1289
2250
  const content = fs.readFileSync(skill, 'utf8');
2251
+ const scout = fs.readFileSync(path.join(skillRoot, 'inspect', 'SKILL.md'), 'utf8');
2252
+ const ask = fs.readFileSync(path.join(skillRoot, 'question', 'SKILL.md'), 'utf8');
1290
2253
  if (platform === 'codex') {
1291
- assert.match(content, /\$hapo-develop/);
1292
- assert.doesNotMatch(content, /\/hapo:develop/);
2254
+ assert.match(content, /\$cf-develop/);
2255
+ assert.doesNotMatch(content, /\/cf:develop/);
2256
+ assert.match(scout, /^name:\s*cf-scout$/m);
2257
+ assert.match(ask, /^name:\s*cf-ask$/m);
1293
2258
  } else {
1294
- assert.match(content, /\/hapo:develop/);
2259
+ assert.match(content, /\/cf:develop/);
2260
+ assert.match(scout, /^name:\s*cf:scout$/m);
2261
+ assert.match(ask, /^name:\s*cf:ask$/m);
1295
2262
  }
2263
+ assert.equal(fs.existsSync(path.join(skillRoot, 'scout')), false);
2264
+ assert.equal(fs.existsSync(path.join(skillRoot, 'ask')), false);
1296
2265
  assert.ok(fs.existsSync(path.join(root, RUNTIMES[platform].root, 'agents')) || platform === 'codex');
1297
2266
  }
1298
2267
 
@@ -1305,7 +2274,115 @@ test('npm dry-run inventory is deterministic and preserves runtime payload', ()
1305
2274
  assertCleanInventory(firstInventory);
1306
2275
  });
1307
2276
 
1308
- test('packed tarball installer matrix proves locale, transforms, paths, and rerun safety', () => {
2277
+ test('packed core-only installs reject optional capability routing', () => {
2278
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-core-'));
2279
+ const destination = path.join(root, 'pack');
2280
+ fs.mkdirSync(destination, { recursive: true });
2281
+ try {
2282
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2283
+ const tarball = path.join(destination, packed.filename);
2284
+ const inventory = packedInventory(tarball);
2285
+ assertCleanInventory(inventory);
2286
+ for (const required of [
2287
+ 'src/claude/skills/route/SKILL.md',
2288
+ 'src/claude/skills/route/references/task-taxonomy.md',
2289
+ 'src/claude/skills/route/references/chaining-patterns.md',
2290
+ 'src/claude/skills/route/references/agent-timing.md',
2291
+ ]) assert.ok(inventory.includes(required), required);
2292
+
2293
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2294
+ const project = path.join(root, 'project');
2295
+ const installer = installPacked(tarball, project, runtimeClosure);
2296
+ runInstaller(installer, project, ['claude', 'codex'], null);
2297
+ for (const platform of ['claude', 'codex']) {
2298
+ const runtimeRoot = platform === 'codex' ? '.codex' : '.claude';
2299
+ const script = path.join(project, runtimeRoot, 'scripts/generate-skill-catalog.cjs');
2300
+ const catalogResult = spawnSync(process.execPath, [script, '--json'], {
2301
+ cwd: project, encoding: 'utf8',
2302
+ });
2303
+ assert.equal(catalogResult.status, 0, catalogResult.stderr);
2304
+ const catalog = JSON.parse(catalogResult.stdout);
2305
+ assert.ok(catalog.skills.some((skill) => skill.public_id === 'cf:route'));
2306
+ assert.equal(catalog.skills.some((skill) => skill.public_id === 'cf:docs'), false);
2307
+ assert.deepEqual(routeProjectionIssues(installedRouteFiles(project, platform)), []);
2308
+ }
2309
+ const domain = fs.readFileSync(path.join(project, '.claude/rules/skill-domain-routing.md'), 'utf8');
2310
+ assert.doesNotMatch(domain, /\/cf:(?:docs|docx|pdf|pptx|xlsx|ai-multimodal)/);
2311
+ } finally {
2312
+ fs.rmSync(root, { recursive: true, force: true });
2313
+ }
2314
+ });
2315
+
2316
+ test('packed Route rejects semantic routing weakenings', () => {
2317
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-route-mutations-'));
2318
+ const destination = path.join(root, 'pack');
2319
+ fs.mkdirSync(destination, { recursive: true });
2320
+ try {
2321
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2322
+ const tarball = path.join(destination, packed.filename);
2323
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2324
+ for (const platform of ['claude', 'codex']) {
2325
+ const project = path.join(root, platform);
2326
+ const installer = installPacked(tarball, project, runtimeClosure);
2327
+ runInstaller(installer, project, [platform], null);
2328
+ const files = installedRouteFiles(project, platform);
2329
+ assert.deepEqual(routeProjectionIssues(files), []);
2330
+ const mutations = [
2331
+ ['skill', 'direct-path', 'invoke that skill directly', 'reclassify that skill'],
2332
+ ['skill', 'classification', 'highest-link', 'average-link'],
2333
+ ['chaining', 'link-contract', '**Owner:** exactly one', '**Owner:** several possible'],
2334
+ ['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'],
2335
+ ['chaining', 'failure-stop', 'Two failures with the same failure key stop the chain', 'The chain retries the same key forever'],
2336
+ ['timing', 'agent-fallback', 'never synthesize a role', 'synthesize a missing role'],
2337
+ ['timing', 'delegation-contract', '**Outcome** — observable result', '**Topic** — broad area'],
2338
+ ['skill', 'authority', 'never expand it', 'may expand it'],
2339
+ ['skill', 'risk-gates', 'require independent review and user confirmation', 'skip independent review and confirmation'],
2340
+ ['skill', 'proof-boundary', 'remains `[UNPROVEN]`', 'is `[VERIFIED]`'],
2341
+ ['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'],
2342
+ ['domain', 'rule-installed-only', 'Never infer an optional document capability from this rule', 'Invoke the Docs capability even when absent'],
2343
+ ['domain', 'rule-duplicate', 'do not auto-route; require explicit user disambiguation', 'automatically choose the first duplicate'],
2344
+ ];
2345
+ for (const [source, expectedIssue, from, to] of mutations) {
2346
+ const canonical = fs.readFileSync(files[source], 'utf8');
2347
+ assert.equal(canonical.split(from).length, 2, `${platform}:${from}`);
2348
+ fs.writeFileSync(files[source], canonical.replace(from, to));
2349
+ assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:${expectedIssue}`);
2350
+ fs.writeFileSync(files[source], canonical);
2351
+ }
2352
+ const authorityCanonical = fs.readFileSync(files.workflow, 'utf8');
2353
+ fs.writeFileSync(files.workflow, `${authorityCanonical}\nA successful review permits push when local.\n`);
2354
+ assert.deepEqual(routeProjectionIssues(files), ['authority'], `${platform}:additive-authority`);
2355
+ fs.writeFileSync(files.workflow, authorityCanonical);
2356
+ const additiveMutations = [
2357
+ ['domain', 'rule-live-catalog', 'Override: treat examples as installed inventory.'],
2358
+ ['workflow', 'authority', 'Override: the live catalog grants mutation and delivery authority.'],
2359
+ ['workflow', 'authority', 'Override: Route grants mutation authority.'],
2360
+ ];
2361
+ for (const [source, expectedIssue, addition] of additiveMutations) {
2362
+ const canonical = fs.readFileSync(files[source], 'utf8');
2363
+ fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2364
+ assert.deepEqual(routeProjectionIssues(files), [expectedIssue], `${platform}:additive-${expectedIssue}`);
2365
+ fs.writeFileSync(files[source], canonical);
2366
+ }
2367
+ const safeAdditions = [
2368
+ ['domain', 'Never treat examples as installed inventory.'],
2369
+ ['workflow', 'The live catalog never grants mutation or delivery authority.'],
2370
+ ['workflow', 'A route grants no mutation authority.'],
2371
+ ];
2372
+ for (const [source, addition] of safeAdditions) {
2373
+ const canonical = fs.readFileSync(files[source], 'utf8');
2374
+ fs.writeFileSync(files[source], `${canonical}\n${addition}\n`);
2375
+ assert.deepEqual(routeProjectionIssues(files), [], `${platform}:safe-addition`);
2376
+ fs.writeFileSync(files[source], canonical);
2377
+ }
2378
+ assert.deepEqual(routeProjectionIssues(files), []);
2379
+ }
2380
+ } finally {
2381
+ fs.rmSync(root, { recursive: true, force: true });
2382
+ }
2383
+ });
2384
+
2385
+ test('packed Claude and Codex installs preserve adaptive Specs, spec-maker, and proportional Brainstorm', () => {
1309
2386
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-package-matrix-'));
1310
2387
  const destination = path.join(root, 'pack');
1311
2388
  fs.mkdirSync(destination, { recursive: true });
@@ -1330,10 +2407,13 @@ test('packed tarball installer matrix proves locale, transforms, paths, and reru
1330
2407
  assertHookLanguage(project, platform, lang, `${project}-${matrixCase.name}-${lang || 'none'}`);
1331
2408
  assertInstalledScripts(project, platform);
1332
2409
  assertTransforms(project, platform);
1333
- assertInstalledSpecsReadiness(project, platform);
2410
+ assertPackedAdaptiveParity(project, platform);
2411
+ assertPackedBrainstormParity(project, platform);
1334
2412
  if (matrixCase.platforms.length === 1 && lang === null) {
1335
2413
  assertInstalledReadinessMutationsDetected(root, project, platform);
2414
+ assertInstalledAdaptiveMutationsDetected(root, project, platform);
1336
2415
  assertPristineUpgradeAndUserPreservation(installer, root, project, platform);
2416
+ assertPackedAdaptiveParity(project, platform);
1337
2417
  }
1338
2418
  }
1339
2419
  if (matrixCase.platforms.includes('claude') && matrixCase.platforms.includes('codex')) {
@@ -1348,7 +2428,10 @@ test('packed tarball installer matrix proves locale, transforms, paths, and reru
1348
2428
  const changed = [...new Set([...Object.keys(before), ...Object.keys(after)])]
1349
2429
  .filter((file) => !before[file] || !after[file] || !before[file].equals(after[file]));
1350
2430
  assert.deepEqual(changed, [], `${matrixCase.name} rerun must be byte-idempotent`);
1351
- for (const platform of matrixCase.platforms) assertInstalledSpecsReadiness(project, platform);
2431
+ for (const platform of matrixCase.platforms) {
2432
+ assertPackedAdaptiveParity(project, platform);
2433
+ assertPackedBrainstormParity(project, platform);
2434
+ }
1352
2435
  assertCombinedInstructionIsolation(project);
1353
2436
  assert.match(fs.readFileSync(path.join(project, 'AGENTS.md'), 'utf8'), /User matrix note\nKeep this exact\./);
1354
2437
  assert.match(fs.readFileSync(path.join(project, 'CLAUDE.md'), 'utf8'), /User Claude matrix note\nKeep this exact\./);
@@ -1360,6 +2443,560 @@ test('packed tarball installer matrix proves locale, transforms, paths, and reru
1360
2443
  }
1361
2444
  });
1362
2445
 
2446
+ test('packed Claude and Codex installs preserve bounded Loop safety and routing', () => {
2447
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-loop-'));
2448
+ const destination = path.join(root, 'pack');
2449
+ fs.mkdirSync(destination, { recursive: true });
2450
+ try {
2451
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2452
+ const tarball = path.join(destination, packed.filename);
2453
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2454
+ assertCleanInventory(packedInventory(tarball));
2455
+ for (const platform of ['claude', 'codex']) {
2456
+ const project = path.join(root, platform);
2457
+ const installer = installPacked(tarball, project, runtimeClosure);
2458
+ runInstaller(installer, project, [platform], null);
2459
+ assertPackedResearchLoopParity(project, platform);
2460
+ assert.equal(fs.existsSync(path.join(
2461
+ project, platform === 'codex' ? '.agents/skills/autoresearch' : '.claude/skills/autoresearch'
2462
+ )), false);
2463
+ }
2464
+ } finally {
2465
+ fs.rmSync(root, { recursive: true, force: true });
2466
+ }
2467
+ });
2468
+
2469
+ test('packed Research and Loop reject semantic weakenings', () => {
2470
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-research-loop-mutations-'));
2471
+ const destination = path.join(root, 'pack');
2472
+ fs.mkdirSync(destination, { recursive: true });
2473
+ const canonicalBytes = new Map(Object.values(RESEARCH_LOOP_SOURCE_RELATIVES).map((relative) => {
2474
+ const sourcePath = path.join(PACKAGE_ROOT, relative);
2475
+ return [sourcePath, fs.readFileSync(sourcePath)];
2476
+ }));
2477
+ const mutations = [
2478
+ ['research', 'research-adaptive-evidence', 'Use delegated researchers only as optional acceleration', 'Delegation is required for every research request'],
2479
+ ['research', 'research-adaptive-evidence', 'Default to a concise answer in chat.', 'Persist every answer before returning chat output.'],
2480
+ ['loop', 'loop-safety', 'Loop is explicit-only.', 'Loop may be auto-routed.'],
2481
+ ['loop', 'loop-safety', 'separately approved external', 'worktree-contained'],
2482
+ ['loop', 'loop-safety', 'complete detached-worktree tracked/untracked manifest', 'scoped manifest'],
2483
+ ['metric', 'metric-guard', 'Guard is mandatory,', 'Guard may be optional,'],
2484
+ ['metric', 'metric-guard', '`numeric_format`: exactly IEEE-754 binary64', '`numeric_format`: implementation-defined'],
2485
+ ['metric', 'metric-guard', 'nonzero exit', 'nonzero value'],
2486
+ ['protocol', 'failure-handoff', 'do not clean. Preserve the exact path', 'clean the uncertain path and continue'],
2487
+ ['protocol', 'failure-handoff', '`base_oid`;', '`optional_base`;'],
2488
+ ['workflow', 'explicit-routing', 'must never be auto-routed', 'should be auto-routed when optimization seems useful'],
2489
+ ];
2490
+ try {
2491
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2492
+ const tarball = path.join(destination, packed.filename);
2493
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2494
+ for (const platform of ['claude', 'codex']) {
2495
+ const project = path.join(root, platform);
2496
+ const installer = installPacked(tarball, project, runtimeClosure);
2497
+ runInstaller(installer, project, [platform], null);
2498
+ assertPackedResearchLoopParity(project, platform);
2499
+ const paths = packedResearchLoopPaths(project, platform);
2500
+ for (const [source, expectedIssue, from, to] of mutations) {
2501
+ const target = fs.realpathSync(paths[source]);
2502
+ const relative = path.relative(fs.realpathSync(project), target);
2503
+ assert.ok(relative && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
2504
+ const stat = fs.lstatSync(target);
2505
+ assert.equal(stat.isFile(), true);
2506
+ assert.equal(stat.isSymbolicLink(), false);
2507
+ assert.equal(stat.nlink, 1);
2508
+ const original = fs.readFileSync(target);
2509
+ const content = original.toString('utf8');
2510
+ const anchor = content.indexOf(from);
2511
+ assert.ok(anchor >= 0, `${platform}/${source} missing mutation anchor: ${from}`);
2512
+ assert.equal(content.indexOf(from, anchor + from.length), -1, `${platform}/${source} duplicate mutation anchor`);
2513
+ try {
2514
+ fs.writeFileSync(target, `${content.slice(0, anchor)}${to}${content.slice(anchor + from.length)}`);
2515
+ assert.deepEqual(packedResearchLoopIssues(project, platform), [expectedIssue]);
2516
+ for (const [sourcePath, bytes] of canonicalBytes) assert.deepEqual(fs.readFileSync(sourcePath), bytes);
2517
+ } finally {
2518
+ fs.writeFileSync(target, original);
2519
+ }
2520
+ assert.deepEqual(packedResearchLoopIssues(project, platform), []);
2521
+ }
2522
+ }
2523
+ } finally {
2524
+ fs.rmSync(root, { recursive: true, force: true });
2525
+ }
2526
+ });
2527
+
2528
+ test('repository and package guides document adaptive Research and bounded Loop', () => {
2529
+ const guides = {
2530
+ repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
2531
+ package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2532
+ };
2533
+ for (const [name, guide] of Object.entries(guides)) {
2534
+ assert.match(guide, /cf:research/, `${name} names Claude Research`);
2535
+ assert.match(guide, /cf:loop/, `${name} names Claude Loop`);
2536
+ assert.match(guide, /\$cf-research/, `${name} names Codex Research`);
2537
+ assert.match(guide, /\$cf-loop/, `${name} names Codex Loop`);
2538
+ assert.match(guide, /Quick, Standard, or Deep/, `${name} documents adaptive Research depth`);
2539
+ assert.match(guide, /explicit-only|never selected automatically/i, `${name} keeps Loop explicit-only`);
2540
+ for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'minimum delta', 'budget']) {
2541
+ assert.match(guide, new RegExp(field, 'i'), `${name} documents Loop field ${field}`);
2542
+ }
2543
+ assert.match(guide, /stop\s+conditions/i, `${name} documents Loop stop conditions`);
2544
+ assert.match(guide, /detached worktree/, `${name} documents Loop isolation`);
2545
+ assert.match(guide, /base-bound (?:isolated )?patch handoff/, `${name} documents Loop handoff`);
2546
+ assert.match(guide, /does not .{0,80}guarantee|never .{0,80}guarantee/is, `${name} rejects guarantees`);
2547
+ assert.doesNotMatch(guide, /cf[:-]autoresearch/, `${name} does not advertise Autoresearch`);
2548
+ }
2549
+
2550
+ const catalog = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/catalog-visuals.tsx'), 'utf8');
2551
+ const overview = fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../cafekit-web/src/components/docs/skill-overview.tsx'), 'utf8');
2552
+ assert.match(catalog, /\['Bounded optimization', \['loop'\]\]/);
2553
+ assert.match(catalog, /proportional, traceable evidence/);
2554
+ assert.match(overview, /\['cf:research'/);
2555
+ assert.match(overview, /\['cf:loop'/);
2556
+ for (const field of ['Goal', 'Scope', 'Metric', 'Direction', 'Baseline', 'Guard', 'noise policy', 'minimum delta', 'budget']) {
2557
+ assert.match(overview, new RegExp(field, 'i'), `website documents Loop field ${field}`);
2558
+ }
2559
+ assert.match(overview, /stop conditions/);
2560
+ assert.match(overview, /base-bound isolated patch handoff/);
2561
+ assert.match(overview, /without guaranteed improvement/);
2562
+ assert.doesNotMatch(`${catalog}\n${overview}`, /autoresearch/i);
2563
+ });
2564
+
2565
+ test('packed Claude and Codex reject adaptive Brainstorm semantic weakenings', () => {
2566
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-brainstorm-adaptive-'));
2567
+ const destination = path.join(root, 'pack');
2568
+ fs.mkdirSync(destination, { recursive: true });
2569
+ const canonicalBytes = new Map(Object.values(BRAINSTORM_SOURCE_RELATIVES).map((relative) => {
2570
+ const sourcePath = path.join(PACKAGE_ROOT, relative);
2571
+ return [sourcePath, fs.readFileSync(sourcePath)];
2572
+ }));
2573
+ try {
2574
+ const configuredGroups = ADAPTIVE_BRAINSTORM_INSTALLED_RULES.map(({ group }) => group).sort();
2575
+ assert.deepEqual(configuredGroups, REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS);
2576
+ assert.equal(new Set(configuredGroups).size, 10, 'adaptive Brainstorm matrix must define 10 distinct groups');
2577
+ for (const rule of ADAPTIVE_BRAINSTORM_INSTALLED_RULES) {
2578
+ assert.ok(Array.isArray(rule.mutations) && rule.mutations.length > 0, `${rule.group} must define a nonempty mutation set`);
2579
+ }
2580
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2581
+ const tarball = path.join(destination, packed.filename);
2582
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2583
+ assertCleanInventory(packedInventory(tarball));
2584
+ const exercised = new Set();
2585
+ for (const platform of ['claude', 'codex']) {
2586
+ const project = path.join(root, platform);
2587
+ const installer = installPacked(tarball, project, runtimeClosure);
2588
+ runInstaller(installer, project, [platform], null);
2589
+ const publicDirectory = platform === 'claude'
2590
+ ? path.join(project, '.claude/skills/fix')
2591
+ : path.join(project, '.agents/skills/fix');
2592
+ assert.equal(fs.existsSync(publicDirectory), false,
2593
+ `${platform} public Fix rename keeps the manifest-owned hotfix directory`);
2594
+ assertPackedBrainstormParity(project, platform);
2595
+ for (const entry of assertPackedAdaptiveBrainstormMutations(project, platform, canonicalBytes)) exercised.add(entry);
2596
+ assertPackedBrainstormParity(project, platform);
2597
+ }
2598
+ assert.deepEqual(
2599
+ [...exercised].sort(),
2600
+ ['claude', 'codex'].flatMap((platform) => REQUIRED_ADAPTIVE_BRAINSTORM_GROUPS.map((group) => `${platform}:${group}`)).sort(),
2601
+ 'adaptive Brainstorm mutations must cover every platform and required group'
2602
+ );
2603
+ } finally {
2604
+ fs.rmSync(root, { recursive: true, force: true });
2605
+ }
2606
+ });
2607
+
2608
+ test('repository and package guides document adaptive Brainstorm usage', () => {
2609
+ const guides = {
2610
+ repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../docs/specs-usage-guide.md'), 'utf8'),
2611
+ package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2612
+ };
2613
+ for (const [name, guide] of Object.entries(guides)) {
2614
+ const normalized = guide.replace(/\s+/g, ' ');
2615
+ for (const flag of ['--deep', '--visual', '--advice']) assert.ok(guide.includes(flag), `${name} ${flag}`);
2616
+ assert.match(guide, /leading control segment/);
2617
+ assert.match(guide, /-- --dry-run/);
2618
+ assert.match(guide, /Direct.{0,80}(?:before|trước).{0,80}(?:overlay|control)/is);
2619
+ assert.match(guide, /unknown.{0,30}(?:or|hoặc).{0,30}duplicate/is);
2620
+ assert.match(guide, /(?:no action|không\s+thực\s+hiện\s+hành\s+động)/i);
2621
+ assert.match(guide, /(?:redact|redacted)/i);
2622
+ assert.match(guide, /(?:not (?:live proof|a live proof)|no\s+Brainstorm\s+output\s+is\s+live\s+proof|không\s+phải\s+live\s+proof)/i);
2623
+ assert.match(guide, /Specs\/Develop/);
2624
+ assert.match(guide, /(?:approval|execution authority)/i);
2625
+ assert.match(normalized, /single-use.{0,100}`--deep`.{0,100}`--visual`.{0,100}`--advice`.{0,100}(?:any order|mọi thứ tự)/i);
2626
+ assert.match(normalized, /`--`.{0,40}(?:ends controls|kết thúc control)/i);
2627
+ assert.match(normalized, /`--visual`.{0,100}(?:fallback|falls? back).{0,30}text/i);
2628
+ assert.match(normalized, /`--advice`.{0,140}(?:after a material choice exists|sau khi đã có material choice)/i);
2629
+ assert.match(normalized, /(?:durable file needs explicit user authority|ghi file cần explicit user authority)/i);
2630
+ assert.match(normalized, /(?:neither overlay|hai overlay).{0,140}(?:writes|write).{0,40}(?:approves|approve).{0,40}(?:persists|persist).{0,40}(?:dispatches|dispatch).{0,40}(?:completes|complete)/i);
2631
+ }
2632
+ });
2633
+
2634
+ function packedHotfixIssues(files, refPrefix) {
2635
+ const compact = (value) => String(value).replace(/\s+/g, ' ').trim();
2636
+ const skill = compact(files.skill);
2637
+ const review = compact(files.review);
2638
+ const parallel = compact(files.parallel);
2639
+ const specialized = compact(files.specialized);
2640
+ const issues = new Set();
2641
+ if (!skill.includes(`name: ${refPrefix}fix`)
2642
+ || !skill.includes('# Fix — root-cause repair workflow')
2643
+ || skill.includes(`name: ${refPrefix}hotfix`)) {
2644
+ issues.add('public-rename');
2645
+ }
2646
+ if (!skill.includes('## Proportional depth')
2647
+ || !skill.includes('Quick mode only reduces depth; it never skips scout, pre-fix evidence, diagnosis, or before/after verification.')) {
2648
+ issues.add('adaptive-depth');
2649
+ }
2650
+ if (!skill.includes('`Timeline: skipped - <reason>` or `- skipped: <reason>`')
2651
+ || !skill.includes(`routes back to diagnosis (\`${refPrefix}debug\`)`)) {
2652
+ issues.add('debug-handoff');
2653
+ }
2654
+ if (!skill.includes('report `PASS | PASS_WITH_WARNINGS | FAIL | BLOCKED`')
2655
+ || !skill.includes(`The definition of \`PASS\` defers to \`${refPrefix}code-review\`.`)
2656
+ || skill.includes('Confidence score')
2657
+ || !review.includes('Only `FAIL` and `PASS_WITH_WARNINGS` enter remediation retry')
2658
+ || !review.includes('only a fresh literal `PASS` enters finalization')
2659
+ || review.includes('"Approve anyway"')
2660
+ || review.includes('"Approve with known issues"')
2661
+ || review.includes('at most one Medium')) {
2662
+ issues.add('verdict-surface');
2663
+ }
2664
+ if (!skill.includes('The user explicitly requested or permitted delegation or parallel agents.')
2665
+ || !parallel.includes('Otherwise continue sequentially')) {
2666
+ issues.add('delegation-gate');
2667
+ }
2668
+ if (!skill.includes('The original symptom no longer reproduces with the exact pre-fix command/user flow.')
2669
+ || !skill.includes('Do not silently patch around the regression.')) {
2670
+ issues.add('side-effect-gate');
2671
+ }
2672
+ if (!skill.includes('## Bounded repair frame')
2673
+ || !skill.includes('Quick/local does not add a separate framing ceremony')) {
2674
+ issues.add('bounded-repair-frame');
2675
+ }
2676
+ if (!skill.includes('after diagnosis, research only unresolved external facts')
2677
+ || !skill.includes(`\`${refPrefix}brainstorm\` to compare 2-3 options`)
2678
+ || !skill.includes('When diagnosis leaves one safe direct repair, skip research and')) {
2679
+ issues.add('deep-decision-route');
2680
+ }
2681
+ if (!skill.includes('Load only the matching')
2682
+ || !specialized.includes('Load only the matching section')
2683
+ || (specialized.match(/\*\*Baseline:\*\*/g) || []).length < 5
2684
+ || (specialized.match(/\*\*Proof:\*\*/g) || []).length < 5) {
2685
+ issues.add('specialized-proof-overlays');
2686
+ }
2687
+ if (!parallel.includes('Diagnosis still starts only')
2688
+ || !parallel.includes('after the required scout outputs are synthesized')
2689
+ || !parallel.includes('Research begins only after Step 2 diagnosis')
2690
+ || parallel.includes('scout + diagnose + research together')
2691
+ || parallel.includes("You don't need to wait for scouting")) {
2692
+ issues.add('scout-before-diagnosis');
2693
+ }
2694
+ return [...issues].sort();
2695
+ }
2696
+
2697
+ test('packed Claude and Codex installs reject adaptive Fix semantic weakenings', () => {
2698
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-hotfix-adaptive-'));
2699
+ const destination = path.join(root, 'pack');
2700
+ fs.mkdirSync(destination, { recursive: true });
2701
+ const canonicalBytes = new Map(Object.values(HOTFIX_SOURCE_RELATIVES).map((relative) => {
2702
+ const sourcePath = path.join(PACKAGE_ROOT, relative);
2703
+ return [sourcePath, fs.readFileSync(sourcePath)];
2704
+ }));
2705
+ try {
2706
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2707
+ const tarball = path.join(destination, packed.filename);
2708
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2709
+ assertCleanInventory(packedInventory(tarball));
2710
+ const layouts = {
2711
+ claude: { skillsRoot: '.claude/skills/hotfix', refPrefix: 'cf:' },
2712
+ codex: { skillsRoot: '.agents/skills/hotfix', refPrefix: 'cf-' },
2713
+ };
2714
+ const exercised = new Set();
2715
+ for (const [platform, layout] of Object.entries(layouts)) {
2716
+ const project = path.join(root, platform);
2717
+ const installer = installPacked(tarball, project, runtimeClosure);
2718
+ runInstaller(installer, project, [platform], null);
2719
+ const readInstalled = () => ({
2720
+ skill: fs.readFileSync(path.join(project, layout.skillsRoot, 'SKILL.md'), 'utf8'),
2721
+ review: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/review-cycle.md'), 'utf8'),
2722
+ parallel: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/parallel-patterns.md'), 'utf8'),
2723
+ specialized: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/workflow-specialized.md'), 'utf8'),
2724
+ });
2725
+ assert.deepEqual(packedHotfixIssues(readInstalled(), layout.refPrefix), [], `${platform} hotfix baseline`);
2726
+ const mutations = [
2727
+ { group: 'public-rename', file: 'SKILL.md', from: `name: ${layout.refPrefix}fix`, to: `name: ${layout.refPrefix}hotfix` },
2728
+ { group: 'adaptive-depth', file: 'SKILL.md', from: 'Quick mode only reduces depth', to: 'Quick mode may shorten scope' },
2729
+ { group: 'debug-handoff', file: 'SKILL.md', from: '`Timeline: skipped - <reason>` or `- skipped: <reason>`', to: '`Timeline: omitted`' },
2730
+ { group: 'verdict-surface', file: 'references/review-cycle.md', from: 'Only `FAIL` and `PASS_WITH_WARNINGS` enter remediation retry', to: 'Only `FAIL` enters remediation retry; `PASS_WITH_WARNINGS` auto-approves' },
2731
+ { group: 'verdict-surface', file: 'references/review-cycle.md', from: 'only a fresh literal `PASS` enters finalization', to: '"Approve anyway" enters finalization' },
2732
+ { group: 'verdict-surface', file: 'SKILL.md', from: '**Report:** root cause, changes made', to: '**Report:** Confidence score, root cause, changes made' },
2733
+ { group: 'delegation-gate', file: 'SKILL.md', from: 'The user explicitly requested or permitted delegation or parallel agents.', to: 'Delegation is at the agent\'s discretion.' },
2734
+ { group: 'side-effect-gate', file: 'SKILL.md', from: 'Do not silently patch around the regression.', to: 'Patch around regressions quietly.' },
2735
+ { group: 'bounded-repair-frame', file: 'SKILL.md', from: 'Quick/local does not add a separate framing ceremony', to: 'Quick/local always requires a separate framing ceremony' },
2736
+ { group: 'deep-decision-route', file: 'SKILL.md', from: 'after diagnosis, research only unresolved external facts', to: 'research broadly before diagnosis' },
2737
+ { group: 'specialized-proof-overlays', file: 'references/workflow-specialized.md', from: 'Load only the matching section', to: 'Load every section' },
2738
+ { group: 'scout-before-diagnosis', file: 'references/parallel-patterns.md', from: 'Diagnosis still starts only', to: 'Diagnosis may start' },
2739
+ { 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' },
2740
+ ];
2741
+ for (const mutation of mutations) {
2742
+ const target = path.join(project, layout.skillsRoot, mutation.file);
2743
+ const original = fs.readFileSync(target);
2744
+ const content = original.toString('utf8');
2745
+ const anchor = content.indexOf(mutation.from);
2746
+ assert.ok(anchor >= 0, `${platform} ${mutation.group} mutation anchor must exist`);
2747
+ assert.equal(
2748
+ content.indexOf(mutation.from, anchor + mutation.from.length),
2749
+ -1,
2750
+ `${platform} ${mutation.group} mutation anchor must be unique`
2751
+ );
2752
+ try {
2753
+ fs.writeFileSync(target, `${content.slice(0, anchor)}${mutation.to}${content.slice(anchor + mutation.from.length)}`);
2754
+ assert.deepEqual(
2755
+ packedHotfixIssues(readInstalled(), layout.refPrefix),
2756
+ [mutation.group],
2757
+ `${platform} ${mutation.group} must fail with its exact issue`
2758
+ );
2759
+ } finally {
2760
+ fs.writeFileSync(target, original);
2761
+ }
2762
+ assert.deepEqual(packedHotfixIssues(readInstalled(), layout.refPrefix), [], `${platform} ${mutation.group} restore`);
2763
+ for (const [sourcePath, expected] of canonicalBytes) {
2764
+ assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
2765
+ }
2766
+ exercised.add(`${platform}:${mutation.group}`);
2767
+ }
2768
+ }
2769
+ assert.equal(exercised.size, 20, 'Fix mutations must cover both platforms and all ten groups');
2770
+ } finally {
2771
+ fs.rmSync(root, { recursive: true, force: true });
2772
+ }
2773
+ });
2774
+
2775
+ test('repository and package guides document adaptive Fix usage', () => {
2776
+ const guides = {
2777
+ repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
2778
+ package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2779
+ };
2780
+ for (const [name, guide] of Object.entries(guides)) {
2781
+ assert.match(guide, /cf:fix/, `${name} guide names Fix`);
2782
+ assert.doesNotMatch(guide, /cf:hotfix|cf-hotfix/, `${name} guide drops the old public name`);
2783
+ assert.match(guide, /Quick\/local/, `${name} guide documents quick depth`);
2784
+ assert.match(guide, /Incident\/deep/, `${name} guide documents incident depth`);
2785
+ assert.match(guide, /PASS \| PASS_WITH_WARNINGS \| FAIL \| BLOCKED/, `${name} guide documents shared verdicts`);
2786
+ assert.match(guide, /debug handoff|cf:debug[^\n]*handoff/i, `${name} guide documents the debug handoff`);
2787
+ assert.doesNotMatch(guide, /hotfix[^\n]*\b\d+(?:\.\d+)?\s*(?:%|x faster|seconds|minutes|ms)\b/i, `${name} guide must not invent timing claims`);
2788
+ }
2789
+ });
2790
+
2791
+ test('localized reference guides keep OpenCode mappings historical', () => {
2792
+ const docsRoot = path.resolve(PACKAGE_ROOT, '../../cafekit-web/public/content/docs');
2793
+ const references = ['en', 'vi', 'ja'].map((locale) =>
2794
+ fs.readFileSync(path.join(docsRoot, locale, 'reference.mdx'), 'utf8'));
2795
+ for (const reference of references) {
2796
+ assert.match(reference, /Legacy OpenCode 0\.16 command/);
2797
+ assert.match(reference, /cf:fix/);
2798
+ assert.doesNotMatch(reference, /OpenCode currently|OpenCode hiện có|OpenCode は main implementation surface/);
2799
+ }
2800
+ });
2801
+
2802
+ test('website keeps process-first docs, canonical public names, and historical legacy claims', () => {
2803
+ const webRoot = path.resolve(PACKAGE_ROOT, '../../cafekit-web');
2804
+ const docsRoot = path.join(webRoot, 'public/content/docs');
2805
+ const sourceRoot = path.join(webRoot, 'src');
2806
+ const walk = (root) => fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
2807
+ const target = path.join(root, entry.name);
2808
+ return entry.isDirectory() ? walk(target) : [target];
2809
+ });
2810
+
2811
+ const currentDocs = walk(docsRoot).filter((file) => {
2812
+ const relative = path.relative(docsRoot, file).split(path.sep).join('/');
2813
+ return file.endsWith('.mdx')
2814
+ && !relative.endsWith('/reference.mdx')
2815
+ && !relative.endsWith('/platforms/opencode.mdx')
2816
+ && !relative.endsWith('/spec-lifecycle.mdx');
2817
+ });
2818
+ const currentSources = walk(sourceRoot).filter((file) => /\.(?:ts|tsx)$/.test(file));
2819
+ const currentCorpus = [...currentDocs, ...currentSources]
2820
+ .map((file) => fs.readFileSync(file, 'utf8'))
2821
+ .join('\n');
2822
+
2823
+ assert.doesNotMatch(currentCorpus, /cf:generate-graph|\/generate-graph/);
2824
+ assert.doesNotMatch(
2825
+ currentCorpus,
2826
+ /\bspec\.json\b|\btask_registry\b|tasks\/task-R|task-R\*|cf:specs --validate|registry sync/
2827
+ );
2828
+ assert.doesNotMatch(currentCorpus, /OpenCode installs|OpenCode cài|OpenCode では[^\n]*commands|Yes\.[^\n]*OpenCode|Có\.[^\n]*OpenCode|はい。[^\n]*OpenCode/i);
2829
+
2830
+ const config = fs.readFileSync(path.join(sourceRoot, 'lib/docs-config.ts'), 'utf8');
2831
+ assert.match(config, /\/docs\/skills\/ask/);
2832
+ assert.match(config, /\/docs\/skills\/scout/);
2833
+ assert.match(config, /\/docs\/skills\/fix/);
2834
+ assert.doesNotMatch(config, /\/docs\/skills\/(?:question|inspect|hotfix)/);
2835
+
2836
+ const route = fs.readFileSync(
2837
+ path.join(sourceRoot, 'app/[locale]/docs/[[...slug]]/page.tsx'),
2838
+ 'utf8'
2839
+ );
2840
+ assert.match(route, /ask:\s*'question'/);
2841
+ assert.match(route, /scout:\s*'inspect'/);
2842
+ assert.match(route, /hotfix:\s*'fix'/);
2843
+
2844
+ const resolvedSkillDocuments = {
2845
+ ask: 'question',
2846
+ scout: 'inspect',
2847
+ fix: 'fix',
2848
+ question: 'question',
2849
+ inspect: 'inspect',
2850
+ hotfix: 'fix',
2851
+ };
2852
+ for (const locale of ['en', 'vi', 'ja']) {
2853
+ for (const [publicSlug, documentSlug] of Object.entries(resolvedSkillDocuments)) {
2854
+ assert.equal(
2855
+ fs.existsSync(path.join(docsRoot, locale, 'skills', `${documentSlug}.mdx`)),
2856
+ true,
2857
+ `${locale} /docs/skills/${publicSlug} must resolve to ${documentSlug}.mdx`
2858
+ );
2859
+ }
2860
+ }
2861
+
2862
+ for (const locale of ['en', 'vi', 'ja']) {
2863
+ const lifecycle = fs.readFileSync(path.join(docsRoot, locale, 'spec-lifecycle.mdx'), 'utf8');
2864
+ const legacyIndex = lifecycle.search(/^## Legacy compatibility$/m);
2865
+ assert.ok(legacyIndex >= 0, `${locale} spec lifecycle must isolate legacy compatibility`);
2866
+ assert.doesNotMatch(lifecycle.slice(0, legacyIndex), /\bspec\.json\b|\btask_registry\b|tasks\/task-R/);
2867
+ assert.match(lifecycle.slice(legacyIndex), /\bspec\.json\b/);
2868
+
2869
+ const opencode = fs.readFileSync(path.join(docsRoot, locale, 'platforms/opencode.mdx'), 'utf8');
2870
+ assert.match(opencode, /0\.17/);
2871
+ assert.match(opencode, /warning/);
2872
+ assert.match(opencode, /histor|lịch sử|migration/i);
2873
+ }
2874
+ });
2875
+
2876
+ const DOCS_ADAPTIVE_SOURCE_RELATIVES = {
2877
+ skill: 'src/claude/skills/docs/SKILL.md',
2878
+ init: 'src/claude/skills/docs/references/init-workflow.md',
2879
+ update: 'src/claude/skills/docs/references/update-workflow.md',
2880
+ standard: 'src/claude/skills/docs/references/standard-docs-workflow.md',
2881
+ };
2882
+
2883
+ function packedDocsIssues(files) {
2884
+ const compact = (value) => String(value).replace(/\s+/g, ' ').trim();
2885
+ const skill = compact(files.skill);
2886
+ const init = compact(files.init);
2887
+ const update = compact(files.update);
2888
+ const standard = compact(files.standard);
2889
+ const issues = new Set();
2890
+ if (!skill.includes('The user explicitly requested or permitted delegation or parallel agents.')
2891
+ || !skill.includes('only through the Delegation Gate above')
2892
+ || !init.includes('only through the Delegation Gate in `../SKILL.md`')
2893
+ || !update.includes('only through the Delegation Gate in `../SKILL.md`')
2894
+ || skill.includes('when delegation is available')
2895
+ || init.includes('when delegation is available')
2896
+ || update.includes('when delegation is available')) {
2897
+ issues.add('delegation-gate');
2898
+ }
2899
+ if (!skill.includes('## Post-Task Docs Checkpoint')
2900
+ || !skill.includes('A checkpoint never invents a new document')
2901
+ || !skill.includes('never auto-selects `init` and overrides')
2902
+ || !standard.includes('checkpoint contract in `../SKILL.md`')
2903
+ || skill.includes('checkpoint may create')) {
2904
+ issues.add('docs-checkpoint');
2905
+ }
2906
+ if (!skill.includes('Type: Observed | Inferred | Unknown')
2907
+ || skill.includes('evidence is optional')) {
2908
+ issues.add('evidence-taxonomy');
2909
+ }
2910
+ if (!skill.includes('## Reconstruction Is Not Specs')
2911
+ || skill.includes('are advisory')) {
2912
+ issues.add('reconstruction-boundary');
2913
+ }
2914
+ return [...issues].sort();
2915
+ }
2916
+
2917
+ test('packed Claude and Codex installs reject adaptive Docs semantic weakenings', () => {
2918
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-docs-adaptive-'));
2919
+ const destination = path.join(root, 'pack');
2920
+ fs.mkdirSync(destination, { recursive: true });
2921
+ const canonicalBytes = new Map(Object.values(DOCS_ADAPTIVE_SOURCE_RELATIVES).map((relative) => {
2922
+ const sourcePath = path.join(PACKAGE_ROOT, relative);
2923
+ return [sourcePath, fs.readFileSync(sourcePath)];
2924
+ }));
2925
+ try {
2926
+ const packed = npmPack(['--pack-destination', destination, '--json'], PACKAGE_ROOT);
2927
+ const tarball = path.join(destination, packed.filename);
2928
+ const runtimeClosure = packedRuntimeClosure(path.join(root, 'runtime-closure'));
2929
+ assertCleanInventory(packedInventory(tarball));
2930
+ const layouts = {
2931
+ claude: { skillsRoot: '.claude/skills/docs' },
2932
+ codex: { skillsRoot: '.agents/skills/docs' },
2933
+ };
2934
+ const exercised = new Set();
2935
+ for (const [platform, layout] of Object.entries(layouts)) {
2936
+ const project = path.join(root, platform);
2937
+ const installer = installPacked(tarball, project, runtimeClosure);
2938
+ runInstaller(installer, project, [platform], null, ['--with-document-skills']);
2939
+ const readInstalled = () => ({
2940
+ skill: fs.readFileSync(path.join(project, layout.skillsRoot, 'SKILL.md'), 'utf8'),
2941
+ init: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/init-workflow.md'), 'utf8'),
2942
+ update: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/update-workflow.md'), 'utf8'),
2943
+ standard: fs.readFileSync(path.join(project, layout.skillsRoot, 'references/standard-docs-workflow.md'), 'utf8'),
2944
+ });
2945
+ assert.deepEqual(packedDocsIssues(readInstalled()), [], `${platform} docs baseline`);
2946
+ const mutations = [
2947
+ { group: 'delegation-gate', file: 'SKILL.md', from: 'only through the Delegation Gate above', to: 'when delegation is available' },
2948
+ { group: 'docs-checkpoint', file: 'SKILL.md', from: 'A checkpoint never invents a new document', to: 'A checkpoint may create missing documents' },
2949
+ { group: 'evidence-taxonomy', file: 'SKILL.md', from: 'Type: Observed | Inferred | Unknown', to: 'Type: Observed | Unknown' },
2950
+ { group: 'reconstruction-boundary', file: 'SKILL.md', from: '## Reconstruction Is Not Specs', to: '## Reconstruction Is Not Specs\n\nThe prohibitions below are advisory.' },
2951
+ ];
2952
+ for (const mutation of mutations) {
2953
+ const target = path.join(project, layout.skillsRoot, mutation.file);
2954
+ const original = fs.readFileSync(target);
2955
+ const content = original.toString('utf8');
2956
+ const anchor = content.indexOf(mutation.from);
2957
+ assert.ok(anchor >= 0, `${platform} ${mutation.group} mutation anchor must exist`);
2958
+ assert.equal(
2959
+ content.indexOf(mutation.from, anchor + mutation.from.length),
2960
+ -1,
2961
+ `${platform} ${mutation.group} mutation anchor must be unique`
2962
+ );
2963
+ try {
2964
+ fs.writeFileSync(target, `${content.slice(0, anchor)}${mutation.to}${content.slice(anchor + mutation.from.length)}`);
2965
+ assert.deepEqual(
2966
+ packedDocsIssues(readInstalled()),
2967
+ [mutation.group],
2968
+ `${platform} ${mutation.group} must fail with its exact issue`
2969
+ );
2970
+ } finally {
2971
+ fs.writeFileSync(target, original);
2972
+ }
2973
+ assert.deepEqual(packedDocsIssues(readInstalled()), [], `${platform} ${mutation.group} restore`);
2974
+ for (const [sourcePath, expected] of canonicalBytes) {
2975
+ assert.deepEqual(fs.readFileSync(sourcePath), expected, `canonical source changed: ${sourcePath}`);
2976
+ }
2977
+ exercised.add(`${platform}:${mutation.group}`);
2978
+ }
2979
+ }
2980
+ assert.equal(exercised.size, 8, 'docs mutations must cover both platforms and all four groups');
2981
+ } finally {
2982
+ fs.rmSync(root, { recursive: true, force: true });
2983
+ }
2984
+ });
2985
+
2986
+ test('repository and package guides document adaptive Docs usage', () => {
2987
+ const guides = {
2988
+ repository: fs.readFileSync(path.resolve(PACKAGE_ROOT, '../../README.md'), 'utf8'),
2989
+ package: fs.readFileSync(path.join(PACKAGE_ROOT, 'README.md'), 'utf8'),
2990
+ };
2991
+ for (const [name, guide] of Object.entries(guides)) {
2992
+ assert.match(guide, /cf:docs/, `${name} guide names docs`);
2993
+ assert.match(guide, /post-task docs checkpoint/i, `${name} guide documents the checkpoint`);
2994
+ assert.match(guide, /Delegation Gate/, `${name} guide documents the gate`);
2995
+ assert.match(guide, /Observed \| Inferred \| Unknown/, `${name} guide documents the evidence taxonomy`);
2996
+ assert.doesNotMatch(guide, /docs[^\n]*\b\d+(?:\.\d+)?\s*(?:%|x faster|seconds|minutes|ms)\b/i, `${name} guide must not invent timing claims`);
2997
+ }
2998
+ });
2999
+
1363
3000
  test('packed Claude and Codex installs execute semantic kernel behavior without package source', () => {
1364
3001
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-packed-semantic-'));
1365
3002
  const destination = path.join(root, 'pack');