@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,39 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Where a Claude hook keeps its own generated state: crash logs and the
5
+ * gate/tollgate caches.
6
+ *
7
+ * Hooks run from two places. In an installed project they live in
8
+ * `<runtime-dir>/hooks/`, whose `.logs/` is gitignored, so writing beside the hook is
9
+ * correct. During this package's tests they run straight from
10
+ * `packages/spec/src/claude/hooks/`, where the same write lands inside the
11
+ * source tree as an untracked directory. That changes the worktree digest the
12
+ * receipt provenance is bound to, and the completion gate then reports every
13
+ * done task as stale. It surfaced three times as a false alarm before the cause
14
+ * was found, so source-tree runs write to a temp directory instead.
15
+ *
16
+ * Keeping the rule here means a new hook cannot reintroduce the bug by copying
17
+ * the old `path.join(__dirname, '.logs')` line.
18
+ */
19
+
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ // This module ships in `hooks/lib/`, so the hook directory is always its parent.
24
+ const HOOKS_DIR = path.join(__dirname, '..');
25
+ const SOURCE_MARKER = `${path.sep}packages${path.sep}spec${path.sep}src`;
26
+
27
+ /** True when `dir` sits inside this package's source tree rather than an install. */
28
+ function isSourceTree(dir) {
29
+ return String(dir || '').includes(SOURCE_MARKER);
30
+ }
31
+
32
+ /** Absolute directory for hook-generated state; create it before writing. */
33
+ function hookStateDir() {
34
+ return isSourceTree(HOOKS_DIR)
35
+ ? path.join(os.tmpdir(), 'cafekit-hook-logs', require('./runtime-dir.cjs').runtimeDirName().slice(1))
36
+ : path.join(HOOKS_DIR, '.logs');
37
+ }
38
+
39
+ module.exports = { hookStateDir, isSourceTree };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) 2026 Haposoft. MIT License.
3
+ *
4
+ * Runtime-directory derivation for the gate hooks.
5
+ *
6
+ * The hooks used to hardcode `.claude` in every project-runtime path, so a copy installed
7
+ * under `.omp/hooks/` or `.codex/hooks/` read another platform's config, never matched
8
+ * installed-root detection, and wrote session state into a directory that did not exist.
9
+ * This module ships in `hooks/lib/`, so the runtime root is always two levels up from
10
+ * here and its basename is the answer: `.claude`, `.codex`, or `.omp`.
11
+ *
12
+ * In the source tree the same files live at `packages/spec/src/<platform>/hooks/lib/`, so
13
+ * the basename is the undotted platform name; it is mapped to its dotted form so tests
14
+ * that run hooks from source see the same directory an install would. A dotted basename
15
+ * always wins over the source-tree check, because an install can legitimately sit under a
16
+ * path that happens to contain `/packages/spec/src/`.
17
+ *
18
+ * Anything else yields `.claude`, which is exactly the behaviour every caller had before
19
+ * this module existed. There is deliberately no "derived" flag: nobody would read it.
20
+ *
21
+ * The source-tree marker is inlined rather than required from `hook-state-dir.cjs` so a
22
+ * fixture that copies a hook by hand needs to copy only this one library.
23
+ */
24
+
25
+ const path = require('path');
26
+
27
+ const SOURCE_MARKER = `${path.sep}packages${path.sep}spec${path.sep}src${path.sep}`;
28
+ const HOOKS_DIR = path.resolve(__dirname, '..');
29
+ const RUNTIME_ROOT = path.resolve(HOOKS_DIR, '..');
30
+
31
+ /** `.claude`, `.codex`, `.omp`, or whatever dotted folder this hook tree is installed under. */
32
+ function runtimeDirName() {
33
+ const base = path.basename(RUNTIME_ROOT);
34
+ if (base.startsWith('.')) return base;
35
+ if (HOOKS_DIR.includes(SOURCE_MARKER)) return `.${base}`;
36
+ return '.claude';
37
+ }
38
+
39
+ /** Absolute runtime directory for a project, e.g. `<cwd>/.omp`. */
40
+ function runtimeDir(cwd) {
41
+ return path.join(cwd, runtimeDirName());
42
+ }
43
+
44
+ /** Absolute path under the runtime directory, e.g. `runtimePath(cwd, 'runtime.json')`. */
45
+ function runtimePath(cwd, ...segments) {
46
+ return path.join(runtimeDir(cwd), ...segments);
47
+ }
48
+
49
+ module.exports = { runtimeDirName, runtimeDir, runtimePath };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Copyright (c) 2026 Haposoft. MIT License.
3
+ *
4
+ * Secret-output guardrail keyword matcher.
5
+ *
6
+ * These patterns detect prompts about credential handling. They are deliberately
7
+ * separate from the privacy hooks: those gate whether a sensitive file may be READ,
8
+ * while this list drives a soft reminder about whether a value may be PRINTED.
9
+ * Approval for the first is not approval for the second.
10
+ */
11
+
12
+ const SECRET_KEYWORD_PATTERNS = [
13
+ /\.env(?:\b|$)/i,
14
+ /(?:^|[^A-Za-z0-9])credentials?(?:$|[^A-Za-z0-9])/i,
15
+ /(?:^|[^A-Za-z0-9])secrets?(?:$|[^A-Za-z0-9])/i,
16
+ /(?:^|[^A-Za-z0-9])api[\s_-]*keys?(?:$|[^A-Za-z0-9])/i,
17
+ /(?:^|[^A-Za-z0-9])access[\s_-]*keys?(?:$|[^A-Za-z0-9])/i,
18
+ /(?:^|[^A-Za-z0-9])tokens?(?:$|[^A-Za-z0-9])/i,
19
+ /(?:^|[^A-Za-z0-9])bearer(?:$|[^A-Za-z0-9])/i,
20
+ /(?:^|[^A-Za-z0-9])jwts?(?:$|[^A-Za-z0-9])/i,
21
+ /(?:^|[^A-Za-z0-9])private[\s_-]*keys?(?:$|[^A-Za-z0-9])/i,
22
+ /(?:^|[^A-Za-z0-9])oauth(?:[\s_-]+client)?[\s_-]*secrets?(?:$|[^A-Za-z0-9])/i,
23
+ /(?:^|[^A-Za-z0-9])client[\s_-]*secrets?(?:$|[^A-Za-z0-9])/i,
24
+ /(?:^|[^A-Za-z0-9])cloud[\s_-]*keys?(?:$|[^A-Za-z0-9])/i,
25
+ /(?:^|[^A-Za-z0-9])env[\s_-]+files?(?:$|[^A-Za-z0-9])/i,
26
+ ];
27
+
28
+ /** The reminder text. It names no value and quotes no prompt. */
29
+ const REMINDER = [
30
+ '## Secret handling',
31
+ '- Do not print raw credentials, API keys, tokens, JWTs, private keys, or secret values into the conversation.',
32
+ '- Use [redacted], variable names, counts, or high-level status instead.',
33
+ '- Approval to read a sensitive file or command output does not grant permission to print raw values.',
34
+ '- If a value is needed for a machine action, pass it through a non-echoing path and report only success or failure.',
35
+ ].join('\n');
36
+
37
+ function containsSecretKeyword(prompt) {
38
+ if (typeof prompt !== 'string' || prompt.trim() === '') return false;
39
+ return SECRET_KEYWORD_PATTERNS.some((pattern) => pattern.test(prompt));
40
+ }
41
+
42
+ module.exports = { SECRET_KEYWORD_PATTERNS, REMINDER, containsSecretKeyword };
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) 2026 Haposoft. MIT License.
4
+ *
5
+ * PreCompact Hook — precompact.cjs
6
+ * Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
7
+ *
8
+ * Compaction drops the transcript that told the session where it was. The
9
+ * existing SessionStart warning tells the operator something was lost but not
10
+ * what, so recovery starts from a blank slate and the session re-derives facts
11
+ * it already had. This hook writes down the machine-checkable anchors just
12
+ * before the drop: branch, HEAD, how dirty the tree is, and which task the
13
+ * packet says is in flight. `session.cjs` prints them back on the compact
14
+ * restart.
15
+ *
16
+ * Only facts the runtime can re-derive are recorded. Nothing here is proof of
17
+ * work and nothing here grants an authorization; a compacted session must still
18
+ * re-ask for a pending confirmation.
19
+ *
20
+ * Storage: the hook state directory (temp when run from source).
21
+ * Exit: 0 always (fail-open).
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const { spawnSync } = require('child_process');
27
+
28
+ const RECOVERY_FILE = 'compact-recovery.json';
29
+
30
+ function gitLine(root, args) {
31
+ const result = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' });
32
+ if (result.error || result.status !== 0) return null;
33
+ const value = String(result.stdout || '').trim();
34
+ return value === '' ? null : value;
35
+ }
36
+
37
+ function projectRoot(payload) {
38
+ const configured = typeof process.env.CLAUDE_PROJECT_DIR === 'string' ? process.env.CLAUDE_PROJECT_DIR.trim() : '';
39
+ const candidates = [configured, typeof payload.cwd === 'string' ? payload.cwd.trim() : '', process.cwd()];
40
+ for (const candidate of candidates) {
41
+ if (!candidate) continue;
42
+ try { return fs.realpathSync(path.resolve(candidate)); } catch { /* next */ }
43
+ }
44
+ return process.cwd();
45
+ }
46
+
47
+ /** The one task the packet currently says is in flight, if any. */
48
+ function activeTask(root) {
49
+ try {
50
+ const RESOLVER = require(path.join(__dirname, '..', 'scripts', 'spec-resolver.cjs'));
51
+ if (typeof RESOLVER.resolveWorkflowCandidate !== 'function') return null;
52
+ const resolved = RESOLVER.resolveWorkflowCandidate({ projectRoot: root, runtime: {}, target: null });
53
+ if (!resolved || resolved.error || resolved.layoutKind !== 'process-v3') return null;
54
+ const registry = resolved.taskRegistry || {};
55
+ const inProgress = Object.keys(registry).filter((taskPath) => registry[taskPath]?.status === 'in_progress');
56
+ return {
57
+ feature: resolved.featureName || null,
58
+ in_progress: inProgress,
59
+ counts: Object.values(registry).reduce((acc, task) => {
60
+ const status = task?.status || 'pending';
61
+ acc[status] = (acc[status] || 0) + 1;
62
+ return acc;
63
+ }, {}),
64
+ };
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ try {
71
+ const stdin = fs.readFileSync(0, 'utf8').trim();
72
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
73
+ const payload = stdin ? normalizeHookPayload(JSON.parse(stdin)) : {};
74
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) process.exit(0);
75
+
76
+ const root = projectRoot(payload);
77
+ const status = gitLine(root, ['status', '--porcelain']);
78
+ const record = {
79
+ captured_at: new Date().toISOString(),
80
+ trigger: typeof payload.trigger === 'string' ? payload.trigger : null,
81
+ session_id: typeof payload.session_id === 'string' ? payload.session_id : null,
82
+ project_root: root,
83
+ branch: gitLine(root, ['rev-parse', '--abbrev-ref', 'HEAD']),
84
+ head: gitLine(root, ['rev-parse', '--short', 'HEAD']),
85
+ dirty_count: status === null ? null : status.split('\n').filter((line) => line.trim() !== '').length,
86
+ workflow: activeTask(root),
87
+ };
88
+
89
+ const dir = require('./lib/hook-state-dir.cjs').hookStateDir();
90
+ fs.mkdirSync(dir, { recursive: true });
91
+ const target = path.join(dir, RECOVERY_FILE);
92
+ const temp = `${target}.${process.pid}.tmp`;
93
+ fs.writeFileSync(temp, `${JSON.stringify(record, null, 2)}\n`);
94
+ fs.renameSync(temp, target);
95
+ process.exit(0);
96
+ } catch (error) {
97
+ try {
98
+ const dir = require('./lib/hook-state-dir.cjs').hookStateDir();
99
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
100
+ fs.appendFileSync(
101
+ path.join(dir, 'hook-log.jsonl'),
102
+ `${JSON.stringify({ ts: new Date().toISOString(), hook: 'precompact', status: 'crash', error: error.message })}\n`,
103
+ );
104
+ } catch (_) { /* fail-open */ }
105
+ process.exit(0);
106
+ }
@@ -41,9 +41,10 @@ try {
41
41
  /\.env\.(example|sample|template|test)$/i
42
42
  ];
43
43
 
44
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
44
45
  function readRuntime(cwd) {
45
46
  try {
46
- const file = path.join(cwd, '.claude', 'runtime.json');
47
+ const file = runtimePath(cwd, 'runtime.json');
47
48
  return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
48
49
  } catch {
49
50
  return {};
@@ -200,7 +201,8 @@ try {
200
201
  const stdin = fs.readFileSync(0, 'utf8').trim();
201
202
  if (!stdin) process.exit(0);
202
203
 
203
- const data = JSON.parse(stdin);
204
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
205
+ const data = normalizeHookPayload(JSON.parse(stdin));
204
206
  const toolName = data.tool_name || '';
205
207
  const toolInput = data.tool_input || {};
206
208
  const cwd = typeof data.cwd === 'string' && data.cwd.trim() ? data.cwd : process.cwd();
@@ -16,9 +16,10 @@ try {
16
16
  const os = require('os');
17
17
  const path = require('path');
18
18
 
19
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
19
20
  function readRuntime(cwd) {
20
21
  try {
21
- const p = path.join(cwd, '.claude', 'runtime.json');
22
+ const p = runtimePath(cwd, 'runtime.json');
22
23
  return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : null;
23
24
  } catch { return null; }
24
25
  }
@@ -48,7 +49,8 @@ try {
48
49
  const stdin = fs.readFileSync(0, 'utf8').trim();
49
50
  if (!stdin) process.exit(0);
50
51
 
51
- const payload = JSON.parse(stdin);
52
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
53
+ const payload = normalizeHookPayload(JSON.parse(stdin));
52
54
  const sessionId = payload.session_id || null;
53
55
  const payloadCwd = typeof payload.cwd === 'string' ? payload.cwd.trim() : '';
54
56
  const cwd = payloadCwd || process.env.PROJECT_ROOT || process.cwd();
@@ -93,7 +95,7 @@ try {
93
95
  } catch (e) {
94
96
  try {
95
97
  const fs = require('fs'), p = require('path');
96
- const d = p.join(__dirname, '.logs');
98
+ const d = require('./lib/hook-state-dir.cjs').hookStateDir();
97
99
  if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
98
100
  fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
99
101
  JSON.stringify({ ts: new Date().toISOString(), hook: 'rules', status: 'crash', error: e.message }) + '\n');
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) 2026 Haposoft. MIT License.
4
+ *
5
+ * UserPromptSubmit Hook — secret-output-guardrail.cjs
6
+ * Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
7
+ *
8
+ * The privacy hook gates whether a sensitive file may be read. Nothing gated what
9
+ * happens after that approval, so an approved read could still put a raw key into
10
+ * the transcript — where it survives compaction, is written to disk, and is replayed
11
+ * to the model on every later turn. This injects a reminder when the prompt itself is
12
+ * about credential handling.
13
+ *
14
+ * It reads the prompt and never echoes it: no prompt text and no matched value is
15
+ * written out, so the guardrail cannot itself become the leak.
16
+ *
17
+ * Exit: 0 always (fail-open)
18
+ */
19
+
20
+ try {
21
+ const fs = require('fs');
22
+ const { REMINDER, containsSecretKeyword } = require('./lib/secret-keywords.cjs');
23
+
24
+ const stdin = fs.readFileSync(0, 'utf8').trim();
25
+ if (!stdin) process.exit(0);
26
+
27
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
28
+ const payload = normalizeHookPayload(JSON.parse(stdin));
29
+ const prompt = String(payload.prompt || payload.user_prompt || '');
30
+ if (!containsSecretKeyword(prompt)) process.exit(0);
31
+
32
+ console.log(REMINDER);
33
+ process.exit(0);
34
+
35
+ } catch (e) {
36
+ try {
37
+ const fs = require('fs'), p = require('path');
38
+ const d = require('./lib/hook-state-dir.cjs').hookStateDir();
39
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
40
+ fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
41
+ JSON.stringify({ ts: new Date().toISOString(), hook: 'secret-output-guardrail', status: 'crash', error: e.message }) + '\n');
42
+ } catch (_) {}
43
+ process.exit(0);
44
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
6
7
  const { spawnSync } = require('child_process');
7
8
  const STATE = require('./completion-authority-state.cjs');
8
9
  const PATH_SAFETY = require('./lib/runtime-path-safety.cjs');
@@ -28,7 +29,7 @@ function inside(root, target) {
28
29
 
29
30
  function projectRoot(payload = {}) {
30
31
  const installedRoot = path.resolve(__dirname, '..', '..');
31
- if (fs.existsSync(path.join(installedRoot, '.claude', 'hooks', path.basename(__filename)))) {
32
+ if (fs.existsSync(runtimePath(installedRoot, 'hooks', path.basename(__filename)))) {
32
33
  try { return STATE.canonicalProjectRoot(installedRoot); } catch {}
33
34
  }
34
35
  for (const candidate of [process.env.CLAUDE_PROJECT_DIR, process.env.PROJECT_ROOT]) {
@@ -70,7 +71,7 @@ function parseClaim(message) {
70
71
  }
71
72
 
72
73
  function validatorFor(root) {
73
- const installedRoot = path.join(root, '.claude');
74
+ const installedRoot = runtimeDir(root);
74
75
  const installedHook = path.join(installedRoot, 'hooks', path.basename(__filename));
75
76
  if (fs.existsSync(installedHook)) {
76
77
  const canonicalHook = PATH_SAFETY.canonicalRegularFile(root, installedHook, 'installed semantic hook');
@@ -82,7 +83,7 @@ function validatorFor(root) {
82
83
 
83
84
  const sourceRuntime = path.resolve(__dirname, '..');
84
85
  const sourceRoot = path.resolve(sourceRuntime, '..');
85
- if (path.basename(sourceRuntime) !== 'claude' || path.basename(sourceRoot) !== 'src') {
86
+ if (`.${path.basename(sourceRuntime)}` !== runtimeDirName() || path.basename(sourceRoot) !== 'src') {
86
87
  throw new Error('semantic validator runtime root is not recognized');
87
88
  }
88
89
  const validator = path.join(sourceRuntime, 'scripts', 'validate-spec-output.cjs');
@@ -143,7 +144,8 @@ function verifyAttestation(root, specFile, featureName, semanticDigest) {
143
144
  if (require.main === module) {
144
145
  try {
145
146
  const raw = fs.readFileSync(0, 'utf8').trim();
146
- if (raw) recordFromSubagentStop(JSON.parse(raw));
147
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
148
+ if (raw) recordFromSubagentStop(normalizeHookPayload(JSON.parse(raw)));
147
149
  } catch (error) {
148
150
  process.stderr.write(`CafeKit semantic review SubagentStop claim rejected: ${error.message}\n`);
149
151
  }
@@ -14,6 +14,7 @@
14
14
  try {
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
17
18
  const os = require('os');
18
19
  const { execSync } = require('child_process');
19
20
 
@@ -49,16 +50,16 @@ try {
49
50
  }
50
51
 
51
52
  const installedRoot = path.resolve(__dirname, '..', '..');
52
- const installedHook = path.join(installedRoot, '.claude', 'hooks', path.basename(__filename));
53
+ const installedHook = runtimePath(installedRoot, 'hooks', path.basename(__filename));
53
54
  if (fs.existsSync(installedHook)) return installedRoot;
54
55
 
55
56
  try { return fs.realpathSync(process.cwd()); } catch { return path.resolve(process.cwd()); }
56
57
  }
57
58
 
58
- /** Read .claude/runtime.json config */
59
+ /** Read <runtime>/runtime.json config */
59
60
  function readRuntime(cwd) {
60
61
  try {
61
- const p = path.join(cwd, '.claude', 'runtime.json');
62
+ const p = runtimePath(cwd, 'runtime.json');
62
63
  return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : {};
63
64
  } catch { return {}; }
64
65
  }
@@ -143,12 +144,12 @@ try {
143
144
  }
144
145
 
145
146
  /**
146
- * Read the installed CafeKit version from .claude/cafekit.json.
147
+ * Read the installed CafeKit version from <runtime>/cafekit.json.
147
148
  * Returns null if not found.
148
149
  */
149
150
  function getInstalledVersion(cwd) {
150
151
  try {
151
- const p = path.join(cwd, '.claude', 'cafekit.json');
152
+ const p = runtimePath(cwd, 'cafekit.json');
152
153
  if (!fs.existsSync(p)) return null;
153
154
  const meta = JSON.parse(fs.readFileSync(p, 'utf8'));
154
155
  return typeof meta.version === 'string' ? meta.version : null;
@@ -158,14 +159,14 @@ try {
158
159
  /**
159
160
  * Check for a newer CafeKit version and return an update notice string,
160
161
  * or an empty string if already up to date or check fails.
161
- * Result is cached in .claude/.cafekit-update-cache.json for 12 hours.
162
+ * Result is cached in <runtime>/.cafekit-update-cache.json for 12 hours.
162
163
  */
163
164
  async function checkCafeKitUpdate(cwd) {
164
165
  const installed = getInstalledVersion(cwd);
165
166
  if (!installed) return '';
166
167
 
167
168
  const CACHE_TTL = 12 * 60 * 60 * 1000; // 12 hours
168
- const cachePath = path.join(cwd, '.claude', '.cafekit-update-cache.json');
169
+ const cachePath = runtimePath(cwd, '.cafekit-update-cache.json');
169
170
 
170
171
  // Return cached result if still fresh.
171
172
  try {
@@ -195,7 +196,8 @@ try {
195
196
 
196
197
  (async () => {
197
198
  const stdin = fs.readFileSync(0, 'utf8').trim();
198
- const payload = stdin ? JSON.parse(stdin) : {};
199
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
200
+ const payload = stdin ? normalizeHookPayload(JSON.parse(stdin)) : {};
199
201
  const source = payload.source || 'unknown';
200
202
  const envFile = process.env.CLAUDE_ENV_FILE;
201
203
  const cwd = projectRoot();
@@ -261,6 +263,32 @@ try {
261
263
  console.log('Any pending confirmations requested via AskUserQuestion might have been lost.');
262
264
  console.log('Do not proceed without explicitly asking the user again to ensure safety.');
263
265
  console.log('Use AskUserQuestion: "The chat context was compressed. Do I still have permission to proceed?"');
266
+
267
+ // Anchors captured by the PreCompact hook. These are re-derivable facts, not
268
+ // proof and not an authorization: they save the session from rebuilding its
269
+ // bearings, and nothing more.
270
+ try {
271
+ const fs2 = require('fs'), p2 = require('path');
272
+ const file = p2.join(require('./lib/hook-state-dir.cjs').hookStateDir(), 'compact-recovery.json');
273
+ const record = JSON.parse(fs2.readFileSync(file, 'utf8'));
274
+ const parts = [];
275
+ if (record.branch) parts.push(`branch ${record.branch}`);
276
+ if (record.head) parts.push(`HEAD ${record.head}`);
277
+ if (typeof record.dirty_count === 'number') parts.push(`${record.dirty_count} uncommitted path(s)`);
278
+ console.log('\n=== State before compaction ===');
279
+ if (record.captured_at) console.log(`Captured: ${record.captured_at}`);
280
+ if (parts.length) console.log(`Repository: ${parts.join(' | ')}`);
281
+ const workflow = record.workflow;
282
+ if (workflow && workflow.feature) {
283
+ const counts = Object.entries(workflow.counts || {}).map(([k, v]) => `${v} ${k}`).join(', ');
284
+ console.log(`Feature: ${workflow.feature}${counts ? ` (${counts})` : ''}`);
285
+ if (Array.isArray(workflow.in_progress) && workflow.in_progress.length > 0) {
286
+ console.log(`In flight: ${workflow.in_progress.join(', ')}`);
287
+ }
288
+ }
289
+ console.log('Re-read these from disk before acting; they describe the moment before compaction.');
290
+ console.log('=== End of pre-compaction state ===');
291
+ } catch (_) { /* no capture available */ }
264
292
  }
265
293
 
266
294
  process.exit(0);
@@ -269,7 +297,7 @@ try {
269
297
  } catch (e) {
270
298
  try {
271
299
  const fs = require('fs'), p = require('path');
272
- const d = p.join(__dirname, '.logs');
300
+ const d = require('./lib/hook-state-dir.cjs').hookStateDir();
273
301
  if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
274
302
  fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
275
303
  JSON.stringify({ ts: new Date().toISOString(), hook: 'session', status: 'crash', error: e.message }) + '\n');
@@ -15,6 +15,7 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
18
19
 
19
20
  function projectRoot(payload = {}) {
20
21
  const configured = typeof process.env.CLAUDE_PROJECT_DIR === 'string'
@@ -25,7 +26,7 @@ function projectRoot(payload = {}) {
25
26
  }
26
27
 
27
28
  const installedRoot = path.resolve(__dirname, '..', '..');
28
- const installedHook = path.join(installedRoot, '.claude', 'hooks', path.basename(__filename));
29
+ const installedHook = runtimePath(installedRoot, 'hooks', path.basename(__filename));
29
30
  if (fs.existsSync(installedHook)) return installedRoot;
30
31
 
31
32
  const sourceFixture = typeof process.env.PROJECT_ROOT === 'string'
@@ -44,7 +45,7 @@ function projectRoot(payload = {}) {
44
45
 
45
46
  function logCrash(error) {
46
47
  try {
47
- const d = path.join(__dirname, '.logs');
48
+ const d = require('./lib/hook-state-dir.cjs').hookStateDir();
48
49
  if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
49
50
  fs.appendFileSync(
50
51
  path.join(d, 'hook-log.jsonl'),
@@ -78,8 +79,13 @@ try {
78
79
  throw new Error('hook payload must be a JSON object');
79
80
  }
80
81
 
81
- // Never re-block a continuation caused by our own block (infinite-loop guard).
82
- if (payload.stop_hook_active === true) process.exit(0);
82
+ // Never re-block a continuation caused by our own block (infinite-loop guard). Grok
83
+ // spells this `stopHookActive`; both are accepted here rather than through the payload
84
+ // reader, because the catch at the end of this file answers a block, so a reader that
85
+ // threw above the guard would block every completion instead of only a foreign one.
86
+ // The rest of this hook is already host-neutral: sessionIdentity() accepts sessionId
87
+ // and sessionID, the resolver reads camelCase targets, and projectRoot reads cwd.
88
+ if (payload.stop_hook_active === true || payload.stopHookActive === true) process.exit(0);
83
89
 
84
90
  let POLICY;
85
91
  let RESOLVER;
@@ -108,7 +114,7 @@ try {
108
114
  // Missing/malformed runtime.json keeps the gate ON (fail-closed, valve 3 style).
109
115
  let runtime = {};
110
116
  try {
111
- const rp = path.join(cwd, '.claude', 'runtime.json');
117
+ const rp = runtimePath(cwd, 'runtime.json');
112
118
  if (fs.existsSync(rp)) runtime = JSON.parse(fs.readFileSync(rp, 'utf8'));
113
119
  } catch { /* gate stays on */ }
114
120
  // Active-spec discovery via shared resolver — explicit target if present, else fail on ambiguity.
@@ -202,7 +208,7 @@ try {
202
208
  process.exit(0);
203
209
  }
204
210
  const taskRegistry = resolved.taskRegistry || activeSpec.task_registry || {};
205
- const cacheFile = path.join(__dirname, '.logs', 'spec-gate-last.json');
211
+ const cacheFile = path.join(require('./lib/hook-state-dir.cjs').hookStateDir(), 'spec-gate-last.json');
206
212
  const cacheExists = fs.existsSync(cacheFile);
207
213
  let cache = {};
208
214
  if (cacheExists) {
@@ -220,15 +226,19 @@ try {
220
226
  if (staleFlashTasks.length > 0) {
221
227
  process.stdout.write(JSON.stringify({
222
228
  decision: 'block',
223
- reason: `Completion gate: ${staleFlashTasks.length} task(s) marked done with FLASH_UNVERIFIED (${staleFlashTasks.join(', ')}). Run /hapo:test for exact proof, then use explicit sync-finalize.`
229
+ reason: `Completion gate: ${staleFlashTasks.length} task(s) marked done with FLASH_UNVERIFIED (${staleFlashTasks.join(', ')}). Run /cf:test for exact proof, then use explicit sync-finalize.`
224
230
  }) + '\n');
225
231
  process.exit(0);
226
232
  }
227
233
 
228
- // Cache hardening: every Stop revalidates the canonical receipt for
229
- // every task currently marked done, so a cached PASS cannot hide later
230
- // mutations (deletion, placeholder, changed Verification/Command/Exit/
231
- // Base/Head, stale provenance). Cache is an optimization, not truth.
234
+ // Cache hardening: every Stop re-reads and re-checks the receipt of every
235
+ // task currently marked done, so a cached PASS cannot hide later mutations
236
+ // (deletion, placeholder, changed Verification/Command/Exit, missing
237
+ // output, edited artifact bytes). Base/Head are compared against the live
238
+ // runtime only while the task file differs from its committed bytes or the
239
+ // tree outside the specs root is dirty; a committed, unchanged receipt on a
240
+ // clean tree is checked on structure alone. Cache is an optimization, not
241
+ // truth.
232
242
  const featureCache = cacheExists ? (cache[featureName] || {}) : {};
233
243
  const allDoneTasks = Object.keys(taskRegistry).filter((tp) =>
234
244
  (taskRegistry[tp]?.status || 'pending') === 'done'
@@ -13,10 +13,11 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
+ const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
16
17
 
17
18
  function logCrash(error) {
18
19
  try {
19
- const d = path.join(__dirname, '.logs');
20
+ const d = require('./lib/hook-state-dir.cjs').hookStateDir();
20
21
  if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
21
22
  fs.appendFileSync(
22
23
  path.join(d, 'hook-log.jsonl'),
@@ -49,7 +50,8 @@ try {
49
50
  const stdin = fs.readFileSync(0, 'utf8').trim();
50
51
  if (!stdin) process.exit(0);
51
52
 
52
- const payload = JSON.parse(stdin);
53
+ const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
54
+ const payload = normalizeHookPayload(JSON.parse(stdin));
53
55
  const cwd = payload.cwd || process.cwd();
54
56
 
55
57
  let POLICY;
@@ -69,7 +71,7 @@ try {
69
71
  // Read runtime configuration if exists
70
72
  let runtime = {};
71
73
  try {
72
- const p = path.join(cwd, '.claude', 'runtime.json');
74
+ const p = runtimePath(cwd, 'runtime.json');
73
75
  if (fs.existsSync(p)) runtime = JSON.parse(fs.readFileSync(p, 'utf8'));
74
76
  } catch { /* ignore */ }
75
77
 
@@ -77,7 +79,9 @@ try {
77
79
  // (The Stop completion gate has its own toggle: spec.completion_gate.)
78
80
  if (runtime.spec && runtime.spec.tollgate === false) process.exit(0);
79
81
 
80
- const baseDir = process.env.PROJECT_ROOT || cwd;
82
+ // Same rule as spec-gate: an installed hook trusts its own location over the environment.
83
+ const installedRoot = path.basename(path.resolve(__dirname, '..')).startsWith('.') ? path.resolve(__dirname, '..', '..') : null;
84
+ const baseDir = installedRoot || process.env.PROJECT_ROOT || cwd;
81
85
  const explicitFeature = payload.featureName || payload.feature || payload.explicitFeature || null;
82
86
  const explicitPath = payload.specPath || payload.spec_path || payload.featurePath || null;
83
87
  const resolveWorkflow = typeof RESOLVER.resolveWorkflowCandidate === 'function'
@@ -182,7 +186,7 @@ try {
182
186
  total: taskEntries.length,
183
187
  featureReceiptPresent,
184
188
  });
185
- const cacheFile = path.join(__dirname, '.logs', 'tollgate-last.txt');
189
+ const cacheFile = path.join(require('./lib/hook-state-dir.cjs').hookStateDir(), 'tollgate-last.txt');
186
190
 
187
191
  let lastKey = '';
188
192
  if (stateKey) {
@@ -222,11 +226,11 @@ try {
222
226
  lines.push('- Migration required: add `Specs-Contract: process-first-ready-v1` after the plan title before tasks can enter Next.');
223
227
  }
224
228
  lines.push('- Sync each flat task `Status:` only after verified work; task proof belongs in that task\'s inline `## Receipt`.');
225
- lines.push(`- Workflow source: \`specs/${featureName}/plan.md\`; Stop revalidates every task currently marked done.`);
229
+ lines.push(`- Workflow source: \`specs/${featureName}/plan.md\`; Stop re-checks every done receipt, binding Base/Head to the runtime until the task file is committed, unchanged, and the tree outside the specs root is clean.`);
226
230
  } else {
227
231
  lines.push('- Sync `spec.json` + task Markdown status after verified work; task proof belongs in `receipts/<task-basename>.md`.');
228
232
  lines.push(`- Create \`feature-receipt.md\` once after final integration proof${featureReceiptPresent ? ' (present)' : ' (not required before closeout)'}.`);
229
- lines.push(`- Validate with \`node .claude/scripts/validate-spec-output.cjs specs/${featureName}\`; hooks revalidate receipt bytes but never grant approval.`);
233
+ lines.push(`- Validate with \`node ${runtimeDirName()}/scripts/validate-spec-output.cjs specs/${featureName}\`; hooks revalidate receipt bytes but never grant approval.`);
230
234
  }
231
235
  lines.push('');
232
236