@haposoft/cafekit 0.16.0-rc.8 → 0.16.1

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 (323) hide show
  1. package/README.md +117 -28
  2. package/bin/__tests__/codex-hooks-ownership.test.js +239 -0
  3. package/bin/__tests__/codex-hooks.test.js +175 -0
  4. package/bin/__tests__/codex-native.test.js +1882 -70
  5. package/bin/__tests__/develop-contract.test.js +442 -23
  6. package/bin/__tests__/grok-envelope.test.js +271 -0
  7. package/bin/__tests__/grok-platform.test.js +133 -0
  8. package/bin/__tests__/hook-payload.test.js +194 -0
  9. package/bin/__tests__/installer-safety.test.js +3 -3
  10. package/bin/__tests__/omp-bridge.test.js +168 -0
  11. package/bin/__tests__/omp-hooks.test.js +178 -0
  12. package/bin/__tests__/omp-runtime.test.js +150 -0
  13. package/bin/__tests__/optional-skill-inventory.test.js +249 -0
  14. package/bin/__tests__/output-styles.test.js +214 -0
  15. package/bin/__tests__/package-inventory.test.js +1735 -98
  16. package/bin/__tests__/runtime-dir.test.js +158 -0
  17. package/bin/__tests__/runtime-schema.test.js +113 -0
  18. package/bin/__tests__/secret-output-guardrail.test.js +122 -0
  19. package/bin/__tests__/skill-routing-source.test.js +172 -0
  20. package/bin/__tests__/specs-v2-execution-closeout.test.js +28 -1
  21. package/bin/install.js +15 -5
  22. package/bin/lib/codex-install.js +170 -5
  23. package/bin/lib/context.js +64 -3
  24. package/bin/lib/i18n.js +15 -36
  25. package/bin/lib/skill-deps.js +5 -5
  26. package/bin/phases/claude-runtime.js +23 -0
  27. package/bin/phases/claude-settings.js +8 -0
  28. package/bin/phases/codex-hooks.js +156 -0
  29. package/bin/phases/codex-runtime.js +29 -38
  30. package/bin/phases/copy-payload.js +15 -2
  31. package/bin/phases/omp-runtime.js +140 -0
  32. package/bin/phases/root-config.js +3 -0
  33. package/bin/phases/select-platform.js +22 -1
  34. package/bin/phases/select-skill-bundles.js +96 -0
  35. package/bin/phases/skill-inventory.js +71 -0
  36. package/bin/phases/skills-setup.js +9 -2
  37. package/bin/phases/summary.js +0 -4
  38. package/bin/phases/write-metadata.js +8 -5
  39. package/package.json +8 -2
  40. package/src/claude/CLAUDE.md +3 -3
  41. package/src/claude/agents/brainstormer.md +52 -69
  42. package/src/claude/agents/code-auditor.md +32 -14
  43. package/src/claude/agents/debugger.md +35 -20
  44. package/src/claude/agents/deployer.md +12 -5
  45. package/src/claude/agents/docs-keeper.md +11 -1
  46. package/src/claude/agents/implementer.md +25 -13
  47. package/src/claude/agents/inspector.md +6 -3
  48. package/src/claude/agents/project-manager.md +1 -1
  49. package/src/claude/agents/researcher.md +26 -55
  50. package/src/claude/agents/spec-maker.md +12 -7
  51. package/src/claude/agents/strategist.md +52 -0
  52. package/src/claude/agents/test-runner.md +117 -132
  53. package/src/claude/agents/ui-ux-designer.md +1 -1
  54. package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -0
  55. package/src/claude/hooks/__tests__/precompact.test.js +158 -0
  56. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +2 -0
  57. package/src/claude/hooks/__tests__/spec-gate.test.js +348 -3
  58. package/src/claude/hooks/__tests__/state.test.js +7 -0
  59. package/src/claude/hooks/__tests__/statusline.test.js +196 -0
  60. package/src/claude/hooks/agent.cjs +13 -8
  61. package/src/claude/hooks/completion-authority.cjs +10 -4
  62. package/src/claude/hooks/docs-sync.cjs +14 -11
  63. package/src/claude/hooks/inspect-block.cjs +28 -8
  64. package/src/claude/hooks/lib/config.cjs +8 -2
  65. package/src/claude/hooks/lib/counter.cjs +7 -5
  66. package/src/claude/hooks/lib/hook-payload.cjs +160 -0
  67. package/src/claude/hooks/lib/hook-state-dir.cjs +39 -0
  68. package/src/claude/hooks/lib/runtime-dir.cjs +49 -0
  69. package/src/claude/hooks/lib/secret-keywords.cjs +42 -0
  70. package/src/claude/hooks/precompact.cjs +106 -0
  71. package/src/claude/hooks/privacy-block.cjs +4 -2
  72. package/src/claude/hooks/rules.cjs +5 -3
  73. package/src/claude/hooks/secret-output-guardrail.cjs +44 -0
  74. package/src/claude/hooks/semantic-review-authority.cjs +6 -4
  75. package/src/claude/hooks/session.cjs +37 -9
  76. package/src/claude/hooks/spec-gate.cjs +21 -11
  77. package/src/claude/hooks/spec-state.cjs +11 -7
  78. package/src/claude/hooks/state.cjs +7 -5
  79. package/src/claude/hooks/task-scaffold-guard.cjs +30 -9
  80. package/src/claude/hooks/usage.cjs +6 -5
  81. package/src/claude/migration-manifest.json +31 -15
  82. package/src/claude/output-styles/coding-level-0-eli5.md +103 -0
  83. package/src/claude/output-styles/coding-level-1-junior.md +124 -0
  84. package/src/claude/output-styles/coding-level-2-mid.md +146 -0
  85. package/src/claude/output-styles/coding-level-3-senior.md +148 -0
  86. package/src/claude/output-styles/coding-level-4-lead.md +159 -0
  87. package/src/claude/output-styles/coding-level-5-god.md +91 -0
  88. package/src/claude/references/debugger/core-philosophy.md +5 -3
  89. package/src/claude/references/debugger/frontend-verification.md +2 -2
  90. package/src/claude/references/debugger/log-ci-analysis.md +13 -1
  91. package/src/claude/references/debugger/parallel-agent-hydration.md +19 -34
  92. package/src/claude/references/debugger/performance-diagnostics.md +2 -2
  93. package/src/claude/references/debugger/root-cause-tracing.md +37 -6
  94. package/src/claude/references/debugger/side-effect-gate.md +3 -1
  95. package/src/claude/references/debugger/verification-protocol.md +13 -12
  96. package/src/claude/rules/orchestrator.md +7 -1
  97. package/src/claude/rules/process-management.md +39 -0
  98. package/src/claude/rules/review-audit-self-decision.md +48 -0
  99. package/src/claude/rules/skill-domain-routing.md +26 -24
  100. package/src/claude/rules/skill-workflow-routing.md +22 -14
  101. package/src/claude/rules/state-sync.md +7 -1
  102. package/src/claude/rules/workflow.md +5 -3
  103. package/src/claude/runtime.json +1 -0
  104. package/src/claude/runtime.schema.json +205 -0
  105. package/src/claude/scripts/generate-skill-catalog.cjs +156 -122
  106. package/src/claude/scripts/provenance.cjs +29 -9
  107. package/src/claude/scripts/spec-receipt.cjs +65 -4
  108. package/src/claude/scripts/workflow-policy.cjs +1 -1
  109. package/src/claude/settings/settings.json +16 -0
  110. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  111. package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
  112. package/src/claude/skills/brainstorm/SKILL.md +193 -172
  113. package/src/claude/skills/brainstorm/references/question-framework.md +105 -167
  114. package/src/claude/skills/chrome-devtools/SKILL.md +1 -1
  115. package/src/claude/skills/code-review/SKILL.md +43 -25
  116. package/src/claude/skills/code-review/references/spec-compliance-review.md +3 -4
  117. package/src/claude/skills/code-review/references/verification-gate.md +1 -1
  118. package/src/claude/skills/debug/SKILL.md +48 -19
  119. package/src/claude/skills/delegate/SKILL.md +1 -1
  120. package/src/claude/skills/develop/SKILL.md +92 -65
  121. package/src/claude/skills/develop/references/parallel-waves.md +36 -63
  122. package/src/claude/skills/develop/references/quality-gate.md +31 -4
  123. package/src/claude/skills/develop/references/subagent-patterns.md +21 -7
  124. package/src/claude/skills/docs/SKILL.md +42 -23
  125. package/src/claude/skills/docs/references/init-workflow.md +4 -4
  126. package/src/claude/skills/docs/references/reconstruct-workflow.md +6 -6
  127. package/src/claude/skills/docs/references/standard-docs-workflow.md +6 -2
  128. package/src/claude/skills/docs/references/summarize-workflow.md +2 -2
  129. package/src/claude/skills/docs/references/update-workflow.md +9 -7
  130. package/src/claude/skills/docs/templates/requirements-as-is.md +1 -1
  131. package/src/claude/skills/docx/SKILL.md +1 -1
  132. package/src/claude/skills/git/SKILL.md +1 -1
  133. package/src/claude/skills/git/references/commit-protocols.md +1 -1
  134. package/src/claude/skills/git/references/finish-branch.md +1 -1
  135. package/src/claude/skills/hotfix/SKILL.md +159 -170
  136. package/src/claude/skills/hotfix/references/diagnosis-protocol.md +14 -9
  137. package/src/claude/skills/hotfix/references/parallel-patterns.md +27 -13
  138. package/src/claude/skills/hotfix/references/prevention-gate.md +5 -0
  139. package/src/claude/skills/hotfix/references/review-cycle.md +21 -15
  140. package/src/claude/skills/hotfix/references/workflow-specialized.md +53 -21
  141. package/src/claude/skills/inspect/SKILL.md +55 -46
  142. package/src/claude/skills/inspect/references/internal-inspection.md +21 -5
  143. package/src/claude/skills/loop/SKILL.md +114 -0
  144. package/src/claude/skills/loop/references/bounded-loop-protocol.md +104 -0
  145. package/src/claude/skills/loop/references/metric-and-guard-contract.md +76 -0
  146. package/src/claude/skills/pdf/SKILL.md +1 -1
  147. package/src/claude/skills/pptx/SKILL.md +1 -1
  148. package/src/claude/skills/question/SKILL.md +16 -13
  149. package/src/claude/skills/research/SKILL.md +70 -43
  150. package/src/claude/skills/route/SKILL.md +94 -0
  151. package/src/claude/skills/route/references/agent-timing.md +46 -0
  152. package/src/claude/skills/route/references/chaining-patterns.md +53 -0
  153. package/src/claude/skills/route/references/task-taxonomy.md +48 -0
  154. package/src/claude/skills/specs/SKILL.md +51 -71
  155. package/src/claude/skills/specs/references/review.md +11 -8
  156. package/src/claude/skills/specs/references/templates.md +34 -18
  157. package/src/claude/skills/specs/templates/requirements-init.md +1 -1
  158. package/src/claude/skills/specs/templates/research.md +2 -2
  159. package/src/claude/skills/sync/SKILL.md +6 -6
  160. package/src/claude/skills/sync/references/sync-protocols.md +1 -1
  161. package/src/claude/skills/test/SKILL.md +120 -120
  162. package/src/claude/skills/test/references/execution-strategy.md +208 -361
  163. package/src/claude/skills/test/references/failure-triage.md +55 -121
  164. package/src/claude/skills/test/references/test-memory.md +20 -34
  165. package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
  166. package/src/claude/skills/web-testing/SKILL.md +1 -1
  167. package/src/claude/skills/xlsx/SKILL.md +1 -1
  168. package/src/claude/status.cjs +135 -33
  169. package/src/codex/AGENTS.md +14 -4
  170. package/src/codex/hooks/docs-sync.cjs +8 -3
  171. package/src/codex/hooks/lib/hook-context.cjs +18 -4
  172. package/src/codex/hooks/rules.cjs +29 -1
  173. package/src/codex/hooks/secret-output-guardrail.cjs +30 -0
  174. package/src/codex/hooks/spec-gate.cjs +3 -8
  175. package/src/codex/hooks/spec-state.cjs +3 -2
  176. package/src/codex/hooks.json +5 -0
  177. package/src/codex/rules/state-sync.md +7 -1
  178. package/src/codex/runtime.json +2 -0
  179. package/src/common/.env.example +1 -1
  180. package/src/omp/extensions/cafekit-bridge.mjs +233 -0
  181. package/src/omp/hooks/privacy-block.cjs +250 -0
  182. package/src/omp/runtime.json +33 -0
  183. package/bin/lib/rtk-run.js +0 -28
  184. package/bin/phases/setup-rtk.js +0 -159
  185. package/src/claude/skills/backend-development/SKILL.md +0 -101
  186. package/src/claude/skills/backend-development/references/backend-api-design.md +0 -495
  187. package/src/claude/skills/backend-development/references/backend-architecture.md +0 -454
  188. package/src/claude/skills/backend-development/references/backend-authentication.md +0 -338
  189. package/src/claude/skills/backend-development/references/backend-code-quality.md +0 -659
  190. package/src/claude/skills/backend-development/references/backend-debugging.md +0 -904
  191. package/src/claude/skills/backend-development/references/backend-devops.md +0 -491
  192. package/src/claude/skills/backend-development/references/backend-mindset.md +0 -387
  193. package/src/claude/skills/backend-development/references/backend-performance.md +0 -397
  194. package/src/claude/skills/backend-development/references/backend-security.md +0 -342
  195. package/src/claude/skills/backend-development/references/backend-technologies.md +0 -256
  196. package/src/claude/skills/backend-development/references/backend-testing.md +0 -429
  197. package/src/claude/skills/devops/.env.example +0 -76
  198. package/src/claude/skills/devops/SKILL.md +0 -102
  199. package/src/claude/skills/devops/references/browser-rendering.md +0 -305
  200. package/src/claude/skills/devops/references/cloudflare-d1-kv.md +0 -123
  201. package/src/claude/skills/devops/references/cloudflare-platform.md +0 -271
  202. package/src/claude/skills/devops/references/cloudflare-r2-storage.md +0 -280
  203. package/src/claude/skills/devops/references/cloudflare-workers-advanced.md +0 -312
  204. package/src/claude/skills/devops/references/cloudflare-workers-apis.md +0 -309
  205. package/src/claude/skills/devops/references/cloudflare-workers-basics.md +0 -418
  206. package/src/claude/skills/devops/references/docker-basics.md +0 -297
  207. package/src/claude/skills/devops/references/docker-compose.md +0 -292
  208. package/src/claude/skills/devops/references/gcloud-platform.md +0 -297
  209. package/src/claude/skills/devops/references/gcloud-services.md +0 -304
  210. package/src/claude/skills/devops/references/kubernetes-basics.md +0 -95
  211. package/src/claude/skills/devops/references/kubernetes-helm-advanced.md +0 -70
  212. package/src/claude/skills/devops/references/kubernetes-helm.md +0 -81
  213. package/src/claude/skills/devops/references/kubernetes-kubectl.md +0 -74
  214. package/src/claude/skills/devops/references/kubernetes-security-advanced.md +0 -94
  215. package/src/claude/skills/devops/references/kubernetes-security.md +0 -89
  216. package/src/claude/skills/devops/references/kubernetes-troubleshooting-advanced.md +0 -74
  217. package/src/claude/skills/devops/references/kubernetes-troubleshooting.md +0 -49
  218. package/src/claude/skills/devops/references/kubernetes-workflows-advanced.md +0 -75
  219. package/src/claude/skills/devops/references/kubernetes-workflows.md +0 -76
  220. package/src/claude/skills/devops/scripts/cloudflare_deploy.py +0 -269
  221. package/src/claude/skills/devops/scripts/docker_optimize.py +0 -332
  222. package/src/claude/skills/devops/scripts/requirements.txt +0 -20
  223. package/src/claude/skills/devops/scripts/tests/requirements.txt +0 -3
  224. package/src/claude/skills/devops/scripts/tests/test_cloudflare_deploy.py +0 -285
  225. package/src/claude/skills/devops/scripts/tests/test_docker_optimize.py +0 -436
  226. package/src/claude/skills/frontend-design/SKILL.md +0 -135
  227. package/src/claude/skills/frontend-design/references/ai-multimodal-overview.md +0 -165
  228. package/src/claude/skills/frontend-design/references/analysis-best-practices.md +0 -80
  229. package/src/claude/skills/frontend-design/references/analysis-prompts.md +0 -141
  230. package/src/claude/skills/frontend-design/references/analysis-techniques.md +0 -118
  231. package/src/claude/skills/frontend-design/references/animejs.md +0 -396
  232. package/src/claude/skills/frontend-design/references/anti-slop-rules.md +0 -103
  233. package/src/claude/skills/frontend-design/references/asset-generation.md +0 -337
  234. package/src/claude/skills/frontend-design/references/bento-motion-engine.md +0 -142
  235. package/src/claude/skills/frontend-design/references/design-extraction-overview.md +0 -71
  236. package/src/claude/skills/frontend-design/references/extraction-best-practices.md +0 -141
  237. package/src/claude/skills/frontend-design/references/extraction-output-templates.md +0 -162
  238. package/src/claude/skills/frontend-design/references/extraction-prompts.md +0 -127
  239. package/src/claude/skills/frontend-design/references/magicui-components.md +0 -129
  240. package/src/claude/skills/frontend-design/references/performance-guardrails.md +0 -169
  241. package/src/claude/skills/frontend-design/references/premium-design-patterns.md +0 -93
  242. package/src/claude/skills/frontend-design/references/redesign-audit-checklist.md +0 -114
  243. package/src/claude/skills/frontend-design/references/technical-accessibility.md +0 -119
  244. package/src/claude/skills/frontend-design/references/technical-best-practices.md +0 -97
  245. package/src/claude/skills/frontend-design/references/technical-optimization.md +0 -44
  246. package/src/claude/skills/frontend-design/references/technical-overview.md +0 -90
  247. package/src/claude/skills/frontend-design/references/technical-workflows.md +0 -150
  248. package/src/claude/skills/frontend-design/references/visual-analysis-overview.md +0 -95
  249. package/src/claude/skills/frontend-design/references/workflow-3d.md +0 -102
  250. package/src/claude/skills/frontend-design/references/workflow-describe.md +0 -87
  251. package/src/claude/skills/frontend-design/references/workflow-immersive.md +0 -87
  252. package/src/claude/skills/frontend-design/references/workflow-quick.md +0 -57
  253. package/src/claude/skills/frontend-design/references/workflow-screenshot.md +0 -63
  254. package/src/claude/skills/frontend-design/references/workflow-video.md +0 -74
  255. package/src/claude/skills/frontend-development/SKILL.md +0 -406
  256. package/src/claude/skills/frontend-development/resources/common-patterns.md +0 -331
  257. package/src/claude/skills/frontend-development/resources/complete-examples.md +0 -872
  258. package/src/claude/skills/frontend-development/resources/component-patterns.md +0 -502
  259. package/src/claude/skills/frontend-development/resources/data-fetching.md +0 -767
  260. package/src/claude/skills/frontend-development/resources/file-organization.md +0 -502
  261. package/src/claude/skills/frontend-development/resources/loading-and-error-states.md +0 -501
  262. package/src/claude/skills/frontend-development/resources/performance.md +0 -406
  263. package/src/claude/skills/frontend-development/resources/routing-guide.md +0 -364
  264. package/src/claude/skills/frontend-development/resources/styling-guide.md +0 -428
  265. package/src/claude/skills/frontend-development/resources/typescript-standards.md +0 -418
  266. package/src/claude/skills/mobile-development/SKILL.md +0 -218
  267. package/src/claude/skills/mobile-development/references/mobile-android.md +0 -604
  268. package/src/claude/skills/mobile-development/references/mobile-best-practices.md +0 -544
  269. package/src/claude/skills/mobile-development/references/mobile-debugging.md +0 -1089
  270. package/src/claude/skills/mobile-development/references/mobile-frameworks.md +0 -465
  271. package/src/claude/skills/mobile-development/references/mobile-ios.md +0 -496
  272. package/src/claude/skills/mobile-development/references/mobile-mindset.md +0 -542
  273. package/src/claude/skills/react-best-practices/AGENTS.md +0 -2249
  274. package/src/claude/skills/react-best-practices/README.md +0 -123
  275. package/src/claude/skills/react-best-practices/SKILL.md +0 -128
  276. package/src/claude/skills/react-best-practices/metadata.json +0 -15
  277. package/src/claude/skills/react-best-practices/rules/_sections.md +0 -46
  278. package/src/claude/skills/react-best-practices/rules/_template.md +0 -28
  279. package/src/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  280. package/src/claude/skills/react-best-practices/rules/advanced-use-latest.md +0 -49
  281. package/src/claude/skills/react-best-practices/rules/async-api-routes.md +0 -38
  282. package/src/claude/skills/react-best-practices/rules/async-defer-await.md +0 -80
  283. package/src/claude/skills/react-best-practices/rules/async-dependencies.md +0 -36
  284. package/src/claude/skills/react-best-practices/rules/async-parallel.md +0 -28
  285. package/src/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -99
  286. package/src/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -59
  287. package/src/claude/skills/react-best-practices/rules/bundle-conditional.md +0 -31
  288. package/src/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +0 -49
  289. package/src/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  290. package/src/claude/skills/react-best-practices/rules/bundle-preload.md +0 -50
  291. package/src/claude/skills/react-best-practices/rules/client-event-listeners.md +0 -74
  292. package/src/claude/skills/react-best-practices/rules/client-swr-dedup.md +0 -56
  293. package/src/claude/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
  294. package/src/claude/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
  295. package/src/claude/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
  296. package/src/claude/skills/react-best-practices/rules/js-cache-storage.md +0 -70
  297. package/src/claude/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
  298. package/src/claude/skills/react-best-practices/rules/js-early-exit.md +0 -50
  299. package/src/claude/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
  300. package/src/claude/skills/react-best-practices/rules/js-index-maps.md +0 -37
  301. package/src/claude/skills/react-best-practices/rules/js-length-check-first.md +0 -49
  302. package/src/claude/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
  303. package/src/claude/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
  304. package/src/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
  305. package/src/claude/skills/react-best-practices/rules/rendering-activity.md +0 -26
  306. package/src/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  307. package/src/claude/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
  308. package/src/claude/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
  309. package/src/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  310. package/src/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  311. package/src/claude/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
  312. package/src/claude/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
  313. package/src/claude/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
  314. package/src/claude/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
  315. package/src/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
  316. package/src/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  317. package/src/claude/skills/react-best-practices/rules/rerender-memo.md +0 -44
  318. package/src/claude/skills/react-best-practices/rules/rerender-transitions.md +0 -40
  319. package/src/claude/skills/react-best-practices/rules/server-after-nonblocking.md +0 -73
  320. package/src/claude/skills/react-best-practices/rules/server-cache-lru.md +0 -41
  321. package/src/claude/skills/react-best-practices/rules/server-cache-react.md +0 -26
  322. package/src/claude/skills/react-best-practices/rules/server-parallel-fetching.md +0 -79
  323. package/src/claude/skills/react-best-practices/rules/server-serialization.md +0 -38
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Phase helper: Codex hooks.json merge.
3
+ *
4
+ * `.codex/hooks.json` is a shared configuration file, not a CafeKit payload. It was
5
+ * previously copied verbatim, which made a single user-added hook turn the whole file
6
+ * into a user-modified artifact: a normal install then preserved CafeKit's stale hooks,
7
+ * and `--force-overwrite` destroyed the user's. Merging per command removes that choice.
8
+ *
9
+ * This mirrors how `.claude/settings.json` is handled, so hooks.json likewise stays
10
+ * outside the ownership model: CafeKit owns its own entries, the user owns theirs, and
11
+ * neither erases the other. Honors dry-run.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { PLATFORMS } = require('../lib/context');
17
+
18
+ const CODEX_SRC = path.join(__dirname, '../../src/codex');
19
+ const WINDOWS_HOOK_COMMAND = /^node "\.codex\/hooks\/([a-z0-9-]+\.cjs)"$/;
20
+
21
+ /** The hook script a command runs, which is stable across installs and platforms. */
22
+ function hookScript(command) {
23
+ if (typeof command !== 'string') return null;
24
+ const match = command.match(/[/\\]hooks[/\\]([a-z0-9-]+\.cjs)/i);
25
+ return match ? match[1] : null;
26
+ }
27
+
28
+ /**
29
+ * Rewrite a managed `commandWindows` into a path-encoded invocation. Absolute Windows
30
+ * paths cannot be quoted safely inside the Codex command string, so the path travels
31
+ * base64url and is decoded by the launcher.
32
+ */
33
+ function materializeWindowsCommand(handler, projectRoot) {
34
+ if (typeof handler.commandWindows !== 'string') return handler;
35
+ const match = handler.commandWindows.match(WINDOWS_HOOK_COMMAND);
36
+ if (!match) throw new Error(`Unsupported Codex Windows hook command: ${handler.commandWindows}`);
37
+ const hookPath = path.join(fs.realpathSync(projectRoot), '.codex', 'hooks', match[1]);
38
+ const encoded = Buffer.from(hookPath, 'utf8').toString('base64url');
39
+ return {
40
+ ...handler,
41
+ commandWindows: 'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' + encoded,
42
+ };
43
+ }
44
+
45
+ /** Drop CafeKit hooks the manifest has retired, leaving foreign entries untouched. */
46
+ function pruneObsoleteCodexHooks(config, ctx) {
47
+ const obsolete = ctx.manifest?.obsolete?.settingsHookCommandSubstrings || [];
48
+ if (!config.hooks || obsolete.length === 0) return { config, removed: 0 };
49
+
50
+ let removed = 0;
51
+ const pruned = {};
52
+ for (const [eventName, groups] of Object.entries(config.hooks)) {
53
+ if (!Array.isArray(groups)) { pruned[eventName] = groups; continue; }
54
+ const keptGroups = [];
55
+ for (const group of groups) {
56
+ if (!Array.isArray(group?.hooks)) { keptGroups.push(group); continue; }
57
+ const keptHooks = group.hooks.filter((handler) => {
58
+ const command = handler?.command || '';
59
+ const isObsolete = obsolete.some((substring) => command.includes(substring));
60
+ if (isObsolete) removed += 1;
61
+ return !isObsolete;
62
+ });
63
+ if (keptHooks.length > 0) keptGroups.push({ ...group, hooks: keptHooks });
64
+ }
65
+ if (keptGroups.length > 0) pruned[eventName] = keptGroups;
66
+ }
67
+ return { config: removed > 0 ? { ...config, hooks: pruned } : config, removed };
68
+ }
69
+
70
+ /**
71
+ * Merge CafeKit-managed Codex hooks into the project's hooks.json, preserving every
72
+ * entry CafeKit did not author. Codex only.
73
+ */
74
+ function mergeCodexHooks(ctx, platformKey, projectRoot = process.cwd()) {
75
+ if (platformKey !== 'codex') return;
76
+
77
+ const templatePath = path.join(CODEX_SRC, 'hooks.json');
78
+ if (!fs.existsSync(templatePath)) {
79
+ ctx.ui.warn('Codex hooks template not found: src/codex/hooks.json');
80
+ return;
81
+ }
82
+ const managed = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
83
+ const targetPath = path.join(PLATFORMS.codex.folder, 'hooks.json');
84
+
85
+ let existing = {};
86
+ if (fs.existsSync(targetPath)) {
87
+ // A malformed hooks.json must not abort the install and must never be
88
+ // overwritten blind: skip the merge and say why.
89
+ try {
90
+ existing = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
91
+ } catch (error) {
92
+ ctx.ui.warn(`Codex hooks: ${targetPath} is not valid JSON (${error.message}). `
93
+ + 'Skipping the hooks merge — fix the file and re-run the installer.');
94
+ ctx.results.errors++;
95
+ return;
96
+ }
97
+ }
98
+
99
+ const { config: base, removed } = pruneObsoleteCodexHooks({ ...existing }, ctx);
100
+ if (removed > 0) {
101
+ ctx.ui.detail(` ↻ ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: removed ${removed} obsolete hook(s)`);
102
+ ctx.results.updated++;
103
+ }
104
+
105
+ const merged = { ...base };
106
+ if (typeof managed.description === 'string' && typeof merged.description !== 'string') {
107
+ merged.description = managed.description;
108
+ }
109
+ merged.hooks = { ...(base.hooks || {}) };
110
+
111
+ let added = 0;
112
+ for (const [eventName, managedGroups] of Object.entries(managed.hooks || {})) {
113
+ const groups = Array.isArray(merged.hooks[eventName]) ? [...merged.hooks[eventName]] : [];
114
+ // A script already registered anywhere under this event stays where the user put
115
+ // it. Matching on the script rather than the whole command survives the path
116
+ // differences between installs and platforms.
117
+ const registered = new Set(
118
+ groups.flatMap((group) => (group?.hooks || []).map((handler) => hookScript(handler?.command))).filter(Boolean),
119
+ );
120
+
121
+ for (const managedGroup of managedGroups) {
122
+ const wanted = (managedGroup.hooks || [])
123
+ .filter((handler) => {
124
+ const script = hookScript(handler?.command);
125
+ return script && !registered.has(script);
126
+ })
127
+ .map((handler) => materializeWindowsCommand(handler, projectRoot));
128
+ if (wanted.length === 0) continue;
129
+
130
+ const matcher = managedGroup.matcher || '';
131
+ const target = groups.find((group) => (group?.matcher || '') === matcher && Array.isArray(group?.hooks));
132
+ if (target) {
133
+ target.hooks = [...target.hooks, ...wanted];
134
+ } else {
135
+ groups.push({ ...managedGroup, hooks: wanted });
136
+ }
137
+ for (const handler of wanted) registered.add(hookScript(handler.command));
138
+ added += wanted.length;
139
+ ctx.ui.detail(` ✓ ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: ${eventName} merged (+${wanted.length})`);
140
+ }
141
+
142
+ merged.hooks[eventName] = groups;
143
+ }
144
+
145
+ if (added === 0 && removed === 0) {
146
+ ctx.ui.detail(` → ${ctx.dryRun ? '[dry-run] ' : ''}Codex hooks: already registered`);
147
+ ctx.results.unchanged++;
148
+ }
149
+
150
+ if (!ctx.dryRun) {
151
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
152
+ fs.writeFileSync(targetPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
153
+ }
154
+ }
155
+
156
+ module.exports = { mergeCodexHooks, pruneObsoleteCodexHooks, hookScript };
@@ -5,57 +5,32 @@ const path = require('path');
5
5
  const { PLATFORMS } = require('../lib/context');
6
6
  const { writeManagedFile, copyManagedTree } = require('../lib/managed-writer');
7
7
  const { report, treeAction } = require('./report');
8
- const { copyRulesDirectory } = require('./claude-runtime');
8
+ const { copyRulesDirectory, copyOutputStylesDirectory } = require('./claude-runtime');
9
9
  const {
10
10
  managedRange,
11
11
  upsertManagedCodexBlock
12
12
  } = require('../lib/codex-install');
13
+ const { mergeCodexHooks } = require('./codex-hooks');
13
14
 
14
15
  const SRC = path.join(__dirname, '../../src');
15
16
  const CODEX_SRC = path.join(SRC, 'codex');
16
17
 
18
+ // hooks.json is merged by `codex-hooks.js`, not copied: it is a shared config file
19
+ // the user also writes to, so a verbatim copy would either preserve CafeKit's stale
20
+ // hooks or destroy the user's.
17
21
  const CODEX_OWN_RUNTIME = [
18
22
  ['gitignore', '.gitignore'],
19
- ['runtime.json', 'runtime.json'],
20
- ['hooks.json', 'hooks.json']
23
+ ['runtime.json', 'runtime.json']
21
24
  ];
22
25
 
23
- const WINDOWS_HOOK_COMMAND = /^node "\.codex\/hooks\/([a-z0-9-]+\.cjs)"$/;
24
-
25
- function materializeWindowsHookCommands(content, projectRoot = process.cwd()) {
26
- const config = JSON.parse(content);
27
- const canonicalRoot = fs.realpathSync(projectRoot);
28
-
29
- for (const groups of Object.values(config.hooks || {})) {
30
- for (const group of groups) {
31
- for (const handler of group.hooks || []) {
32
- if (typeof handler.commandWindows !== 'string') continue;
33
- const match = handler.commandWindows.match(WINDOWS_HOOK_COMMAND);
34
- if (!match) {
35
- throw new Error(`Unsupported Codex Windows hook command: ${handler.commandWindows}`);
36
- }
37
- const hookPath = path.join(canonicalRoot, '.codex', 'hooks', match[1]);
38
- const encodedPath = Buffer.from(hookPath, 'utf8').toString('base64url');
39
- handler.commandWindows = (
40
- 'node -e "process.argv[1]=Buffer.from(process.argv[1],\'base64url\').toString(\'utf8\');require(\'module\').runMain()" ' +
41
- encodedPath
42
- );
43
- }
44
- }
45
- }
46
-
47
- return `${JSON.stringify(config, null, 2)}\n`;
48
- }
49
-
50
- function writeSourceFile(ctx, platformKey, src, dest, label, transform) {
26
+ function writeSourceFile(ctx, platformKey, src, dest, label) {
51
27
  const platform = PLATFORMS[platformKey];
52
28
  const { action } = writeManagedFile({
53
29
  src,
54
30
  dest,
55
31
  platformFolder: platform.folder,
56
32
  ctx,
57
- tracker: ctx.trackers[platformKey],
58
- transform
33
+ tracker: ctx.trackers[platformKey]
59
34
  });
60
35
  report(ctx, action, label);
61
36
  }
@@ -68,10 +43,7 @@ function installRuntimeFiles(ctx, platformKey) {
68
43
  platformKey,
69
44
  path.join(CODEX_SRC, sourceRel),
70
45
  path.join(platform.folder, targetRel),
71
- `Codex runtime: ${targetRel}`,
72
- sourceRel === 'hooks.json'
73
- ? (content) => materializeWindowsHookCommands(content, process.cwd())
74
- : undefined
46
+ `Codex runtime: ${targetRel}`
75
47
  );
76
48
  }
77
49
  writeSourceFile(
@@ -82,6 +54,8 @@ function installRuntimeFiles(ctx, platformKey) {
82
54
  'Codex skills: .agents/.gitignore'
83
55
  );
84
56
 
57
+ mergeCodexHooks(ctx, platformKey);
58
+
85
59
  const agg = copyManagedTree({
86
60
  src: path.join(CODEX_SRC, 'hooks'),
87
61
  dest: path.join(platform.folder, 'hooks'),
@@ -92,6 +66,15 @@ function installRuntimeFiles(ctx, platformKey) {
92
66
  report(ctx, treeAction(agg), 'Codex native hooks');
93
67
  // Keep one canonical command analyzer in the Claude source tree, then
94
68
  // materialize the same bytes into the installed Codex runtime.
69
+ // One canonical runtime schema lives in the Claude source tree; both installed
70
+ // runtimes reference it as ./runtime.schema.json beside their runtime.json.
71
+ writeSourceFile(
72
+ ctx,
73
+ platformKey,
74
+ path.join(SRC, 'claude', 'runtime.schema.json'),
75
+ path.join(platform.folder, 'runtime.schema.json'),
76
+ 'Codex runtime schema'
77
+ );
95
78
  writeSourceFile(
96
79
  ctx,
97
80
  platformKey,
@@ -99,6 +82,13 @@ function installRuntimeFiles(ctx, platformKey) {
99
82
  path.join(platform.folder, 'hooks', 'lib', 'privacy-command-analysis.cjs'),
100
83
  'Codex privacy command analyzer'
101
84
  );
85
+ writeSourceFile(
86
+ ctx,
87
+ platformKey,
88
+ path.join(SRC, 'claude', 'hooks', 'lib', 'secret-keywords.cjs'),
89
+ path.join(platform.folder, 'hooks', 'lib', 'secret-keywords.cjs'),
90
+ 'Codex secret keyword matcher'
91
+ );
102
92
  writeSourceFile(
103
93
  ctx,
104
94
  platformKey,
@@ -154,9 +144,10 @@ function installCodexRuntime(ctx, platformKey) {
154
144
  if (platformKey !== 'codex') return ctx;
155
145
  installRuntimeFiles(ctx, platformKey);
156
146
  copyRulesDirectory(ctx, platformKey);
147
+ copyOutputStylesDirectory(ctx, platformKey);
157
148
  installNativeRuleOverrides(ctx, platformKey);
158
149
  installManagedAgentsMd(ctx);
159
150
  return ctx;
160
151
  }
161
152
 
162
- module.exports = { installCodexRuntime, materializeWindowsHookCommands };
153
+ module.exports = { installCodexRuntime };
@@ -127,12 +127,18 @@ function copyPlatformFiles(ctx, platformKey) {
127
127
  });
128
128
  }
129
129
 
130
- // Additional required skills.
130
+ // Core skills plus the optional document bundle selected for this runtime.
131
131
  let requiredSkills = [];
132
132
  if (hasPlatformCapability(platformKey, 'skills')) {
133
133
  requiredSkills = ctx.manifest?.skills?.required || [];
134
+ if (ctx.documentSkills?.[platformKey]?.enabled) {
135
+ requiredSkills = [
136
+ ...requiredSkills,
137
+ ...(ctx.manifest?.skills?.bundles?.documentSkills || [])
138
+ ];
139
+ }
134
140
  }
135
- requiredSkills
141
+ [...new Set(requiredSkills)]
136
142
  .filter((skillName) => skillName !== 'specs')
137
143
  .forEach((skillName) => {
138
144
  const skillSource = path.join(skillsSourceDir, skillName);
@@ -183,7 +189,14 @@ function copyPlatformFiles(ctx, platformKey) {
183
189
  } else {
184
190
  report(ctx, 'missing', `agents: ${platform.agentsDir}`);
185
191
  }
192
+ }
186
193
 
194
+ // ── Scripts ─────────────────────────────────────────────
195
+ // Gated on its own capability. This block used to sit inside the agents guard,
196
+ // so a platform with agents: false and scripts: true (omp) never received
197
+ // .omp/scripts/, and every Stop-time gate that requires ../scripts/*.cjs answered
198
+ // "Completion gate unavailable" on each session_stop.
199
+ if (hasPlatformCapability(platformKey, 'scripts')) {
187
200
  const scriptsSourceDir = path.join(SRC, 'claude/scripts');
188
201
  if (fs.existsSync(scriptsSourceDir)) {
189
202
  const scriptsDest = getRuntimeSupportTargetDir(platformKey, 'scripts');
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Phase: Oh My Pi runtime.
5
+ *
6
+ * omp discovers `.claude/skills` and `.agents/skills` on its own, so CafeKit copies no
7
+ * skill payload here. What omp does not have is CafeKit's enforcement chain, so this
8
+ * phase provisions what carries it:
9
+ *
10
+ * .omp/hooks/ the gate scripts: the Claude set, with omp's overlay on top
11
+ * .omp/extensions/ the bridge omp auto-loads, which dispatches those scripts
12
+ * .omp/runtime.json the hooks' configuration, beside the schema that describes it
13
+ * .omp/rules/ the rules the hooks inject
14
+ *
15
+ * The hooks are portable: they derive their runtime directory from their own location,
16
+ * so the Claude bytes run unchanged under `.omp/`. Only the files that genuinely differ
17
+ * for omp live in `src/omp/hooks/` and are written over the Claude set afterwards, so a
18
+ * change to a shared hook reaches omp without anyone remembering to re-fork it.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { PLATFORMS } = require('../lib/context');
24
+ const { writeManagedFile, copyManagedTree } = require('../lib/managed-writer');
25
+ const { report, treeAction } = require('./report');
26
+ const { copyRulesDirectory } = require('./claude-runtime');
27
+
28
+ const SRC = path.join(__dirname, '../../src');
29
+ const CLAUDE_SRC = path.join(SRC, 'claude');
30
+ const OMP_SRC = path.join(SRC, 'omp');
31
+
32
+ /** Files under `src/omp/hooks/` that replace their Claude counterpart, relative to the hooks dir. */
33
+ function overlayFiles(dir = path.join(OMP_SRC, 'hooks'), prefix = '') {
34
+ if (!fs.existsSync(dir)) return [];
35
+ const found = [];
36
+ for (const name of fs.readdirSync(dir)) {
37
+ const rel = prefix ? `${prefix}/${name}` : name;
38
+ if (fs.statSync(path.join(dir, name)).isDirectory()) found.push(...overlayFiles(path.join(dir, name), rel));
39
+ else found.push(rel);
40
+ }
41
+ return found.sort();
42
+ }
43
+
44
+ /**
45
+ * The Claude hook set is the list the migration manifest ships to `.claude/`, not a
46
+ * directory walk: the source tree also holds `__tests__/`, which must never install.
47
+ */
48
+ function claudeHookFiles(ctx) {
49
+ const files = ctx.manifest?.runtime?.files || [];
50
+ return files.filter((rel) => rel.startsWith('hooks/')).map((rel) => rel.slice('hooks/'.length));
51
+ }
52
+
53
+ function writeSourceFile(ctx, platformKey, src, dest, label) {
54
+ const platform = PLATFORMS[platformKey];
55
+ const { action } = writeManagedFile({
56
+ src,
57
+ dest,
58
+ platformFolder: platform.folder,
59
+ ctx,
60
+ tracker: ctx.trackers[platformKey]
61
+ });
62
+ report(ctx, action, label);
63
+ }
64
+
65
+ function installHooks(ctx, platformKey) {
66
+ const platform = PLATFORMS[platformKey];
67
+ const overlay = new Set(overlayFiles());
68
+ // Claude set first, skipping anything the overlay is about to replace so a file is
69
+ // written once and the manifest records the bytes that actually land.
70
+ for (const rel of claudeHookFiles(ctx)) {
71
+ if (overlay.has(rel)) continue;
72
+ writeSourceFile(
73
+ ctx,
74
+ platformKey,
75
+ path.join(CLAUDE_SRC, 'hooks', rel),
76
+ path.join(platform.folder, 'hooks', rel),
77
+ `omp hooks: ${rel}`
78
+ );
79
+ }
80
+ for (const rel of overlay) {
81
+ writeSourceFile(
82
+ ctx,
83
+ platformKey,
84
+ path.join(OMP_SRC, 'hooks', rel),
85
+ path.join(platform.folder, 'hooks', rel),
86
+ `omp hooks (overlay): ${rel}`
87
+ );
88
+ }
89
+ }
90
+
91
+ function installRuntimeFiles(ctx, platformKey) {
92
+ const platform = PLATFORMS[platformKey];
93
+ writeSourceFile(
94
+ ctx,
95
+ platformKey,
96
+ path.join(OMP_SRC, 'runtime.json'),
97
+ path.join(platform.folder, 'runtime.json'),
98
+ 'omp runtime: runtime.json'
99
+ );
100
+ // One canonical runtime schema lives in the Claude source tree; every installed
101
+ // runtime references it as ./runtime.schema.json beside its runtime.json.
102
+ writeSourceFile(
103
+ ctx,
104
+ platformKey,
105
+ path.join(CLAUDE_SRC, 'runtime.schema.json'),
106
+ path.join(platform.folder, 'runtime.schema.json'),
107
+ 'omp runtime schema'
108
+ );
109
+ }
110
+
111
+ function installBridge(ctx, platformKey) {
112
+ const platform = PLATFORMS[platformKey];
113
+ const bridgeSource = path.join(OMP_SRC, 'extensions');
114
+ const bridgeDest = path.join(platform.folder, 'extensions');
115
+ if (fs.existsSync(bridgeSource)) {
116
+ const bridge = copyManagedTree({
117
+ src: bridgeSource,
118
+ dest: bridgeDest,
119
+ platformFolder: platform.folder,
120
+ ctx,
121
+ tracker: ctx.trackers[platformKey]
122
+ });
123
+ report(ctx, treeAction(bridge), 'omp bridge extension');
124
+ } else if (!ctx.dryRun) {
125
+ // A half-provisioned install is visibly empty rather than silently absent.
126
+ fs.mkdirSync(bridgeDest, { recursive: true });
127
+ ctx.ui.detail(' ✓ omp extensions directory created (bridge not present)');
128
+ }
129
+ }
130
+
131
+ function installOmpRuntime(ctx, platformKey) {
132
+ if (platformKey !== 'omp') return;
133
+ installHooks(ctx, platformKey);
134
+ installRuntimeFiles(ctx, platformKey);
135
+ installBridge(ctx, platformKey);
136
+ copyRulesDirectory(ctx, platformKey);
137
+ return ctx;
138
+ }
139
+
140
+ module.exports = { installOmpRuntime, overlayFiles, claudeHookFiles };
@@ -110,6 +110,9 @@ function ensureGitignore(ctx) {
110
110
  // Local runtime payload — reinstall with npx; keep out of git
111
111
  '.claude/',
112
112
  '.codex/',
113
+ // omp auto-executes every file in .omp/extensions/, so a committed copy
114
+ // would run arbitrary code on clone. Keep it out of git like the rest.
115
+ '.omp/',
113
116
  '.agents/'
114
117
  ];
115
118
  const prefix = ctx.dryRun ? '[dry-run] ' : '';
@@ -11,7 +11,8 @@ const path = require('path');
11
11
  const {
12
12
  PLATFORMS,
13
13
  detectPlatforms,
14
- getPlatformKeys
14
+ getPlatformKeys,
15
+ resolvePlatformAliases
15
16
  } = require('../lib/context');
16
17
  const { SUPPORTED, LANGUAGE_LABELS, OTHER_LABEL } = require('../lib/i18n');
17
18
 
@@ -36,7 +37,25 @@ function getInstalledLocale(platformKeys = getPlatformKeys()) {
36
37
  }
37
38
 
38
39
  /** First step: pick the installer/UI language (interactive only). */
40
+ /**
41
+ * Resolve host aliases on the requested platform list, once, before anything reads it.
42
+ *
43
+ * selectLanguage() filters this list through PLATFORMS to decide which installed runtime
44
+ * to take a saved locale from, so an unresolved alias would empty that list and fall back
45
+ * to scanning every platform folder — `--platform grok` in a project that also has
46
+ * `.codex/` would then silently take Codex's language.
47
+ */
48
+ function applyPlatformAliases(ctx) {
49
+ const requested = ctx.options.platforms || [];
50
+ if (requested.length === 0) return ctx;
51
+ const { platforms, aliased } = resolvePlatformAliases(requested);
52
+ ctx.options.platforms = platforms;
53
+ ctx.aliasedHost = aliased;
54
+ return ctx;
55
+ }
56
+
39
57
  async function selectLanguage(ctx) {
58
+ applyPlatformAliases(ctx);
40
59
  // Restore the saved locale BEFORE the interactivity check: a non-interactive
41
60
  // upgrade (--yes) must not forget the configured language. Skipping this left
42
61
  // ctx at the 'en' default, and patchRuntimeLocale then clobbered the user's
@@ -161,6 +180,7 @@ async function resolvePlatforms(ctx) {
161
180
  ctx.ui.info(ctx.t('installingFor', {
162
181
  names: platformNames(requested)
163
182
  }));
183
+ if (ctx.aliasedHost) ctx.ui.info(ctx.t('grokCompatNotice'));
164
184
  reportInstallMode(ctx);
165
185
  return ctx;
166
186
  }
@@ -244,6 +264,7 @@ function getInstalledPlatform() {
244
264
 
245
265
  module.exports = {
246
266
  selectLanguage,
267
+ applyPlatformAliases,
247
268
  promptPlatformSelection,
248
269
  resolvePlatforms,
249
270
  getInstalledPlatform,
@@ -0,0 +1,96 @@
1
+ /** Resolve the optional document-skills bundle for each selected runtime. */
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { PLATFORMS } = require('../lib/context');
6
+
7
+ function readMetadata(platformKey) {
8
+ const target = path.join(PLATFORMS[platformKey].folder, 'cafekit.json');
9
+ if (!fs.existsSync(target)) return { state: 'absent', target, metadata: null };
10
+ try {
11
+ const metadata = JSON.parse(fs.readFileSync(target, 'utf8'));
12
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
13
+ return { state: 'invalid', target, metadata: null, reason: 'expected a JSON object' };
14
+ }
15
+ return { state: 'valid', target, metadata };
16
+ } catch (error) {
17
+ return { state: 'invalid', target, metadata: null, reason: error.message };
18
+ }
19
+ }
20
+
21
+ function explicitSelection(options) {
22
+ if (options.withDocumentSkills) {
23
+ return { enabled: true, selectionSource: 'cli-opt-in' };
24
+ }
25
+ if (options.withoutDocumentSkills) {
26
+ return { enabled: false, selectionSource: 'cli-opt-out' };
27
+ }
28
+ return null;
29
+ }
30
+
31
+ async function selectDocumentSkills(ctx) {
32
+ const explicit = explicitSelection(ctx.options);
33
+ const unresolvedFresh = [];
34
+
35
+ for (const platformKey of ctx.platforms) {
36
+ if (explicit) {
37
+ ctx.documentSkills[platformKey] = { ...explicit };
38
+ continue;
39
+ }
40
+
41
+ const metadataResult = readMetadata(platformKey);
42
+ const metadata = metadataResult.metadata;
43
+ if (metadata?.schemaVersion >= 2 && typeof metadata.documentSkills?.enabled === 'boolean') {
44
+ ctx.documentSkills[platformKey] = {
45
+ enabled: metadata.documentSkills.enabled,
46
+ selectionSource: metadata.documentSkills.selectionSource || 'persisted'
47
+ };
48
+ } else if (metadataResult.state === 'valid') {
49
+ ctx.documentSkills[platformKey] = {
50
+ enabled: true,
51
+ selectionSource: 'legacy-upgrade'
52
+ };
53
+ } else if (metadataResult.state === 'invalid') {
54
+ ctx.documentSkills[platformKey] = {
55
+ enabled: true,
56
+ selectionSource: 'metadata-recovery'
57
+ };
58
+ ctx.ui.warn(ctx.t('documentSkillsMetadataRecovery', {
59
+ path: metadataResult.target
60
+ }));
61
+ } else if (fs.existsSync(path.join(PLATFORMS[platformKey].folder, 'cafekit-manifest.json'))) {
62
+ ctx.documentSkills[platformKey] = {
63
+ enabled: true,
64
+ selectionSource: 'legacy-upgrade'
65
+ };
66
+ } else {
67
+ unresolvedFresh.push(platformKey);
68
+ }
69
+ }
70
+
71
+ let freshEnabled = false;
72
+ let source = 'default-fresh';
73
+ if (unresolvedFresh.length > 0 && ctx.interactive) {
74
+ const answer = await ctx.ui.confirm(
75
+ { message: ctx.t('documentSkillsConfirm'), initialValue: false },
76
+ false
77
+ );
78
+ if (ctx.ui.isCancel(answer)) {
79
+ ctx.cancelled = true;
80
+ return ctx;
81
+ }
82
+ freshEnabled = Boolean(answer);
83
+ source = 'interactive';
84
+ }
85
+
86
+ for (const platformKey of unresolvedFresh) {
87
+ ctx.documentSkills[platformKey] = {
88
+ enabled: freshEnabled,
89
+ selectionSource: source
90
+ };
91
+ }
92
+
93
+ return ctx;
94
+ }
95
+
96
+ module.exports = { readMetadata, explicitSelection, selectDocumentSkills };
@@ -0,0 +1,71 @@
1
+ /** Ownership-aware removal for retired and deselected skill directories. */
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { PLATFORMS } = require('../lib/context');
6
+ const { hashFile } = require('../lib/manifest');
7
+
8
+ function listTreeEntries(root) {
9
+ const entries = [];
10
+ const visit = (current) => {
11
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
12
+ const target = path.join(current, entry.name);
13
+ entries.push({ target, entry });
14
+ if (entry.isDirectory() && !entry.isSymbolicLink()) visit(target);
15
+ }
16
+ };
17
+ visit(root);
18
+ return entries;
19
+ }
20
+
21
+ function classifySkillTree(ctx, platformKey, skillName) {
22
+ const platform = PLATFORMS[platformKey];
23
+ const tracker = ctx.trackers[platformKey];
24
+ const target = path.join(platform.skillsDir, skillName);
25
+ if (!fs.existsSync(target)) return { target, removable: false, absent: true };
26
+ if (!fs.lstatSync(target).isDirectory()) {
27
+ return { target, removable: false, absent: false };
28
+ }
29
+
30
+ const entries = listTreeEntries(target);
31
+ const ownership = ctx.ownership?.[platform.folder]?.files || {};
32
+ const unsafe = entries.some(({ target: item, entry }) => {
33
+ if (!entry.isFile() || entry.isSymbolicLink()) return !entry.isDirectory();
34
+ const key = tracker.keyFor(item);
35
+ return ownership[key]?.sha256 !== hashFile(item);
36
+ });
37
+ return { target, removable: entries.length > 0 && !unsafe, absent: false };
38
+ }
39
+
40
+ function removeSkill(ctx, platformKey, skillName) {
41
+ const tracker = ctx.trackers[platformKey];
42
+ const classified = classifySkillTree(ctx, platformKey, skillName);
43
+ if (classified.absent) return;
44
+
45
+ if (!classified.removable) {
46
+ ctx.ui.warn(`Preserved user-owned skill: ${classified.target}`);
47
+ ctx.results.preserved++;
48
+ ctx.results.preservedFiles.push(classified.target);
49
+ return;
50
+ }
51
+
52
+ const prefix = `${tracker.keyFor(classified.target).replace(/\/$/, '')}/`;
53
+ if (!ctx.dryRun) {
54
+ fs.rmSync(classified.target, { recursive: true, force: true });
55
+ tracker.prunePrefix(prefix);
56
+ }
57
+ ctx.ui.detail(` ↻ ${ctx.dryRun ? '[dry-run] ' : ''}Removed skill: ${classified.target}`);
58
+ ctx.results.updated++;
59
+ }
60
+
61
+ function reconcileSkillInventory(ctx, platformKey) {
62
+ const retired = ctx.manifest?.obsolete?.skills || [];
63
+ for (const skillName of retired) removeSkill(ctx, platformKey, skillName);
64
+
65
+ if (!ctx.documentSkills?.[platformKey]?.enabled) {
66
+ const optional = ctx.manifest?.skills?.bundles?.documentSkills || [];
67
+ for (const skillName of optional) removeSkill(ctx, platformKey, skillName);
68
+ }
69
+ }
70
+
71
+ module.exports = { listTreeEntries, classifySkillTree, removeSkill, reconcileSkillInventory };