@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
@@ -37,7 +37,7 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
37
37
  If $ARGUMENTS contains a phase number, load context:
38
38
 
39
39
  ```bash
40
- _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
40
+ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd_run is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; case "$(gsd_run runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') GSD_IDENTITY_STATUS=ok;; esac; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
41
41
  GSD_WS=""
42
42
  echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
43
43
  PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
@@ -61,15 +61,26 @@ MVP_MODE=$(gsd_run query phase.mvp-mode "${phase_number}" ${GSD_WS} --pick activ
61
61
  </step>
62
62
 
63
63
  <step name="verify_pre_hooks">
64
- **Verify:pre gate dispatch.** Before verification begins, dispatch every active
65
- gate hook registered at the `verify:pre` loop extension point. Each gate is
66
- data-driven — resolved from the capability registry, not hardcoded here.
64
+ **Verify:pre capability dispatch.** Before verification begins, dispatch every
65
+ active hook registered at the `verify:pre` loop extension point — of **every**
66
+ kind, not gates alone. Each hook is data-driven — resolved from the capability
67
+ registry, not hardcoded here.
67
68
 
68
69
  ```bash
69
70
  VERIFY_PRE_HOOKS_JSON=$(gsd_run loop render-hooks verify:pre --raw)
70
71
  PHASE_DIR=$(printf '%s' "$INIT" | jq -r '.phase_dir // empty')
71
72
  ```
72
73
 
74
+ Read the `activeHooks` array from `VERIFY_PRE_HOOKS_JSON` in-context (do NOT pipe through a shell parser).
75
+
76
+ **If `activeHooks` is empty or absent:** skip silently to `check_active_session`.
77
+
78
+ **Contribution dispatch:** inject every `kind == "contribution"` fragment per @gsd-core/references/loop-hook-dispatch.md (skip when none), before the steps and gates below.
79
+
80
+ **Step dispatch:** dispatch every `kind == "step"` hook per @gsd-core/references/loop-hook-dispatch.md (skip when none) — not one shape of one. A step here is advisory: it never blocks the start of UAT, and a step that errors is routed by its own `onError` without failing verification. ⚠ **Validate `ref.command` in-context before any shell use** (third-party manifest input) — loop-hook-dispatch.md § `step`.
81
+
82
+ Record the union of `produces` artefact names declared by the active step entries as `VERIFY_PRE_PRODUCED` — `extract_tests` consumes it below. An entry declaring `produces: []` contributes nothing, which is the normal case.
83
+
73
84
  ⚠ **Validate `check` before shell use** (third-party manifest input) — `loop-hook-dispatch.md` § `gate`.
74
85
 
75
86
  Resolve active gate hooks from `VERIFY_PRE_HOOKS_JSON` where `kind == "gate"`.
@@ -187,6 +198,35 @@ Read the JSON result (`mode`, `total`, `all_auto_covered`, `auto_passed[]`, `pre
187
198
 
188
199
  The cold-start smoke test injection below still applies in `coverage` mode.
189
200
 
