@haposoft/cafekit 0.16.0-rc.7 → 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 (299) 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 +295 -0
  4. package/bin/__tests__/codex-native.test.js +2289 -34
  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 +2131 -16
  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 +24 -13
  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 +87 -4
  46. package/src/claude/hooks/__tests__/state.test.js +254 -3
  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 +32 -5
  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/spec-receipt.cjs +76 -21
  89. package/src/claude/scripts/spec-resolver.cjs +144 -4
  90. package/src/claude/scripts/workflow-policy.cjs +1 -1
  91. package/src/claude/settings/settings.json +16 -0
  92. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  93. package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
  94. package/src/claude/skills/brainstorm/SKILL.md +193 -172
  95. package/src/claude/skills/brainstorm/references/question-framework.md +105 -167
  96. package/src/claude/skills/chrome-devtools/SKILL.md +1 -1
  97. package/src/claude/skills/code-review/SKILL.md +43 -25
  98. package/src/claude/skills/code-review/references/spec-compliance-review.md +3 -4
  99. package/src/claude/skills/code-review/references/verification-gate.md +1 -1
  100. package/src/claude/skills/debug/SKILL.md +48 -19
  101. package/src/claude/skills/delegate/SKILL.md +1 -1
  102. package/src/claude/skills/develop/SKILL.md +92 -65
  103. package/src/claude/skills/develop/references/parallel-waves.md +36 -63
  104. package/src/claude/skills/develop/references/quality-gate.md +31 -4
  105. package/src/claude/skills/develop/references/subagent-patterns.md +21 -7
  106. package/src/claude/skills/docs/SKILL.md +42 -23
  107. package/src/claude/skills/docs/references/init-workflow.md +4 -4
  108. package/src/claude/skills/docs/references/reconstruct-workflow.md +6 -6
  109. package/src/claude/skills/docs/references/standard-docs-workflow.md +6 -2
  110. package/src/claude/skills/docs/references/summarize-workflow.md +2 -2
  111. package/src/claude/skills/docs/references/update-workflow.md +9 -7
  112. package/src/claude/skills/docs/templates/requirements-as-is.md +1 -1
  113. package/src/claude/skills/docx/SKILL.md +1 -1
  114. package/src/claude/skills/git/SKILL.md +1 -1
  115. package/src/claude/skills/git/references/commit-protocols.md +1 -1
  116. package/src/claude/skills/git/references/finish-branch.md +1 -1
  117. package/src/claude/skills/hotfix/SKILL.md +159 -170
  118. package/src/claude/skills/hotfix/references/diagnosis-protocol.md +14 -9
  119. package/src/claude/skills/hotfix/references/parallel-patterns.md +27 -13
  120. package/src/claude/skills/hotfix/references/prevention-gate.md +5 -0
  121. package/src/claude/skills/hotfix/references/review-cycle.md +21 -15
  122. package/src/claude/skills/hotfix/references/workflow-specialized.md +53 -21
  123. package/src/claude/skills/inspect/SKILL.md +55 -46
  124. package/src/claude/skills/inspect/references/internal-inspection.md +21 -5
  125. package/src/claude/skills/loop/SKILL.md +114 -0
  126. package/src/claude/skills/loop/references/bounded-loop-protocol.md +104 -0
  127. package/src/claude/skills/loop/references/metric-and-guard-contract.md +76 -0
  128. package/src/claude/skills/pdf/SKILL.md +1 -1
  129. package/src/claude/skills/pptx/SKILL.md +1 -1
  130. package/src/claude/skills/question/SKILL.md +16 -13
  131. package/src/claude/skills/research/SKILL.md +70 -43
  132. package/src/claude/skills/route/SKILL.md +94 -0
  133. package/src/claude/skills/route/references/agent-timing.md +46 -0
  134. package/src/claude/skills/route/references/chaining-patterns.md +53 -0
  135. package/src/claude/skills/route/references/task-taxonomy.md +48 -0
  136. package/src/claude/skills/specs/SKILL.md +48 -57
  137. package/src/claude/skills/specs/references/review.md +26 -8
  138. package/src/claude/skills/specs/references/templates.md +76 -31
  139. package/src/claude/skills/specs/templates/requirements-init.md +1 -1
  140. package/src/claude/skills/specs/templates/research.md +2 -2
  141. package/src/claude/skills/sync/SKILL.md +6 -6
  142. package/src/claude/skills/sync/references/sync-protocols.md +1 -1
  143. package/src/claude/skills/test/SKILL.md +120 -120
  144. package/src/claude/skills/test/references/execution-strategy.md +208 -361
  145. package/src/claude/skills/test/references/failure-triage.md +55 -121
  146. package/src/claude/skills/test/references/test-memory.md +20 -34
  147. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  148. package/src/claude/skills/web-testing/SKILL.md +1 -1
  149. package/src/claude/skills/xlsx/SKILL.md +1 -1
  150. package/src/claude/status.cjs +135 -33
  151. package/src/codex/AGENTS.md +14 -4
  152. package/src/codex/hooks/docs-sync.cjs +8 -3
  153. package/src/codex/hooks/lib/hook-context.cjs +18 -4
  154. package/src/codex/hooks/rules.cjs +29 -1
  155. package/src/codex/hooks/secret-output-guardrail.cjs +30 -0
  156. package/src/codex/hooks/spec-gate.cjs +3 -8
  157. package/src/codex/hooks/spec-state.cjs +34 -5
  158. package/src/codex/hooks.json +5 -0
  159. package/src/codex/runtime.json +2 -0
  160. package/src/common/.env.example +1 -1
  161. package/src/claude/skills/backend-development/SKILL.md +0 -101
  162. package/src/claude/skills/backend-development/references/backend-api-design.md +0 -495
  163. package/src/claude/skills/backend-development/references/backend-architecture.md +0 -454
  164. package/src/claude/skills/backend-development/references/backend-authentication.md +0 -338
  165. package/src/claude/skills/backend-development/references/backend-code-quality.md +0 -659
  166. package/src/claude/skills/backend-development/references/backend-debugging.md +0 -904
  167. package/src/claude/skills/backend-development/references/backend-devops.md +0 -491
  168. package/src/claude/skills/backend-development/references/backend-mindset.md +0 -387
  169. package/src/claude/skills/backend-development/references/backend-performance.md +0 -397
  170. package/src/claude/skills/backend-development/references/backend-security.md +0 -342
  171. package/src/claude/skills/backend-development/references/backend-technologies.md +0 -256
  172. package/src/claude/skills/backend-development/references/backend-testing.md +0 -429
  173. package/src/claude/skills/devops/.env.example +0 -76
  174. package/src/claude/skills/devops/SKILL.md +0 -102
  175. package/src/claude/skills/devops/references/browser-rendering.md +0 -305
  176. package/src/claude/skills/devops/references/cloudflare-d1-kv.md +0 -123
  177. package/src/claude/skills/devops/references/cloudflare-platform.md +0 -271
  178. package/src/claude/skills/devops/references/cloudflare-r2-storage.md +0 -280
  179. package/src/claude/skills/devops/references/cloudflare-workers-advanced.md +0 -312
  180. package/src/claude/skills/devops/references/cloudflare-workers-apis.md +0 -309
  181. package/src/claude/skills/devops/references/cloudflare-workers-basics.md +0 -418
  182. package/src/claude/skills/devops/references/docker-basics.md +0 -297
  183. package/src/claude/skills/devops/references/docker-compose.md +0 -292
  184. package/src/claude/skills/devops/references/gcloud-platform.md +0 -297
  185. package/src/claude/skills/devops/references/gcloud-services.md +0 -304
  186. package/src/claude/skills/devops/references/kubernetes-basics.md +0 -95
  187. package/src/claude/skills/devops/references/kubernetes-helm-advanced.md +0 -70
  188. package/src/claude/skills/devops/references/kubernetes-helm.md +0 -81
  189. package/src/claude/skills/devops/references/kubernetes-kubectl.md +0 -74
  190. package/src/claude/skills/devops/references/kubernetes-security-advanced.md +0 -94
  191. package/src/claude/skills/devops/references/kubernetes-security.md +0 -89
  192. package/src/claude/skills/devops/references/kubernetes-troubleshooting-advanced.md +0 -74
  193. package/src/claude/skills/devops/references/kubernetes-troubleshooting.md +0 -49
  194. package/src/claude/skills/devops/references/kubernetes-workflows-advanced.md +0 -75
  195. package/src/claude/skills/devops/references/kubernetes-workflows.md +0 -76
  196. package/src/claude/skills/devops/scripts/cloudflare_deploy.py +0 -269
  197. package/src/claude/skills/devops/scripts/docker_optimize.py +0 -332
  198. package/src/claude/skills/devops/scripts/requirements.txt +0 -20
  199. package/src/claude/skills/devops/scripts/tests/requirements.txt +0 -3
  200. package/src/claude/skills/devops/scripts/tests/test_cloudflare_deploy.py +0 -285
  201. package/src/claude/skills/devops/scripts/tests/test_docker_optimize.py +0 -436
  202. package/src/claude/skills/frontend-design/SKILL.md +0 -135
  203. package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +0 -165
  204. package/src/claude/skills/frontend-design/references/analysis-best-practices.md +0 -80
  205. package/src/claude/skills/frontend-design/references/analysis-prompts.md +0 -141
  206. package/src/claude/skills/frontend-design/references/analysis-techniques.md +0 -118
  207. package/src/claude/skills/frontend-design/references/animejs.md +0 -396
  208. package/src/claude/skills/frontend-design/references/anti-slop-rules.md +0 -103
  209. package/src/claude/skills/frontend-design/references/asset-generation.md +0 -337
  210. package/src/claude/skills/frontend-design/references/bento-motion-engine.md +0 -142
  211. package/src/claude/skills/frontend-design/references/design-extraction-overview.md +0 -71
  212. package/src/claude/skills/frontend-design/references/extraction-best-practices.md +0 -141
  213. package/src/claude/skills/frontend-design/references/extraction-output-templates.md +0 -162
  214. package/src/claude/skills/frontend-design/references/extraction-prompts.md +0 -127
  215. package/src/claude/skills/frontend-design/references/magicui-components.md +0 -129
  216. package/src/claude/skills/frontend-design/references/performance-guardrails.md +0 -169
  217. package/src/claude/skills/frontend-design/references/premium-design-patterns.md +0 -93
  218. package/src/claude/skills/frontend-design/references/redesign-audit-checklist.md +0 -114
  219. package/src/claude/skills/frontend-design/references/technical-accessibility.md +0 -119
  220. package/src/claude/skills/frontend-design/references/technical-best-practices.md +0 -97
  221. package/src/claude/skills/frontend-design/references/technical-optimization.md +0 -44
  222. package/src/claude/skills/frontend-design/references/technical-overview.md +0 -90
  223. package/src/claude/skills/frontend-design/references/technical-workflows.md +0 -150
  224. package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +0 -95
  225. package/src/claude/skills/frontend-design/references/workflow-3d.md +0 -102
  226. package/src/claude/skills/frontend-design/references/workflow-describe.md +0 -87
  227. package/src/claude/skills/frontend-design/references/workflow-immersive.md +0 -87
  228. package/src/claude/skills/frontend-design/references/workflow-quick.md +0 -57
  229. package/src/claude/skills/frontend-design/references/workflow-screenshot.md +0 -63
  230. package/src/claude/skills/frontend-design/references/workflow-video.md +0 -74
  231. package/src/claude/skills/frontend-development/SKILL.md +0 -406
  232. package/src/claude/skills/frontend-development/resources/common-patterns.md +0 -331
  233. package/src/claude/skills/frontend-development/resources/complete-examples.md +0 -872
  234. package/src/claude/skills/frontend-development/resources/component-patterns.md +0 -502
  235. package/src/claude/skills/frontend-development/resources/data-fetching.md +0 -767
  236. package/src/claude/skills/frontend-development/resources/file-organization.md +0 -502
  237. package/src/claude/skills/frontend-development/resources/loading-and-error-states.md +0 -501
  238. package/src/claude/skills/frontend-development/resources/performance.md +0 -406
  239. package/src/claude/skills/frontend-development/resources/routing-guide.md +0 -364
  240. package/src/claude/skills/frontend-development/resources/styling-guide.md +0 -428
  241. package/src/claude/skills/frontend-development/resources/typescript-standards.md +0 -418
  242. package/src/claude/skills/mobile-development/SKILL.md +0 -218
  243. package/src/claude/skills/mobile-development/references/mobile-android.md +0 -604
  244. package/src/claude/skills/mobile-development/references/mobile-best-practices.md +0 -544
  245. package/src/claude/skills/mobile-development/references/mobile-debugging.md +0 -1089
  246. package/src/claude/skills/mobile-development/references/mobile-frameworks.md +0 -465
  247. package/src/claude/skills/mobile-development/references/mobile-ios.md +0 -496
  248. package/src/claude/skills/mobile-development/references/mobile-mindset.md +0 -542
  249. package/src/claude/skills/react-best-practices/AGENTS.md +0 -2249
  250. package/src/claude/skills/react-best-practices/README.md +0 -123
  251. package/src/claude/skills/react-best-practices/SKILL.md +0 -128
  252. package/src/claude/skills/react-best-practices/metadata.json +0 -15
  253. package/src/claude/skills/react-best-practices/rules/_sections.md +0 -46
  254. package/src/claude/skills/react-best-practices/rules/_template.md +0 -28
  255. package/src/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  256. package/src/claude/skills/react-best-practices/rules/advanced-use-latest.md +0 -49
  257. package/src/claude/skills/react-best-practices/rules/async-api-routes.md +0 -38
  258. package/src/claude/skills/react-best-practices/rules/async-defer-await.md +0 -80
  259. package/src/claude/skills/react-best-practices/rules/async-dependencies.md +0 -36
  260. package/src/claude/skills/react-best-practices/rules/async-parallel.md +0 -28
  261. package/src/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -99
  262. package/src/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -59
  263. package/src/claude/skills/react-best-practices/rules/bundle-conditional.md +0 -31
  264. package/src/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +0 -49
  265. package/src/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  266. package/src/claude/skills/react-best-practices/rules/bundle-preload.md +0 -50
  267. package/src/claude/skills/react-best-practices/rules/client-event-listeners.md +0 -74
  268. package/src/claude/skills/react-best-practices/rules/client-swr-dedup.md +0 -56
  269. package/src/claude/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
  270. package/src/claude/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
  271. package/src/claude/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
  272. package/src/claude/skills/react-best-practices/rules/js-cache-storage.md +0 -70
  273. package/src/claude/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
  274. package/src/claude/skills/react-best-practices/rules/js-early-exit.md +0 -50
  275. package/src/claude/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
  276. package/src/claude/skills/react-best-practices/rules/js-index-maps.md +0 -37
  277. package/src/claude/skills/react-best-practices/rules/js-length-check-first.md +0 -49
  278. package/src/claude/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
  279. package/src/claude/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
  280. package/src/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
  281. package/src/claude/skills/react-best-practices/rules/rendering-activity.md +0 -26
  282. package/src/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  283. package/src/claude/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
  284. package/src/claude/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
  285. package/src/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  286. package/src/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  287. package/src/claude/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
  288. package/src/claude/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
  289. package/src/claude/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
  290. package/src/claude/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
  291. package/src/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
  292. package/src/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  293. package/src/claude/skills/react-best-practices/rules/rerender-memo.md +0 -44
  294. package/src/claude/skills/react-best-practices/rules/rerender-transitions.md +0 -40
  295. package/src/claude/skills/react-best-practices/rules/server-after-nonblocking.md +0 -73
  296. package/src/claude/skills/react-best-practices/rules/server-cache-lru.md +0 -41
  297. package/src/claude/skills/react-best-practices/rules/server-cache-react.md +0 -26
  298. package/src/claude/skills/react-best-practices/rules/server-parallel-fetching.md +0 -79
  299. 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
+ });