@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
@@ -0,0 +1,249 @@
1
+ const assert = require('node:assert/strict');
2
+ const fs = require('node:fs');
3
+ const os = require('node:os');
4
+ const path = require('node:path');
5
+ const test = require('node:test');
6
+ const { spawnSync } = require('node:child_process');
7
+
8
+ const {
9
+ loadClaudeMigrationManifest,
10
+ parseInstallerArgs,
11
+ PLATFORMS
12
+ } = require('../lib/context');
13
+ const manifestLib = require('../lib/manifest');
14
+ const { selectDocumentSkills } = require('../phases/select-skill-bundles');
15
+ const { reconcileSkillInventory } = require('../phases/skill-inventory');
16
+ const installerPath = path.resolve(__dirname, '..', 'install.js');
17
+
18
+ function withTempProject(run) {
19
+ const previous = process.cwd();
20
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-optional-skills-'));
21
+ process.chdir(root);
22
+ try {
23
+ return run(root);
24
+ } finally {
25
+ process.chdir(previous);
26
+ fs.rmSync(root, { recursive: true, force: true });
27
+ }
28
+ }
29
+
30
+ async function withTempProjectAsync(run) {
31
+ const previous = process.cwd();
32
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-optional-skills-'));
33
+ process.chdir(root);
34
+ try {
35
+ return await run(root);
36
+ } finally {
37
+ process.chdir(previous);
38
+ fs.rmSync(root, { recursive: true, force: true });
39
+ }
40
+ }
41
+
42
+ function silentUi() {
43
+ return {
44
+ confirm: async (_options, fallback) => fallback,
45
+ isCancel: () => false,
46
+ warn: () => {},
47
+ detail: () => {}
48
+ };
49
+ }
50
+
51
+ test('manifest separates core, optional documents, and retired skills', () => {
52
+ const manifest = loadClaudeMigrationManifest();
53
+ assert.equal(manifest.skills.required.includes('route'), true);
54
+ assert.deepEqual(manifest.skills.bundles.documentSkills, [
55
+ 'ai-multimodal', 'docs', 'docx', 'pdf', 'pptx', 'xlsx'
56
+ ]);
57
+ for (const skill of manifest.skills.bundles.documentSkills) {
58
+ assert.equal(manifest.skills.required.includes(skill), false);
59
+ }
60
+ assert.deepEqual(manifest.obsolete.skills, [
61
+ 'backend-development', 'frontend-development', 'frontend-design',
62
+ 'mobile-development', 'devops', 'react-best-practices'
63
+ ]);
64
+ });
65
+
66
+ test('document skill flags are mutually exclusive', () => {
67
+ assert.throws(
68
+ () => parseInstallerArgs(['node', 'install', '--with-document-skills', '--without-document-skills']),
69
+ /cannot be used together/
70
+ );
71
+ });
72
+
73
+ test('selection defaults fresh non-interactive OFF and preserves legacy upgrade ON', async () => {
74
+ await withTempProjectAsync(async () => {
75
+ const base = {
76
+ platforms: ['claude', 'codex'],
77
+ options: {},
78
+ documentSkills: {},
79
+ interactive: false,
80
+ ui: silentUi(),
81
+ t: (key) => key
82
+ };
83
+ fs.mkdirSync('.codex', { recursive: true });
84
+ fs.writeFileSync('.codex/cafekit.json', '{"schemaVersion":1,"platform":"codex"}\n');
85
+
86
+ await selectDocumentSkills(base);
87
+ assert.deepEqual(base.documentSkills.claude, {
88
+ enabled: false,
89
+ selectionSource: 'default-fresh'
90
+ });
91
+ assert.deepEqual(base.documentSkills.codex, {
92
+ enabled: true,
93
+ selectionSource: 'legacy-upgrade'
94
+ });
95
+ });
96
+ });
97
+
98
+ test('interactive cancellation stops before selecting a fresh bundle', async () => {
99
+ await withTempProjectAsync(async () => {
100
+ const ctx = {
101
+ platforms: ['claude'],
102
+ options: {},
103
+ documentSkills: {},
104
+ interactive: true,
105
+ cancelled: false,
106
+ ui: {
107
+ confirm: async () => 'cancel',
108
+ isCancel: (value) => value === 'cancel'
109
+ },
110
+ t: (key) => key
111
+ };
112
+ await selectDocumentSkills(ctx);
113
+ assert.equal(ctx.cancelled, true);
114
+ assert.equal(ctx.documentSkills.claude, undefined);
115
+ });
116
+ });
117
+
118
+ for (const platformKey of ['claude', 'codex']) {
119
+ test(`${platformKey} prunes pristine optional skills and preserves modified skills`, () => {
120
+ withTempProject(() => {
121
+ const platform = PLATFORMS[platformKey];
122
+ const docsFile = path.join(platform.skillsDir, 'docs', 'SKILL.md');
123
+ const pdfFile = path.join(platform.skillsDir, 'pdf', 'SKILL.md');
124
+ fs.mkdirSync(path.dirname(docsFile), { recursive: true });
125
+ fs.mkdirSync(path.dirname(pdfFile), { recursive: true });
126
+ fs.writeFileSync(docsFile, 'owned\n');
127
+ fs.writeFileSync(pdfFile, 'owned then edited\n');
128
+
129
+ const ownership = {
130
+ schemaVersion: 1,
131
+ version: 'old',
132
+ files: {}
133
+ };
134
+ const recordRoot = platform.ownership?.recordRoot || platform.folder;
135
+ const key = (file) => path.relative(recordRoot, file).replace(/\\/g, '/');
136
+ ownership.files[key(docsFile)] = { sha256: manifestLib.hashFile(docsFile), version: 'old' };
137
+ ownership.files[key(pdfFile)] = { sha256: manifestLib.sha256('before edit\n'), version: 'old' };
138
+
139
+ const ctx = {
140
+ manifest: { obsolete: { skills: [] }, skills: { bundles: { documentSkills: ['docs', 'pdf'] } } },
141
+ documentSkills: { [platformKey]: { enabled: false, selectionSource: 'cli-opt-out' } },
142
+ ownership: { [platform.folder]: ownership },
143
+ trackers: {
144
+ [platformKey]: manifestLib.createTracker(platform.folder, 'next', platform.ownership)
145
+ },
146
+ dryRun: false,
147
+ ui: silentUi(),
148
+ results: { updated: 0, preserved: 0, preservedFiles: [] }
149
+ };
150
+
151
+ reconcileSkillInventory(ctx, platformKey);
152
+ assert.equal(fs.existsSync(path.dirname(docsFile)), false);
153
+ assert.equal(fs.existsSync(path.dirname(pdfFile)), true);
154
+ assert.equal(ctx.results.updated, 1);
155
+ assert.equal(ctx.results.preserved, 1);
156
+ });
157
+ });
158
+ }
159
+
160
+ test('fresh combined install defaults optional bundle OFF', () => {
161
+ withTempProject((root) => {
162
+ const result = spawnSync(process.execPath, [
163
+ installerPath, '--platform', 'claude,codex', '--yes'
164
+ ], { cwd: root, encoding: 'utf8' });
165
+ assert.equal(result.status, 0, result.stderr || result.stdout);
166
+
167
+ for (const platformKey of ['claude', 'codex']) {
168
+ const platform = PLATFORMS[platformKey];
169
+ assert.equal(fs.existsSync(path.join(platform.skillsDir, 'docs')), false);
170
+ assert.equal(fs.existsSync(path.join(platform.skillsDir, 'route', 'SKILL.md')), true);
171
+ const metadata = JSON.parse(fs.readFileSync(path.join(platform.folder, 'cafekit.json'), 'utf8'));
172
+ assert.equal(metadata.schemaVersion, 2);
173
+ assert.equal(metadata.documentSkills.enabled, false);
174
+ }
175
+ });
176
+ });
177
+
178
+ test('explicit opt-in installs all six optional skills on both runtimes', () => {
179
+ withTempProject((root) => {
180
+ const result = spawnSync(process.execPath, [
181
+ installerPath, '--platform', 'claude,codex', '--yes', '--with-document-skills'
182
+ ], { cwd: root, encoding: 'utf8' });
183
+ assert.equal(result.status, 0, result.stderr || result.stdout);
184
+
185
+ const optional = loadClaudeMigrationManifest().skills.bundles.documentSkills;
186
+ for (const platformKey of ['claude', 'codex']) {
187
+ const platform = PLATFORMS[platformKey];
188
+ for (const skill of optional) {
189
+ assert.equal(fs.existsSync(path.join(platform.skillsDir, skill, 'SKILL.md')), true, `${platformKey}:${skill}`);
190
+ }
191
+ const catalog = spawnSync(process.execPath, [
192
+ path.join(platform.folder, 'scripts', 'generate-skill-catalog.cjs'), '--json'
193
+ ], { cwd: root, encoding: 'utf8' });
194
+ assert.equal(catalog.status, 0, catalog.stderr);
195
+ const catalogData = JSON.parse(catalog.stdout);
196
+ assert.equal(catalogData.skills.some((skill) => skill.public_id === 'cf:route'), true);
197
+ assert.equal(catalogData.skills.some((skill) => skill.public_id === 'cf:docs'), true);
198
+ const metadata = JSON.parse(fs.readFileSync(path.join(platform.folder, 'cafekit.json'), 'utf8'));
199
+ assert.deepEqual(metadata.documentSkills, {
200
+ enabled: true,
201
+ selectionSource: 'cli-opt-in'
202
+ });
203
+ }
204
+ });
205
+ });
206
+
207
+ test('corrupt metadata preserves an installed document bundle', () => {
208
+ withTempProject((root) => {
209
+ const install = (...args) => spawnSync(process.execPath, [
210
+ installerPath, '--platform', 'claude', '--yes', ...args
211
+ ], { cwd: root, encoding: 'utf8' });
212
+ assert.equal(install('--with-document-skills').status, 0);
213
+ fs.writeFileSync(path.join(root, '.claude', 'cafekit.json'), '{broken\n');
214
+
215
+ const recovered = install();
216
+ assert.equal(recovered.status, 0, recovered.stderr || recovered.stdout);
217
+ assert.equal(fs.existsSync(path.join(root, '.claude', 'skills', 'docs', 'SKILL.md')), true);
218
+ const metadata = JSON.parse(fs.readFileSync(path.join(root, '.claude', 'cafekit.json'), 'utf8'));
219
+ assert.deepEqual(metadata.documentSkills, {
220
+ enabled: true,
221
+ selectionSource: 'metadata-recovery'
222
+ });
223
+ });
224
+ });
225
+
226
+ test('modified retired skill is preserved but excluded from automatic routing', () => {
227
+ withTempProject((root) => {
228
+ const retiredRoot = path.join(root, '.claude', 'skills', 'backend-development');
229
+ fs.mkdirSync(retiredRoot, { recursive: true });
230
+ fs.writeFileSync(path.join(retiredRoot, 'SKILL.md'), [
231
+ '---', 'name: cf:backend-development',
232
+ 'description: "User-modified retired skill."', '---', '# Preserved', '',
233
+ ].join('\n'));
234
+ const result = spawnSync(process.execPath, [
235
+ installerPath, '--platform', 'claude', '--yes'
236
+ ], { cwd: root, encoding: 'utf8' });
237
+ assert.equal(result.status, 0, result.stderr || result.stdout);
238
+ assert.equal(fs.existsSync(path.join(retiredRoot, 'SKILL.md')), true);
239
+
240
+ const catalog = spawnSync(process.execPath, [
241
+ path.join(root, '.claude', 'scripts', 'generate-skill-catalog.cjs'), '--json'
242
+ ], { cwd: root, encoding: 'utf8' });
243
+ assert.equal(catalog.status, 0, catalog.stderr);
244
+ const parsed = JSON.parse(catalog.stdout);
245
+ assert.equal(parsed.skills.some((skill) => skill.directory === 'backend-development'), false);
246
+ assert.ok(parsed.diagnostics.some((item) =>
247
+ item.directory === 'backend-development' && item.code === 'retired_skill'));
248
+ });
249
+ });
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ // Output styles are a Claude Code surface: Claude Code discovers .claude/output-styles/
4
+ // alongside agents, skills and rules, and Codex CLI has no equivalent. These tests pin
5
+ // what installs where, and that every style stays loadable — a style with broken
6
+ // frontmatter is silently unavailable rather than loudly wrong.
7
+
8
+ const assert = require('node:assert/strict');
9
+ const fs = require('node:fs');
10
+ const os = require('node:os');
11
+ const path = require('node:path');
12
+ const { spawnSync } = require('node:child_process');
13
+ const { test } = require('node:test');
14
+
15
+ const PACKAGE_ROOT = path.join(__dirname, '../..');
16
+ const INSTALLER = path.join(PACKAGE_ROOT, 'bin/install.js');
17
+ const SOURCE_DIR = path.join(PACKAGE_ROOT, 'src/claude/output-styles');
18
+ const EXPECTED = [
19
+ 'coding-level-0-eli5.md',
20
+ 'coding-level-1-junior.md',
21
+ 'coding-level-2-mid.md',
22
+ 'coding-level-3-senior.md',
23
+ 'coding-level-4-lead.md',
24
+ 'coding-level-5-god.md',
25
+ ];
26
+
27
+ function inTempProject(run) {
28
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cafekit-output-styles-'));
29
+ try {
30
+ return run(root);
31
+ } finally {
32
+ fs.rmSync(root, { recursive: true, force: true });
33
+ }
34
+ }
35
+
36
+ function install(root, platform) {
37
+ return spawnSync(
38
+ process.execPath,
39
+ [INSTALLER, '--platform', platform, '--yes'],
40
+ { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } }
41
+ );
42
+ }
43
+
44
+ /** Parse the leading `---` frontmatter block into a flat key/value map. */
45
+ function frontmatter(content) {
46
+ const match = content.match(/^---\n([\s\S]*?)\n---\n/);
47
+ if (!match) return null;
48
+ const fields = {};
49
+ for (const line of match[1].split('\n')) {
50
+ const pair = line.match(/^([a-z][a-z0-9-]*):\s*(.*)$/i);
51
+ if (pair) fields[pair[1]] = pair[2].trim();
52
+ }
53
+ return fields;
54
+ }
55
+
56
+ test('the source ships exactly the six coding levels', () => {
57
+ const found = fs.readdirSync(SOURCE_DIR).filter((name) => name.endsWith('.md')).sort();
58
+ assert.deepEqual(found, EXPECTED);
59
+ });
60
+
61
+ test('every style carries the frontmatter Claude Code reads', () => {
62
+ for (const name of EXPECTED) {
63
+ const fields = frontmatter(fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'));
64
+ assert.ok(fields, `${name} has no frontmatter block, so it cannot load`);
65
+ assert.ok(fields.name, `${name} has no name`);
66
+ assert.ok(fields.description, `${name} has no description`);
67
+ // These styles change how answers are written, not what the agent is allowed to do.
68
+ assert.equal(
69
+ fields['keep-coding-instructions'], 'true',
70
+ `${name} must keep the base coding instructions, or the style would replace them`
71
+ );
72
+ }
73
+ });
74
+
75
+ test('each level declares a distinct name and description', () => {
76
+ const names = new Set();
77
+ const descriptions = new Set();
78
+ for (const file of EXPECTED) {
79
+ const fields = frontmatter(fs.readFileSync(path.join(SOURCE_DIR, file), 'utf8'));
80
+ names.add(fields.name);
81
+ descriptions.add(fields.description);
82
+ }
83
+ assert.equal(names.size, EXPECTED.length, 'two styles share a name and would be indistinguishable');
84
+ assert.equal(descriptions.size, EXPECTED.length, 'two styles share a description');
85
+ });
86
+
87
+ test('a Claude install places every style where Claude Code discovers them', () => {
88
+ inTempProject((root) => {
89
+ const result = install(root, 'claude');
90
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
91
+ const installed = path.join(root, '.claude', 'output-styles');
92
+ for (const name of EXPECTED) {
93
+ assert.equal(fs.existsSync(path.join(installed, name)), true, `missing ${name}`);
94
+ assert.equal(
95
+ fs.readFileSync(path.join(installed, name), 'utf8'),
96
+ fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'),
97
+ `${name} was altered on the way in`
98
+ );
99
+ }
100
+ });
101
+ });
102
+
103
+ test('a Codex install ships the same styles, since its rules hook injects them', () => {
104
+ inTempProject((root) => {
105
+ const result = install(root, 'codex');
106
+ assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
107
+ const installed = path.join(root, '.codex', 'output-styles');
108
+ for (const name of EXPECTED) {
109
+ assert.equal(
110
+ fs.readFileSync(path.join(installed, name), 'utf8'),
111
+ fs.readFileSync(path.join(SOURCE_DIR, name), 'utf8'),
112
+ `${name} differs from the one canonical source`
113
+ );
114
+ }
115
+ assert.equal(JSON.parse(fs.readFileSync(path.join(root, '.codex', 'runtime.json'), 'utf8')).codingLevel, 1);
116
+ });
117
+ });
118
+
119
+ /** Run the installed Codex rules hook and return what it injects. */
120
+ function runRulesHook(root, sessionId) {
121
+ const result = spawnSync(process.execPath, [path.join(root, '.codex', 'hooks', 'rules.cjs')], {
122
+ cwd: root,
123
+ input: JSON.stringify({ session_id: sessionId, cwd: root }),
124
+ encoding: 'utf8',
125
+ });
126
+ assert.equal(result.status, 0, result.stderr);
127
+ return result.stdout;
128
+ }
129
+
130
+ function setCodingLevel(root, level) {
131
+ const file = path.join(root, '.codex', 'runtime.json');
132
+ const runtime = JSON.parse(fs.readFileSync(file, 'utf8'));
133
+ runtime.codingLevel = level;
134
+ fs.writeFileSync(file, `${JSON.stringify(runtime, null, 2)}\n`);
135
+ }
136
+
137
+ test('the Codex rules hook injects the style the coding level names', () => {
138
+ inTempProject((root) => {
139
+ assert.equal(install(root, 'codex').status, 0);
140
+
141
+ const junior = runRulesHook(root, 'session-junior');
142
+ assert.match(junior, /## Communication style/);
143
+ assert.match(junior, /Junior Developer Communication Mode/);
144
+ // The body uses `---` as a horizontal rule, so check a frontmatter-only marker.
145
+ assert.doesNotMatch(junior, /keep-coding-instructions/, 'the frontmatter must be stripped, not injected');
146
+ assert.doesNotMatch(junior, /^description:/m, 'the frontmatter must be stripped, not injected');
147
+ assert.match(junior, /## Rules/, 'the existing rules injection must survive');
148
+
149
+ setCodingLevel(root, 5);
150
+ const expert = runRulesHook(root, 'session-expert');
151
+ assert.match(expert, /God Mode Communication/);
152
+ assert.doesNotMatch(expert, /Junior Developer Communication Mode/);
153
+ });
154
+ });
155
+
156
+ test('the Codex style is injected once per session, not once per turn', () => {
157
+ inTempProject((root) => {
158
+ assert.equal(install(root, 'codex').status, 0);
159
+ const first = runRulesHook(root, 'one-session');
160
+ assert.match(first, /## Communication style/);
161
+ assert.equal(runRulesHook(root, 'one-session'), '', 'a second turn in the same session must inject nothing');
162
+ assert.match(runRulesHook(root, 'another-session'), /## Communication style/);
163
+ });
164
+ });
165
+
166
+ test('an absent or out-of-range coding level injects no style but keeps the rules', () => {
167
+ inTempProject((root) => {
168
+ assert.equal(install(root, 'codex').status, 0);
169
+ for (const [level, label] of [[null, 'null'], [9, 'out of range'], ['1', 'a string']]) {
170
+ setCodingLevel(root, level);
171
+ const out = runRulesHook(root, `session-${label.replace(/\s/g, '-')}`);
172
+ assert.doesNotMatch(out, /## Communication style/, `${label} must not select a style`);
173
+ assert.match(out, /## Rules/, `${label} must not break the existing rules injection`);
174
+ }
175
+ });
176
+ });
177
+
178
+ test('a Claude install seeds the default style but never overwrites the user choice', () => {
179
+ inTempProject((root) => {
180
+ assert.equal(install(root, 'claude').status, 0);
181
+ const settingsPath = path.join(root, '.claude', 'settings.json');
182
+ const seeded = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
183
+ assert.equal(seeded.outputStyle, 'Junior Developer Mode (Level 1)');
184
+
185
+ seeded.outputStyle = 'God Mode (Level 5)';
186
+ fs.writeFileSync(settingsPath, `${JSON.stringify(seeded, null, 2)}\n`);
187
+
188
+ assert.equal(install(root, 'claude').status, 0);
189
+ assert.equal(
190
+ JSON.parse(fs.readFileSync(settingsPath, 'utf8')).outputStyle, 'God Mode (Level 5)',
191
+ 'a reinstall reset a preference the user had chosen'
192
+ );
193
+
194
+ const forced = spawnSync(process.execPath, [INSTALLER, '--platform', 'claude', '--force-overwrite', '--yes'],
195
+ { cwd: root, encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } });
196
+ assert.equal(forced.status, 0, `${forced.stdout}\n${forced.stderr}`);
197
+ assert.equal(
198
+ JSON.parse(fs.readFileSync(settingsPath, 'utf8')).outputStyle, 'God Mode (Level 5)',
199
+ '--force-overwrite repairs managed files; it must not reset taste'
200
+ );
201
+ });
202
+ });
203
+
204
+ test('a style edited by the user survives a reinstall', () => {
205
+ inTempProject((root) => {
206
+ assert.equal(install(root, 'claude').status, 0);
207
+ const target = path.join(root, '.claude', 'output-styles', 'coding-level-5-god.md');
208
+ const edited = `${fs.readFileSync(target, 'utf8')}\n\nAlways answer in Vietnamese.\n`;
209
+ fs.writeFileSync(target, edited);
210
+
211
+ assert.equal(install(root, 'claude').status, 0);
212
+ assert.equal(fs.readFileSync(target, 'utf8'), edited, 'the reinstall discarded a user edit');
213
+ });
214
+ });