@opengsd/gsd-core 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +1 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-dom-verifier.md +169 -0
  7. package/agents/gsd-eval-auditor.md +1 -1
  8. package/agents/gsd-executor.md +17 -9
  9. package/agents/gsd-framework-selector.md +1 -3
  10. package/agents/gsd-intel-updater.md +1 -1
  11. package/agents/gsd-mempalace-curator.md +0 -1
  12. package/agents/gsd-pattern-mapper.md +11 -0
  13. package/agents/gsd-phase-researcher.md +3 -1
  14. package/agents/gsd-plan-checker.md +15 -55
  15. package/agents/gsd-planner.md +6 -4
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +2 -2
  18. package/agents/gsd-roadmapper.md +15 -11
  19. package/agents/gsd-ui-checker.md +63 -4
  20. package/agents/gsd-ui-researcher.md +41 -3
  21. package/agents/gsd-verifier.md +1 -1
  22. package/bin/install.js +609 -134
  23. package/commands/gsd/discuss-phase.md +1 -1
  24. package/commands/gsd/import.md +1 -1
  25. package/commands/gsd/quick.md +8 -4
  26. package/gsd-core/bin/gsd-tools.cjs +567 -51
  27. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  28. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  29. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  30. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  31. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  32. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  33. package/gsd-core/bin/lib/audit.cjs +163 -41
  34. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  35. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  36. package/gsd-core/bin/lib/capability-registry.cjs +336 -95
  37. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  38. package/gsd-core/bin/lib/capability-validator.cjs +205 -18
  39. package/gsd-core/bin/lib/check-command-router.cjs +145 -5
  40. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  41. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  43. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  44. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  45. package/gsd-core/bin/lib/commands.cjs +543 -44
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  47. package/gsd-core/bin/lib/config-loader.cjs +118 -29
  48. package/gsd-core/bin/lib/config.cjs +92 -2
  49. package/gsd-core/bin/lib/configuration.cjs +129 -37
  50. package/gsd-core/bin/lib/core-utils.cjs +84 -7
  51. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  52. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  53. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  54. package/gsd-core/bin/lib/frontmatter.cjs +840 -305
  55. package/gsd-core/bin/lib/gap-checker.cjs +27 -3
  56. package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
  57. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  58. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
  59. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  60. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  61. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  62. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  63. package/gsd-core/bin/lib/init.cjs +120 -41
  64. package/gsd-core/bin/lib/install-engine.cjs +68 -3
  65. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
  66. package/gsd-core/bin/lib/install-profiles.cjs +78 -4
  67. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  68. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  69. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  70. package/gsd-core/bin/lib/intel.cjs +101 -26
  71. package/gsd-core/bin/lib/io.cjs +160 -15
  72. package/gsd-core/bin/lib/learnings.cjs +85 -14
  73. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  74. package/gsd-core/bin/lib/markdown-table.cjs +52 -4
  75. package/gsd-core/bin/lib/milestone.cjs +90 -5
  76. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  77. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  78. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  79. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  80. package/gsd-core/bin/lib/phase-id.cjs +70 -4
  81. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  82. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  83. package/gsd-core/bin/lib/phase.cjs +405 -84
  84. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  85. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  86. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  87. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  88. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
  89. package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
  90. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  91. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  92. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  93. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  94. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  95. package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
  96. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  97. package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
  98. package/gsd-core/bin/lib/roadmap.cjs +74 -19
  99. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
  100. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
  101. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
  102. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  103. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  104. package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
  105. package/gsd-core/bin/lib/smart-entry.cjs +12 -22
  106. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  107. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  108. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  109. package/gsd-core/bin/lib/state-document.cjs +186 -0
  110. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  111. package/gsd-core/bin/lib/state-transition.cjs +517 -101
  112. package/gsd-core/bin/lib/state.cjs +946 -163
  113. package/gsd-core/bin/lib/surface.cjs +10 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  115. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  116. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  117. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  118. package/gsd-core/bin/lib/uat.cjs +1376 -125
  119. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  120. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  121. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  122. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  123. package/gsd-core/bin/lib/vendor/README.md +43 -5
  124. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  125. package/gsd-core/bin/lib/verification.cjs +14 -1
  126. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  127. package/gsd-core/bin/lib/verify.cjs +95 -40
  128. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  129. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  130. package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
  131. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  132. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  133. package/gsd-core/bin/shared/exit-codes.json +8 -0
  134. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  135. package/gsd-core/bin/shared/model-catalog.json +8 -1
  136. package/gsd-core/references/agent-contracts.md +3 -2
  137. package/gsd-core/references/api-coverage.md +24 -2
  138. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  139. package/gsd-core/references/checkpoints.md +37 -19
  140. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  141. package/gsd-core/references/edge-probe.md +8 -0
  142. package/gsd-core/references/execute-mvp-tdd.md +1 -3
  143. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  144. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  145. package/gsd-core/references/failing-direction.md +78 -0
  146. package/gsd-core/references/gate-prompts.md +1 -1
  147. package/gsd-core/references/git-integration.md +5 -5
  148. package/gsd-core/references/git-planning-commit.md +3 -3
  149. package/gsd-core/references/gsd-run-resolver.md +1 -1
  150. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  151. package/gsd-core/references/model-profiles.md +1 -1
  152. package/gsd-core/references/nyquist-compliance.md +74 -0
  153. package/gsd-core/references/offer-next.md +3 -5
  154. package/gsd-core/references/phase-argument-parsing.md +3 -3
  155. package/gsd-core/references/planner-failing-direction.md +53 -0
  156. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  157. package/gsd-core/references/planner-revision.md +1 -1
  158. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  159. package/gsd-core/references/planning-config.md +37 -8
  160. package/gsd-core/references/reviewer-instances.md +31 -0
  161. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  162. package/gsd-core/references/tdd.md +1 -3
  163. package/gsd-core/references/ui-brand.md +65 -21
  164. package/gsd-core/references/ui-consideration-probe.md +1 -1
  165. package/gsd-core/references/universal-anti-patterns.md +2 -2
  166. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  167. package/gsd-core/references/verify-mvp-mode.md +1 -1
  168. package/gsd-core/references/workstream-flag.md +11 -11
  169. package/gsd-core/templates/README.md +1 -1
  170. package/gsd-core/templates/SECURITY.md +3 -3
  171. package/gsd-core/templates/UI-SPEC.md +25 -3
  172. package/gsd-core/templates/VALIDATION.md +3 -3
  173. package/gsd-core/templates/phase-prompt.md +3 -0
  174. package/gsd-core/templates/state.md +7 -0
  175. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  176. package/gsd-core/workflows/add-backlog.md +1 -1
  177. package/gsd-core/workflows/add-phase.md +3 -3
  178. package/gsd-core/workflows/add-tests.md +3 -8
  179. package/gsd-core/workflows/add-todo.md +1 -1
  180. package/gsd-core/workflows/ai-integration-phase.md +4 -9
  181. package/gsd-core/workflows/audit-fix.md +12 -3
  182. package/gsd-core/workflows/audit-milestone.md +9 -9
  183. package/gsd-core/workflows/audit-uat.md +17 -2
  184. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  185. package/gsd-core/workflows/autonomous.md +10 -26
  186. package/gsd-core/workflows/check-todos.md +1 -1
  187. package/gsd-core/workflows/cleanup.md +2 -2
  188. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  189. package/gsd-core/workflows/code-review-fix.md +1 -1
  190. package/gsd-core/workflows/code-review.md +121 -40
  191. package/gsd-core/workflows/complete-milestone.md +15 -10
  192. package/gsd-core/workflows/debug.md +5 -3
  193. package/gsd-core/workflows/diagnose-issues.md +12 -6
  194. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  195. package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
  196. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  197. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  198. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
  199. package/gsd-core/workflows/discuss-phase.md +1 -1
  200. package/gsd-core/workflows/do.md +3 -6
  201. package/gsd-core/workflows/docs-update.md +5 -4
  202. package/gsd-core/workflows/edit-phase.md +1 -1
  203. package/gsd-core/workflows/eval-review.md +4 -9
  204. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  205. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
  206. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  207. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  208. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  209. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
  210. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  211. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  212. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  213. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  214. package/gsd-core/workflows/execute-phase.md +38 -54
  215. package/gsd-core/workflows/execute-plan.md +17 -12
  216. package/gsd-core/workflows/explore.md +1 -1
  217. package/gsd-core/workflows/extract-learnings.md +1 -1
  218. package/gsd-core/workflows/fast.md +2 -2
  219. package/gsd-core/workflows/forensics.md +1 -1
  220. package/gsd-core/workflows/graduation.md +5 -5
  221. package/gsd-core/workflows/health.md +3 -6
  222. package/gsd-core/workflows/import.md +14 -11
  223. package/gsd-core/workflows/inbox.md +4 -5
  224. package/gsd-core/workflows/ingest-docs.md +44 -11
  225. package/gsd-core/workflows/insert-phase.md +5 -5
  226. package/gsd-core/workflows/list-seeds.md +5 -3
  227. package/gsd-core/workflows/list-workspaces.md +1 -1
  228. package/gsd-core/workflows/manager.md +12 -23
  229. package/gsd-core/workflows/map-codebase.md +1 -1
  230. package/gsd-core/workflows/milestone-summary.md +1 -1
  231. package/gsd-core/workflows/mvp-phase.md +2 -2
  232. package/gsd-core/workflows/new-milestone.md +9 -21
  233. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  234. package/gsd-core/workflows/new-project.md +12 -26
  235. package/gsd-core/workflows/new-workspace.md +1 -1
  236. package/gsd-core/workflows/next.md +2 -2
  237. package/gsd-core/workflows/pause-work.md +1 -1
  238. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  239. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  240. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  241. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  242. package/gsd-core/workflows/plan-phase.md +121 -42
  243. package/gsd-core/workflows/plan-review-convergence.md +46 -9
  244. package/gsd-core/workflows/plant-seed.md +2 -2
  245. package/gsd-core/workflows/pr-branch.md +187 -51
  246. package/gsd-core/workflows/profile-user.md +16 -14
  247. package/gsd-core/workflows/progress.md +27 -12
  248. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  249. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
  250. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  251. package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
  252. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  253. package/gsd-core/workflows/quick.md +20 -29
  254. package/gsd-core/workflows/remove-phase.md +4 -4
  255. package/gsd-core/workflows/remove-workspace.md +2 -2
  256. package/gsd-core/workflows/resume-project.md +8 -12
  257. package/gsd-core/workflows/review.md +193 -15
  258. package/gsd-core/workflows/scan.md +1 -1
  259. package/gsd-core/workflows/secure-phase.md +2 -2
  260. package/gsd-core/workflows/settings-advanced.md +7 -9
  261. package/gsd-core/workflows/settings-integrations.md +64 -31
  262. package/gsd-core/workflows/settings.md +3 -5
  263. package/gsd-core/workflows/ship.md +12 -6
  264. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  265. package/gsd-core/workflows/sketch.md +12 -18
  266. package/gsd-core/workflows/smart-entry.md +3 -5
  267. package/gsd-core/workflows/spec-phase.md +23 -1
  268. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  269. package/gsd-core/workflows/spike.md +20 -31
  270. package/gsd-core/workflows/stats.md +2 -2
  271. package/gsd-core/workflows/sync-skills.md +1 -1
  272. package/gsd-core/workflows/thread.md +11 -7
  273. package/gsd-core/workflows/transition.md +5 -5
  274. package/gsd-core/workflows/ui-phase.md +10 -16
  275. package/gsd-core/workflows/ui-review.md +6 -10
  276. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  277. package/gsd-core/workflows/undo.md +8 -16
  278. package/gsd-core/workflows/update.md +6 -10
  279. package/gsd-core/workflows/validate-phase.md +2 -2
  280. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  281. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  282. package/gsd-core/workflows/verify-work.md +57 -18
  283. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  284. package/hooks/dist/gsd-config-reload.js +18 -12
  285. package/hooks/dist/gsd-context-monitor.js +19 -10
  286. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  287. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  288. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  289. package/hooks/dist/gsd-cursor-stop.js +2 -1
  290. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  291. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  292. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  293. package/hooks/dist/gsd-graphify-update.sh +22 -18
  294. package/hooks/dist/gsd-node-runner.sh +76 -0
  295. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  296. package/hooks/dist/gsd-prompt-guard.js +16 -7
  297. package/hooks/dist/gsd-read-guard.js +16 -7
  298. package/hooks/dist/gsd-read-injection-scanner.js +17 -8
  299. package/hooks/dist/gsd-session-state.sh +1 -0
  300. package/hooks/dist/gsd-statusline.js +215 -26
  301. package/hooks/dist/gsd-validate-commit.sh +80 -6
  302. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  303. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  304. package/hooks/dist/gsd-workflow-guard.js +34 -16
  305. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  306. package/hooks/dist/gsd-write-guard.js +35 -25
  307. package/hooks/dist/lib/cli-exit.js +560 -0
  308. package/hooks/dist/lib/exit-code-registry.js +98 -0
  309. package/hooks/dist/lib/git-probe.js +84 -0
  310. package/hooks/dist/lib/hook-exit.js +81 -0
  311. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  312. package/hooks/gsd-agent-isolation-guard.js +77 -38
  313. package/hooks/gsd-config-reload.js +18 -12
  314. package/hooks/gsd-context-monitor.js +19 -10
  315. package/hooks/gsd-cursor-post-tool.js +3 -1
  316. package/hooks/gsd-cursor-pre-tool.js +3 -1
  317. package/hooks/gsd-cursor-session-start.js +2 -1
  318. package/hooks/gsd-cursor-stop.js +2 -1
  319. package/hooks/gsd-cursor-subagent-start.js +28 -23
  320. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  321. package/hooks/gsd-ensure-canonical-path.js +2 -1
  322. package/hooks/gsd-graphify-update.sh +22 -18
  323. package/hooks/gsd-node-runner.sh +76 -0
  324. package/hooks/gsd-phase-boundary.sh +1 -0
  325. package/hooks/gsd-prompt-guard.js +16 -7
  326. package/hooks/gsd-read-guard.js +16 -7
  327. package/hooks/gsd-read-injection-scanner.js +17 -8
  328. package/hooks/gsd-session-state.sh +1 -0
  329. package/hooks/gsd-statusline.js +215 -26
  330. package/hooks/gsd-validate-commit.sh +80 -6
  331. package/hooks/gsd-windsurf-pre-command.js +16 -11
  332. package/hooks/gsd-windsurf-pre-write.js +22 -13
  333. package/hooks/gsd-workflow-guard.js +34 -16
  334. package/hooks/gsd-worktree-path-guard.js +36 -21
  335. package/hooks/gsd-write-guard.js +35 -25
  336. package/hooks/lib/cli-exit.js +560 -0
  337. package/hooks/lib/exit-code-registry.js +98 -0
  338. package/hooks/lib/git-probe.js +84 -0
  339. package/hooks/lib/hook-exit.js +81 -0
  340. package/hooks/managed-hooks-registry.cjs +3 -0
  341. package/package.json +12 -7
  342. package/scripts/base64-scan.sh +74 -12
  343. package/scripts/build-hooks.js +5 -0
  344. package/scripts/check-glossary-refs.cjs +77 -15
  345. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  346. package/scripts/ci-check-job-near-cap.cjs +49 -0
  347. package/scripts/ci-pr-mergeability.cjs +262 -0
  348. package/scripts/ci-test-scope.cjs +45 -12
  349. package/scripts/ci-timeout-report.cjs +230 -0
  350. package/scripts/docs-guard-registry.cjs +396 -0
  351. package/scripts/gen-capability-registry.cjs +8 -6
  352. package/scripts/gen-exit-code-docs.cjs +318 -0
  353. package/scripts/gen-exit-code-registry.cjs +891 -0
  354. package/scripts/gen-features.cjs +836 -0
  355. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  356. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  357. package/scripts/gen-loop-host-contract.cjs +134 -1
  358. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  359. package/scripts/gen-state-md-docs.cjs +727 -0
  360. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  361. package/scripts/lib/ci-job-timing.cjs +72 -0
  362. package/scripts/lib/cli-exit.cjs +546 -44
  363. package/scripts/lib/drift-scan.cjs +32 -2
  364. package/scripts/lib/exit-code-registry.cjs +98 -0
  365. package/scripts/lib/ndjson-reporter.cjs +119 -0
  366. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  367. package/scripts/lint-docs-guard-registration.cjs +495 -0
  368. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  369. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  370. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  371. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  372. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  373. package/scripts/lint-phase-enumeration-drift.cjs +21 -8
  374. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  375. package/scripts/lint-removed-but-needed.cjs +184 -16
  376. package/scripts/lint-seam-enforcement.cjs +182 -0
  377. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  378. package/scripts/lint-source-test-name-collision.cjs +241 -0
  379. package/scripts/lint-state-write-path-drift.cjs +337 -432
  380. package/scripts/lint-test-file-count.allowlist.json +122 -4
  381. package/scripts/lint-test-file-count.cjs +25 -3
  382. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  383. package/scripts/lint-vendored-deps.cjs +208 -35
  384. package/scripts/mutation-matrix.cjs +599 -50
  385. package/scripts/prompt-injection-scan.sh +75 -14
  386. package/scripts/secret-scan.sh +75 -13
  387. package/scripts/select-docs-guards.cjs +56 -0
  388. package/scripts/sync-runtime-launcher.cjs +22 -3
  389. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  390. package/skills/gsd-import/SKILL.md +1 -1
  391. package/skills/gsd-quick/SKILL.md +8 -4
  392. package/vscode/package.json +1 -1
  393. package/bin/lib/ui-safety-gate.cjs +0 -109
  394. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  395. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -56,6 +56,7 @@
