@opengsd/gsd-core 1.11.0 → 1.13.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 (498) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  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 +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  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 +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -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,14 @@ 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;
12
20
  // #3582: gsd-core/bin/lib/*.cjs (semver-compare.cjs, state-document.cjs,
13
21
  // active-workstream-store.cjs, planning-workspace.cjs — required below) and
14
22
  // package-identity.cjs are tsc build artifacts (ADR-457), gitignored and
@@ -23,8 +31,12 @@ if (require.main === module) {
23
31
  const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
24
32
  ensureRuntimeBuild();
25
33
  } catch (e) {
26
- process.stdout.write('');
27
- process.exit(0);
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);
28
40
  }
29
41
  }
30
42
  const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
@@ -169,13 +181,27 @@ function readStateFileOrNull(statePath) {
169
181
  * - null when no .planning marker is found at all (GSD not present), or
170
182
  * when a workstream DOES resolve but its STATE.md doesn't exist yet
171
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.
172
192
  */
173
- function readGsdState(dir) {
193
+ function readGsdState(dir, opts = {}) {
194
+ const { stateFreshness = false } = opts;
174
195
  const home = os.homedir();
175
196
  let current = dir;
176
197
  for (let i = 0; i < 10; i++) {
177
198
  const flatState = readStateFileOrNull(path.join(current, '.planning', 'STATE.md'));
178
- 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
+ }
179
205
 
180
206
  if (listAvailableWorkstreams(current).length > 0) {
181
207
  let resolvedWs = null;
@@ -187,7 +213,11 @@ function readGsdState(dir) {
187
213
 
188
214
  if (!resolvedWs) return { noActiveWorkstream: true };
189
215
 
190
- 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;
191
221
  }
192
222
 
193
223
  const parent = path.dirname(current);
@@ -238,6 +268,10 @@ function parseStateMd(content) {
238
268
  if (key === 'active_phase') state.activePhase = (v === 'null' || v === '') ? null : v;
239
269
  // next_action: recommended command when idle (discuss-phase / plan-phase / execute-phase / verify-phase)
240
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;
241
275
  }
242
276
  // next_phases supports both flow array and block-list YAML forms.
243
277
  const npFlowMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
@@ -355,8 +389,12 @@ function formatGsdState(s) {
355
389
  // Scene 2: idle + a recommended next command is visible to the user.
356
390
  // Surfaces "what to run next" without the user opening STATE.md.
357
391
  parts.push(`next ${s.nextAction} ${phasesStr}`);
358
- } else if (Number(s.percent) === 100 || (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases)) {
359
- // Scene 3: milestone complete (every phase done).
392
+ } else if (Number(s.percent) === 100 || (Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases))) {
393
+ // Scene 3: milestone complete (every phase done). #3945: the counters are
394
+ // regex-captured STRINGS, so the old `cp && tp && cp === tp` guard fired on
395
+ // the empty set ('0' is truthy, '0' === '0') — "0% · milestone complete".
396
+ // Numeric coercion + a non-empty denominator makes "nothing to measure"
397
+ // stop meaning "everything is done".
360
398
  parts.push('milestone complete');
361
399
  } else {
362
400
  // Backward-compatible default — preserved EXACTLY for STATE.md files that
@@ -371,6 +409,10 @@ function formatGsdState(s) {
371
409
  }
372
410
  }
373
411
 
412
+ // #2734: STATE.md freshness marker — opt-in, appended last.
413
+ const fresh = formatStateFreshness(s.freshness);
414
+ if (fresh) parts.push(fresh);
415
+
374
416
  return parts.join(' · ');
375
417
  }
376
418
 
@@ -462,7 +504,7 @@ function formatGsdStateCompact(s) {
462
504
  // still completes) wins over milestone-complete (Scene 3), even if a
463
505
  // non-atomic STATE.md edit leaves percent=100 alongside a lifecycle phase.
464
506
  const done = !s.activePhase && (Number(s.percent) === 100 ||
465
- (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases));
507
+ (Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases)));
466
508
 
467
509
  if (done) {
468
510
  parts.push('complete');
@@ -476,6 +518,10 @@ function formatGsdStateCompact(s) {
476
518
  }
477
519
  }
478
520
 
521
+ // #2734: STATE.md freshness marker \u2014 opt-in, appended last.
522
+ const fresh = formatStateFreshness(s.freshness);
523
+ if (fresh) parts.push(fresh);
524
+
479
525
  return parts.join(' \u00b7 ');
480
526
  }
