@opengsd/gsd-core 1.14.0 → 1.16.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 (551) 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 +85 -5
  4. package/README.ja-JP.md +3 -3
  5. package/README.ko-KR.md +3 -3
  6. package/README.pt-BR.md +3 -3
  7. package/README.zh-CN.md +3 -3
  8. package/agents/gsd-code-fixer.compact.md +7 -6
  9. package/agents/gsd-code-fixer.md +9 -8
  10. package/agents/gsd-code-reviewer.compact.md +5 -3
  11. package/agents/gsd-code-reviewer.md +8 -6
  12. package/agents/gsd-debug-session-manager.compact.md +17 -2
  13. package/agents/gsd-debug-session-manager.md +17 -2
  14. package/agents/gsd-debugger.md +3 -3
  15. package/agents/gsd-eval-auditor.compact.md +1 -1
  16. package/agents/gsd-eval-auditor.md +1 -1
  17. package/agents/gsd-executor.md +17 -12
  18. package/agents/gsd-intel-updater.compact.md +1 -1
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +2 -2
  21. package/agents/gsd-phase-researcher.md +19 -11
  22. package/agents/gsd-plan-checker.md +15 -9
  23. package/agents/gsd-planner.md +15 -11
  24. package/agents/gsd-project-researcher.compact.md +1 -1
  25. package/agents/gsd-project-researcher.md +1 -1
  26. package/agents/gsd-research-synthesizer.compact.md +1 -1
  27. package/agents/gsd-research-synthesizer.md +1 -1
  28. package/agents/gsd-ui-auditor.compact.md +21 -30
  29. package/agents/gsd-ui-auditor.md +166 -37
  30. package/agents/gsd-ui-researcher.compact.md +1 -1
  31. package/agents/gsd-ui-researcher.md +1 -1
  32. package/agents/gsd-verifier.md +37 -14
  33. package/bin/install.js +764 -287
  34. package/commands/gsd/add-tests.md +6 -1
  35. package/commands/gsd/ai-integration-phase.md +6 -1
  36. package/commands/gsd/audit-fix.md +5 -0
  37. package/commands/gsd/audit-milestone.md +6 -1
  38. package/commands/gsd/autonomous.md +7 -2
  39. package/commands/gsd/capture.md +9 -5
  40. package/commands/gsd/code-review.md +7 -2
  41. package/commands/gsd/complete-milestone.md +4 -0
  42. package/commands/gsd/config.md +7 -3
  43. package/commands/gsd/debug.md +11 -7
  44. package/commands/gsd/discuss-phase.md +7 -3
  45. package/commands/gsd/docs-update.md +12 -7
  46. package/commands/gsd/eval-review.md +6 -1
  47. package/commands/gsd/execute-phase.md +12 -7
  48. package/commands/gsd/extract-learnings.md +5 -0
  49. package/commands/gsd/fast.md +4 -0
  50. package/commands/gsd/forensics.md +5 -1
  51. package/commands/gsd/graphify.md +10 -6
  52. package/commands/gsd/health.md +5 -0
  53. package/commands/gsd/help.md +7 -2
  54. package/commands/gsd/import.md +7 -3
  55. package/commands/gsd/inbox.md +5 -0
  56. package/commands/gsd/ingest-docs.md +5 -1
  57. package/commands/gsd/manager.md +6 -1
  58. package/commands/gsd/map-codebase.md +7 -3
  59. package/commands/gsd/mempalace-capture.md +12 -4
  60. package/commands/gsd/mempalace-recall.md +5 -1
  61. package/commands/gsd/milestone-summary.md +5 -1
  62. package/commands/gsd/mvp-phase.md +8 -3
  63. package/commands/gsd/new-milestone.md +6 -1
  64. package/commands/gsd/new-project.md +5 -0
  65. package/commands/gsd/next.md +6 -1
  66. package/commands/gsd/ns-context.md +4 -0
  67. package/commands/gsd/ns-ideate.md +4 -0
  68. package/commands/gsd/ns-manage.md +4 -0
  69. package/commands/gsd/ns-project.md +4 -0
  70. package/commands/gsd/ns-review.md +4 -0
  71. package/commands/gsd/ns-workflow.md +4 -0
  72. package/commands/gsd/onboard.md +6 -1
  73. package/commands/gsd/pause-work.md +5 -1
  74. package/commands/gsd/phase.md +8 -4
  75. package/commands/gsd/plan-phase.md +6 -1
  76. package/commands/gsd/plan-review-convergence.md +11 -7
  77. package/commands/gsd/pr-branch.md +4 -0
  78. package/commands/gsd/profile-user.md +5 -1
  79. package/commands/gsd/progress.md +7 -2
  80. package/commands/gsd/quick-batch.md +21 -9
  81. package/commands/gsd/quick.md +12 -7
  82. package/commands/gsd/review.md +7 -4
  83. package/commands/gsd/secure-phase.md +6 -1
  84. package/commands/gsd/ship.md +5 -0
  85. package/commands/gsd/sketch.md +7 -2
  86. package/commands/gsd/spec-phase.md +5 -1
  87. package/commands/gsd/spike.md +8 -3
  88. package/commands/gsd/surface.md +5 -1
  89. package/commands/gsd/thread.md +4 -0
  90. package/commands/gsd/ui-phase.md +6 -1
  91. package/commands/gsd/ui-review.md +6 -1
  92. package/commands/gsd/ultraplan-phase.md +5 -1
  93. package/commands/gsd/undo.md +5 -1
  94. package/commands/gsd/update.md +6 -2
  95. package/commands/gsd/validate-phase.md +6 -1
  96. package/commands/gsd/verify-work.md +6 -1
  97. package/commands/gsd/workspace.md +7 -3
  98. package/gsd-core/bin/gsd-tools.cjs +477 -78
  99. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  100. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  101. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  102. package/gsd-core/bin/lib/audit.cjs +144 -42
  103. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  104. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  105. package/gsd-core/bin/lib/capability-registry.cjs +197 -222
  106. package/gsd-core/bin/lib/capability-validator.cjs +16 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +343 -207
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  117. package/gsd-core/bin/lib/config.cjs +76 -19
  118. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  119. package/gsd-core/bin/lib/coverage.cjs +4 -8
  120. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  121. package/gsd-core/bin/lib/decisions.cjs +30 -14
  122. package/gsd-core/bin/lib/drift.cjs +177 -42
  123. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  124. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  125. package/gsd-core/bin/lib/frontmatter.cjs +426 -234
  126. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  127. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  128. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  129. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  130. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  131. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  132. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  133. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  134. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  135. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  136. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  137. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  138. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  139. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  140. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  142. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  143. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  144. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  145. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  146. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  147. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  148. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  149. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  150. package/gsd-core/bin/lib/graphify.cjs +10 -2
  151. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  152. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  153. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  154. package/gsd-core/bin/lib/init.cjs +364 -132
  155. package/gsd-core/bin/lib/install-engine.cjs +30 -33
  156. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  157. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  158. package/gsd-core/bin/lib/io.cjs +122 -3
  159. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  160. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  161. package/gsd-core/bin/lib/milestone.cjs +37 -6
  162. package/gsd-core/bin/lib/model-resolver.cjs +171 -62
  163. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  164. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  165. package/gsd-core/bin/lib/pattern.cjs +10 -0
  166. package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
  167. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  168. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  169. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  170. package/gsd-core/bin/lib/phase-id.cjs +110 -8
  171. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  172. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  173. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  174. package/gsd-core/bin/lib/phase.cjs +489 -88
  175. package/gsd-core/bin/lib/plan-document.cjs +142 -20
  176. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  177. package/gsd-core/bin/lib/planning-document.cjs +692 -0
  178. package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
  179. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  180. package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
  181. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  184. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  185. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  186. package/gsd-core/bin/lib/project-root.cjs +41 -2
  187. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  188. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  189. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  190. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  191. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  192. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  193. package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
  194. package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
  195. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
  196. package/gsd-core/bin/lib/roadmap.cjs +405 -88
  197. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
  198. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
  199. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  200. package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
  201. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
  202. package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
  203. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  204. package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
  205. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  206. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  207. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  208. package/gsd-core/bin/lib/state-document.cjs +100 -22
  209. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  210. package/gsd-core/bin/lib/state.cjs +256 -105
  211. package/gsd-core/bin/lib/surface.cjs +19 -2
  212. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
  213. package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
  214. package/gsd-core/bin/lib/uat.cjs +432 -7
  215. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  216. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
  217. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  218. package/gsd-core/bin/lib/vendor/README.md +31 -9
  219. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  220. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  221. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  222. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  223. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  224. package/gsd-core/bin/lib/verification.cjs +1378 -274
  225. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  226. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  227. package/gsd-core/bin/lib/verify.cjs +357 -531
  228. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  229. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  230. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  231. package/gsd-core/bin/lib/workstream.cjs +11 -2
  232. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  233. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  234. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
  235. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  236. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  237. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  238. package/gsd-core/references/checkpoints.md +5 -3
  239. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  240. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  241. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  242. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  243. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  244. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  245. package/gsd-core/references/edge-probe.md +195 -21
  246. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  247. package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
  248. package/gsd-core/references/execute-phase-response-language.md +1 -1
  249. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  250. package/gsd-core/references/gsd-run-resolver.md +1 -1
  251. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  252. package/gsd-core/references/model-profiles.md +1 -1
  253. package/gsd-core/references/offer-next.md +1 -1
  254. package/gsd-core/references/phase-argument-parsing.md +9 -7
  255. package/gsd-core/references/phase-id-convention.md +28 -0
  256. package/gsd-core/references/planner-gap-closure.md +2 -0
  257. package/gsd-core/references/planner-load-graph-context.md +24 -13
  258. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  259. package/gsd-core/references/planning-config.md +12 -3
  260. package/gsd-core/references/spidr-splitting.md +1 -1
  261. package/gsd-core/references/tdd.md +37 -8
  262. package/gsd-core/references/ui-consideration-probe.md +10 -5
  263. package/gsd-core/references/verifier-phase-gates.md +5 -2
  264. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  265. package/gsd-core/references/verify-mvp-mode.md +2 -2
  266. package/gsd-core/references/workstream-flag.md +33 -3
  267. package/gsd-core/references/worktree-path-safety.md +321 -0
  268. package/gsd-core/templates/README.md +1 -1
  269. package/gsd-core/templates/UAT.md +17 -1
  270. package/gsd-core/templates/config.json +2 -11
  271. package/gsd-core/templates/verification-report.md +1 -1
  272. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  273. package/gsd-core/workflows/add-backlog.md +1 -1
  274. package/gsd-core/workflows/add-phase.md +8 -7
  275. package/gsd-core/workflows/add-tests.md +4 -3
  276. package/gsd-core/workflows/add-todo.md +6 -5
  277. package/gsd-core/workflows/ai-integration-phase.md +13 -4
  278. package/gsd-core/workflows/audit-fix.md +1 -1
  279. package/gsd-core/workflows/audit-milestone.md +4 -3
  280. package/gsd-core/workflows/audit-uat.md +1 -1
  281. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  282. package/gsd-core/workflows/autonomous.md +43 -23
  283. package/gsd-core/workflows/check-todos.md +7 -6
  284. package/gsd-core/workflows/cleanup.md +2 -2
  285. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  286. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  287. package/gsd-core/workflows/code-review-fix.md +112 -25
  288. package/gsd-core/workflows/code-review.md +146 -135
  289. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  290. package/gsd-core/workflows/complete-milestone.md +13 -8
  291. package/gsd-core/workflows/debug.md +32 -7
  292. package/gsd-core/workflows/diagnose-issues.md +3 -2
  293. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  294. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  295. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  296. package/gsd-core/workflows/discuss-phase.md +4 -3
  297. package/gsd-core/workflows/do.md +2 -2
  298. package/gsd-core/workflows/docs-update.md +6 -5
  299. package/gsd-core/workflows/edit-phase.md +4 -3
  300. package/gsd-core/workflows/eval-review.md +14 -5
  301. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  302. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
  303. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  304. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  305. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  306. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  307. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  308. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  309. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  310. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  311. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  312. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  313. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  314. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  315. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  316. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  317. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  318. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  319. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  320. package/gsd-core/workflows/execute-phase.md +96 -178
  321. package/gsd-core/workflows/execute-plan.md +18 -23
  322. package/gsd-core/workflows/explore.md +4 -4
  323. package/gsd-core/workflows/extract-learnings.md +4 -2
  324. package/gsd-core/workflows/fast.md +1 -1
  325. package/gsd-core/workflows/forensics.md +1 -1
  326. package/gsd-core/workflows/graduation.md +1 -1
  327. package/gsd-core/workflows/health.md +3 -2
  328. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  329. package/gsd-core/workflows/help/modes/full.md +5 -5
  330. package/gsd-core/workflows/help/modes/topic.md +15 -5
  331. package/gsd-core/workflows/import.md +4 -3
  332. package/gsd-core/workflows/inbox.md +2 -2
  333. package/gsd-core/workflows/ingest-docs.md +3 -3
  334. package/gsd-core/workflows/insert-phase.md +4 -3
  335. package/gsd-core/workflows/list-seeds.md +1 -1
  336. package/gsd-core/workflows/list-workspaces.md +1 -1
  337. package/gsd-core/workflows/manager.md +6 -4
  338. package/gsd-core/workflows/map-codebase.md +5 -4
  339. package/gsd-core/workflows/milestone-summary.md +3 -2
  340. package/gsd-core/workflows/mvp-phase.md +14 -14
  341. package/gsd-core/workflows/new-milestone.md +11 -11
  342. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  343. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  344. package/gsd-core/workflows/new-project.md +7 -7
  345. package/gsd-core/workflows/new-workspace.md +2 -2
  346. package/gsd-core/workflows/next.md +1 -1
  347. package/gsd-core/workflows/note.md +1 -1
  348. package/gsd-core/workflows/onboard.md +1 -1
  349. package/gsd-core/workflows/pause-work.md +2 -2
  350. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  351. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  352. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  353. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  354. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  355. package/gsd-core/workflows/plan-phase.md +50 -24
  356. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  357. package/gsd-core/workflows/plant-seed.md +62 -20
  358. package/gsd-core/workflows/pr-branch.md +113 -13
  359. package/gsd-core/workflows/profile-user.md +2 -2
  360. package/gsd-core/workflows/progress.md +19 -49
  361. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
  362. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  363. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  364. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  365. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  366. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  367. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  368. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  369. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  370. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  371. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  372. package/gsd-core/workflows/quick-batch.md +15 -10
  373. package/gsd-core/workflows/quick.md +62 -39
  374. package/gsd-core/workflows/reapply-patches.md +9 -3
  375. package/gsd-core/workflows/remove-phase.md +3 -2
  376. package/gsd-core/workflows/remove-workspace.md +2 -2
  377. package/gsd-core/workflows/resume-project.md +3 -2
  378. package/gsd-core/workflows/review.md +33 -17
  379. package/gsd-core/workflows/scan.md +3 -2
  380. package/gsd-core/workflows/secure-phase.md +13 -13
  381. package/gsd-core/workflows/settings-advanced.md +30 -10
  382. package/gsd-core/workflows/settings-integrations.md +2 -3
  383. package/gsd-core/workflows/settings.md +4 -4
  384. package/gsd-core/workflows/ship.md +14 -13
  385. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  386. package/gsd-core/workflows/sketch.md +1 -1
  387. package/gsd-core/workflows/smart-entry.md +2 -2
  388. package/gsd-core/workflows/spec-phase.md +15 -5
  389. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  390. package/gsd-core/workflows/spike.md +1 -1
  391. package/gsd-core/workflows/stats.md +1 -1
  392. package/gsd-core/workflows/sync-skills.md +5 -5
  393. package/gsd-core/workflows/thread.md +2 -2
  394. package/gsd-core/workflows/transition.md +13 -23
  395. package/gsd-core/workflows/ui-phase.md +48 -11
  396. package/gsd-core/workflows/ui-review.md +21 -6
  397. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  398. package/gsd-core/workflows/undo.md +339 -20
  399. package/gsd-core/workflows/update.md +7 -7
  400. package/gsd-core/workflows/validate-phase.md +12 -13
  401. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  402. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  403. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  404. package/gsd-core/workflows/verify-work.md +129 -55
  405. package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
  406. package/hooks/dist/gsd-check-update-worker.js +8 -0
  407. package/hooks/dist/gsd-check-update.js +8 -0
  408. package/hooks/dist/gsd-context-monitor.js +31 -8
  409. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  410. package/hooks/dist/gsd-secret-read-guard.js +161 -4
  411. package/hooks/dist/gsd-statusline.js +85 -20
  412. package/hooks/dist/gsd-update-banner.js +8 -0
  413. package/hooks/dist/gsd-validate-commit.sh +63 -4
  414. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  415. package/hooks/dist/gsd-workflow-guard.js +5 -4
  416. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  417. package/hooks/dist/lib/cli-exit.js +12 -0
  418. package/hooks/dist/lib/git-probe.js +17 -1
  419. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  420. package/hooks/gsd-agent-isolation-guard.js +32 -0
  421. package/hooks/gsd-check-update-worker.js +8 -0
  422. package/hooks/gsd-check-update.js +8 -0
  423. package/hooks/gsd-context-monitor.js +31 -8
  424. package/hooks/gsd-cursor-subagent-start.js +8 -0
  425. package/hooks/gsd-secret-read-guard.js +161 -4
  426. package/hooks/gsd-statusline.js +85 -20
  427. package/hooks/gsd-update-banner.js +8 -0
  428. package/hooks/gsd-validate-commit.sh +63 -4
  429. package/hooks/gsd-windsurf-pre-write.js +11 -2
  430. package/hooks/gsd-workflow-guard.js +5 -4
  431. package/hooks/gsd-worktree-path-guard.js +6 -2
  432. package/hooks/hooks.json +5 -5
  433. package/hooks/lib/cli-exit.js +12 -0
  434. package/hooks/lib/git-probe.js +17 -1
  435. package/hooks/lib/isolation-sentinel.js +2 -2
  436. package/package.json +22 -4
  437. package/scripts/build-hooks.js +15 -6
  438. package/scripts/changeset/parse.cjs +52 -4
  439. package/scripts/check-contract-drift.cjs +127 -11
  440. package/scripts/ci-timeout-report.cjs +770 -4
  441. package/scripts/command-contract-helpers.cjs +15 -8
  442. package/scripts/docs-guard-registry.cjs +34 -0
  443. package/scripts/gen-features.cjs +13 -8
  444. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  445. package/scripts/gen-loop-host-contract.cjs +79 -1
  446. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  447. package/scripts/gen-plugin-skills.cjs +87 -1
  448. package/scripts/gen-research-agents.cjs +24 -31
  449. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  450. package/scripts/gen-test-timings.cjs +32 -7
  451. package/scripts/lib/cli-exit.cjs +12 -0
  452. package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
  453. package/scripts/lib/ndjson-reporter.cjs +31 -5
  454. package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
  455. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  456. package/scripts/lib/vendor-bundle.cjs +59 -0
  457. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  458. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  459. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  460. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  461. package/scripts/lint-descriptions.cjs +7 -3
  462. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
  463. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  464. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  465. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  466. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  467. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  468. package/scripts/lint-phase-id-drift.cjs +319 -5
  469. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  470. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  471. package/scripts/lint-response-language-coverage.cjs +3 -0
  472. package/scripts/lint-retired-runtime-name.cjs +619 -0
  473. package/scripts/lint-skill-deps.cjs +7 -3
  474. package/scripts/lint-state-write-path-drift.cjs +93 -0
  475. package/scripts/lint-test-file-count.allowlist.json +38 -9
  476. package/scripts/lint-test-file-count.cjs +34 -1
  477. package/scripts/lint-vendored-deps.cjs +41 -5
  478. package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
  479. package/scripts/mutation-matrix.cjs +50 -5
  480. package/scripts/prompt-injection-scan.sh +4 -0
  481. package/scripts/release-tarball-smoke.cjs +194 -1
  482. package/scripts/require-issue-link-policy.cjs +6 -2
  483. package/scripts/sync-runtime-launcher.cjs +184 -2
  484. package/scripts/verify-npm-publish.cjs +76 -20
  485. package/skills/gsd-add-tests/SKILL.md +6 -1
  486. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  487. package/skills/gsd-audit-fix/SKILL.md +5 -0
  488. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  489. package/skills/gsd-autonomous/SKILL.md +7 -2
  490. package/skills/gsd-capture/SKILL.md +9 -5
  491. package/skills/gsd-code-review/SKILL.md +7 -2
  492. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  493. package/skills/gsd-config/SKILL.md +8 -4
  494. package/skills/gsd-debug/SKILL.md +11 -7
  495. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  496. package/skills/gsd-docs-update/SKILL.md +12 -7
  497. package/skills/gsd-eval-review/SKILL.md +6 -1
  498. package/skills/gsd-execute-phase/SKILL.md +12 -7
  499. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  500. package/skills/gsd-fast/SKILL.md +4 -0
  501. package/skills/gsd-forensics/SKILL.md +5 -1
  502. package/skills/gsd-graphify/SKILL.md +10 -6
  503. package/skills/gsd-health/SKILL.md +5 -0
  504. package/skills/gsd-help/SKILL.md +7 -2
  505. package/skills/gsd-import/SKILL.md +7 -3
  506. package/skills/gsd-inbox/SKILL.md +5 -0
  507. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  508. package/skills/gsd-manager/SKILL.md +6 -1
  509. package/skills/gsd-map-codebase/SKILL.md +7 -3
  510. package/skills/gsd-mempalace-capture/SKILL.md +12 -4
  511. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  512. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  513. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  514. package/skills/gsd-new-milestone/SKILL.md +6 -1
  515. package/skills/gsd-new-project/SKILL.md +5 -0
  516. package/skills/gsd-next/SKILL.md +6 -1
  517. package/skills/gsd-ns-context/SKILL.md +4 -0
  518. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  519. package/skills/gsd-ns-manage/SKILL.md +4 -0
  520. package/skills/gsd-ns-project/SKILL.md +4 -0
  521. package/skills/gsd-ns-review/SKILL.md +4 -0
  522. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  523. package/skills/gsd-onboard/SKILL.md +6 -1
  524. package/skills/gsd-pause-work/SKILL.md +5 -1
  525. package/skills/gsd-phase/SKILL.md +8 -4
  526. package/skills/gsd-plan-phase/SKILL.md +6 -1
  527. package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
  528. package/skills/gsd-pr-branch/SKILL.md +4 -0
  529. package/skills/gsd-profile-user/SKILL.md +5 -1
  530. package/skills/gsd-progress/SKILL.md +7 -2
  531. package/skills/gsd-quick/SKILL.md +16 -10
  532. package/skills/gsd-quick-batch/SKILL.md +21 -9
  533. package/skills/gsd-review/SKILL.md +7 -4
  534. package/skills/gsd-review-backlog/SKILL.md +3 -2
  535. package/skills/gsd-secure-phase/SKILL.md +6 -1
  536. package/skills/gsd-ship/SKILL.md +5 -0
  537. package/skills/gsd-sketch/SKILL.md +7 -2
  538. package/skills/gsd-spec-phase/SKILL.md +5 -1
  539. package/skills/gsd-spike/SKILL.md +8 -3
  540. package/skills/gsd-surface/SKILL.md +5 -1
  541. package/skills/gsd-thread/SKILL.md +4 -0
  542. package/skills/gsd-ui-phase/SKILL.md +6 -1
  543. package/skills/gsd-ui-review/SKILL.md +6 -1
  544. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  545. package/skills/gsd-undo/SKILL.md +5 -1
  546. package/skills/gsd-update/SKILL.md +6 -2
  547. package/skills/gsd-validate-phase/SKILL.md +6 -1
  548. package/skills/gsd-verify-work/SKILL.md +6 -1
  549. package/skills/gsd-workspace/SKILL.md +7 -3
  550. package/skills/gsd-workstreams/SKILL.md +6 -6
  551. package/vscode/package.json +1 -1