56
56
  const fs = require('fs');
57
57
  const path = require('path');
58
58
  const os = require('os');
59
+ const { allow } = require('./lib/hook-exit.js');
59
60
 
60
61
  // Workspace resolution is shared across the Cursor hooks (#2587) — see
61
62
  // hooks/lib/cursor-workspace.js. Staged next to these scripts by
@@ -342,35 +343,39 @@ function resolveIsolationDecision(data, { clock = Date, realpath = fs.realpathSy
342
343
  return { action: 'allow' };
343
344
  }
344
345
 
345
- // ─── #3566: per-install runtime marker ────────────────────────────────────────
346
- // Same contract as hooks/gsd-agent-isolation-guard.js's readInstallRuntimeMarker
347
- // (mirroring src/model-resolver.cts #2297): bin/install.js writes
348
- // `<install>/gsd-core/.gsd-runtime` beside VERSION for every runtime install;
349
- // this hook ships at `<install>/hooks/`, so the marker is the `gsd-core` sibling
350
- // of this file's own directory — the same sibling-layout assumption the
351
- // require('../gsd-core/bin/lib/…') calls below already make. Epic #3473 B3 owns
352
- // consolidating every marker reader into one shared seam.
353
- let _installMarkerCache; // undefined = unread; null = known absent; string = value
354
-
346
+ // ─── #3897 rung 2: per-install runtime marker, single canonical owner ────────
347
+ // bin/install.js writes `<install>/gsd-core/.gsd-runtime` beside VERSION for
348
+ // every runtime install (#2297); this hook ships at `<install>/hooks/`, so the
349
+ // marker is the `gsd-core` sibling of this file's own directory. Previously
350
+ // this hook held its own private reader/cache (one of four #3897 found); it
351
+ // now delegates to the single canonical owner, `src/runtime-slash.cts`
352
+ // (compiled to gsd-core/bin/lib/runtime-slash.cjs), reached through
353
+ // `ensureRuntimeBuild()` like the other compiled-lib requires in this file
354
+ // (`scripts/lint-hooks-runtime-build-seam.cjs`).
355
355
  function readInstallRuntimeMarker() {
356
- if (_installMarkerCache !== undefined) return _installMarkerCache;
357
356
  try {
358
- const markerPath = path.join(__dirname, '..', 'gsd-core', '.gsd-runtime');
359
- const raw = fs.readFileSync(markerPath, 'utf-8').trim();
360
- _installMarkerCache = raw || null;
357
+ ensureRuntimeBuild();
358
+ const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
359
+ return runtimeSlash.readInstallRuntimeMarker();
361
360
  } catch {
362
- // No marker: dev/source tree, or an install predating #2297 — "no signal
363
- // from this rung", never a resolution failure.
364
- _installMarkerCache = null;
361
+ // Unbuilt runtime library, or any other failure reaching the canonical
362
+ // owner — "no signal from this rung", never a resolution failure.
363
+ return null;
365
364
  }
366
- return _installMarkerCache;
367
365
  }
368
366
 
369
- // Test seam — same contract as model-resolver.cts's #2297 seam; the dev/source
370
- // tree has no marker file, so spawned-hook tests (fresh process, no marker)
371
- // are unaffected.
367
+ // Test seam — forwards to the canonical owner's seam so this hook and
368
+ // runtime-slash.cjs always share one cache (#3897 rung 2). Spawned-hook tests
369
+ // (fresh process, no marker) are unaffected.
372
370
  function _setInstallRuntimeMarkerForTests(value) {
373
- _installMarkerCache = value;
371
+ try {
372
+ ensureRuntimeBuild();
373
+ const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
374
+ runtimeSlash._setInstallRuntimeMarkerForTests(value);
375
+ } catch {
376
+ // Test-only seam; an unbuilt library here means the test itself will fail
377
+ // downstream, which is a louder and more actionable signal than throwing here.
378
+ }
374
379
  }
375
380
 
376
381
  /**
@@ -564,7 +569,7 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
564
569
  function main() {
565
570
  let raw = '';
566
571
  const stdinTimeout = setTimeout(() => {
567
- process.exit(0);
572
+ allow(undefined);
568
573
  }, 10000);
569
574
 
570
575
  process.stdin.setEncoding('utf8');
@@ -20,8 +20,10 @@
20
20
 
21
21
  'use strict';
22
22
 
23
+ const { allow } = require('./lib/hook-exit.js');
24
+
23
25
  const stdinTimeout = setTimeout(() => {
24
- process.exit(0);
26
+ allow(undefined);
25
27
  }, 10000);
26
28
 
27
29
  process.stdin.setEncoding('utf8');
@@ -34,6 +34,7 @@
34
34
  const fs = require('fs');
35
35
  const path = require('path');
36
36
  const os = require('os');
37
+ const { allow } = require('./lib/hook-exit.js');
37
38
 
38
39
  // Immutable, bundled subdirectories that the canonical path must expose. These
39
40
  // are the directories `@~/.claude/gsd-core/<subdir>/...` includes point into.
@@ -301,5 +302,5 @@ if (require.main === module) {
301
302
  } catch (_) {
302
303
  // Best-effort: a canonical-path failure must never abort a session.
303
304
  }
304
- process.exit(0);
305
+ allow(undefined);
305
306
  }
@@ -9,17 +9,20 @@
9
9
  # graphify.auto_update defaults to false so existing users see no behavior change.
10
10
  #
11
11
  # Gates (in fast-fail order — each shaves work off the common non-dispatch path):
12
+ # 0. .planning/config.json exists AND $CI unset/empty (#3729 — both are
13
+ # parse-free shell tests; running them before the Gate 1 node spawn keeps
14
+ # non-GSD repositories and CI off the spawn path entirely, which on
15
+ # Windows otherwise flashes a console window per Bash call)
12
16
  # 1. Stdin payload present and tool_name == "Bash"
13
17
  # 2. tool_input.command matches a HEAD-advancing git op (shell-direct or
14
18
  # the exact `gsd-tools query commit` command shape; the SDK command invokes
15
19
  # git internally, so the literal "git commit" substring never appears —
16
20
  # see #3653)
17
- # 3. $CI is unset/empty
18
- # 4. Inside a git repo
19
- # 5. Current branch == default branch (git.base_branch override, else main/master/trunk)
20
- # 6. .planning/config.json sets graphify.enabled=true AND graphify.auto_update=true
21
- # 7. graphify binary on PATH
22
- # 8. No rebuild already in flight (PID lock — kill -0 check, stale-tolerant)
21
+ # 3. Inside a git repo
22
+ # 4. Current branch == default branch (git.base_branch override, else main/master/trunk)
23
+ # 5. .planning/config.json sets graphify.enabled=true AND graphify.auto_update=true
24
+ # 6. graphify binary on PATH
25
+ # 7. No rebuild already in flight (PID lock — kill -0 check, stale-tolerant)
23
26
  #
24
27
  # When all gates pass:
25
28
  # - Writes .planning/graphs/.last-build-status.json with status="running"
@@ -30,6 +33,13 @@
30
33
 
31
34
  set -uo pipefail
32
35
 
36
+ # Gate 0 — GSD project at all, and not CI (#3729). Both are pure shell tests;
37
+ # they must precede the Gate 1 node spawn so the common non-GSD/CI path pays
38
+ # for zero child processes. Hoisting is behavior-preserving: old Gates 3 and 6
39
+ # made the same decisions, just later.
40
+ [ -f .planning/config.json ] || exit 0
41
+ [ -z "${CI:-}" ] || exit 0
42
+
33
43
  # Gate 1 — tool_name == Bash; extract command
34
44
  INPUT=$(cat 2>/dev/null || true)
35
45
  [ -n "$INPUT" ] || exit 0
@@ -71,22 +81,17 @@ case "$COMMAND" in
71
81
  *) exit 0 ;;
72
82
  esac
73
83
 
74
- # Gate 3 — not CI
75
- [ -z "${CI:-}" ] || exit 0
76
-
77
- # Gate 4 — inside git repo
84
+ # Gate 3 — inside git repo
78
85
  git rev-parse --git-dir >/dev/null 2>&1 || exit 0
79
86
 
80
- # Gate 5 — current branch == default branch
87
+ # Gate 4 — current branch == default branch (config guaranteed by Gate 0)
81
88
  DEFAULT_BRANCH=""
82
- if [ -f .planning/config.json ]; then
83
- DEFAULT_BRANCH=$(node -e '
89
+ DEFAULT_BRANCH=$(node -e '
84
90
  try {
85
91
  const c = require("./.planning/config.json");
86
92
  process.stdout.write(c.git?.base_branch || "");
87
93
  } catch { process.stdout.write(""); }
88
94
  ' 2>/dev/null || echo "")
89
- fi
90
95
  if [ -z "$DEFAULT_BRANCH" ]; then
91
96
  for cand in main master trunk; do
92
97
  if git rev-parse --verify "$cand" >/dev/null 2>&1; then
@@ -100,8 +105,7 @@ fi
100
105
  CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
101
106
  [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ] || exit 0
102
107
 
103
- # Gate 6 — both graphify gates true in config
104
- [ -f .planning/config.json ] || exit 0
108
+ # Gate 5 — both graphify gates true in config (file existence checked at Gate 0)
105
109
  GATES=$(node -e '
106
110
  try {
107
111
  const c = require("./.planning/config.json");
@@ -111,11 +115,11 @@ try {
111
115
  ' 2>/dev/null || echo "0")
112
116
  [ "$GATES" = "1" ] || exit 0
113
117
 
114
- # Gate 7 — graphify on PATH
118
+ # Gate 6 — graphify on PATH
115
119
  GRAPHIFY_BIN=$(command -v graphify 2>/dev/null || true)
116
120
  [ -n "$GRAPHIFY_BIN" ] || exit 0
117
121
 
118
- # Gate 8 — no live rebuild in flight
122
+ # Gate 7 — no live rebuild in flight
119
123
  mkdir -p .planning/graphs
120
124
  LOCK_FILE=".planning/graphs/.rebuild.lock"
121
125
  if [ -f "$LOCK_FILE" ]; then
@@ -0,0 +1,76 @@
1
+ #!/bin/sh
2
+ # gsd-node-runner.sh — GSD portable node resolver (#3662).
3
+ #
4
+ # Managed JS hook commands under --portable-hooks route through this script:
5
+ #
6
+ # bash "<hooks>/gsd-node-runner.sh" "<baked-node-path>" "<script.js>" [args...]
7
+ #
8
+ # so a config root shared across environments (mounted ~/.claude, shared
9
+ # containers) resolves node at hook-fire time instead of depending on the
10
+ # absolute path of whichever environment ran the installer. Candidates, in
11
+ # order — the first executable one wins:
12
+ #
13
+ # 1. the first argument — the install-time node path, tried FIRST and by
14
+ # absolute path so the #2979/#3002/#3017/#3022 minimal-PATH guarantee
15
+ # holds (GUI launches with a stripped PATH still resolve where the
16
+ # baked path exists);
17
+ # 2. `command -v node`, accepted only when it yields an absolute path;
18
+ # 3. the well-known stable layouts: $HOME-derived mise/volta shims, the
19
+ # Homebrew prefixes, /usr/local/bin/node, /usr/bin/node.
20
+ #
21
+ # No bare `node` lookup is ever depended on: a candidate is used only after
22
+ # an explicit executable check, and when nothing resolves this script fails
23
+ # visibly (stderr diagnostic + exit 127) rather than emitting a half-resolved
24
+ # invocation.
25
+ #
26
+ # The candidate list below is a SUPERSET of the inline chain token emitted by
27
+ # buildNodeRunnerChainToken (src/runtime-hooks-surface.cts, #3662) — keep the
28
+ # two lists consistent.
29
+ #
30
+ # Diagnostic escape: GSD_NODE_RUNNER_NO_FALLBACKS=1 disables candidates 2-3
31
+ # (first-argument-only resolution) — used by the test suite and useful to
32
+ # pin down which node a given environment picks.
33
+ set -u
34
+
35
+ preferred=${1:-}
36
+ script=${2:-}
37
+ if [ -n "$script" ]; then
38
+ shift 2
39
+ elif [ -n "$preferred" ]; then
40
+ shift 1
41
+ preferred=
42
+ fi
43
+
44
+ found=''
45
+
46
+ # check <path> — record <path> if it is an absolute, executable file.
47
+ # Absolute = POSIX root (/*) or a win32 drive-letter path (C:/…), which is
48
+ # what the installer bakes on Windows; anything else (a relative `command -v`
49
+ # hit under a relative PATH entry, a bare name) is rejected so repo-cwd
50
+ # content can never reach the runner slot.
51
+ check() {
52
+ case "$1" in
53
+ /*|[A-Za-z]:/*) if [ -x "$1" ]; then found=$1; fi ;;
54
+ esac
55
+ [ -n "$found" ]
56
+ }
57
+
58
+ check "$preferred" || {
59
+ if [ "${GSD_NODE_RUNNER_NO_FALLBACKS:-0}" != "1" ]; then
60
+ path_node=$(command -v node 2>/dev/null || true)
61
+ check "$path_node" ||
62
+ check "${HOME:-}/.local/share/mise/shims/node" ||
63
+ check "${HOME:-}/.volta/bin/node" ||
64
+ check /opt/homebrew/bin/node ||
65
+ check /usr/local/bin/node ||
66
+ check /usr/bin/node ||
67
+ true
68
+ fi
69
+ }
70
+
71
+ if [ -z "$found" ]; then
72
+ echo "gsd-node-runner: no usable node found (preferred: ${preferred:-<none>})" >&2
73
+ exit 127
74
+ fi
75
+
76
+ exec "$found" "$script" "$@"
@@ -6,6 +6,7 @@
6
6
  #
7
7
  # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
8
8
  # Enable with: "hooks": { "community": true } in .planning/config.json
9
+ set -euo pipefail
9
10
 
10
11
  # Check opt-in config — exit silently if not enabled
11
12
  if [ -f .planning/config.json ]; then
@@ -12,6 +12,13 @@
12
12
  // not to create false-positive deadlocks.
13
13
 
14
14
  const path = require('path');
15
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
16
+
17
+ // This guard is advisory-only by design (see header) — it never blocks the
18
+ // Write/Edit it scans, only adds context about it. A crash here must not
19
+ // start blocking now, which is strictly worse than the advisory it exists
20
+ // to add on top of an already-permitted operation (#3911).
21
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
15
22
 
16
23
  // Prompt injection patterns — shared with gsd-read-injection-scanner.js via
17
24
  // hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
@@ -115,7 +122,7 @@ function normalizeKimiPayload(data) {
115
122
  }
116
123
 
117
124
  let input = '';
118
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
125
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
119
126
  process.stdin.setEncoding('utf8');
120
127
  process.stdin.on('data', chunk => input += chunk);
121
128
  process.stdin.on('end', () => {
@@ -126,7 +133,7 @@ process.stdin.on('end', () => {
126
133
 
127
134
  // Only scan Write and Edit operations
128
135
  if (toolName !== 'Write' && toolName !== 'Edit') {
129
- process.exit(0);
136
+ allow(undefined);
130
137
  }
131
138
 
132
139
  // #2595 (review Major 3, sibling sweep): typed read. A non-string
@@ -138,7 +145,7 @@ process.stdin.on('end', () => {
138
145
 
139
146
  // Only scan files going into .planning/ (agent context files)
140
147
  if (!filePath.includes('.planning/') && !filePath.includes('.planning\\')) {
141
- process.exit(0);
148
+ allow(undefined);
142
149
  }
143
150
 
144
151
  // Get the content being written. #3504 (isolated review finding 3): the
@@ -157,7 +164,7 @@ process.stdin.on('end', () => {
157
164
  }
158
165
  }
159
166
  if (!content) {
160
- process.exit(0);
167
+ allow(undefined);
161
168
  }
162
169
 
163
170
  // Scan for injection patterns
@@ -174,7 +181,7 @@ process.stdin.on('end', () => {
174
181
  }
175
182
 
176
183
  if (findings.length === 0) {
177
- process.exit(0);
184
+ allow(undefined);
178
185
  }
179
186
 
180
187
  // Advisory warning — does not block the operation
@@ -191,7 +198,9 @@ process.stdin.on('end', () => {
191
198
 
192
199
  process.stdout.write(JSON.stringify(output));
193
200
  } catch {
194
- // Silent fail — never block tool execution
195
- process.exit(0);
201
+ // Silent fail — never block tool execution.
202
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
203
+ // exit(0) fail-open behavior exactly (#3911).
204
+ crash(ON_CRASH, undefined);
196
205
  }
197
206
  });
@@ -20,6 +20,13 @@
20
20
 
21
21
  const fs = require('fs');
22
22
  const path = require('path');
23
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
24
+
25
+ // This guard is pure advisory UX — a reminder to Read before Write/Edit on
26
+ // non-Claude-Code runtimes. A crash here must not block a legitimate Write/
27
+ // Edit; the worst outcome of failing open is the model hitting the runtime's
28
+ // own read-before-edit rejection it was trying to help avoid (#3911).
29
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
23
30
 
24
31
  // #2304: Kimi's native hook bus delivers Kimi's tool vocabulary in the payload
25
32
  // (Write → WriteFile, Edit/MultiEdit → StrReplaceFile) while the [[hooks]]
@@ -116,7 +123,7 @@ function normalizeKimiPayload(data) {
116
123
  }
117
124
 
118
125
  let input = '';
119
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
126
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
120
127
  process.stdin.setEncoding('utf8');
121
128
  process.stdin.on('data', chunk => input += chunk);
122
129
  process.stdin.on('end', () => {
@@ -127,7 +134,7 @@ process.stdin.on('end', () => {
127
134
 
128
135
  // Only intercept Write and Edit tool calls
129
136
  if (toolName !== 'Write' && toolName !== 'Edit') {
130
- process.exit(0);
137
+ allow(undefined);
131
138
  }
132
139
 
133
140
  // Claude Code natively enforces read-before-edit — skip the advisory (#1984, #2344, #2520).
@@ -151,7 +158,7 @@ process.stdin.on('end', () => {
151
158
  process.env.CLAUDE_SESSION_ID ||
152
159
  process.env.CLAUDECODE;
153
160
  if (isClaudeCode) {
154
- process.exit(0);
161
+ allow(undefined);
155
162
  }
156
163
 
157
164
  // #2595 (review Major 3, sibling sweep): typed read — same class as the
@@ -160,7 +167,7 @@ process.stdin.on('end', () => {
160
167
  ? data.tool_input.file_path
161
168
  : '';
162
169
  if (!filePath) {
163
- process.exit(0);
170
+ allow(undefined);
164
171
  }
165
172
 
166
173
  // Only inject guidance when the file already exists.
@@ -174,7 +181,7 @@ process.stdin.on('end', () => {
174
181
  }
175
182
 
176
183
  if (!fileExists) {
177
- process.exit(0);
184
+ allow(undefined);
178
185
  }
179
186
 
180
187
  const fileName = path.basename(filePath);
@@ -193,7 +200,9 @@ process.stdin.on('end', () => {
193
200
 
194
201
  process.stdout.write(JSON.stringify(output));
195
202
  } catch {
196
- // Silent fail — never block tool execution
197
- process.exit(0);
203
+ // Silent fail — never block tool execution.
204
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
205
+ // exit(0) fail-open behavior exactly (#3911).
206
+ crash(ON_CRASH, undefined);
198
207
  }
199
208
  });
@@ -23,6 +23,13 @@
23
23
 
24
24
  const path = require('path');
25
25
  const fs = require('fs');
26
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
27
+
28
+ // This is a PostToolUse advisory scanner over content the tool call already
29
+ // returned; a crash while scanning must not retroactively block that Read/
30
+ // WebFetch/WebSearch result from reaching the agent — losing the injection
31
+ // check is safer than failing the tool call it is only observing (#3911).
32
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
26
33
 
27
34
  // Summarisation-specific patterns (novel — not in gsd-prompt-guard.js).
28
35
  // These target instructions specifically designed to survive context compression.
@@ -213,7 +220,7 @@ function normalizeKimiPayload(data) {
213
220
  }
214
221
 
215
222
  let inputBuf = '';
216
- const stdinTimeout = setTimeout(() => process.exit(0), 5000);
223
+ const stdinTimeout = setTimeout(() => allow(undefined), 5000);
217
224
  process.stdin.setEncoding('utf8');
218
225
  process.stdin.on('data', chunk => { inputBuf += chunk; });
219
226
  process.stdin.on('end', () => {
@@ -224,7 +231,7 @@ process.stdin.on('end', () => {
224
231
  const toolName = data.tool_name;
225
232
  const SCANNED_TOOLS = new Set(['Read', 'WebFetch', 'WebSearch']);
226
233
  if (!SCANNED_TOOLS.has(toolName)) {
227
- process.exit(0);
234
+ allow(undefined);
228
235
  }
229
236
 
230
237
  // Source label + path-exclusion (path-exclusion applies to file reads only)
@@ -235,8 +242,8 @@ process.stdin.on('end', () => {
235
242
  source = typeof data.tool_input?.file_path === 'string'
236
243
  ? data.tool_input.file_path
237
244
  : '';
238
- if (!source) process.exit(0);
239
- if (isExcludedPath(source)) process.exit(0);
245
+ if (!source) allow(undefined);
246
+ if (isExcludedPath(source)) allow(undefined);
240
247
  } else if (toolName === 'WebFetch') {
241
248
  source = data.tool_input?.url || 'web';
242
249
  } else { // WebSearch
@@ -261,7 +268,7 @@ process.stdin.on('end', () => {
261
268
  }
262
269
 
263
270
  if (!content || content.length < 20) {
264
- process.exit(0);
271
+ allow(undefined);
265
272
  }
266
273
 
267
274
  // Typed findings IR — single source of truth for both the machine-readable
@@ -307,7 +314,7 @@ process.stdin.on('end', () => {
307
314
  }
308
315
 
309
316
  if (findings.length === 0) {
310
- process.exit(0);
317
+ allow(undefined);
311
318
  }
312
319
 
313
320
  // Renders one finding back into the exact prose fragment the advisory has
@@ -349,7 +356,9 @@ process.stdin.on('end', () => {
349
356
 
350
357
  process.stdout.write(JSON.stringify(output));
351
358
  } catch {
352
- // Silent fail — never block tool execution
353
- process.exit(0);
359
+ // Silent fail — never block tool execution.
360
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
361
+ // exit(0) fail-open behavior exactly (#3911).
362
+ crash(ON_CRASH, undefined);
354
363
  }
355
364
  });
@@ -5,6 +5,7 @@
5
5
  #
6
6
  # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
7
7
  # Enable with: "hooks": { "community": true } in .planning/config.json
8
+ set -euo pipefail
8
9
 
9
10
  # Check opt-in config — exit silently if not enabled
10
11
  if [ -f .planning/config.json ]; then