481
527
 
@@ -572,13 +618,153 @@ function buildGitSegment(info) {
572
618
  return ` │ \x1b[2m${info.branch}\x1b[0m${state}`;
573
619
  }
574
620
 
621
+ // --- STATE.md freshness marker (opt-in, #2734) --------------------------------
622
+ //
623
+ // Opt-in via `statusline.show_state_freshness: true`. Renders `state ~N
624
+ // commits back` inside the GSD-state segment when STATE.md's `state_head`
625
+ // stamp (#2573) is at least STATE_HEAD_ADVISORY_COMMITS commits behind HEAD.
626
+ // Same impure-reader -> pure-IR -> pure-formatter shape as the git segment
627
+ // above. See .gsd/phase/feat-2734-statusline-state-freshness/40-design.md.
628
+
629
+ // Deliberate mirror of the fence in src/state.cts (STATE_HEAD_HASH_RE) — kept
630
+ // hook-side rather than requiring state.cjs on the per-render path (measured
631
+ // ~20ms; see design doc "Laws that apply"). tests/gsd-statusline.test.cjs
632
+ // asserts behavioral parity against readStateHeadFreshness rather than
633
+ // comparing source (local/no-source-grep forbids the latter anyway).
634
+ const STATE_HEAD_HASH_RE = /^[0-9a-f]{4,40}$/i;
635
+
636
+ // Mirror of the constant verify.cts's W024 health check thresholds on
637
+ // (STATE_HEAD_ADVISORY_COMMITS). A test asserts equality with verify.cjs's
638
+ // export so the two copies can't drift.
639
+ const STATE_HEAD_ADVISORY_COMMITS = 20;
640
+
641
+ // Same bound class as GIT_STATUS_TIMEOUT_MS above.
642
+ const STATE_FRESHNESS_GIT_TIMEOUT_MS = 1500;
643
+
644
+ /**
645
+ * Pure function: does raw pass the state_head hash fence? Must run BEFORE any
646
+ * value from STATE.md reaches a git argv slot.
647
+ */
648
+ function isValidStateHeadStamp(raw) {
649
+ return typeof raw === 'string' && STATE_HEAD_HASH_RE.test(raw.trim());
650
+ }
651
+
652
+ /**
653
+ * Run `git rev-list --left-right --count <stamp>...HEAD` in root. Returns raw
654
+ * stdout, or null when git is missing, root isn't a repo, the stamp is
655
+ * unknown, or the call times out. Never throws. Only call with a stamp that
656
+ * already passed isValidStateHeadStamp/the hash fence above.
657
+ */
658
+ function readStateHeadCommits(root, stamp) {
659
+ try {
660
+ return childProcess.execFileSync('git',
661
+ ['-C', root, 'rev-list', '--left-right', '--count', `${stamp}...HEAD`],
662
+ { encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
663
+ stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
664
+ } catch (e) {
665
+ return null;
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Pure function: parse `git rev-list --left-right --count A...B` output
671
+ * ("<left>\t<right>"). Returns { left, right } as non-negative integers, or
672
+ * null when text isn't a matching string (covers null, '', 'garbage', '1',
673
+ * 'a\tb', '\t', and any other unparseable shape).
674
+ */
675
+ function parseRevListCounts(text) {
676
+ if (typeof text !== 'string') return null;
677
+ const m = text.match(/^(\d+)\s+(\d+)\s*$/);
678
+ if (!m) return null;
679
+ return { left: parseInt(m[1], 10), right: parseInt(m[2], 10) };
680
+ }
681
+
682
+ /**
683
+ * Impure -> pure IR: derive the freshness signal for a recorded state_head
684
+ * stamp. Returns { state_head, commits_behind, commit_stale } — never throws,
685
+ * every unresolvable input degrades to the all-null-but-state_head shape.
686
+ *
687
+ * Order (each failure returns immediately, no further work):
688
+ * a. hash fence — malformed/absent stamp never reaches a spawn
689
+ * b. repo pinning — root must own its own .git (mirrors projectOwnsItsRepo
690
+ * in src/state.cts: a filesystem-identity check, not a --show-toplevel
691
+ * string compare, which is unreliable on macOS /private/var and Windows
692
+ * 8.3 paths). Costs no subprocess.
693
+ * c. sub_repos guard — a planning.sub_repos workspace's outer HEAD never
694
+ * advances when code lands in nested children, so a "fresh" answer here
695
+ * would be a confident lie. Costs no subprocess.
696
+ * d. one bounded git spawn: rev-list --left-right --count answers ancestry
697
+ * and distance together. left > 0 means the stamp is not an ancestor of
698
+ * HEAD (reset/rebase/force-push) -> unknown, never "fresh".
699
+ */
700
+ function deriveStateFreshness(root, stamp, deps = {}) {
701
+ const { existsSync = fs.existsSync, readConfig = readGsdConfig, readCounts = readStateHeadCommits } = deps;
702
+
703
+ const raw = typeof stamp === 'string' ? stamp.trim() : '';
704
+ const valid = STATE_HEAD_HASH_RE.test(raw);
705
+ const state_head = valid ? raw.slice(0, 7) : null;
706
+ const nullResult = { state_head, commits_behind: null, commit_stale: null };
707
+ if (!valid || !root) return nullResult;
708
+
709
+ try {
710
+ if (!existsSync(path.join(root, '.git'))) return nullResult;
711
+ } catch (e) {
712
+ return nullResult;
713
+ }
714
+
715
+ try {
716
+ const cfg = readConfig(root);
717
+ const sub = getConfigValue(cfg, 'planning.sub_repos') ?? getConfigValue(cfg, 'sub_repos');
718
+ if (Array.isArray(sub) && sub.length > 0) return nullResult;
719
+ } catch (e) {
720
+ return nullResult;
721
+ }
722
+
723
+ const counts = parseRevListCounts(readCounts(root, raw));
724
+ if (!counts || counts.left > 0) return nullResult;
725
+
726
+ return { state_head, commits_behind: counts.right, commit_stale: counts.right > 0 };
727
+ }
728
+
729
+ /**
730
+ * Pure function: format the freshness IR into the marker text, or '' below
731
+ * STATE_HEAD_ADVISORY_COMMITS (including when commits_behind is absent/null —
732
+ * the unknown case must never render, never mind alarm on it).
733
+ */
734
+ function formatStateFreshness(fresh) {
735
+ if (!fresh || typeof fresh.commits_behind !== 'number' || fresh.commits_behind < STATE_HEAD_ADVISORY_COMMITS) return '';
736
+ return `state ~${fresh.commits_behind} commits back`;
737
+ }
738
+
739
+ /**
740
+ * Pure function: single source of truth for statusline config resolution.
741
+ * `runStatusline()` and `renderStatusline()` previously read this config
742
+ * independently, which had drifted into a live divergence between the two
743
+ * entry points — this collapses both onto one resolver.
744
+ *
745
+ * @param {object} cfg — parsed .planning/config.json (readGsdConfig())
746
+ * @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean }}
747
+ */
748
+ function resolveStatuslineOptions(cfg) {
749
+ const showLastCommand = getConfigValue(cfg, 'statusline.show_last_command') === true;
750
+ const cfgPos = getConfigValue(cfg, 'statusline.context_position');
751
+ // Clamp any non-'front' value (including absent/null) to 'end' — the single
752
+ // source of truth for this default; composeStatusline's own coercion stays
753
+ // as belt-and-suspenders defense for direct callers.
754
+ const position = cfgPos === 'front' ? 'front' : 'end';
755
+ const stateFormat = getConfigValue(cfg, 'statusline.state_format') === 'compact' ? 'compact' : 'full';
756
+ const showGit = getConfigValue(cfg, 'statusline.show_git') === true;
757
+ const showStateFreshness = getConfigValue(cfg, 'statusline.show_state_freshness') === true;
758
+ return { showLastCommand, position, stateFormat, showGit, showStateFreshness };
759
+ }
760
+
575
761
  // --- stdin ------------------------------------------------------------------
576
762
 
577
763
  function runStatusline() {
578
764
  let input = '';
579
765
  // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
580
766
  // Windows/Git Bash), exit silently instead of hanging. See #775.
581
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
767
+ const stdinTimeout = setTimeout(() => allow(undefined), 3000);
582
768
  process.stdin.setEncoding('utf8');
583
769
  process.stdin.on('data', chunk => input += chunk);
584
770
  process.stdin.on('end', () => {
@@ -717,31 +903,34 @@ function runStatusline() {
717
903
  // Last-slash-command suffix and context_position config (#2538, #2937).
718
904
  // Reads the active session transcript for the most recent <command-name> tag.
719
905
  // Failure here must never break the statusline — wrap the entire lookup.
906
+ // #2734: config resolution moved to resolveStatuslineOptions() — the single
907
+ // source of truth shared with renderStatusline() below. The two entry
908
+ // points duplicated this resolution byte-for-byte; one copy is what keeps
909
+ // a new key from reaching only one of them.
720
910
  let lastCmdSuffix = '';
721
- let position = 'end';
722
- let stateFormat = 'full';
723
911
  let gitSuffix = '';
912
+ const options = resolveStatuslineOptions(cfg);
724
913
  try {
725
- if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
914
+ if (options.showLastCommand) {
726
915
  const transcriptPath = data.transcript_path;
727
916
  const lastCmd = readLastSlashCommand(transcriptPath);
728
917
  if (lastCmd) {
729
918
  lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
730
919
  }
731
920
  }
732
- const cfgPos = getConfigValue(cfg, 'statusline.context_position');
733
- if (cfgPos != null) position = cfgPos;
734
- if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
735
- if (getConfigValue(cfg, 'statusline.show_git') === true) {
921
+ if (options.showGit) {
736
922
  gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
737
923
  }
738
924
  } catch (e) {
739
925
  // Never break the statusline on config/transcript/git errors
740
926
  }
741
927
 
928
+ // #2734: readGsdState is inside `if (!task)` deliberately — when a todo
929
+ // task is in flight the GSD-state segment is not rendered, so spending a
930
+ // freshness git spawn here would spend a subprocess on discarded output.
742
931
  if (!task) {
743
- const state = readGsdState(dir) || {};
744
- gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
932
+ const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
933
+ gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
745
934
  }
746
935
 
747
936
  // Output
@@ -752,7 +941,7 @@ function runStatusline() {
752
941
  ? `\x1b[2m${gsdStateStr}\x1b[0m`
753
942
  : null;
754
943
 
755
- process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position }));
944
+ process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position: options.position }));
756
945
  } catch (e) {
757
946
  // Silent fail - don't break statusline on parse errors
758
947
  }
@@ -846,6 +1035,9 @@ module.exports = {
846
1035
  shortGsdStatus, formatGsdStateCompact,
847
1036
  compactModelName,
848
1037
  readGitStatus, parseGitStatus, buildGitSegment,
1038
+ STATE_HEAD_ADVISORY_COMMITS, isValidStateHeadStamp,
1039
+ readStateHeadCommits, parseRevListCounts, deriveStateFreshness,
1040
+ formatStateFreshness, resolveStatuslineOptions,
849
1041
  };
850
1042
 
851
1043
  /**
@@ -857,30 +1049,31 @@ function renderStatusline(data) {
857
1049
  const dir = data.workspace?.current_dir || process.cwd();
858
1050
  const dirname = path.basename(dir);
859
1051
 
1052
+ // #2734: config resolution moved to resolveStatuslineOptions() — the single
1053
+ // source of truth shared with runStatusline() above. The two entry points
1054
+ // duplicated this resolution byte-for-byte; one copy is what keeps a new
1055
+ // key from reaching only one of them.
860
1056
  let lastCmdSuffix = '';
861
- let position = 'end';
862
- let stateFormat = 'full';
863
1057
  let gitSuffix = '';
1058
+ let options = { showLastCommand: false, position: 'end', stateFormat: 'full', showGit: false, showStateFreshness: false };
864
1059
  try {
865
1060
  const cfg = readGsdConfig(dir);
866
- if (getConfigValue(cfg, 'statusline.show_last_command') === true) {
1061
+ options = resolveStatuslineOptions(cfg);
1062
+ if (options.showLastCommand) {
867
1063
  const lastCmd = readLastSlashCommand(data.transcript_path);
868
1064
  if (lastCmd) {
869
1065
  lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
870
1066
  }
871
1067
  }
872
- const cfgPos = getConfigValue(cfg, 'statusline.context_position');
873
- if (cfgPos != null) position = cfgPos;
874
- if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
875
- if (getConfigValue(cfg, 'statusline.show_git') === true) {
1068
+ if (options.showGit) {
876
1069
  gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
877
1070
  }
878
1071
  } catch (e) { /* swallow */ }
879
1072
 
880
- const state = readGsdState(dir) || {};
881
- const gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
1073
+ const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
1074
+ const gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
882
1075
  const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
883
- return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position });
1076
+ return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position: options.position });
884
1077
  }
885
1078
 
886
1079
  module.exports.renderStatusline = renderStatusline;