@@ -19,6 +19,10 @@
19
19
  * state signal-resume Remove WAITING.json signal
20
20
  * resolve-model <agent-type> Get model for agent based on profile
21
21
  * find-phase <phase> Find phase directory by number
22
+ * select-revert-commits (--phase P | --plan NN-MM) --range R
23
+ * List commits within R whose DECLARED conventional-commit
24
+ * scope identifies P or NN-MM (exact match; phase mode also
25
+ * accepts a NN-MM-scoped commit). Used by /gsd:undo (#4661).
22
26
  * commit <message> [--files f1 f2] [--no-verify] Commit planning docs
23
27
  * commit-docs-guard enable|disable Opt-in .git/hooks/pre-commit guard
24
28
  * that refuses a commit staging
@@ -60,7 +64,7 @@
60
64
  * roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
61
65
  * roadmap annotate-dependencies <N> Add wave dependency notes + cross-cutting constraints to ROADMAP.md
62
66
  * roadmap validate Validate phase ID convention compliance
63
- * roadmap upgrade [--apply] --convention milestone-prefixed Migrate phase IDs to M-NN convention
67
+ * roadmap upgrade [--apply] --convention <milestone-prefixed|bracket> Migrate phase IDs (dry-run by default)
64
68
  *
65
69
  * Requirements Operations:
66
70
  * requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
@@ -219,6 +223,8 @@
219
223
  * Loop Extension Point Queries (ADR-857 phase 3c):
220
224
  * loop render-hooks <point> Resolve + render active Capability hooks at a loop point
221
225
  * [--config-dir <path>] [--runtime <r>] [--active-cap <capId>]
226
+ * [--after-fingerprint <phaseDir>] (#5105: skip verify:post
227
+ * steps whose declared artifact already exists in phaseDir)
222
228
  * Returns JSON envelope { point, activeHooks, rendered }
223
229
  * Valid points: discuss:pre/post, plan:pre/post,
224
230
  * execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post
@@ -241,6 +247,14 @@
241
247
  * Import a GSD-2 (.gsd/) project back to GSD v1 (.planning/) format
242
248
  */
243
249
 
250
+ // #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
251
+ // for unchanged modules. Only as the entry (a test require() is unaffected),
252
+ // skipped under V8 coverage, optional (Bun stubs it), and never fatal.
253
+ // NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
254
+ if (require.main === module && !process.env.NODE_V8_COVERAGE) {
255
+ try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
256
+ }
257
+
244
258
  const fs = require('fs');
245
259
  const path = require('path');
246
260
 
@@ -272,7 +286,7 @@ try {
272
286
 
273
287
  const { ExitError, runMain, resolveContractVersion } = require('./lib/cli-exit.cjs');
274
288
  const io = require('./lib/io.cjs');
275
- const { error, ERROR_REASON, setJsonErrorMode, output, formatDiagnosticToken } = io;
289
+ const { error, ERROR_REASON, setJsonErrorMode, output, formatDiagnosticToken, captureStdoutSyncWrites, resolveAtFileOutput } = io;
276
290
  const projectRoot = require('./lib/project-root.cjs');
277
291
  // Resolve findProjectRoot lazily at call time rather than binding it at module
278
292
  // load. It is sourced from project-root.cjs; a call-time lookup is robust
@@ -487,6 +501,7 @@ function dispatchCapabilityCommand({ command, args, cwd, raw, error, registry, r
487
501
  _result = fn({ args, cwd, raw, error });
488
502
  } catch (e) {
489
503
  if (e instanceof ExitError) throw e; // intentional structured error from the router (honors --json-errors) — propagate untouched
504
+ if (isVerificationStatusError(e)) throw e; // #5118: translated once, centrally, by main()
490
505
  error(
491
506
  'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" threw: ' + (e && e.message ? e.message : String(e)),
492
507
  ERROR_REASON.SDK_FAIL_FAST,
@@ -602,6 +617,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
602
617
  _result = fn({ args, cwd, raw, error });
603
618
  } catch (e) {
604
619
  if (e instanceof ExitError) throw e;
620
+ if (isVerificationStatusError(e)) throw e; // #5118: translated once, centrally, by main()
605
621
  error(
606
622
  'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" threw: ' + (e && e.message ? e.message : String(e)),
607
623
  ERROR_REASON.SDK_FAIL_FAST,
@@ -949,6 +965,219 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
949
965
  if (!handled) phase.cmdFindPhase(cwd, args[1], raw);
950
966
  }
951
967
 
968
+ /**
969
+ * #4906 Phase 5 (issue #4661, absorbed from the original bug report):
970
+ * `gsd-tools.cjs query select-revert-commits --phase <id>|--plan <id>
971
+ * --range <git-revision-range>`.
972
+ *
973
+ * Replaces the two `git log --oneline | grep -E ...` pipelines
974
+ * `gsd-core/workflows/undo.md` used to run for `/gsd:undo --phase`/`--plan`
975
+ * selection. `--range` is the ALREADY-COMPUTED `$UNDO_RANGE` the workflow's
976
+ * own phase-directory anchor logic derives — this subcommand changes only
977
+ * the subject-matching step, never the ranging behavior.
978
+ *
979
+ * Validates `--phase`/`--range <id>` via `validatePhaseNumber`
980
+ * (src/security.cts) BEFORE running any git command — an invalid id is
981
+ * refused here, never reaching git or the selection regex (closes bug
982
+ * class 1 of #4661 by construction). A `--plan NN-MM` id is two
983
+ * phase-number-shaped segments joined by the FIRST `-`: `validatePhaseNumber`'s
984
+ * bracket-style alternative requires a leading letter, so a plain numeric
985
+ * plan id never matches it whole — each segment is validated on its own
986
+ * instead of forking a second grammar.
987
+ *
988
+ * Selection itself is delegated to `selectCommitsByDeclaredScope`
989
+ * (src/undo-commit-selection.cts), which parses each commit subject
990
+ * through the SAME anchored conventional-commit header regex the
991
+ * changelog/PR-title gate uses and compares the declared scope to the
992
+ * validated id by exact string equality — no regex is ever built from
993
+ * user input.
994
+ *
995
+ * Output: JSON `{selected, classified}` by default; with `--raw`, one
996
+ * `<full-sha> <subject>` line per selected commit (preserves the visual
997
+ * shape `git log --oneline` produced, so the workflow's downstream
998
+ * confirm-screen display, >50 truncation count, and `git revert
999
+ * --no-commit` loop over COMMITS are unchanged).
1000
+ */
1001
+ function routeSelectRevertCommits({ args, cwd, raw, error, invokingCwd }) {
1002
+ // #4906/#4465 review fix: `cwd` here has already been remapped to the
1003
+ // MAIN worktree root by `resolveMainWorktreeCwd` (main(), applied
1004
+ // BLANKET to every command before dispatch — correct for a router that
1005
+ // reads `.planning/`, e.g. `find-phase`, but wrong for this one). This
1006
+ // command reads NO `.planning/` content — it only runs `git log` — and
1007
+ // that git operation must see the CALLER's own worktree: its HEAD, its
1008
+ // branch, its reachable commit range. Using the remapped `cwd` instead
1009
+ // silently resolves `--range` against a DIFFERENT repository checkout
1010
+ // (reproduced: from a linked worktree with no local `.planning/`, a
1011
+ // `--range HEAD~3..HEAD` that succeeds when run directly in that
1012
+ // worktree fails with "ambiguous argument" through this command,
1013
+ // because it silently ran against main's shorter HEAD instead —
1014
+ // tests/undo-commit-selection-4465.test.cjs, "linked worktree: the main
1015
+ // worktree's planning is the same repository, and is not refused").
1016
+ // `invokingCwd` (threaded from main() through dispatchHostCommand) is
1017
+ // the pre-remap value; falling back to `cwd` keeps direct callers that
1018
+ // omit it (e.g. `dispatchHostCommand` invoked without the new field)
1019
+ // working exactly as before rather than crashing on `undefined`.
1020
+ const gitCwd = invokingCwd || cwd;
1021
+ const { validatePhaseNumber } = require('./lib/security.cjs');
1022
+ const { normalizePhaseName } = require('./lib/phase-id.cjs');
1023
+ const { selectCommitsByDeclaredScope } = require('./lib/undo-commit-selection.cjs');
1024
+ const { execGit } = require('./lib/shell-command-projection.cjs');
1025
+
1026
+ // #4906 Phase 5 review fix: the RETIRED phase-mode grep's `0*` prefix
1027
+ // (`\(0*${TARGET_PHASE}(-[0-9]+)?\):`) tolerated an UNPADDED user-supplied
1028
+ // phase number against a zero-padded commit scope — `--phase 3` matched
1029
+ // `feat(03-01): ...` because `0*3` matches the literal text `03`.
1030
+ // `selectCommitsByDeclaredScope` compares by exact string equality, and
1031
+ // `gsd-core/workflows/undo.md`'s parse_arguments step passes `TARGET_PHASE`
1032
+ // through with NO normalization, so without this, `--phase 3` would
1033
+ // silently select nothing against real (zero-padded) commit scopes — a
1034
+ // behavior regression, not just a documented known-limit. Zero-pad via
1035
+ // `normalizePhaseName` (src/phase-id.cts), the canonical owner of this
1036
+ // exact operation (confirmed: `normalizePhaseName('3') === '03'`).
1037
+ //
1038
+ // GUARDED to digit-first ids only: `normalizePhaseName` also runs
1039
+ // `stripProjectCodePrefix`, which strips a leading `LETTERS-`-shaped
1040
+ // prefix UNCONDITIONALLY — `normalizePhaseName('PROJ-42') === '42'`,
1041
+ // discarding the "PROJ-" entirely (confirmed by direct call). That is
1042
+ // exactly the shape `validatePhaseNumber`'s bracket-style alternative
1043
+ // legitimately accepts as a phase id in ITS OWN right (`PROJ-42`,
1044
+ // `AUTH-101` — see tests/security.test.cjs). Running a letter-first id
1045
+ // through it would silently widen the match (a wildcard-style
1046
+ // over-match reintroducing bug classes 1/2). The old grep's `0*` was
1047
+ // never meaningful for a letter-first target anyway — `0*` before a
1048
+ // non-'0' character matches zero repetitions, a no-op — so a
1049
+ // letter-first id needs neither padding nor this normalization.
1050
+ // `PROJECT_CODE_PREFIX_STRIP_RE_I` (`src/phase-id.cts:33`) itself
1051
+ // requires `^[A-Z]`, so it can never fire on a digit-first id — the
1052
+ // guard below is therefore exactly the safe/unsafe boundary.
1053
+ const normalizePaddedPhase = (id) => (/^\d/.test(id) ? normalizePhaseName(id) : id);
1054
+
1055
+ const parsed = parseNamedArgsOrExit(
1056
+ args,
1057
+ { valueFlags: ['phase', 'plan', 'range'], positionals: 1 },
1058
+ error,
1059
+ );
1060
+ const rawPhase = parsed.phase;
1061
+ const rawPlan = parsed.plan;
1062
+ const range = parsed.range;
1063
+
1064
+ if ((rawPhase && rawPlan) || (!rawPhase && !rawPlan)) {
1065
+ error('select-revert-commits requires exactly one of --phase <id> or --plan <id>', ERROR_REASON.USAGE);
1066
+ return;
1067
+ }
1068
+ if (!range) {
1069
+ error('select-revert-commits requires --range <git-revision-range>', ERROR_REASON.USAGE);
1070
+ return;
1071
+ }
1072
+ // Belt-and-suspenders alongside the --end-of-options guard below (mirrors
1073
+ // src/git-base-branch.cts's isSafeRevisionRef posture for the same class
1074
+ // of argument): refuse an option-shaped range before it ever reaches git.
1075
+ if (typeof range !== 'string' || range.startsWith('-')) {
1076
+ error(`Invalid --range: ${JSON.stringify(range)}`, ERROR_REASON.USAGE);
1077
+ return;
1078
+ }
1079
+
1080
+ let mode;
1081
+ let normalizedId;
1082
+ if (rawPhase) {
1083
+ mode = 'phase';
1084
+ const check = validatePhaseNumber(rawPhase);
1085
+ if (!check.valid) {
1086
+ error(`Invalid --phase: ${check.error}`, ERROR_REASON.USAGE);
1087
+ return;
1088
+ }
1089
+ normalizedId = normalizePaddedPhase(check.normalized);
1090
+ } else {
1091
+ mode = 'plan';
1092
+ // Split on the FIRST `-` only, giving exactly two segments (phase,
1093
+ // plan). This is deliberately NOT a general N-segment composite-id
1094
+ // parser: checked against docs/reference/plan-md.md (frontmatter
1095
+ // `plan` field + `.planning/phases/<NN>-<slug>/<NN>-<PP>-PLAN.md`
1096
+ // layout) and every `--plan` usage in gsd-core/workflows/*.md, the only
1097
+ // plan-id shape this repo documents or emits is the plain two-segment
1098
+ // `NN-MM`. A THREE-segment purely-numeric shape (`NN-MM-PP`) does exist
1099
+ // elsewhere in the codebase, but as a MILESTONE-phase-plan composite
1100
+ // under the (already-deprecated-forward) `milestone-prefixed`
1101
+ // `phase_id_convention` — a different id space than phase-plan, which
1102
+ // `docs/adr/612-bracket-phase-id-convention.md:15` itself documents as
1103
+ // having "no deterministic parse" once a token carries both a
1104
+ // milestone-joined phase AND a plan (the exact ambiguity that
1105
+ // motivated bracket's own `[PROJECT.MM] PP-PP` grammar). No function in
1106
+ // `src/phase-id.cts` validates the phase-PLAN grammar end-to-end
1107
+ // either: `parsePhaseId` explicitly REJECTS a bare `NN-MM` token by
1108
+ // design (its own doc comment lists `02-04` as a rejected "ambiguous /
1109
+ // bare token"), and `getPhaseDirFromPhaseId`'s N-segment dash grammar
1110
+ // is a MILESTONE-phase(-subphase) directory-name constructor — a
1111
+ // structurally similar but semantically different id space (first
1112
+ // segment = milestone, not phase) whose return value doesn't map back
1113
+ // to (phase, plan) anyway. Given no suitable whole-string validator
1114
+ // exists and no real usage needs more than two segments, a `NN-MM-PP`
1115
+ // id is refused today (see
1116
+ // tests/undo-commit-selection.test.cjs "refuses a 3-segment plan id"),
1117
+ // not silently mis-parsed — extending this to N segments is a product
1118
+ // decision for whichever future issue actually needs it. The same
1119
+ // "only the plain two-segment shape is supported" standard applies to
1120
+ // a bracket-style PHASE segment here too: `--plan PROJ-42-01` splits
1121
+ // on the first `-` into phase segment `PROJ` (no internal dash) and
1122
+ // plan segment `42-01`, and `validatePhaseNumber`'s bracket branch
1123
+ // requires the internal dash on ITS side (`PROJ-42`, not bare `PROJ`),
1124
+ // so the phase segment fails and the whole id is refused — bracket-style
1125
+ // ids are supported for `--phase` only, never as a `--plan` segment (no
1126
+ // real usage found; see tests/undo-commit-selection.test.cjs "bracket-
1127
+ // style phase segment in --plan is refused").
1128
+ const dashIdx = rawPlan.indexOf('-');
1129
+ if (dashIdx === -1) {
1130
+ error(`Invalid --plan: "${rawPlan}" (expected NN-MM)`, ERROR_REASON.USAGE);
1131
+ return;
1132
+ }
1133
+ const phasePart = rawPlan.slice(0, dashIdx);
1134
+ const planPart = rawPlan.slice(dashIdx + 1);
1135
+ const phaseCheck = validatePhaseNumber(phasePart);
1136
+ if (!phaseCheck.valid) {
1137
+ error(`Invalid --plan: "${rawPlan}" (phase segment: ${phaseCheck.error})`, ERROR_REASON.USAGE);
1138
+ return;
1139
+ }
1140
+ const planCheck = validatePhaseNumber(planPart);
1141
+ if (!planCheck.valid) {
1142
+ error(`Invalid --plan: "${rawPlan}" (plan segment: ${planCheck.error})`, ERROR_REASON.USAGE);
1143
+ return;
1144
+ }
1145
+ normalizedId = `${normalizePaddedPhase(phaseCheck.normalized)}-${normalizePaddedPhase(planCheck.normalized)}`;
1146
+ }
1147
+
1148
+ const GIT_LOG_TIMEOUT_MS = 15000;
1149
+ const gitResult = execGit(
1150
+ ['log', '--format=%H%x00%s', '--no-merges', '--no-decorate', '--end-of-options', range],
1151
+ { cwd: gitCwd, timeout: GIT_LOG_TIMEOUT_MS },
1152
+ );
1153
+ if (gitResult.exitCode !== 0) {
1154
+ // `execGit` (src/shell-command-projection.cts) exposes `timedOut` on
1155
+ // its SpawnResultOutput — thread it through so a hung `git log` reads
1156
+ // distinctly from an ordinary non-zero exit with no stderr, which
1157
+ // otherwise both surfaced as the same unhelpful "(no stderr)" message.
1158
+ const gitFailureDetail = gitResult.timedOut
1159
+ ? `git log timed out after ${GIT_LOG_TIMEOUT_MS}ms`
1160
+ : `git log failed: ${gitResult.stderr || '(no stderr)'}`;
1161
+ error(
1162
+ `select-revert-commits: ${gitFailureDetail} for range "${range}"`,
1163
+ ERROR_REASON.USAGE,
1164
+ );
1165
+ return;
1166
+ }
1167
+
1168
+ const commits = (gitResult.stdout ? gitResult.stdout.split('\n') : [])
1169
+ .filter((line) => line.length > 0)
1170
+ .map((line) => {
1171
+ const nulIdx = line.indexOf('\0');
1172
+ return nulIdx === -1 ? null : { sha: line.slice(0, nulIdx), subject: line.slice(nulIdx + 1) };
1173
+ })
1174
+ .filter((c) => c !== null);
1175
+
1176
+ const result = selectCommitsByDeclaredScope(commits, normalizedId, mode);
1177
+ const rawLines = result.selected.map((c) => `${c.sha} ${c.subject}`).join('\n');
1178
+ output(result, raw, rawLines);
1179
+ }
1180
+
952
1181
  function routeCommit({ args, cwd, raw, error }) {
953
1182
  const amend = args.includes('--amend');
954
1183
  const noVerify = args.includes('--no-verify');
@@ -1183,7 +1412,18 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1183
1412
  // First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
1184
1413
  if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
1185
1414
  }
1186
- const filename = getProjectInstructionFile(pifRuntime);
1415
+ // A retired runtime id now THROWS rather than resolving (#4709 AC#1).
1416
+ // Map it to the same clean single-line error routeSkillsRoot emits for
1417
+ // an unknown runtime — a CLI must not answer a bad flag value with a
1418
+ // stack trace. The thrown message already names the successor and the
1419
+ // retiring issue, so it is surfaced verbatim.
1420
+ let filename;
1421
+ try {
1422
+ filename = getProjectInstructionFile(pifRuntime);
1423
+ } catch (err) {
1424
+ if (err && err.code === 'GSD_RETIRED_RUNTIME') error(err.message);
1425
+ throw err;
1426
+ }
1187
1427
  process.stdout.write(filename + '\n');
1188
1428
  }
1189
1429
 
@@ -1252,9 +1492,21 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1252
1492
  // claims. `--directory` wins outright when given explicitly;
1253
1493
  // otherwise a supplied `--quick-id` + `--slug` pair derives the
1254
1494
  // canonical permalink the same way `workflows/quick.md` renders it.
1495
+ // #4906 Phase 3 (#4958, evidence #4736): `--status` is the OPTIONAL
1496
+ // widening that closes the last gap between this CLI and
1497
+ // `workflows/quick.md`'s own Step 7c row shapes. `appendQuickTaskRow`
1498
+ // (markdown-table.cjs) has always accepted `status` — the schema's
1499
+ // `Status` column exists precisely for `$VALIDATE_MODE` runs — but
1500
+ // nothing on this CLI surface could set it, so quick.md's
1501
+ // VALIDATE_MODE branch had no escaping-safe path and kept
1502
+ // interpolating `${DESCRIPTION}` into raw markdown (#4736 D1: an
1503
+ // unescaped `|` in a task description permanently rags the table).
1504
+ // Omitted, `status` stays undefined and `appendQuickTaskRow` falls
1505
+ // back to its own `'—'` default — unchanged for every existing
1506
+ // caller (fast.md, quick.md's non-VALIDATE_MODE branch).
1255
1507
  const qtaParsed = parseNamedArgsOrExit(
1256
1508
  qtaArgs,
1257
- { valueFlags: ['task', 'quick-id', 'slug', 'directory'], positionals: 'rest' },
1509
+ { valueFlags: ['task', 'quick-id', 'slug', 'directory', 'status'], positionals: 'rest' },
1258
1510
  error,
1259
1511
  );
1260
1512
  const qtaTask = qtaParsed.task || args[1];
@@ -1265,13 +1517,17 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1265
1517
  const qtaSlug = qtaParsed['slug'] || undefined;
1266
1518
  const qtaDirectory = qtaParsed['directory']
1267
1519
  || (qtaQuickId && qtaSlug ? `[${qtaQuickId}-${qtaSlug}](./quick/${qtaQuickId}-${qtaSlug}/)` : undefined);
1520
+ const qtaStatus = qtaParsed['status'] || undefined;
1268
1521
 
1269
1522
  const statePath = path.join(cwd, '.planning', 'STATE.md');
1270
1523
  if (!fs.existsSync(statePath)) {
1271
1524
  error(`quick-tasks-append: STATE.md not found at ${statePath}`, ERROR_REASON.USAGE);
1272
1525
  }
1273
1526
 
1274
- const date = new Date().toISOString().slice(0, 10);
1527
+ // #4905: an operator-facing date, so the local calendar day (#2136)
1528
+ // through the clock seam, which honors the GSD_NOW_MS pin (#474).
1529
+ const { realClock } = require('./lib/clock.cjs');
1530
+ const date = realClock.localToday();
1275
1531
  const { execGit } = require('./lib/shell-command-projection.cjs');
1276
1532
  const hashResult = execGit(['rev-parse', '--short', 'HEAD'], { cwd });
1277
1533
  const commit = hashResult.exitCode === 0 && hashResult.stdout ? hashResult.stdout : '—';
@@ -1302,6 +1558,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1302
1558
  description: qtaTask,
1303
1559
  date,
1304
1560
  commit,
1561
+ status: qtaStatus,
1305
1562
  quickId: qtaQuickId,
1306
1563
  directory: qtaDirectory,
1307
1564
  });
@@ -2524,6 +2781,63 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2524
2781
  };
2525
2782
  try {
2526
2783
  nodeFs.mkdirSync(sentinelDir, { recursive: true });
2784
+ // Never write through a project-supplied .gsd symlink. Resolve the
2785
+ // directory as well so an ancestor redirect cannot escape this project.
2786
+ if (nodeFs.lstatSync(sentinelDir).isSymbolicLink() ||
2787
+ nodePath.relative(nodeFs.realpathSync(cwd), nodeFs.realpathSync(sentinelDir)) !== '.gsd') {
2788
+ throw new Error('sentinel directory is a symlink or resolves outside the project');
2789
+ }
2790
+ // User projects do not inherit gsd-core's root .gitignore. Ignore this
2791
+ // tool-owned directory locally before writing the first sentinel. An
2792
+ // ordinary ignore-file IO failure must not prevent recording isolation.
2793
+ const ignorePath = nodePath.join(sentinelDir, '.gitignore');
2794
+ try {
2795
+ nodeFs.writeFileSync(ignorePath, '*\n', { flag: 'wx' });
2796
+ } catch (ignoreError) {
2797
+ if (ignoreError.code === 'EEXIST') {
2798
+ // Keep a pre-existing ignore file. A bare '*' already ignores the
2799
+ // whole directory, including this file and the sentinel temp file.
2800
+ // Read through an fd opened without following links where supported;
2801
+ // replace by rename rather than append through the path, so a link
2802
+ // swapped in after the check cannot redirect a write elsewhere.
2803
+ let fd;
2804
+ let ignoreTmpPath;
2805
+ try {
2806
+ if (nodeFs.lstatSync(ignorePath).isSymbolicLink()) {
2807
+ throw new Error('sentinel ignore file is a symlink');
2808
+ }
2809
+ fd = nodeFs.openSync(ignorePath, nodeFs.constants.O_RDONLY | (nodeFs.constants.O_NOFOLLOW || 0));
2810
+ if (!nodeFs.fstatSync(fd).isFile()) throw new Error('sentinel ignore file is not a regular file');
2811
+ const current = nodeFs.readFileSync(fd, 'utf8');
2812
+ nodeFs.closeSync(fd);
2813
+ fd = undefined;
2814
+ if (!current.includes('# gsd-core dispatch sentinel') && !/^\*\r?$/m.test(current)) {
2815
+ const additionalRules = `${current.endsWith('\n') ? '' : '\n'}# gsd-core dispatch sentinel\n` +
2816
+ '/dispatch-isolation-sentinel.json\n' +
2817
+ '/dispatch-isolation-sentinel.json.tmp-*\n' +
2818
+ '/.gitignore\n';
2819
+ ignoreTmpPath = `${ignorePath}.tmp-${process.pid}-${require('crypto').randomBytes(6).toString('hex')}`;
2820
+ nodeFs.writeFileSync(ignoreTmpPath, current + additionalRules, { flag: 'wx' });
2821
+ if (nodeFs.lstatSync(ignorePath).isSymbolicLink()) {
2822
+ throw new Error('sentinel ignore file is a symlink');
2823
+ }
2824
+ nodeFs.renameSync(ignoreTmpPath, ignorePath);
2825
+ ignoreTmpPath = null;
2826
+ }
2827
+ } catch (existingIgnoreError) {
2828
+ if (existingIgnoreError.code === 'ELOOP' || existingIgnoreError.message === 'sentinel ignore file is a symlink') {
2829
+ throw new Error('sentinel ignore file is a symlink');
2830
+ }
2831
+ // Ignore seeding is best-effort; the sentinel is still useful if
2832
+ // this path cannot be read or updated.
2833
+ } finally {
2834
+ if (fd !== undefined) nodeFs.closeSync(fd);
2835
+ if (ignoreTmpPath) {
2836
+ try { nodeFs.unlinkSync(ignoreTmpPath); } catch (_) { /* best-effort cleanup */ }
2837
+ }
2838
+ }
2839
+ }
2840
+ }
2527
2841
  // Atomic write: unique temp file + rename, so a concurrent reader (a
2528
2842
  // guard hook firing mid-write) never observes a partially-written
2529
2843
  // sentinel. Unique per-process+time so concurrent orchestrator-worktree
@@ -2834,8 +3148,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2834
3148
  const coverage = require('./lib/coverage.cjs');
2835
3149
  const options = parseNamedArgsOrExit(args, { valueFlags: ['summary', 'file'], positionals: 2 }, error);
2836
3150
  coverage.cmdClassify(cwd, options, raw);
3151
+ } else if (subcommand === 'complete-session') {
3152
+ const uat = require('./lib/uat.cjs');
3153
+ const uatPath = args[2];
3154
+ const options = parseNamedArgsOrExit(args, { valueFlags: ['message'], positionals: 3 }, error);
3155
+ return uat.cmdUatCompleteSession(cwd, uatPath, { message: options.message }, raw);
2837
3156
  } else {
2838
- error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage', ERROR_REASON.SDK_UNKNOWN_COMMAND);
3157
+ error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage, complete-session', ERROR_REASON.SDK_UNKNOWN_COMMAND);
2839
3158
  }
2840
3159
  }