201
+ **Verify:pre produced-artefact seam (#3866).** If `VERIFY_PRE_PRODUCED` (recorded in
202
+ `verify_pre_hooks`) is empty or absent, skip this paragraph entirely — derivation is unchanged.
203
+ Otherwise, for each artefact name in it, locate the artefact the producing step wrote under
204
+ `$PHASE_DIR` and merge its checkpoints into the test list **additively**.
205
+
206
+ **The artefact contract.** A consumable artefact is a Markdown file holding a list of checkpoint
207
+ entries in the **same shape `extract_tests` already emits and `create_uat_file` already consumes** —
208
+ each entry a `name` (brief test name) and an `expected` (specific, user-observable outcome).
209
+ Nothing else is read: extra fields are ignored, not an error. There is no new schema and no new
210
+ parser — a producing step writes what a checkpoint already looks like. An artefact that yields zero
211
+ parseable entries is treated exactly like an absent one (see below).
212
+
213
+ Merge rules:
214
+
215
+ - ⚠ **Validate the artefact name in-context before resolving it** (third-party manifest input).
216
+ An artefact name is a registry-declared name, **not** a path: check the value you read from
217
+ `produces` against `^[A-Za-z0-9][A-Za-z0-9._-]*$` yourself — **never** by pasting it into a
218
+ shell command to be tested there. A name carrying `/`, `..`, a leading `-`, a leading path
219
+ separator, or any shell metacharacter is a malformed manifest: record a warning, skip that
220
+ name, continue. Only a validated name is resolved, and only against what the step wrote inside
221
+ `$PHASE_DIR` — never above it, and never through a symlink that leaves it.
222
+ - A name with no artefact on disk means that step was inactive, skipped, or failed. Note it to the
223
+ user and derive normally — **never drop a deliverable and never block UAT over it.**
224
+ - Merged entries are added to, never subtracted from, what `coverage:` classification and the
225
+ prose fallback produce. An `auto_passed[]` entry stays un-presented; a `present[]` entry stays a
226
+ human checkpoint. This seam can deepen UAT, not suppress it.
227
+ - Deduplicate against already-derived checkpoints by `name`, keeping the earlier entry's
228
+ `expected` text so a produced artefact cannot silently rewrite a criterion.
229
+
190
230
  **Extract testable deliverables from SUMMARY.md (legacy fallback — used when `mode: legacy`):**
191
231
 
192
232
  Parse for:
