@opengsd/gsd-core 1.10.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 (544) 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 +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -7,10 +7,43 @@
7
7
  #
8
8
  # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
9
9
  # Enable with: "hooks": { "community": true } in .planning/config.json
10
+ set -euo pipefail
11
+
12
+ # Temp files created below for subprocess stderr capture (config read, command
13
+ # extraction, classifier). A single EXIT trap replaces three hand-rolled
14
+ # mktemp/rm-f pairs so an early or unexpected exit path can never leak one —
15
+ # and a future fourth check does not need its own copy (#3911 review).
16
+ # Idempotent and failure-proof by construction: unset vars expand to "" (a
17
+ # no-op rm -f target), and `|| true` guarantees the trap itself never changes
18
+ # the script's exit status.
19
+ ENABLED_ERR=""
20
+ CMD_ERR=""
21
+ CLASSIFY_ERR=""
22
+ cleanup_temp_files() {
23
+ rm -f "${ENABLED_ERR:-}" "${CMD_ERR:-}" "${CLASSIFY_ERR:-}" 2>/dev/null || true
24
+ }
25
+ trap cleanup_temp_files EXIT
10
26
 
11
27
  # Check opt-in config — exit silently if not enabled
12
28
  if [ -f .planning/config.json ]; then
13
- ENABLED=$(node -e "try{const c=require('./.planning/config.json');process.stdout.write(c.hooks?.community===true?'1':'0')}catch{process.stdout.write('0')}" 2>/dev/null)
29
+ ENABLED_ERR=$(mktemp)
30
+ ENABLED=$(node -e "
31
+ try{
32
+ const c=require('./.planning/config.json');
33
+ process.stdout.write(c.hooks?.community===true?'1':'0');
34
+ }catch(e){
35
+ process.stderr.write('CONFIG_READ_FAILED: '+(e&&e.message?e.message:String(e)));
36
+ process.exit(3);
37
+ }
38
+ " 2>"$ENABLED_ERR") || CONFIG_STATUS=$?
39
+ CONFIG_STATUS=${CONFIG_STATUS:-0}
40
+ if [ "$CONFIG_STATUS" != "0" ]; then
41
+ # Could not determine the opt-in flag at all (node missing, JSON parse
42
+ # error other than absence, etc.) — distinct from ".planning/config.json
43
+ # exists and legitimately disables the hook". Say so and pass, per #3838.
44
+ echo "gsd-validate-commit.sh: could not read .planning/config.json (opt-in check) — validator disabled for this call. $(cat "$ENABLED_ERR")" >&2
45
+ exit 0
46
+ fi
14
47
  if [ "$ENABLED" != "1" ]; then exit 0; fi
15
48
  else
16
49
  exit 0
@@ -19,17 +52,58 @@ fi
19
52
  INPUT=$(cat)
20
53
 
21
54
  # Extract command from JSON using Node (handles escaping correctly, no jq needed)
22
- CMD=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.command||'')}catch{}})" 2>/dev/null)
55
+ CMD_ERR=$(mktemp)
56
+ CMD=$(echo "$INPUT" | node -e "
57
+ let d='';
58
+ process.stdin.on('data',c=>d+=c);
59
+ process.stdin.on('end',()=>{
60
+ try{
61
+ process.stdout.write(JSON.parse(d).tool_input?.command||'');
62
+ }catch(e){
63
+ process.stderr.write('COMMAND_EXTRACTION_FAILED: '+(e&&e.message?e.message:String(e)));
64
+ process.exit(3);
65
+ }
66
+ });
67
+ " 2>"$CMD_ERR") || CMD_STATUS=$?
68
+ CMD_STATUS=${CMD_STATUS:-0}
69
+ if [ "$CMD_STATUS" != "0" ]; then
70
+ # Could not extract tool_input.command at all (node missing, malformed
71
+ # JSON, etc.) — distinct from "there is genuinely no command field". Say
72
+ # so and pass, per #3838.
73
+ echo "gsd-validate-commit.sh: could not extract tool_input.command from the hook payload — validator disabled for this call. $(cat "$CMD_ERR")" >&2
74
+ exit 0
75
+ fi
23
76
 
24
77
  # Only check git commit commands.
25
78
  # Delegates to hooks/lib/git-cmd.js isGitSubcommand() — the canonical token-walk
26
79
  # classifier that handles env-prefix, -C path, and full-path git invocations.
27
80
  # A naive `^git\s+commit` regex misses all three; this guard fixes that (#3129).
28
81
  HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
29
- if GIT_CMD_LIB="$HOOK_DIR/lib/git-cmd.js" node -e "
30
- const {isGitSubcommand}=require(process.env.GIT_CMD_LIB);
31
- process.exit(isGitSubcommand(process.argv[1],'commit')?0:1);
32
- " "$CMD" 2>/dev/null; then
82
+ CLASSIFY_ERR=$(mktemp)
83
+ GIT_CMD_LIB="$HOOK_DIR/lib/git-cmd.js" node -e "
84
+ try {
85
+ const {isGitSubcommand}=require(process.env.GIT_CMD_LIB);
86
+ process.exit(isGitSubcommand(process.argv[1],'commit')?0:1);
87
+ } catch(e) {
88
+ process.stderr.write('CLASSIFIER_THREW: '+(e&&e.message?e.message:String(e)));
89
+ process.exit(3);
90
+ }
91
+ " "$CMD" 2>"$CLASSIFY_ERR" || CLASSIFY_STATUS=$?
92
+ CLASSIFY_STATUS=${CLASSIFY_STATUS:-0}
93
+ if [ "$CLASSIFY_STATUS" != "0" ] && [ "$CLASSIFY_STATUS" != "1" ]; then
94
+ # 0 = is a git commit (validate below); 1 = genuinely not a git commit
95
+ # (real negative, pass silently) — the ONLY intentional non-zero exit the
96
+ # script above ever produces on success. Any other status — 127 node
97
+ # missing, or 3 from the try/catch above when the git-cmd.js require chain
98
+ # throws (e.g. its built dependency, gsd-core/bin/lib/token-scanner.cjs, is
99
+ # a gitignored build artifact and absent on a fresh checkout — run
100
+ # `npm run build:lib`) — means the classifier could not run at all. Say so
101
+ # on stderr and pass (#3838): PreToolUse stderr does not disturb the JSON
102
+ # protocol.
103
+ echo "gsd-validate-commit.sh: could not classify the command via hooks/lib/git-cmd.js (exit $CLASSIFY_STATUS) — validator disabled for this call. If this persists, run \`npm run build:lib\`. $(cat "$CLASSIFY_ERR")" >&2
104
+ exit 0
105
+ fi
106
+ if [ "$CLASSIFY_STATUS" = "0" ]; then
33
107
  # Extract message from -m flag
34
108
  MSG=""
35
109
  if [[ "$CMD" =~ -m[[:space:]]+\"([^\"]+)\" ]]; then
@@ -47,6 +47,16 @@
47
47
 
48
48
  'use strict';
49
49
 
50
+ const { allow, deny } = require('./lib/hook-exit.js');
51
+
52
+ // #3911 (ADR-3889 Phase 7): the exit(2) call site (block(), below) is
53
+ // migrated to hook-exit.js's deny(undefined, reason) now that
54
+ // hooks/lib/cli-exit.js's terminateNow emits fd 1 and fd 2 (stderrPayload)
55
+ // in INDEPENDENT try/catch blocks — `payload=undefined` cleanly skips the
56
+ // fd 1 write (preserving "nothing written to stdout") instead of throwing
57
+ // into a shared catch that used to also swallow the fd 2 write, which is
58
+ // what silently dropped the deny reason before this fix.
59
+
50
60
  // No realistic destructive command comes anywhere close to this length.
51
61
  const MAX_COMMAND_LENGTH = 4096;
52
62
 
@@ -244,16 +254,11 @@ function destructiveReason(cmd) {
244
254
  }
245
255
 
246
256
  function block(reason) {
247
- process.stderr.write(`GSD windsurf pre_run_command guard: ${reason}\n`);
248
- process.exit(2);
249
- }
250
-
251
- function allow() {
252
- process.exit(0);
257
+ deny(undefined, `GSD windsurf pre_run_command guard: ${reason}\n`);
253
258
  }
254
259
 
255
260
  let input = '';
256
- const stdinTimeout = setTimeout(() => process.exit(0), 10000);
261
+ const stdinTimeout = setTimeout(() => allow(undefined), 10000);
257
262
  process.stdin.setEncoding('utf8');
258
263
  process.stdin.on('data', (chunk) => { input += chunk; });
259
264
  process.stdin.on('end', () => {
@@ -262,14 +267,14 @@ process.stdin.on('end', () => {
262
267
  const data = JSON.parse(input || '{}');
263
268
  const toolInfo = (data && typeof data.tool_info === 'object' && data.tool_info) || {};
264
269
  const commandLine = typeof toolInfo.command_line === 'string' ? toolInfo.command_line : '';
265
- if (!commandLine) { allow(); return; }
266
- if (commandLine.length > MAX_COMMAND_LENGTH) { allow(); return; }
270
+ if (!commandLine) { allow(undefined); return; }
271
+ if (commandLine.length > MAX_COMMAND_LENGTH) { allow(undefined); return; }
267
272
 
268
273
  const reason = destructiveReason(commandLine);
269
274
  if (reason) { block(reason); return; }
270
- allow();
275
+ allow(undefined);
271
276
  } catch {
272
277
  // Silent fail-open — never block a valid tool call due to a hook bug.
273
- allow();
278
+ allow(undefined);
274
279
  }
275
280
  });
@@ -30,6 +30,13 @@
30
30
  const fs = require('fs');
31
31
  const path = require('path');
32
32
  const { spawnSync } = require('child_process');
33
+ const { allow, deny } = require('./lib/hook-exit.js');
34
+ const { reportIfUndetermined } = require('./lib/git-probe.js');
35
+
36
+ // #3911 (ADR-3889 Phase 7): the exit(2) call site (block(), below) is
37
+ // migrated to hook-exit.js's deny(undefined, reason) — see
38
+ // gsd-windsurf-pre-command.js's identical note for the fixed defect
39
+ // (terminateNow's fd 1/fd 2 writes now run in independent try/catch blocks).
33
40
 
34
41
  const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000, windowsHide: true };
35
42
 
@@ -54,16 +61,11 @@ function nearestExistingDir(start) {
54
61
  }
55
62
 
56
63
  function block(reason) {
57
- process.stderr.write(`GSD windsurf pre_write_code guard: ${reason}\n`);
58
- process.exit(2);
59
- }
60
-
61
- function allow() {
62
- process.exit(0);
64
+ deny(undefined, `GSD windsurf pre_write_code guard: ${reason}\n`);
63
65
  }
64
66
 
65
67
  let input = '';
66
- const stdinTimeout = setTimeout(() => process.exit(0), 10000);
68
+ const stdinTimeout = setTimeout(() => allow(undefined), 10000);
67
69
  process.stdin.setEncoding('utf8');
68
70
  process.stdin.on('data', (chunk) => { input += chunk; });
69
71
  process.stdin.on('end', () => {
@@ -72,14 +74,19 @@ process.stdin.on('end', () => {
72
74
  const data = JSON.parse(input || '{}');
73
75
  const toolInfo = (data && typeof data.tool_info === 'object' && data.tool_info) || {};
74
76
  const rawFilePath = typeof toolInfo.file_path === 'string' ? toolInfo.file_path : '';
75
- if (!rawFilePath) { allow(); return; }
77
+ if (!rawFilePath) { allow(undefined); return; }
76
78
 
77
79
  const cwd = process.cwd();
78
80
 
79
81
  // Determine the active project's git root. No git root at all -> nothing
80
82
  // to enforce a boundary against -> fail open.
81
83
  const cwdTopResult = git(['rev-parse', '--show-toplevel'], cwd);
82
- if (cwdTopResult.status !== 0 || !cwdTopResult.stdout) { allow(); return; }
84
+ // #3911: a timeout/spawn-failure result is indistinguishable from a clean
85
+ // "no git root here" answer by status/stdout alone — reportIfUndetermined
86
+ // is a no-op on a genuine negative and only fires when the probe itself
87
+ // could not run. The allow() below is UNCHANGED either way.
88
+ reportIfUndetermined('gsd-windsurf-pre-write', 'git rev-parse --show-toplevel (cwd)', cwdTopResult);
89
+ if (cwdTopResult.status !== 0 || !cwdTopResult.stdout) { allow(undefined); return; }
83
90
  const cwdTopRaw = cwdTopResult.stdout.trim();
84
91
 
85
92
  const filePath = path.isAbsolute(rawFilePath) ? path.resolve(rawFilePath) : path.resolve(cwd, rawFilePath);
@@ -95,14 +102,16 @@ process.stdin.on('end', () => {
95
102
  }
96
103
  })(),
97
104
  );
98
- if (!checkDir) { allow(); return; } // synthetic path with no existing ancestor — fail open
105
+ if (!checkDir) { allow(undefined); return; } // synthetic path with no existing ancestor — fail open
99
106
 
100
107
  const fileTopResult = git(['rev-parse', '--show-toplevel'], checkDir);
108
+ reportIfUndetermined('gsd-windsurf-pre-write', 'git rev-parse --show-toplevel (file location)', fileTopResult);
101
109
  if (fileTopResult.status !== 0 || !fileTopResult.stdout) {
102
110
  // Not inside any git worktree. Distinguish "inside a .git/ internals
103
111
  // directory" (dangerous — BLOCK) from "outside all git repos entirely"
104
112
  // (not the escape vector this guard targets — fail open).
105
113
  const insideGitDir = git(['rev-parse', '--is-inside-git-dir'], checkDir);
114
+ reportIfUndetermined('gsd-windsurf-pre-write', 'git rev-parse --is-inside-git-dir', insideGitDir);
106
115
  if (insideGitDir.status === 0 && insideGitDir.stdout && insideGitDir.stdout.trim() === 'true') {
107
116
  block(
108
117
  `'${filePath}' is inside a git internal (.git) directory, not the active project at ` +
@@ -111,12 +120,12 @@ process.stdin.on('end', () => {
111
120
  );
112
121
  return;
113
122
  }
114
- allow();
123
+ allow(undefined);
115
124
  return;
116
125
  }
117
126
 
118
127
  const fileTopRaw = fileTopResult.stdout.trim();
119
- if (fileTopRaw === cwdTopRaw) { allow(); return; }
128
+ if (fileTopRaw === cwdTopRaw) { allow(undefined); return; }
120
129
 
121
130
  // BLOCK: file resolves to a different git root than the active project.
122
131
  block(
@@ -127,6 +136,6 @@ process.stdin.on('end', () => {
127
136
  );
128
137
  } catch {
129
138
  // Silent fail-open — never block a valid tool call due to a hook bug.
130
- allow();
139
+ allow(undefined);
131
140
  }
132
141
  });
@@ -4,9 +4,15 @@
4
4
  // Detects when Claude attempts file edits outside a GSD workflow context
5
5
  // (no active /gsd- skill or Task subagent) and injects an advisory warning.
6
6
  //
7
- // This is a SOFT guard — it advises, not blocks. The edit still proceeds.
8
- // The warning nudges Claude to use /gsd:quick or /gsd:fast instead of
9
- // making direct edits that bypass state tracking.
7
+ // This is a SOFT guard for edits — it advises, not blocks. The edit still
8
+ // proceeds. The warning nudges Claude to use /gsd:quick or /gsd:fast instead
9
+ // of making direct edits that bypass state tracking.
10
+ //
11
+ // ONE hard block lives here: `git add -f` on an agent/worktree-agent branch
12
+ // (WORKTREE_AGENT_FORCE_ADD_FORBIDDEN) — and that block leg fails CLOSED on
13
+ // internal error (#3504): when the guard is enabled and the blocking context
14
+ // holds, a thrown error exits 2 (block), not 0. The advisory legs keep the
15
+ // fail-open posture — a broken advisory must never wedge every tool call.
10
16
  //
11
17
  // Enable via config: hooks.workflow_guard: true (default: false)
12
18
  // Only triggers on Write/Edit tool calls to non-.planning/ files.
@@ -14,40 +20,58 @@
14
20
  const fs = require('fs');
15
21
  const path = require('path');
16
22
  const { spawnSync } = require('child_process');
17
- const { tokenize } = require('./lib/git-cmd.js');
23
+ const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
24
+ const { HOOK_ON_CRASH, allow, deny, crash } = require('./lib/hook-exit.js');
25
+ const { reportIfUndetermined } = require('./lib/git-probe.js');
26
+
27
+ // This guard is almost entirely advisory (fail open — a broken advisory must
28
+ // never wedge every tool call), with ONE hard block (#3504 force-add-on-
29
+ // agent-branch) that fails CLOSED on internal error instead. That split is
30
+ // re-derived dynamically inside the outer catch (failClosedBlockContext), not
31
+ // a fixed per-hook policy, so ON_CRASH names only the FINAL, unconditional
32
+ // fallback reached when the fail-closed context does not apply — i.e. the
33
+ // historical exit(0). Declared ONCE here so that fallback states its policy
34
+ // explicitly rather than inheriting a default (#3911).
35
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
18
36
 
19
37
  function forceGitAddCwds(command, defaultCwd) {
20
38
  const tokens = tokenize(command || '');
21
39
  const separators = new Set(['&&', '||', ';', '|']);
22
40
  const cwdList = [];
23
- for (let i = 0; i < tokens.length; i++) {
24
- if (path.basename(tokens[i]) !== 'git') continue;
41
+ // #3504: per-segment walk driven by git-cmd.js's canonical skipToSubcommand.
42
+ // The previous inline walk knew six global flags and silently missed the
43
+ // rest — `git -c core.hooksPath=/tmp/x add -f x`, `git --no-optional-locks
44
+ // add -f x`, `--literal-pathspecs`, `--namespace=…` and friends all fell
45
+ // through to a silent exit 0, a no-crash bypass the fail-closed catch is
46
+ // structurally blind to (it only fires on throws). Sharing the classifier
47
+ // makes the block's flag knowledge exactly the classifier's.
48
+ const segments = [];
49
+ let start = 0;
50
+ for (let i = 0; i <= tokens.length; i++) {
51
+ if (i === tokens.length || separators.has(tokens[i])) {
52
+ if (i > start) segments.push(tokens.slice(start, i));
53
+ start = i + 1;
54
+ }
55
+ }
56
+ for (const seg of segments) {
57
+ const subIdx = skipToSubcommand(seg);
58
+ if (subIdx === -1 || subIdx >= seg.length || seg[subIdx] !== 'add') continue;
25
59
 
26
- let j = i + 1;
60
+ // Resolve a `-C <dir>` (separate-arg form) preceding the subcommand so
61
+ // the branch is probed at the repository the add targets.
27
62
  let gitCwd = defaultCwd;
28
- while (j < tokens.length) {
29
- const token = tokens[j];
30
- const flagName = token.includes('=') ? token.slice(0, token.indexOf('=')) : token;
31
- if (token === '-C' && tokens[j + 1]) {
32
- gitCwd = path.resolve(gitCwd, tokens[j + 1]);
33
- j += 2;
34
- continue;
35
- }
36
- if (['-C', '--git-dir', '--work-tree'].includes(flagName) && !token.includes('=')) {
37
- j += 2;
38
- continue;
39
- }
40
- if (['--git-dir', '--work-tree', '--no-pager', '-p', '-P'].includes(flagName)) {
41
- j++;
63
+ for (let k = 0; k < subIdx; ) {
64
+ if (seg[k] === '-C' && k + 1 < subIdx) {
65
+ gitCwd = path.resolve(gitCwd, seg[k + 1]);
66
+ k += 2;
42
67
  continue;
43
68
  }
44
- break;
69
+ k++;
45
70
  }
46
71
 
47
- if (tokens[j] !== 'add') continue;
48
- for (let k = j + 1; k < tokens.length && !separators.has(tokens[k]); k++) {
49
- if (tokens[k] === '--') break;
50
- if (tokens[k] === '--force' || tokens[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(tokens[k])) {
72
+ for (let k = subIdx + 1; k < seg.length; k++) {
73
+ if (seg[k] === '--') break;
74
+ if (seg[k] === '--force' || seg[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(seg[k])) {
51
75
  cwdList.push(gitCwd);
52
76
  break;
53
77
  }
@@ -62,11 +86,93 @@ function currentBranch(cwd) {
62
86
  encoding: 'utf8',
63
87
  stdio: ['ignore', 'pipe', 'ignore'],
64
88
  windowsHide: true,
89
+ // #3504: bounded — this ran unbounded before, an indefinite hang under a
90
+ // wedged git would hang every PreToolUse call. Host wiring allows a 5s
91
+ // budget for the whole hook, so the probe gets 2s of it; a timeout
92
+ // returns '' (branch unknown), which both the block decision and the
93
+ // fail-closed re-check treat as "cannot establish agent branch".
94
+ timeout: 2000,
95
+ killSignal: 'SIGTERM',
65
96
  });
97
+ // #3911: a timeout/spawn-failure here previously degraded to '' exactly
98
+ // like a clean "not on a branch" answer — indistinguishable from the
99
+ // caller's point of view. reportIfUndetermined is a no-op on a genuine
100
+ // negative and only emits a diagnostic when the probe itself could not
101
+ // run; the '' fallback (and therefore this hook's exit code) is unchanged.
102
+ reportIfUndetermined('gsd-workflow-guard', 'git branch --show-current', result);
66
103
  if (result.status !== 0) return '';
67
104
  return result.stdout.trim();
68
105
  }
69
106
 
107
+ // Agent-branch predicate, shared by the happy-path detector and the fail-closed
108
+ // re-check so the block's scope cannot drift between the two paths (#3504).
109
+ function isAgentBranch(branch) {
110
+ return /^(worktree-)?agent-/.test(branch);
111
+ }
112
+
113
+ // Typed cwd read, shared by both paths for the same reason: a non-string cwd
114
+ // degrades to process.cwd() instead of throwing inside path.join().
115
+ function payloadCwd(data) {
116
+ return typeof data?.cwd === 'string' && data.cwd ? data.cwd : process.cwd();
117
+ }
118
+
119
+ // The force-add block payload, emitted by the happy-path detector and the
120
+ // fail-closed catch — one source so the two exits cannot drift. `origin`
121
+ // distinguishes them structurally ('force-add-detected' vs 'fail-closed') so a
122
+ // consumer — or the model reading Kimi's stderr feedback — is never told a
123
+ // force-add was detected when the call was in fact blocked unanalyzed.
124
+ function emitForceAddBlock(origin) {
125
+ const failClosed = origin === 'fail-closed';
126
+ const reason = failClosed
127
+ ? 'workflow guard internal error on an agent branch - failing closed. The command was NOT analyzed and was NOT confirmed to be a force-add. Retry the call or inspect the guard.'
128
+ : 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.';
129
+ const output = {
130
+ decision: 'block',
131
+ code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
132
+ origin: failClosed ? 'fail-closed' : 'force-add-detected',
133
+ reason,
134
+ };
135
+ // Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304) — deny's
136
+ // stderrPayload keeps fd 2 to the plain reason string, matching the
137
+ // pre-migration two-write byte-for-byte.
138
+ deny(output, output.reason);
139
+ }
140
+
141
+ // #3504 fail-closed context re-derivation. Runs INSIDE the outer catch, after
142
+ // an internal error, and answers exactly one question: does the blocking
143
+ // context of the force-add guard hold for this payload? Each stage is guarded —
144
+ // a re-derivation that itself throws must degrade to "cannot establish" (false),
145
+ // never take down the catch. Deliberately does NOT re-detect the force-add: the
146
+ // error may live in the detector itself, and on an agent branch with the guard
147
+ // enabled, a Bash call under an internal error is conservative-correct to block.
148
+ // Anything it cannot establish (unparseable payload, non-Bash tool, guard
149
+ // disabled, branch not determinably agent-*) fails open, preserving the
150
+ // advisory legs' fail-open posture.
151
+ function failClosedBlockContext(rawInput) {
152
+ let data;
153
+ try {
154
+ data = normalizeKimiPayload(JSON.parse(rawInput));
155
+ } catch {
156
+ return false;
157
+ }
158
+ if (data === null || typeof data !== 'object' || data.tool_name !== 'Bash') return false;
159
+ const cwd = payloadCwd(data);
160
+ let enabled;
161
+ try {
162
+ enabled = workflowGuardEnabled(cwd);
163
+ } catch {
164
+ return false;
165
+ }
166
+ if (!enabled) return false;
167
+ let branch;
168
+ try {
169
+ branch = currentBranch(cwd);
170
+ } catch {
171
+ return false;
172
+ }
173
+ return isAgentBranch(branch);
174
+ }
175
+
70
176
  function workflowGuardEnabled(cwd) {
71
177
  const configPath = path.join(cwd, '.planning', 'config.json');
72
178
  if (!fs.existsSync(configPath)) return false;
@@ -173,49 +279,50 @@ function normalizeKimiPayload(data) {
173
279
  }
174
280
 
175
281
  let input = '';
176
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
282
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
177
283
  process.stdin.setEncoding('utf8');
178
284
  process.stdin.on('data', chunk => input += chunk);
179
285
  process.stdin.on('end', () => {
180
286
  clearTimeout(stdinTimeout);
181
287
  try {
182
288
  const data = normalizeKimiPayload(JSON.parse(input));
289
+ // #3504 test-only fault seam: throws right after parse so the fail-closed
290
+ // posture of the outer catch is exercisable — no JSON-expressible input
291
+ // throws in this handler today (#2547/#2595 hardened every read). Gated on
292
+ // GSD_TEST_MODE as well so a leaked GSD_TEST_WORKFLOW_GUARD_FAULT in a real
293
+ // shell cannot wedge a production session. The fault's failure direction
294
+ // is CLOSED: the catch below may block, never bypass a block.
295
+ if (process.env.GSD_TEST_MODE === '1' && process.env.GSD_TEST_WORKFLOW_GUARD_FAULT === '1') {
296
+ throw new Error('GSD_TEST_WORKFLOW_GUARD_FAULT: injected fault');
297
+ }
183
298
  const toolName = data.tool_name;
184
299
  const cwd = data.cwd || process.cwd();
185
300
  const isWorkflowGuardEnabled = workflowGuardEnabled(cwd);
186
301
 
187
302
  if (toolName === 'Bash') {
188
303
  if (!isWorkflowGuardEnabled) {
189
- process.exit(0);
304
+ allow(undefined);
190
305
  }
191
306
  const command = data.tool_input?.command || '';
192
307
  for (const gitCwd of forceGitAddCwds(command, cwd)) {
193
308
  const branch = currentBranch(gitCwd);
194
309
  if (/^(worktree-)?agent-/.test(branch)) {
195
- const output = {
196
- decision: 'block',
197
- code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
198
- reason: 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.',
199
- };
200
- process.stdout.write(JSON.stringify(output));
201
- // Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304)
202
- process.stderr.write(output.reason);
203
- process.exit(2);
310
+ emitForceAddBlock();
204
311
  }
205
312
  }
206
- process.exit(0);
313
+ allow(undefined);
207
314
  }
208
315
 
209
316
  // Only guard Write, Edit, and MultiEdit tool calls
210
317
  if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
211
- process.exit(0);
318
+ allow(undefined);
212
319
  }
213
320
 
214
321
  // Check if we're inside a GSD workflow (Task subagent or /gsd- skill)
215
322
  // Subagents have a session_id that differs from the parent
216
323
  // and typically have a description field set by the orchestrator
217
324
  if (data.tool_input?.is_subagent || data.session_type === 'task') {
218
- process.exit(0);
325
+ allow(undefined);
219
326
  }
220
327
 
221
328
  // Check the file being edited
@@ -230,7 +337,7 @@ process.stdin.on('end', () => {
230
337
 
231
338
  // Allow edits to .planning/ files (GSD state management)
232
339
  if (filePath.includes('.planning/') || filePath.includes('.planning\\')) {
233
- process.exit(0);
340
+ allow(undefined);
234
341
  }
235
342
 
236
343
  // Allow edits to common config/docs files that don't need GSD tracking
@@ -243,11 +350,11 @@ process.stdin.on('end', () => {
243
350
  /settings\.json$/,
244
351
  ];
245
352
  if (allowedPatterns.some(p => p.test(filePath))) {
246
- process.exit(0);
353
+ allow(undefined);
247
354
  }
248
355
 
249
356
  if (!isWorkflowGuardEnabled) {
250
- process.exit(0); // Guard disabled (default) or no GSD project
357
+ allow(undefined); // Guard disabled (default) or no GSD project
251
358
  }
252
359
 
253
360
  // If we get here: GSD project, guard enabled, file edit outside .planning/,
@@ -264,8 +371,17 @@ process.stdin.on('end', () => {
264
371
  };
265
372
 
266
373
  process.stdout.write(JSON.stringify(output));
267
- } catch (e) {
268
- // Silent fail — never block tool execution
269
- process.exit(0);
374
+ } catch {
375
+ // #3504: split posture on internal error. The ONE hard block in this hook
376
+ // (force-add on agent branches) fails CLOSED — if the blocking context can
377
+ // be re-derived from the payload (Bash tool + guard enabled + determinably
378
+ // an agent branch), deny rather than silently allowing. Everything else
379
+ // keeps the historical fail-open posture: a broken advisory guard must
380
+ // never wedge the session's tool calls. ON_CRASH is declared ALLOW at
381
+ // module top for exactly that unconditional fallback (#3911).
382
+ if (failClosedBlockContext(input)) {
383
+ emitForceAddBlock('fail-closed');
384
+ }
385
+ crash(ON_CRASH, undefined);
270
386
  }
271
387
  });