2841
3160
 
@@ -2933,10 +3252,27 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2933
3252
  }
2934
3253
  loopRuntime = value;
2935
3254
  }
3255
+ // --after-fingerprint <phaseDir> (#5105 R2): gate out verify:post
3256
+ // steps whose declared artifact already exists in phaseDir.
3257
+ let loopAfterFingerprint = undefined;
3258
+ const afterFpEqArg = args.find(arg => arg.startsWith('--after-fingerprint='));
3259
+ const afterFpIdx = args.indexOf('--after-fingerprint');
3260
+ if (afterFpEqArg) {
3261
+ const value = afterFpEqArg.slice('--after-fingerprint='.length).trim();
3262
+ if (!value) error('Missing value for --after-fingerprint', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
3263
+ loopAfterFingerprint = value;
3264
+ } else if (afterFpIdx !== -1) {
3265
+ const value = args[afterFpIdx + 1];
3266
+ if (!value || value.startsWith('--')) {
3267
+ error('Missing value for --after-fingerprint', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
3268
+ }
3269
+ loopAfterFingerprint = value;
3270
+ }
2936
3271
  loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
2937
3272
  configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
2938
3273
  activeCap: loopActiveCap,
2939
3274
  runtime: loopRuntime,
3275
+ afterFingerprint: loopAfterFingerprint,
2940
3276
  });