@@ -534,6 +574,8 @@ VERIFY_POST_HOOKS_JSON=$(gsd_run loop render-hooks verify:post --raw)
534
574
  SECURITY_FILE=$(ls "${PHASE_DIR}"/*-SECURITY.md 2>/dev/null | head -1)
535
575
  ```
536
576
 
577
+ **Generic step dispatch:** dispatch every `kind == "step"` hook from `VERIFY_POST_HOOKS_JSON` per @gsd-core/references/loop-hook-dispatch.md (skip silently when none). Each step is advisory and best-effort — honor `onError` and continue. The secure-phase handling below is an additional specialization of one such hook, not a replacement for the generic dispatch.
578
+
537
579
  Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "secure-phase"`.
538
580
 
539
581
  If an active secure-phase step hook exists AND `SECURITY_FILE` is empty, dispatch the registry-provided skill stem:
@@ -633,7 +675,7 @@ All tests passed. Phase {phase} marked complete.
633
675
  <step name="scan_phase_artifacts">
634
676
  Run phase artifact scan to surface any open items before marking phase verified:
635
677
 
636
- `audit-open` is CJS-only until registered on `gsd-tools.cjs query`:
678
+ `audit-open` is CJS-only until registered on `gsd_run query`:
637
679
 
638
680
  ```bash
639
681
  gsd_run query audit-open --json
@@ -647,9 +689,12 @@ Parse the JSON output. For the CURRENT PHASE ONLY, surface:
647
689
  If any are found, display:
648
690
  ```
649
691
  Phase {N} Artifact Check
650
- ─────────────────────────────────────────────────
692
+
693
+ ---
694
+
651
695
  {list each item with status and file path}
652
- ─────────────────────────────────────────────────
696
+
697
+ ---
653
698
  These items are open. Proceed anyway? [Y/n]
654
699
  ```
655
700
 
@@ -685,9 +730,7 @@ Diagnosis runs automatically - no user prompt. Parallel agents investigate simul
685
730
 
686
731
  Display:
687
732
  ```
688
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
689
- GSD ► PLANNING FIXES
690
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
733
+ ### GSD ► PLANNING FIXES
691
734
 
692
735
  ◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
693
736
  ```
@@ -752,9 +795,7 @@ On return:
752
795
 
753
796
  Display:
754
797
  ```
755
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
756
- GSD ► VERIFYING FIX PLANS
757
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
798
+ ### GSD ► VERIFYING FIX PLANS
758
799
 
759
800
  ◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
760
801
  ```
@@ -858,9 +899,7 @@ Wait for user response.
858
899
  **Present completion and next steps:**
859
900
 
860
901
  ```
861
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
862
- GSD ► FIXES READY ✓
863
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
902
+ ### GSD ► FIXES READY ✓
864
903
 
865
904
  **Phase {X}: {Name}** — {N} gap(s) diagnosed, {M} fix plan(s) created
866
905
 
@@ -871,7 +910,7 @@ Wait for user response.
871
910
 
872
911
  Plans verified and ready for execution.
873
912
 
874
- ───────────────────────────────────────────────────────────────
913
+ ---
875
914
 
876
915
  ## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
877
916
 
@@ -879,7 +918,7 @@ Plans verified and ready for execution.
879
918
 
880
919
  `/clear` then `/gsd:execute-phase {phase} --gaps-only`
881
920
 
882
- ───────────────────────────────────────────────────────────────
921
+ ---
883
922
  ```
884
923
  </step>
885
924
 
@@ -65,6 +65,23 @@ const path = require('path');
65
65
  const os = require('os');
66
66
  const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
67
67
  const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
68
+ const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
69
+
70
+ // Required at module top, alongside the other ./lib requires — NOT behind
71
+ // ensureRuntimeBuild() below. Terminating on a parse/timeout failure must
72
+ // never depend on the gitignored build artifacts this hook self-heals for
73
+ // its own registry lookups (#3911).
74
+ //
75
+ // This guard's outer catch (main(), below) has always exited 0 (fail open):
76
+ // that outer catch only covers payload PARSING failing before applicability
77
+ // could even be determined (malformed stdin JSON, etc.) — the guard's real
78
+ // fail-closed logic (a GSD project whose dispatch-isolation configuration
79
+ // cannot be verified) is handled separately, inside evaluateDispatch/
80
+ // resolveIsolationState, and already returns a 'block' decision through the
81
+ // normal exit-2 path rather than through this catch. So an unparseable
82
+ // payload has nothing to enforce; allowing it preserves today's behavior
83
+ // exactly.
84
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
68
85
  // #3582: gsd-core/bin/lib/*.cjs (runtime-name-policy.cjs, capability-registry.cjs
69
86
  // below) are tsc build artifacts (ADR-457), gitignored and absent on a raw
70
87
  // plugin-marketplace / git-clone install that never ran `npm run build:lib`.
@@ -95,40 +112,40 @@ function parseHarnessFlag(flag) {
95
112
  return { param: m[1], value: m[2] };
96
113
  }
97
114
 
98
- // ─── #3566: per-install runtime marker ────────────────────────────────────────
115
+ // ─── #3897 rung 2: per-install runtime marker, single canonical owner ────────
99
116
  // bin/install.js writes `<install>/gsd-core/.gsd-runtime` for EVERY runtime
100
117
  // install (#2297), co-located with VERSION. Unlike `~/.gsd/defaults.json` —
101
118
  // which is host-wide and names whichever runtime's install ran LAST, the exact
102
119
  // leakage #2840's config.cjs change exists to prevent — the marker describes
103
120
  // THIS install, which is the property runtime identity needs on a machine
104
- // with 2+ runtimes. Mirrors readInstallRuntimeMarker in src/model-resolver.cts
105
- // (same cache + test-seam shape); this hook cannot import that module without
106
- // dragging the whole model-resolution stack into a PreToolUse hot path, so the
107
- // 5-line read lives here against the same sibling-layout assumption the hook's
108
- // own require('../gsd-core/bin/lib/…') already makes. Epic #3473 B3 owns
109
- // consolidating every marker reader into one shared seam.
110
- let _installMarkerCache; // undefined = unread; null = known absent; string = value
111
-
121
+ // with 2+ runtimes. Previously this hook held its own private reader/cache
122
+ // (one of four #3897 found); it now delegates to the single canonical owner,
123
+ // `src/runtime-slash.cts` (compiled to gsd-core/bin/lib/runtime-slash.cjs),
124
+ // reached through `ensureRuntimeBuild()` like every other compiled-lib require
125
+ // in this file (`scripts/lint-hooks-runtime-build-seam.cjs`).
112
126
  function readInstallRuntimeMarker() {
113
- if (_installMarkerCache !== undefined) return _installMarkerCache;
114
127
  try {
115
- const markerPath = path.join(__dirname, '..', 'gsd-core', '.gsd-runtime');
116
- const raw = fs.readFileSync(markerPath, 'utf-8').trim();
117
- _installMarkerCache = raw || null;
128
+ ensureRuntimeBuild();
129
+ const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
130
+ return runtimeSlash.readInstallRuntimeMarker();
118
131
  } catch {
119
- // No marker: dev/source tree, or an install predating #2297 — "no signal
120
- // from this rung", never a resolution failure. Falls through to the
121
- // defaults rung below.
122
- _installMarkerCache = null;
132
+ // Unbuilt runtime library, or any other failure reaching the canonical
133
+ // owner — "no signal from this rung" (N4), never a resolution failure.
134
+ return null;
123
135
  }
124
- return _installMarkerCache;
125
136
  }
126
137
 
127
- // Test seam for the marker rung (the dev/source tree has no marker file, so
128
- // the read always bottoms out at null there — same seam contract as
129
- // model-resolver.cts's _setInstallRuntimeMarkerForTests, #2297).
138
+ // Test seam for the marker rung — forwards to the canonical owner's seam so
139
+ // this hook and runtime-slash.cjs always share one cache (#3897 rung 2).
130
140
  function _setInstallRuntimeMarkerForTests(value) {
131
- _installMarkerCache = value;
141
+ try {
142
+ ensureRuntimeBuild();
143
+ const runtimeSlash = require('../gsd-core/bin/lib/runtime-slash.cjs');
144
+ runtimeSlash._setInstallRuntimeMarkerForTests(value);
145
+ } catch {
146
+ // Test-only seam; an unbuilt library here means the test itself will fail
147
+ // downstream, which is a louder and more actionable signal than throwing here.
148
+ }
132
149
  }
133
150
 
134
151
  /**
@@ -282,18 +299,40 @@ function resolveRegistryIsolation(cwd, configPath) {
282
299
 
283
300
  if (isolation === 'harness-worktree') {
284
301
  let useWorktrees = true;
302
+ // #3972: the opt-out read shares the ONE owner every other
303
+ // isolation-deciding surface uses — planning-workspace's
304
+ // worktreesOptedOut ladder (scoped own-key wins; root inherited under
305
+ // the GSD_WORKSTREAM gate; strict === false). A flat single-file read
306
+ // here made a workstream-LOCAL opt-out invisible, so the sentinel-absent
307
+ // fallback denied a sequential dispatch the config explicitly allowed.
308
+ // Reached through ensureRuntimeBuild() like every other compiled-lib
309
+ // require in this file (scripts/lint-hooks-runtime-build-seam.cjs); if
310
+ // the library is unreachable the flat-root read below remains as the
311
+ // degraded fallback — today's behavior, never worse.
312
+ let ladderAnswered = false;
285
313
  try {
286
- const raw = fs.readFileSync(configPath, 'utf-8');
287
- const parsedCfg = JSON.parse(raw);
288
- if (parsedCfg && typeof parsedCfg === 'object' && parsedCfg.workflow &&
289
- typeof parsedCfg.workflow === 'object' && parsedCfg.workflow.use_worktrees === false) {
290
- useWorktrees = false;
291
- }
314
+ ensureRuntimeBuild();
315
+ const { worktreesOptedOut } = require('../gsd-core/bin/lib/planning-workspace.cjs');
316
+ useWorktrees = !worktreesOptedOut(cwd);
317
+ ladderAnswered = true;
292
318
  } catch {
293
- // Unreadable config already propagated to the outer caller's catch
294
- // before this point in practice (resolveRuntimeIdentity reads it
295
- // first); tolerate defensively and keep the conservative (enforce)
296
- // default rather than silently disabling the guard.
319
+ // Unbuilt runtime library or a ladder failure — fall through to the
320
+ // legacy flat-root read (conservative: keep enforcing).
321
+ }
322
+ if (!ladderAnswered) {
323
+ try {
324
+ const raw = fs.readFileSync(configPath, 'utf-8');
325
+ const parsedCfg = JSON.parse(raw);
326
+ if (parsedCfg && typeof parsedCfg === 'object' && parsedCfg.workflow &&
327
+ typeof parsedCfg.workflow === 'object' && parsedCfg.workflow.use_worktrees === false) {
328
+ useWorktrees = false;
329
+ }
330
+ } catch {
331
+ // Unreadable config already propagated to the outer caller's catch
332
+ // before this point in practice (resolveRuntimeIdentity reads it
333
+ // first); tolerate defensively and keep the conservative (enforce)
334
+ // default rather than silently disabling the guard.
335
+ }
297
336
  }
298
337
  if (!useWorktrees) isolation = 'none';
299
338
  }
@@ -476,7 +515,7 @@ function evaluateDispatch(data, { clock = Date } = {}) {
476
515
  /* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
477
516
  function main() {
478
517
  let input = '';
479
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
518
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
480
519
  process.stdin.setEncoding('utf8');
481
520
  process.stdin.on('data', (chunk) => { input += chunk; });
482
521
  process.stdin.on('end', () => {
@@ -486,18 +525,18 @@ function main() {
486
525
  const decision = evaluateDispatch(data);
487
526
  if (decision.action === 'block') {
488
527
  const out = { decision: 'block', reason: decision.reason, reason_code: decision.reasonCode };
489
- process.stdout.write(JSON.stringify(out));
490
528
  // Kimi feeds stderr (not stdout) back to the model on exit 2.
491
- process.stderr.write(decision.reason);
492
- process.exit(2);
529
+ deny(out, decision.reason);
493
530
  }
494
- process.exit(0);
531
+ allow(undefined);
495
532
  } catch {
496
533
  // Silent fail — never block valid tool calls due to hook errors
497
534
  // (malformed payload, etc.). This is distinct from resolveIsolationState's
498
535
  // internal error handling, which DOES deny — this outer catch only
499
536
  // covers payload parsing before applicability could even be determined.
500
- process.exit(0);
537
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
538
+ // exit(0) fail-open behavior exactly (#3911).
539
+ crash(ON_CRASH, undefined);
501
540
  }
502
541
  });
503
542
  }
@@ -20,11 +20,17 @@
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 hook only injects an advisory config-reload summary; a crash mid-parse
26
+ // must not block the session or the FileChanged event that triggered it — the
27
+ // agent simply keeps using the config context it already had (#3911).
28
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
23
29
 
24
30
  let input = '';
25
31
  // Timeout guard: if stdin does not close within 8s exit silently rather than
26
32
  // hanging until Claude Code kills the process and reports "hook error".
27
- const stdinTimeout = setTimeout(() => process.exit(0), 8000);
33
+ const stdinTimeout = setTimeout(() => allow(undefined), 8000);
28
34
  process.stdin.setEncoding('utf8');
29
35
  process.stdin.on('data', chunk => (input += chunk));
30
36
  process.stdin.on('end', () => {
@@ -42,24 +48,23 @@ process.stdin.on('end', () => {
42
48
  // inject spurious additionalContext.
43
49
  const basename = path.basename(filePath);
44
50
  if (basename !== 'config.json') {
45
- process.exit(0);
51
+ allow(undefined);
46
52
  }
47
53
  const expectedPath = path.resolve(cwd, '.planning', 'config.json');
48
54
  if (path.resolve(filePath) !== expectedPath) {
49
- process.exit(0);
55
+ allow(undefined);
50
56
  }
51
57
 
52
58
  // On unlink (deletion) emit a brief notice and exit
53
59
  if (event === 'unlink') {
54
- process.stdout.write(JSON.stringify({
60
+ allow({
55
61
  hookSpecificOutput: {
56
62
  hookEventName: 'FileChanged',
57
63
  additionalContext:
58
64
  'GSD config (.planning/config.json) was deleted. ' +
59
65
  'Falling back to built-in defaults for this session.',
60
66
  },
61
- }));
62
- process.exit(0);
67
+ });
63
68
  }
64
69
 
65
70
  // Read the updated config file
@@ -68,17 +73,16 @@ process.stdin.on('end', () => {
68
73
  const raw = fs.readFileSync(filePath, 'utf8');
69
74
  config = JSON.parse(raw);
70
75
  } catch (e) {
71
- if (e && e.code === 'ENOENT') process.exit(0);
76
+ if (e && e.code === 'ENOENT') allow(undefined);
72
77
  // Malformed JSON — inform the agent without crashing
73
- process.stdout.write(JSON.stringify({
78
+ allow({
74
79
  hookSpecificOutput: {
75
80
  hookEventName: 'FileChanged',
76
81
  additionalContext:
77
82
  'GSD config (.planning/config.json) was modified but could not be parsed. ' +
78
83
  'Check the file for JSON syntax errors.',
79
84
  },
80
- }));
81
- process.exit(0);
85
+ });
82
86
  }
83
87
 
84
88
  // Build a concise summary of key config fields the agent cares about
@@ -127,7 +131,9 @@ process.stdin.on('end', () => {
127
131
  },
128
132
  }));
129
133
  } catch (e) {
130
- // Silent fail — never block the session on a config reload error
131
- process.exit(0);
134
+ // Silent fail — never block the session on a config reload error.
135
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
136
+ // exit(0) fail-open behavior exactly (#3911).
137
+ crash(ON_CRASH, undefined);
132
138
  }
133
139
  });
@@ -22,6 +22,13 @@ const fs = require('fs');
22
22
  const os = require('os');
23
23
  const path = require('path');
24
24
  const { spawn } = require('child_process');
25
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
26
+
27
+ // This hook only injects an advisory context-usage warning; it never blocks
28
+ // the tool call it rides in on. A crash here (e.g. a malformed bridge file)
29
+ // must not turn a PostToolUse advisory into a blocked tool call — losing a
30
+ // context warning is far cheaper than stalling the agent's work (#3911).
31
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
25
32
 
26
33
  const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%
27
34
  const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25%
@@ -33,7 +40,7 @@ let input = '';
33
40
  // Windows/Git Bash, or slow Claude Code piping during large outputs),
34
41
  // exit silently instead of hanging until Claude Code kills the process
35
42
  // and reports "hook error". See #775, #1162.
36
- const stdinTimeout = setTimeout(() => process.exit(0), 10000);
43
+ const stdinTimeout = setTimeout(() => allow(undefined), 10000);
37
44
  process.stdin.setEncoding('utf8');
38
45
  process.stdin.on('data', chunk => input += chunk);
39
46
  process.stdin.on('end', () => {
@@ -43,14 +50,14 @@ process.stdin.on('end', () => {
43
50
  const sessionId = data.session_id;
44
51
 
45
52
  if (!sessionId) {
46
- process.exit(0);
53
+ allow(undefined);
47
54
  }
48
55
 
49
56
  // Reject session IDs that contain path traversal sequences or path separators.
50
57
  // session_id is used to construct file paths in /tmp — an unsanitized value
51
58
  // could escape the temp directory and read or write arbitrary files.
52
59
  if (/[/\\]|\.\./.test(sessionId)) {
53
- process.exit(0);
60
+ allow(undefined);
54
61
  }
55
62
 
56
63
  // Check if context warnings are disabled via config.
@@ -61,7 +68,7 @@ process.stdin.on('end', () => {
61
68
  const configPath = path.join(cwd, '.planning', 'config.json');
62
69
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
63
70
  if (config.hooks?.context_warnings === false) {
64
- process.exit(0);
71
+ allow(undefined);
65
72
  }
66
73
  } catch (e) {
67
74
  // Missing or unparseable config → proceed with defaults (context warnings enabled)
@@ -77,7 +84,7 @@ process.stdin.on('end', () => {
77
84
  try {
78
85
  metricsRaw = fs.readFileSync(metricsPath, 'utf8');
79
86
  } catch (e) {
80
- if (e && e.code === 'ENOENT') process.exit(0);
87
+ if (e && e.code === 'ENOENT') allow(undefined);
81
88
  throw e;
82
89
  }
83
90
  const metrics = JSON.parse(metricsRaw);
@@ -85,7 +92,7 @@ process.stdin.on('end', () => {
85
92
 
86
93
  // Ignore stale metrics
87
94
  if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) {
88
- process.exit(0);
95
+ allow(undefined);
89
96
  }
90
97
 
91
98
  const remaining = metrics.remaining_percentage;
@@ -93,7 +100,7 @@ process.stdin.on('end', () => {
93
100
 
94
101
  // No warning needed
95
102
  if (remaining > WARNING_THRESHOLD) {
96
- process.exit(0);
103
+ allow(undefined);
97
104
  }
98
105
 
99
106
  // Debounce: check if we warned recently
@@ -121,7 +128,7 @@ process.stdin.on('end', () => {
121
128
  if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
122
129
  // Update counter and exit without warning
123
130
  fs.writeFileSync(warnPath, JSON.stringify(warnData));
124
- process.exit(0);
131
+ allow(undefined);
125
132
  }
126
133
 
127
134
  // Reset debounce counter
@@ -208,7 +215,9 @@ process.stdin.on('end', () => {
208
215
  process.stdout.write(JSON.stringify(output));
209
216
  }
210
217
  } catch (e) {
211
- // Silent fail -- never block tool execution
212
- process.exit(0);
218
+ // Silent fail -- never block tool execution.
219
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
220
+ // exit(0) fail-open behavior exactly (#3911).
221
+ crash(ON_CRASH, undefined);
213
222
  }
214
223
  });
@@ -22,6 +22,8 @@
22
22
 
23
23
  'use strict';
24
24
 
25
+ const { allow } = require('./lib/hook-exit.js');
26
+
25
27
  const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
26
28
  const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
27
29
  const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
@@ -29,7 +31,7 @@ const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
29
31
  let raw = '';
30
32
  const stdinTimeout = setTimeout(() => {
31
33
  // Timeout guard: exit silently rather than hanging.
32
- process.exit(0);
34
+ allow(undefined);
33
35
  }, 10000);
34
36
 
35
37
  process.stdin.setEncoding('utf8');
@@ -22,13 +22,15 @@
22
22
 
23
23
  'use strict';
24
24
 
25
+ const { allow } = require('./lib/hook-exit.js');
26
+
25
27
  const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
26
28
  const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
27
29
  const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
28
30
 
29
31
  let raw = '';
30
32
  const stdinTimeout = setTimeout(() => {
31
- process.exit(0);
33
+ allow(undefined);
32
34
  }, 10000);
33
35
 
34
36
  process.stdin.setEncoding('utf8');
@@ -23,6 +23,7 @@
23
23
  'use strict';
24
24
 
25
25
  const fs = require('fs');
26
+ const { allow } = require('./lib/hook-exit.js');
26
27
 
27
28
  const MSG_PRESENT =
28
29
  'GSD: .planning/STATE.md is present — review the current phase and any blockers before acting.';
@@ -37,7 +38,7 @@ const { resolveStatePath } = require('./lib/cursor-workspace.js');
37
38
  let raw = '';
38
39
  const stdinTimeout = setTimeout(() => {
39
40
  // Timeout guard: exit silently rather than hanging.
40
- process.exit(0);
41
+ allow(undefined);
41
42
  }, 10000);
42
43
 
43
44
  process.stdin.setEncoding('utf8');
@@ -21,6 +21,7 @@
21
21
  'use strict';
22
22
 
23
23
  const fs = require('fs');
24
+ const { allow } = require('./lib/hook-exit.js');
24
25
 
25
26
  // Workspace resolution is shared across the Cursor hooks (#2587) — see
26
27
  // hooks/lib/cursor-workspace.js. Staged next to these scripts by
@@ -29,7 +30,7 @@ const { resolveStatePath } = require('./lib/cursor-workspace.js');
29
30
 
30
31
  let raw = '';
31
32
  const stdinTimeout = setTimeout(() => {
32
- process.exit(0);
33
+ allow(undefined);
33
34
  }, 10000);
34
35
 
35
36
  process.stdin.setEncoding('utf8');