@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
@@ -23,6 +23,13 @@
23
23
 
24
24
  const path = require('path');
25
25
  const fs = require('fs');
26
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
27
+
28
+ // This is a PostToolUse advisory scanner over content the tool call already
29
+ // returned; a crash while scanning must not retroactively block that Read/
30
+ // WebFetch/WebSearch result from reaching the agent — losing the injection
31
+ // check is safer than failing the tool call it is only observing (#3911).
32
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
26
33
 
27
34
  // Summarisation-specific patterns (novel — not in gsd-prompt-guard.js).
28
35
  // These target instructions specifically designed to survive context compression.
@@ -72,23 +79,10 @@ const MARKDOWN_LINK_PATTERNS = [
72
79
  },
73
80
  ];
74
81
 
75
- // Standard injection patterns — mirrors gsd-prompt-guard.js, inlined for hook independence.
76
- const INJECTION_PATTERNS = [
77
- /ignore\s+(all\s+)?previous\s+instructions/i,
78
- /ignore\s+(all\s+)?above\s+instructions/i,
79
- /disregard\s+(all\s+)?previous/i,
80
- /forget\s+(all\s+)?(your\s+)?instructions/i,
81
- /override\s+(system|previous)\s+(prompt|instructions)/i,
82
- /you\s+are\s+now\s+(?:a|an|the)\s+/i,
83
- /act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
84
- /pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
85
- /from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
86
- /(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
87
- /<\/?(?:system|assistant|human)>/i,
88
- /\[SYSTEM\]/i,
89
- /\[INST\]/i,
90
- /<<\s*SYS\s*>>/i,
91
- ];
82
+ // Standard injection patterns — shared with gsd-prompt-guard.js via
83
+ // hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
84
+ // Staging of the lib helper is allowlisted in GSD_HOOK_LIB_FILES (bin/install.js).
85
+ const { INJECTION_PATTERNS } = require('./lib/injection-patterns.js');
92
86
 
93
87
  const ALL_PATTERNS = [...INJECTION_PATTERNS, ...SUMMARISATION_PATTERNS];
94
88
 
@@ -98,6 +92,16 @@ const ALL_PATTERNS = [...INJECTION_PATTERNS, ...SUMMARISATION_PATTERNS];
98
92
  // construction. Normalized to forward slashes to match `p` below.
99
93
  const OWN_BUNDLE_PREFIX = __dirname.replace(/\\/g, '/').replace(/\/+$/, '') + '/';
100
94
 
95
+ // Synthetic rule ids for the finding classes that have no entry in
96
+ // MARKDOWN_LINK_PATTERNS. Frozen and referenced from BOTH the push sites and
97
+ // renderFinding so the two can never drift — a bare literal repeated at each
98
+ // site is how a rename silently falls through to the generic render branch.
99
+ const RULE_IDS = Object.freeze({
100
+ INJECTION_PATTERN: 'INJECTION-PATTERN',
101
+ INVISIBLE_UNICODE: 'INVISIBLE-UNICODE',
102
+ UNICODE_TAG_BLOCK: 'UNICODE-TAG-BLOCK',
103
+ });
104
+
101
105
  function isExcludedPath(filePath) {
102
106
  const p = filePath.replace(/\\/g, '/');
103
107
  return (
@@ -216,7 +220,7 @@ function normalizeKimiPayload(data) {
216
220
  }
217
221
 
218
222
  let inputBuf = '';
219
- const stdinTimeout = setTimeout(() => process.exit(0), 5000);
223
+ const stdinTimeout = setTimeout(() => allow(undefined), 5000);
220
224
  process.stdin.setEncoding('utf8');
221
225
  process.stdin.on('data', chunk => { inputBuf += chunk; });
222
226
  process.stdin.on('end', () => {
@@ -227,7 +231,7 @@ process.stdin.on('end', () => {
227
231
  const toolName = data.tool_name;
228
232
  const SCANNED_TOOLS = new Set(['Read', 'WebFetch', 'WebSearch']);
229
233
  if (!SCANNED_TOOLS.has(toolName)) {
230
- process.exit(0);
234
+ allow(undefined);
231
235
  }
232
236
 
233
237
  // Source label + path-exclusion (path-exclusion applies to file reads only)
@@ -238,8 +242,8 @@ process.stdin.on('end', () => {
238
242
  source = typeof data.tool_input?.file_path === 'string'
239
243
  ? data.tool_input.file_path
240
244
  : '';
241
- if (!source) process.exit(0);
242
- if (isExcludedPath(source)) process.exit(0);
245
+ if (!source) allow(undefined);
246
+ if (isExcludedPath(source)) allow(undefined);
243
247
  } else if (toolName === 'WebFetch') {
244
248
  source = data.tool_input?.url || 'web';
245
249
  } else { // WebSearch
@@ -264,15 +268,22 @@ process.stdin.on('end', () => {
264
268
  }
265
269
 
266
270
  if (!content || content.length < 20) {
267
- process.exit(0);
271
+ allow(undefined);
268
272
  }
269
273
 
274
+ // Typed findings IR — single source of truth for both the machine-readable
275
+ // `findings` array and the rendered advisory prose. Never build these as two
276
+ // parallel arrays: that invites the generative-fix-divergence defect class
277
+ // where the rendered text and the structured data silently drift apart.
270
278
  const findings = [];
271
279
 
272
280
  for (const pattern of ALL_PATTERNS) {
273
281
  if (pattern.test(content)) {
274
282
  // Trim pattern source for readable output
275
- findings.push(pattern.source.replace(/\\s\+/g, '-').replace(/[()\\]/g, '').substring(0, 50));
283
+ findings.push({
284
+ ruleId: RULE_IDS.INJECTION_PATTERN,
285
+ match: pattern.source.replace(/\\s\+/g, '-').replace(/[()\\]/g, '').substring(0, 50),
286
+ });
276
287
  }
277
288
  }
278
289
 
@@ -284,26 +295,36 @@ process.stdin.on('end', () => {
284
295
  const m = line.match(entry.pattern);
285
296
  if (!m) continue;
286
297
  if (entry.safePredicate && entry.safePredicate(line)) continue;
287
- findings.push(`${entry.ruleId}:${m[0].substring(0, 40)}`);
298
+ findings.push({ ruleId: entry.ruleId, match: m[0].substring(0, 40) });
288
299
  }
289
300
  }
290
301
 
291
302
  // Invisible Unicode (zero-width, RTL override, soft hyphen, BOM)
292
303
  if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD\u2060-\u2069]/.test(content)) {
293
- findings.push('invisible-unicode');
304
+ findings.push({ ruleId: RULE_IDS.INVISIBLE_UNICODE, match: null });
294
305
  }
295
306
 
296
307
  // Unicode tag block U+E0000–E007F (invisible instruction injection vector)
297
308
  try {
298
309
  if (/[\u{E0000}-\u{E007F}]/u.test(content)) {
299
- findings.push('unicode-tag-block');
310
+ findings.push({ ruleId: RULE_IDS.UNICODE_TAG_BLOCK, match: null });
300
311
  }
301
312
  } catch {
302
313
  // Engine does not support Unicode property escapes — skip this check
303
314
  }
304
315
 
305
316
  if (findings.length === 0) {
306
- process.exit(0);
317
+ allow(undefined);
318
+ }
319
+
320
+ // Renders one finding back into the exact prose fragment the advisory has
321
+ // always embedded. Kept as the ONLY place that maps IR -> text, so the
322
+ // `additionalContext` string and the `findings` array can never diverge.
323
+ function renderFinding(f) {
324
+ if (f.ruleId === RULE_IDS.INVISIBLE_UNICODE) return 'invisible-unicode';
325
+ if (f.ruleId === RULE_IDS.UNICODE_TAG_BLOCK) return 'unicode-tag-block';
326
+ if (f.ruleId === RULE_IDS.INJECTION_PATTERN) return f.match;
327
+ return `${f.ruleId}:${f.match}`;
307
328
  }
308
329
 
309
330
  const severity = findings.length >= 3 ? 'HIGH' : 'LOW';
@@ -313,7 +334,7 @@ process.stdin.on('end', () => {
313
334
  : 'Single pattern match may be a false positive (e.g., documentation). Proceed with awareness.';
314
335
  const advisory =
315
336
  `\u26a0\ufe0f INJECTION SCAN [${severity}] (${toolName}): "${label}" triggered ` +
316
- `${findings.length} pattern(s): ${findings.join(', ')}. ` +
337
+ `${findings.length} pattern(s): ${findings.map(renderFinding).join(', ')}. ` +
317
338
  `This content is now in your conversation context. ${detail} Source: ${source}`;
318
339
 
319
340
  // Opt-in blocking: only when configured AND high-confidence
@@ -330,12 +351,14 @@ process.stdin.on('end', () => {
330
351
  const output = blocking
331
352
  ? { decision: 'block',
332
353
  reason: `Prompt-injection blocked (${toolName}). ${advisory}`,
333
- hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } }
334
- : { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } };
354
+ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } }
355
+ : { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } };
335
356
 
336
357
  process.stdout.write(JSON.stringify(output));
337
358
  } catch {
338
- // Silent fail — never block tool execution
339
- process.exit(0);
359
+ // Silent fail — never block tool execution.
360
+ // ON_CRASH is declared ALLOW at module top: this preserves today's
361
+ // exit(0) fail-open behavior exactly (#3911).
362
+ crash(ON_CRASH, undefined);
340
363
  }
341
364
  });
@@ -5,6 +5,7 @@
5
5
  #
6
6
  # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
7
7
  # Enable with: "hooks": { "community": true } in .planning/config.json
8
+ set -euo pipefail
8
9
 
9
10
  # Check opt-in config — exit silently if not enabled
10
11
  if [ -f .planning/config.json ]; then
@@ -9,6 +9,36 @@ const os = require('os');
9
9
  // Namespace (not destructured) so tests can inject spawn failures by
10
10
  // monkeypatching childProcess.execFileSync.
11
11
  const childProcess = require('child_process');
12
+ const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
13
+
14
+ // This hook's build-seam outer catch (require.main guard just below) has
15
+ // always exited 0 (fail open — the statusline renders on EVERY prompt, so a
16
+ // build failure must degrade to a blank line rather than break Claude Code's
17
+ // per-render hook). Declared ONCE so that catch's crash() call states its
18
+ // policy explicitly rather than inheriting a default (#3911).
19
+ const ON_CRASH = HOOK_ON_CRASH.ALLOW;
20
+ // #3582: gsd-core/bin/lib/*.cjs (semver-compare.cjs, state-document.cjs,
21
+ // active-workstream-store.cjs, planning-workspace.cjs — required below) and
22
+ // package-identity.cjs are tsc build artifacts (ADR-457), gitignored and
23
+ // absent on a raw plugin-marketplace / git-clone install that never ran
24
+ // `npm run build:lib`. The statusline renders on EVERY prompt, so a build
25
+ // failure here must DEGRADE (print nothing, exit 0) rather than crash
26
+ // Claude Code's per-render statusline hook. Scoped to the spawned-as-a-script
27
+ // path (`require.main === module`) — a test `require()` of this module for
28
+ // its pure helpers assumes a built tree, same as every other hook test.
29
+ if (require.main === module) {
30
+ try {
31
+ const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
32
+ ensureRuntimeBuild();
33
+ } catch (e) {
34
+ // #3911: crash(ON_CRASH, ...) with an undefined payload preserves the
35
+ // pre-migration `process.stdout.write(''); process.exit(0);` byte-for-
36
+ // byte — undefined makes terminateNow's stdout JSON.stringify throw
37
+ // internally (swallowed there), so fd 1 stays untouched, same as writing
38
+ // an explicit empty string did.
39
+ crash(ON_CRASH, undefined);
40
+ }
41
+ }
12
42
  const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
13
43
  const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
14
44
  const { normalizeStateStatus } = require('../gsd-core/bin/lib/state-document.cjs');
@@ -151,13 +181,27 @@ function readStateFileOrNull(statePath) {
151
181
  * - null when no .planning marker is found at all (GSD not present), or
152
182
  * when a workstream DOES resolve but its STATE.md doesn't exist yet
153
183
  * (negative space: mirrors flat-mode's own silent pre-STATE.md window)
184
+ *
185
+ * @param {string} dir
186
+ * @param {{ stateFreshness?: boolean }} [opts] — #2734, additive/default-off.
187
+ * When true and the resolved state carries a truthy `.stateHead`, attaches
188
+ * `state.freshness` (deriveStateFreshness) before returning — `current` at
189
+ * that point is the project root the walk resolved, which is what AC-4's
190
+ * repo-pinning check needs. Never derived when false or the stamp is
191
+ * absent, so existing callers (default opts) spend zero extra spawns.
154
192
  */
155
- function readGsdState(dir) {
193
+ function readGsdState(dir, opts = {}) {
194
+ const { stateFreshness = false } = opts;
156
195
  const home = os.homedir();
157
196
  let current = dir;
158
197
  for (let i = 0; i < 10; i++) {
159
198
  const flatState = readStateFileOrNull(path.join(current, '.planning', 'STATE.md'));
160
- if (flatState !== null) return flatState;
199
+ if (flatState !== null) {
200
+ if (stateFreshness && flatState.stateHead) {
201
+ flatState.freshness = deriveStateFreshness(current, flatState.stateHead);
202
+ }
203
+ return flatState;
204
+ }
161
205
 
162
206
  if (listAvailableWorkstreams(current).length > 0) {
163
207
  let resolvedWs = null;
@@ -169,7 +213,11 @@ function readGsdState(dir) {
169
213
 
170
214
  if (!resolvedWs) return { noActiveWorkstream: true };
171
215
 
172
- return readStateFileOrNull(planningPaths(current, resolvedWs).state);
216
+ const wsState = readStateFileOrNull(planningPaths(current, resolvedWs).state);
217
+ if (wsState !== null && stateFreshness && wsState.stateHead) {
218
+ wsState.freshness = deriveStateFreshness(current, wsState.stateHead);
219
+ }
220
+ return wsState;
173
221
  }
174
222
 
175
223
  const parent = path.dirname(current);
@@ -220,6 +268,10 @@ function parseStateMd(content) {
220
268
  if (key === 'active_phase') state.activePhase = (v === 'null' || v === '') ? null : v;
221
269
  // next_action: recommended command when idle (discuss-phase / plan-phase / execute-phase / verify-phase)
222
270
  if (key === 'next_action') state.nextAction = (v === 'null' || v === '') ? null : v;
271
+ // #2734: state_head — the commit STATE.md was written against, consumed
272
+ // by deriveStateFreshness() below. Mirrors active_phase/next_action's
273
+ // null/empty handling exactly.
274
+ if (key === 'state_head') state.stateHead = (v === 'null' || v === '') ? null : v;
223
275
  }
224
276
  // next_phases supports both flow array and block-list YAML forms.
225
277
  const npFlowMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
@@ -353,6 +405,10 @@ function formatGsdState(s) {
353
405
  }
354
406
  }
355
407
 
408
+ // #2734: STATE.md freshness marker — opt-in, appended last.
409
+ const fresh = formatStateFreshness(s.freshness);
410
+ if (fresh) parts.push(fresh);
411
+
356
412
  return parts.join(' · ');
357
413
  }
358
414
 
@@ -458,6 +514,10 @@ function formatGsdStateCompact(s) {
458
514
  }
459
515
  }
460
516
 
517
+ // #2734: STATE.md freshness marker \u2014 opt-in, appended last.
518
+ const fresh = formatStateFreshness(s.freshness);
519
+ if (fresh) parts.push(fresh);
520
+
461
521
  return parts.join(' \u00b7 ');
462
522
  }
463
523
 
@@ -554,13 +614,153 @@ function buildGitSegment(info) {
554
614
  return ` │ \x1b[2m${info.branch}\x1b[0m${state}`;
555
615
  }
556
616
 
617
+ // --- STATE.md freshness marker (opt-in, #2734) --------------------------------
618
+ //
619
+ // Opt-in via `statusline.show_state_freshness: true`. Renders `state ~N
620
+ // commits back` inside the GSD-state segment when STATE.md's `state_head`
621
+ // stamp (#2573) is at least STATE_HEAD_ADVISORY_COMMITS commits behind HEAD.
622
+ // Same impure-reader -> pure-IR -> pure-formatter shape as the git segment
623
+ // above. See .gsd/phase/feat-2734-statusline-state-freshness/40-design.md.
624
+
625
+ // Deliberate mirror of the fence in src/state.cts (STATE_HEAD_HASH_RE) — kept
626
+ // hook-side rather than requiring state.cjs on the per-render path (measured
627
+ // ~20ms; see design doc "Laws that apply"). tests/gsd-statusline.test.cjs
628
+ // asserts behavioral parity against readStateHeadFreshness rather than
629
+ // comparing source (local/no-source-grep forbids the latter anyway).
630
+ const STATE_HEAD_HASH_RE = /^[0-9a-f]{4,40}$/i;
631
+
632
+ // Mirror of the constant verify.cts's W024 health check thresholds on
633
+ // (STATE_HEAD_ADVISORY_COMMITS). A test asserts equality with verify.cjs's
634
+ // export so the two copies can't drift.
635
+ const STATE_HEAD_ADVISORY_COMMITS = 20;
636
+
637
+ // Same bound class as GIT_STATUS_TIMEOUT_MS above.
638
+ const STATE_FRESHNESS_GIT_TIMEOUT_MS = 1500;
639
+
640
+ /**
641
+ * Pure function: does raw pass the state_head hash fence? Must run BEFORE any
642
+ * value from STATE.md reaches a git argv slot.
643
+ */
644
+ function isValidStateHeadStamp(raw) {
645
+ return typeof raw === 'string' && STATE_HEAD_HASH_RE.test(raw.trim());
646
+ }
647
+
648
+ /**
649
+ * Run `git rev-list --left-right --count <stamp>...HEAD` in root. Returns raw
650
+ * stdout, or null when git is missing, root isn't a repo, the stamp is
651
+ * unknown, or the call times out. Never throws. Only call with a stamp that
652
+ * already passed isValidStateHeadStamp/the hash fence above.
653
+ */
654
+ function readStateHeadCommits(root, stamp) {
655
+ try {
656
+ return childProcess.execFileSync('git',
657
+ ['-C', root, 'rev-list', '--left-right', '--count', `${stamp}...HEAD`],
658
+ { encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
659
+ stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
660
+ } catch (e) {
661
+ return null;
662
+ }
663
+ }
664
+
665
+ /**
666
+ * Pure function: parse `git rev-list --left-right --count A...B` output
667
+ * ("<left>\t<right>"). Returns { left, right } as non-negative integers, or
668
+ * null when text isn't a matching string (covers null, '', 'garbage', '1',
669
+ * 'a\tb', '\t', and any other unparseable shape).
670
+ */
671
+ function parseRevListCounts(text) {
672
+ if (typeof text !== 'string') return null;
673
+ const m = text.match(/^(\d+)\s+(\d+)\s*$/);
674
+ if (!m) return null;
675
+ return { left: parseInt(m[1], 10), right: parseInt(m[2], 10) };
676
+ }
677
+
678
+ /**
679
+ * Impure -> pure IR: derive the freshness signal for a recorded state_head
680
+ * stamp. Returns { state_head, commits_behind, commit_stale } — never throws,
681
+ * every unresolvable input degrades to the all-null-but-state_head shape.
682
+ *
683
+ * Order (each failure returns immediately, no further work):
684
+ * a. hash fence — malformed/absent stamp never reaches a spawn
685
+ * b. repo pinning — root must own its own .git (mirrors projectOwnsItsRepo
686
+ * in src/state.cts: a filesystem-identity check, not a --show-toplevel
687
+ * string compare, which is unreliable on macOS /private/var and Windows
688
+ * 8.3 paths). Costs no subprocess.
689
+ * c. sub_repos guard — a planning.sub_repos workspace's outer HEAD never
690
+ * advances when code lands in nested children, so a "fresh" answer here
691
+ * would be a confident lie. Costs no subprocess.
692
+ * d. one bounded git spawn: rev-list --left-right --count answers ancestry
693
+ * and distance together. left > 0 means the stamp is not an ancestor of
694
+ * HEAD (reset/rebase/force-push) -> unknown, never "fresh".
695
+ */
696
+ function deriveStateFreshness(root, stamp, deps = {}) {
697
+ const { existsSync = fs.existsSync, readConfig = readGsdConfig, readCounts = readStateHeadCommits } = deps;
698
+
699
+ const raw = typeof stamp === 'string' ? stamp.trim() : '';
700
+ const valid = STATE_HEAD_HASH_RE.test(raw);
701
+ const state_head = valid ? raw.slice(0, 7) : null;
702
+ const nullResult = { state_head, commits_behind: null, commit_stale: null };
703
+ if (!valid || !root) return nullResult;
704
+
705
+ try {
706
+ if (!existsSync(path.join(root, '.git'))) return nullResult;
707
+ } catch (e) {
708
+ return nullResult;
709
+ }
710
+
711
+ try {
712
+ const cfg = readConfig(root);
713
+ const sub = getConfigValue(cfg, 'planning.sub_repos') ?? getConfigValue(cfg, 'sub_repos');
714
+ if (Array.isArray(sub) && sub.length > 0) return nullResult;
715
+ } catch (e) {
716
+ return nullResult;
717
+ }
718
+
719
+ const counts = parseRevListCounts(readCounts(root, raw));
720
+ if (!counts || counts.left > 0) return nullResult;
721
+
722
+ return { state_head, commits_behind: counts.right, commit_stale: counts.right > 0 };
723
+ }
724
+
725
+ /**
726
+ * Pure function: format the freshness IR into the marker text, or '' below
727
+ * STATE_HEAD_ADVISORY_COMMITS (including when commits_behind is absent/null —
728
+ * the unknown case must never render, never mind alarm on it).
729
+ */
730
+ function formatStateFreshness(fresh) {
731
+ if (!fresh || typeof fresh.commits_behind !== 'number' || fresh.commits_behind < STATE_HEAD_ADVISORY_COMMITS) return '';
732
+ return `state ~${fresh.commits_behind} commits back`;
733
+ }
734
+
735
+ /**
736
+ * Pure function: single source of truth for statusline config resolution.
737
+ * `runStatusline()` and `renderStatusline()` previously read this config
738
+ * independently, which had drifted into a live divergence between the two
739
+ * entry points — this collapses both onto one resolver.
740
+ *
741
+ * @param {object} cfg — parsed .planning/config.json (readGsdConfig())
742
+ * @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean }}
743
+ */
744
+ function resolveStatuslineOptions(cfg) {
745
+ const showLastCommand = getConfigValue(cfg, 'statusline.show_last_command') === true;
746
+ const cfgPos = getConfigValue(cfg, 'statusline.context_position');
747
+ // Clamp any non-'front' value (including absent/null) to 'end' — the single
748
+ // source of truth for this default; composeStatusline's own coercion stays
749
+ // as belt-and-suspenders defense for direct callers.
750
+ const position = cfgPos === 'front' ? 'front' : 'end';
751
+ const stateFormat = getConfigValue(cfg, 'statusline.state_format') === 'compact' ? 'compact' : 'full';
752
+ const showGit = getConfigValue(cfg, 'statusline.show_git') === true;
753
+ const showStateFreshness = getConfigValue(cfg, 'statusline.show_state_freshness') === true;
754
+ return { showLastCommand, position, stateFormat, showGit, showStateFreshness };
755
+ }
756
+
557
757
  // --- stdin ------------------------------------------------------------------
558
758
 
559
759
  function runStatusline() {
560
760
  let input = '';
561
761
  // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
562
762
  // Windows/Git Bash), exit silently instead of hanging. See #775.
563
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
763
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
564
764
  process.stdin.setEncoding('utf8');
565
765
  process.stdin.on('data', chunk => input += chunk);
566
766
  process.stdin.on('end', () => {
@@ -699,31 +899,34 @@ function runStatusline() {
699
899
  // Last-slash-command suffix and context_position config (#2538, #2937).
700
900
  // Reads the active session transcript for the most recent <command-name> tag.
701
901
  // Failure here must never break the statusline — wrap the entire lookup.
902
+ // #2734: config resolution moved to resolveStatuslineOptions() — the single
903
+ // source of truth shared with renderStatusline() below. The two entry
904
+ // points duplicated this resolution byte-for-byte; one copy is what keeps
905
+ // a new key from reaching only one of them.
702
906
  let lastCmdSuffix = '';
703
- let position = 'end';
704
- let stateFormat = 'full';
705
907
  let gitSuffix = '';
908
+ const options = resolveStatuslineOptions(cfg);
706
909
  try {
707
- if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
910
+ if (options.showLastCommand) {
708
911
  const transcriptPath = data.transcript_path;
709
912
  const lastCmd = readLastSlashCommand(transcriptPath);
710
913
  if (lastCmd) {
711
914
  lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
712
915
  }
713
916
  }
714
- const cfgPos = getConfigValue(cfg, 'statusline.context_position');
715
- if (cfgPos != null) position = cfgPos;
716
- if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
717
- if (getConfigValue(cfg, 'statusline.show_git') === true) {
917
+ if (options.showGit) {
718
918
  gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
719
919
  }
720
920
  } catch (e) {
721
921
  // Never break the statusline on config/transcript/git errors
722
922
  }
723
923
 
924
+ // #2734: readGsdState is inside `if (!task)` deliberately — when a todo
925
+ // task is in flight the GSD-state segment is not rendered, so spending a
926
+ // freshness git spawn here would spend a subprocess on discarded output.
724
927
  if (!task) {
725
- const state = readGsdState(dir) || {};
726
- gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
928
+ const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
929
+ gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
727
930
  }
728
931
 
729
932
  // Output
@@ -734,7 +937,7 @@ function runStatusline() {
734
937
  ? `\x1b[2m${gsdStateStr}\x1b[0m`
735
938
  : null;
736
939
 
737
- process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position }));
940
+ process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position: options.position }));
738
941
  } catch (e) {
739
942
  // Silent fail - don't break statusline on parse errors
740
943
  }
@@ -828,6 +1031,9 @@ module.exports = {
828
1031
  shortGsdStatus, formatGsdStateCompact,
829
1032
  compactModelName,
830
1033
  readGitStatus, parseGitStatus, buildGitSegment,
1034
+ STATE_HEAD_ADVISORY_COMMITS, isValidStateHeadStamp,
1035
+ readStateHeadCommits, parseRevListCounts, deriveStateFreshness,
1036
+ formatStateFreshness, resolveStatuslineOptions,
831
1037
  };
832
1038
 
833
1039
  /**
@@ -839,30 +1045,31 @@ function renderStatusline(data) {
839
1045
  const dir = data.workspace?.current_dir || process.cwd();
840
1046
  const dirname = path.basename(dir);
841
1047
 
1048
+ // #2734: config resolution moved to resolveStatuslineOptions() — the single
1049
+ // source of truth shared with runStatusline() above. The two entry points
1050
+ // duplicated this resolution byte-for-byte; one copy is what keeps a new
1051
+ // key from reaching only one of them.
842
1052
  let lastCmdSuffix = '';
843
- let position = 'end';
844
- let stateFormat = 'full';
845
1053
  let gitSuffix = '';
1054
+ let options = { showLastCommand: false, position: 'end', stateFormat: 'full', showGit: false, showStateFreshness: false };
846
1055
  try {
847
1056
  const cfg = readGsdConfig(dir);
848
- if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
1057
+ options = resolveStatuslineOptions(cfg);
1058
+ if (options.showLastCommand) {
849
1059
  const lastCmd = readLastSlashCommand(data.transcript_path);
850
1060
  if (lastCmd) {
851
1061
  lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
852
1062
  }
853
1063
  }
854
- const cfgPos = getConfigValue(cfg, 'statusline.context_position');
855
- if (cfgPos != null) position = cfgPos;
856
- if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
857
- if (getConfigValue(cfg, 'statusline.show_git') === true) {
1064
+ if (options.showGit) {
858
1065
  gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
859
1066
  }
860
1067
  } catch (e) { /* swallow */ }
861
1068
 
862
- const state = readGsdState(dir) || {};
863
- const gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
1069
+ const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
1070
+ const gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
864
1071
  const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
865
- return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position });
1072
+ return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position: options.position });
866
1073
  }
867
1074
 
868
1075
  module.exports.renderStatusline = renderStatusline;
@@ -15,7 +15,28 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
  const os = require('os');
18
- const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
18
+
19
+ // #3582: gsd-core/bin/lib/package-identity.cjs is a tsc build artifact
20
+ // (ADR-457), gitignored and absent on a raw plugin-marketplace / git-clone
21
+ // install that never ran `npm run build:lib`. This is an opt-in SessionStart
22
+ // hook — a build failure here must DEGRADE, not crash session start. With
23
+ // PACKAGE_NAME left null, buildBannerOutput's own lineage guard
24
+ // (`!cache.package_name || cache.package_name !== PACKAGE_NAME`) always
25
+ // treats the cache as untrusted, so main() falls through to its existing
26
+ // silent "print nothing" path below — no separate degrade branch needed.
27
+ // This try/require/ensureRuntimeBuild/require/catch shape is deliberately
28
+ // duplicated (not extracted to hooks/lib/) — see
29
+ // gsd-check-update-worker.js's identical #3582 comment for why.
30
+ let PACKAGE_NAME = null;
31
+ let updateCacheFileName = 'gsd-update-check.json';
32
+ try {
33
+ const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
34
+ ensureRuntimeBuild();
35
+ ({ PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs'));
36
+ } catch (e) {
37
+ // Runtime library missing/broken and could not self-build — degrade to the
38
+ // fallbacks above rather than crash the SessionStart hook.
39
+ }
19
40
 
20
41
  // Suppress repeat parse-error banners for 24 hours so a genuinely broken
21
42
  // cache file doesn't nag the user every session.