2941
3277
  } else {
2942
3278
  error(
@@ -3006,6 +3342,12 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3006
3342
  worktreeSafety.cmdWorktreeRecordAgent(cwd, args.slice(2));
3007
3343
  } else if (subcommand === 'reap-orphans') {
3008
3344
  worktreeSafety.cmdWorktreeReapOrphans(cwd);
3345
+ } else if (subcommand === 'worker-record') {
3346
+ worktreeSafety.cmdWorktreeWorkerRecord(cwd, args.slice(2));
3347
+ } else if (subcommand === 'worker-status') {
3348
+ worktreeSafety.cmdWorktreeWorkerStatus(cwd, args.slice(2));
3349
+ } else if (subcommand === 'worker-complete') {
3350
+ worktreeSafety.cmdWorktreeWorkerComplete(cwd, args.slice(2));
3009
3351
  } else if (subcommand === 'base-check') {
3010
3352
  require('./lib/worktree-base-ref.cjs').cmdWorktreeBaseCheck(cwd, args.slice(2));
3011
3353
  } else if (subcommand === 'set-baseref') {
@@ -3013,7 +3355,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3013
3355
  } else if (subcommand === 'create') {
3014
3356
  worktreeSafety.cmdWorktreeCreate(cwd, args.slice(2));
3015
3357
  } else {
3016
- error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref, create', ERROR_REASON.SDK_UNKNOWN_COMMAND);
3358
+ error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref, create, worker-record, worker-status, worker-complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
3017
3359
  }
3018
3360
  }
3019
3361
 
@@ -3472,13 +3814,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3472
3814
  || relPath.startsWith('agents/')
3473
3815
  || relPath.startsWith('commands/');
3474
3816
  if (isFrontmatterSurface) {
3475
- const block = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
3817
+ // The block is the one the one fence owner finds (found while implementing #5105).
3818
+ const found = frontmatter.frontmatterRegion(content);
3476
3819
  const missingFields = [];
3477
- if (!block) {
3820
+ if (!found || !found.terminated) {
3478
3821
  missingFields.push('name', 'description');
3479
3822
  } else {
3480
- if (!/^name:\s*\S/m.test(block[1])) missingFields.push('name');
3481
- if (!/^description:\s*\S/m.test(block[1])) missingFields.push('description');
3823
+ if (!/^name:\s*\S/m.test(found.region)) missingFields.push('name');
3824
+ if (!/^description:\s*\S/m.test(found.region)) missingFields.push('description');
3482
3825
  }
3483
3826
  if (missingFields.length > 0) {
3484
3827
  warnings.push({
@@ -4262,7 +4605,19 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
4262
4605
  }
4263
4606
 
4264
4607
  const roadmapStatus = matchedRow.Status;
4265
- const result = comparePhaseStatus({ stateStatus, roadmapStatus });
4608
+ // #5060: normalize the raw Status cell through the Phase Status
4609
+ // Module's `matchRoadmapStatusCell` owner before comparing ranks —
4610
+ // this recognizes a cell with trailing operator prose after its
4611
+ // leading token (e.g. "Complete — shipped") the same way the
4612
+ // ROADMAP writers/readers already do, rather than requiring the
4613
+ // WHOLE cell to equal a bare token. The raw `roadmapStatus` in the
4614
+ // output stays untouched — only the comparison input changes.
4615
+ const { matchRoadmapStatusCell } = require('./lib/phase-status.cjs');
4616
+ const matched = matchRoadmapStatusCell(roadmapStatus);
4617
+ const result = comparePhaseStatus({
4618
+ stateStatus,
4619
+ roadmapStatus: matched ? matched.token.toLowerCase() : roadmapStatus,
4620
+ });
4266
4621
  output({
4267
4622
  verdict: result.verdict,
4268
4623
  phase,
@@ -4407,6 +4762,8 @@ const HOST_COMMAND_ROUTERS = {
4407
4762
  'smart-entry': routeSmartEntry,
4408
4763
  'check': routeCheck,
4409
4764
  'find-phase': routeFindPhase,
4765
+ // #4906 Phase 5 (#4661): commit-scope selection for /gsd:undo --phase/--plan.
4766
+ 'select-revert-commits': routeSelectRevertCommits,
4410
4767
  'commit': routeCommit,
4411
4768
  'check-commit': routeCheckCommit,
4412
4769
  'commit-docs-guard': routeCommitDocsGuard,
@@ -4484,7 +4841,7 @@ const HOST_COMMAND_ROUTERS = {
4484
4841
  // through. Prototype-pollution-safe: own-property lookup rejects
4485
4842
  // `__proto__`/`constructor`/`prototype` command keys (same guard as
4486
4843
  // dispatchCapabilityCommand).
4487
- async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext }) {
4844
+ async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd }) {
4488
4845
  if (
4489
4846
  command === '__proto__' ||
4490
4847
  command === 'constructor' ||
@@ -4499,7 +4856,13 @@ async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValu
4499
4856
  if (typeof router !== 'function') return false;
4500
4857
  // `await` so async host routers (e.g. capability's install/upgrade ops)
4501
4858
  // complete before runCommand returns; sync routers pass through unchanged.
4502
- await router({ args, cwd, raw, error, defaultValue, workstreamContext });
4859
+ // `invokingCwd` (#4906/#4465): the pre-worktree-remap cwd, for the rare
4860
+ // router (currently only `select-revert-commits`) whose own git operation
4861
+ // must see the INVOKING worktree's HEAD, never the main-worktree remap
4862
+ // every other router legitimately wants via `cwd`. Optional — callers/tests
4863
+ // that omit it (every existing one) leave routers that don't read it
4864
+ // byte-unaffected.
4865
+ await router({ args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd });
4503
4866
  return true; // consumed — don't emit "Unknown command"
4504
4867
  }
4505
4868
 
@@ -4532,7 +4895,7 @@ async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValu
4532
4895
  // keep working. No shell is spawned (argv array) — no injection surface beyond
4533
4896
  // the old `timeout … bash -c "$CMD"`.
4534
4897
  function runWithTimeout(argv) {
4535
- const { spawn } = require('node:child_process');
4898
+ const { spawn, spawnSync } = require('node:child_process');
4536
4899
  const os = require('node:os');
4537
4900
 
4538
4901
  const USAGE = 'Usage: gsd_run run-with-timeout <seconds> [--] <command> [args...]';
@@ -4557,9 +4920,11 @@ function runWithTimeout(argv) {
4557
4920
 
4558
4921
  const isWin = process.platform === 'win32';
4559
4922
  // Detached (own process group) on POSIX so a timeout can reap the WHOLE tree —
4560
- // a bare child.kill() misses grandchildren (e.g. a test runner's workers) and
4561
- // would not actually bound the wall clock. Windows has no POSIX process
4562
- // groups; a direct kill is the best portable option there.
4923
+ // a bare child.kill() misses grandchildren (e.g. a test runner's workers).
4924
+ // Windows has no POSIX process groups and process.kill(-pid) is unsupported
4925
+ // there, so EVERY killTree attempt on Windows tree-kills via
4926
+ // `taskkill /PID <pid> /T /F` while the root is alive (see killTree) — by the
4927
+ // time the direct child exits, its descendants are already orphaned.
4563
4928
  const detached = !isWin && secs > 0;
4564
4929
  const spawnFailureCode = (err) =>
4565
4930
  (err && err.code === 'ENOENT' ? 127 : err && err.code === 'EACCES' ? 126 : 125);
@@ -4583,9 +4948,26 @@ function runWithTimeout(argv) {
4583
4948
  // cap's process-group kill (the wrapped child escapes reap → exit 124 never
4584
4949
  // fires) and risks cmd.exe mis-parsing an arg like `-e "setTimeout(()=>{})"`.
4585
4950
  // Only .cmd/.bat are the CVE-2024-27980 EINVAL cases that require mediation.
4951
+ // #4797: the mediation is NOT hand-rolled here — the private `/d /s /c <cmd>
4952
+ // ...args` copy broke on any shim path containing a space (Node quotes the
4953
+ // argv token; `/s` strips the FIRST and LAST quote of the /c string, so
4954
+ // cmd.exe took the pre-space fragment as the program). projectSpawnInvocation
4955
+ // (the declared single owner, #3411/#3617) wraps the WHOLE command line in
4956
+ // one extra quote pair with windowsVerbatimArguments — the shape that
4957
+ // survives spaces. One behavior delta, accepted: the seam DECLINES mediation
4958
+ // when the target or an arg carries CR/LF (the old block mediated anyway) —
4959
+ // the unmediated spawn of a .cmd then fails EINVAL, loud, at the catch below.
4586
4960
  const winShim = isWin && /\.(cmd|bat)$/i.test(path.basename(cmd));
4587
- const spawnCmd = winShim ? (process.env.ComSpec || 'cmd.exe') : cmd;
4588
- const spawnArgs = winShim ? ['/d', '/s', '/c', cmd, ...cmdArgs] : cmdArgs;
4961
+ let spawnCmd = cmd;
4962
+ let spawnArgs = cmdArgs;
4963
+ let spawnOpts;
4964
+ if (winShim) {
4965
+ const { projectSpawnInvocation } = require('./lib/shell-command-projection.cjs');
4966
+ const inv = projectSpawnInvocation(cmd, cmdArgs);
4967
+ spawnCmd = inv.command;
4968
+ spawnArgs = inv.args;
4969
+ if (inv.windowsVerbatimArguments) spawnOpts = { windowsVerbatimArguments: true };
4970
+ }
4589
4971
  // Node's setTimeout delay is a 32-bit signed ms int; a larger value silently
4590
4972
  // clamps to 1ms → a spurious immediate timeout. Cap the budget (~24.8 days).
4591
4973
  const timerMs = Math.min(Math.round(secs * 1000), 2 ** 31 - 1);
@@ -4596,11 +4978,13 @@ function runWithTimeout(argv) {
4596
4978
  return new Promise((resolve) => {
4597
4979
  let child;
4598
4980
  try {
4599
- // #2667: on win32 `.cmd`/`.bat`/`.exe`, spawn cmd.exe with an explicit argv
4600
- // array (spawnCmd/spawnArgs) rather than the shim directly — preserves the
4601
- // array-only, no-shell-string argv contract. `detached` is always false on
4602
- // win32, so it never co-occurs with the cmd.exe mediation.
4603
- child = spawn(spawnCmd, spawnArgs, { stdio: 'inherit', detached });
4981
+ // #2667: on win32 `.cmd`/`.bat` shims, spawn cmd.exe with an explicit
4982
+ // argv ARRAY rather than the shim directly — preserves the array-only,
4983
+ // no-shell-string argv contract. #4797: the exact argv shape (quote
4984
+ // wrapping, verbatim arguments) is projected by projectSpawnInvocation —
4985
+ // see the block above. `detached` is always false on win32, so it never
4986
+ // co-occurs with the cmd.exe mediation.
4987
+ child = spawn(spawnCmd, spawnArgs, { stdio: 'inherit', detached, ...spawnOpts });
4604
4988
  } catch (err) {
4605
4989
  process.stderr.write(`run-with-timeout: ${cmd}: ${err && err.message ? err.message : 'failed to start'}\n`);
4606
4990
  resolve(spawnFailureCode(err));
@@ -4612,6 +4996,27 @@ function runWithTimeout(argv) {
4612
4996
  if (detached && child.pid) {
4613
4997
  try { process.kill(-child.pid, signal); return; } catch { /* group already gone */ }
4614
4998
  }
4999
+ if (isWin && child.pid) {
5000
+ // #4601: Windows has no POSIX process groups, so the tree kill rides
5001
+ // on `taskkill /T`, which walks the child's descendants the way
5002
+ // `process.kill(-pid)` reaches a POSIX group — this is what bounds
5003
+ // the wall clock when the direct child mediates (cmd.exe /c shim) or
5004
+ // spawns its own children. Deliberately NOT gated on the SIGKILL
5005
+ // stage: child.kill on Windows is TerminateProcess regardless of
5006
+ // signal, so by the time the direct child exits its descendants are
5007
+ // orphaned and no taskkill can reach them — the tree kill must ride
5008
+ // the FIRST attempt, while the root is still alive. /F is required:
5009
+ // without it taskkill posts WM_CLOSE, which a headless CLI never
5010
+ // pumps. Spawned as an argv array per the no-shell-for-argv-array
5011
+ // contract, and bounded — a non-zero/absent status means taskkill
5012
+ // lost a race with an exiting process, and we fall through to the
5013
+ // direct kill so the attempt is never weaker than before.
5014
+ const reap = spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], {
5015
+ encoding: 'utf8',
5016
+ timeout: 15000, // taskkill /T is sub-second in practice; bounded so a wedged taskkill can't hang the gate
5017
+ });
5018
+ if (reap.status === 0) return;
5019
+ }
4615
5020
  child.kill(signal);
4616
5021
  } catch { /* already exited */ }
4617
5022
  };
@@ -4694,7 +5099,7 @@ const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <fiel
4694
5099
  'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, planning, profile-questionnaire, ' +
4695
5100
  'profile-sample, progress, project-instruction-file, prompt-budget, quick-batch, quick-tasks-append, quick-tasks-migrate, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, runtime-identity, scaffold, smart-entry, state, ' +
4696
5101
  'config-set-model-profile, dispatch-capacity, dispatch-isolation, dispatch-should-flatten, inspect-dispatch-isolation, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-agent, resolve-dispatch-type, ' +
4697
- 'resolve-execution, review-lane, skill-manifest, skills-root, stamp-codebase-map, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
5102
+ 'resolve-execution, review-lane, select-revert-commits, skill-manifest, skills-root, stamp-codebase-map, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
4698
5103
  'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
4699
5104
  'Global flags:\n' +
4700
5105
  ' --raw Emit raw output without post-processing\n' +
@@ -4794,6 +5199,28 @@ function resolveMainWorktreeCwd(cwd, deps = {}) {
4794
5199
  return worktreeRoot;
4795
5200
  }
4796
5201
 
5202
+ // ─── #5118: an out-of-set verification status thrown past a command ─────────
5203
+ // ADR-5057 Phase 4 closed the verification-status vocabulary: a
5204
+ // *-VERIFICATION.md whose frontmatter `status` is outside `passed |
5205
+ // gaps_found | human_needed` is a hard error (verification.cjs's
5206
+ // VerificationStatusError). A command that reads one report directly
5207
+ // (`verification status`, `phase uat-passed`, `phase complete`) lets it throw;
5208
+ // aggregates carry it in their own results and fail themselves. Either way
5209
+ // the CLI fails through the owner's `failOnVerificationStatusError` — the
5210
+ // error's own message and its own `.reason`; nothing here restates either.
5211
+ function isVerificationStatusError(err) {
5212
+ return err instanceof verification.VerificationStatusError;
5213
+ }
5214
+
5215
+ async function captureTranslatingVerificationStatus(run) {
5216
+ try {
5217
+ return await captureStdoutSyncWrites(run);
5218
+ } catch (err) {
5219
+ if (isVerificationStatusError(err)) verification.failOnVerificationStatusError(err);
5220
+ throw err;
5221
+ }
5222
+ }
5223
+
4797
5224
  async function main() {
4798
5225
  let args = process.argv.slice(2);
4799
5226
 
@@ -4916,10 +5343,25 @@ async function main() {
4916
5343
  cwd = resolvedProjectDir;
4917
5344
  projectDirExplicit = true;
4918
5345
  }
5346
+ // #4894: verification derives its root from a phase directory, not `cwd`, so
5347
+ // hand it the validated explicit root. Always (re)set — `null` when the flag is
5348
+ // absent — so a prior in-process main() call can never leak an override.
5349
+ projectRoot.setExplicitProjectRoot(projectDirExplicit ? cwd : null);
4919
5350
 
4920
5351
  // Resolve worktree root: in a linked worktree, .planning/ lives in the main worktree.
4921
5352
  // However, in monorepo worktrees where the subdirectory itself owns .planning/,
4922
5353
  // skip worktree resolution — the CWD is already the correct project root.
5354
+ //
5355
+ // #4906/#4465: this remap is a BLANKET rewrite applied to every command, before
5356
+ // `command` is even known — most commands need it (they read `.planning/`, which
5357
+ // in a linked worktree lives only in the main one). A command whose own git
5358
+ // operation must run against the INVOKING worktree's OWN HEAD/branch (never the
5359
+ // main worktree's) needs the PRE-remap value instead — preserved here so it can be
5360
+ // threaded through dispatchHostCommand as `invokingCwd` (see routeSelectRevertCommits,
5361
+ // the first consumer: its `git log` must see the caller's own reachable history, not
5362
+ // main's, or a linked-worktree revert range silently resolves against the wrong repo
5363
+ // state — reproduced in tests/undo-commit-selection-4465.test.cjs's linked-worktree case).
5364
+ const preWorktreeRemapCwd = cwd;
4923
5365
  cwd = resolveMainWorktreeCwd(cwd);
4924
5366
 
4925
5367
  // Optional workstream override for parallel milestone work.
@@ -5044,8 +5486,8 @@ async function main() {
5044
5486
  // themselves JSON text, so resolving late would make every large result a
5045
5487
  // false "output was not JSON" (negative space N8).
5046
5488
  if (pickField) {
5047
- const captured = await captureStdoutSyncWrites(async () => {
5048
- await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
5489
+ const captured = await captureTranslatingVerificationStatus(async () => {
5490
+ await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext, preWorktreeRemapCwd);
5049
5491
  });
5050
5492
  const resolved = resolveAtFileOutput(captured);
5051
5493
  let obj;
@@ -5076,58 +5518,15 @@ async function main() {
5076
5518
  // already resolves this, but the normal path wrote @file: to stdout, forcing
5077
5519
  // every workflow to have a bash-specific `if [[ "$INIT" == @file:* ]]` check
5078
5520
  // that breaks on PowerShell and other non-bash shells.
5079
- const captured = await captureStdoutSyncWrites(async () => {
5080
- await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
5521
+ const captured = await captureTranslatingVerificationStatus(async () => {
5522
+ await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext, preWorktreeRemapCwd);
5081
5523
  });
5082
5524
  fs.writeSync(1, resolveAtFileOutput(captured));
5083
5525
  }
5084
5526
 
5085
- function captureStdoutSyncWrites(run) {
5086
- const originalWriteSync = fs.writeSync;
5087
- let captured = '';
5088
-
5089
- fs.writeSync = function patchedWriteSync(fd, data, ...rest) {
5090
- if (fd === 1) {
5091
- if (Buffer.isBuffer(data)) {
5092
- captured += data.toString('utf-8');
5093
- return data.length;
5094
- }
5095
- const text = String(data);
5096
- captured += text;
5097
- let encoding = 'utf-8';
5098
- if (typeof rest[1] === 'string') encoding = rest[1];
5099
- return Buffer.byteLength(text, encoding);
5100
- }
5101
- return originalWriteSync.call(fs, fd, data, ...rest);
5102
- };
5103
-
5104
- const restore = () => {
5105
- fs.writeSync = originalWriteSync;
5106
- };
5107
-
5108
- return Promise.resolve()
5109
- .then(() => run())
5110
- .then(() => {
5111
- restore();
5112
- return captured;
5113
- }, (err) => {
5114
- restore();
5115
- // The wrapped command may have written to stdout BEFORE it threw — e.g. a --raw
5116
- // command that emits a JSON result/error envelope and THEN throws ExitError to set a
5117
- // non-zero exit code (capability set/disable on an unknown id). Without this flush that
5118
- // captured output is silently discarded (the success-path flush at the call site never
5119
- // runs on a throw). Emit it now; the error still propagates so the exit code is preserved.
5120
- if (captured) {
5121
- try { originalWriteSync.call(fs, 1, resolveAtFileOutput(captured)); } catch { /* best-effort flush */ }
5122
- }
5123
- throw err;
5124
- });
5125
- }
5126
-
5127
- function resolveAtFileOutput(captured) {
5128
- if (!captured.startsWith('@file:')) return captured;
5129
- return fs.readFileSync(captured.slice(6), 'utf-8');
5130
- }
5527
+ // captureStdoutSyncWrites and resolveAtFileOutput moved to src/io.cts
5528
+ // (gsd-core/bin/lib/io.cjs) — the ONE shared pair (#5105 S9, review finding
5529
+ // 7), also used by uat.cts's cmdUatCompleteSession.
5131
5530
 
5132
5531
  // A plain object root/intermediate value — everything else (null, an array,
5133
5532
  // a number, a string, a boolean) is treated as non-object for NAMED-key
@@ -5193,7 +5592,7 @@ function extractField(obj, fieldPath) {
5193
5592
  return { found: true, value: current };
5194
5593
  }
5195
5594
 
5196
- async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null) {
5595
+ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null, invokingCwd) {
5197
5596
  switch (command) {
5198
5597
 
5199
5598
  default: {
@@ -5214,7 +5613,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
5214
5613
  // commands (state, …) routed via their `route*Command` router instead of
5215
5614
  // a hardcoded `case` arm. Tried after capability/overlay dispatch and
5216
5615
  // before the unknown-command error.
5217
- if (await dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext })) break;
5616
+ if (await dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd })) break;
5218
5617
 
5219
5618
  // #3243: if the caller passed a dotted form (e.g. "foo.bar"), the shim
5220
5619
  // above split it so `command` here is the head ("foo"). Use