@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
@@ -28,7 +28,7 @@ const planningScopeMod = require("./planning-scope.cjs");
28
28
  const { SCOPE } = planningScopeMod;
29
29
  // eslint-disable-next-line @typescript-eslint/no-require-imports
30
30
  const roadmapParserModule = require("./roadmap-parser.cjs");
31
- const { stripShippedMilestones, extractCurrentMilestone, extractCurrentMilestoneScoped, replaceInCurrentMilestone, listMilestoneHeadings, scanMilestonePhaseIds, collectTablePhaseRows } = roadmapParserModule;
31
+ const { stripShippedMilestones, extractCurrentMilestone, extractCurrentMilestoneScoped, replaceInCurrentMilestone, listMilestoneHeadings, scanMilestonePhaseIds, collectTablePhaseRows, hasPhaseListingTableHeader } = roadmapParserModule;
32
32
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
33
33
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
34
34
  const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
@@ -45,13 +45,18 @@ const { loadConfig: loadConfigForScope } = configLoaderForScope;
45
45
  const scanPhasePlans = require("./plan-scan.cjs");
46
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
47
  const coreUtils = require("./core-utils.cjs");
48
- const { countMatchedSummaries, findUnsummarizedPlans } = coreUtils;
48
+ const { findUnsummarizedPlans } = coreUtils;
49
49
  // eslint-disable-next-line @typescript-eslint/no-require-imports
50
50
  const frontmatter = require("./frontmatter.cjs");
51
- const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
51
+ const { extractFrontmatter, frontmatterRegion, parseMustHavesBlock } = frontmatter;
52
52
  // eslint-disable-next-line @typescript-eslint/no-require-imports
53
53
  const verificationMod = require("./verification.cjs");
54
- const { isPhaseComplete } = verificationMod;
54
+ const { isPhaseComplete, failOnVerificationStatusError, firstStatusError } = verificationMod;
55
+ // #4906 Phase 2 (#4917/ADR-4910): the PlanningDoc parse -> mutate -> serialize
56
+ // seam, mirroring phase.cts's already-migrated `writePlansField` site.
57
+ const planning_document_cjs_1 = require("./planning-document.cjs");
58
+ // #5060: the Phase Status Module owns the ladder; roadmap.cts is a consumer.
59
+ const phase_status_cjs_1 = require("./phase-status.cjs");
55
60
  // ─── coerceTruthToString ──────────────────────────────────────────────────────
56
61
  /**
57
62
  * Coerce an arbitrary YAML scalar/object into a string for cross-cutting
@@ -170,9 +175,9 @@ function searchPhaseInContent(content, escapedPhase, phaseNum, convention) {
170
175
  .find((candidate) => candidate.level <= currentHeading.level);
171
176
  const sectionEnd = nextHeading ? nextHeading.offset : content.length;
172
177
  const section = content.slice(headerIndex, sectionEnd).trim();
173
- // Extract goal if present (supports both **Goal:** and **Goal**: formats)
174
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
175
- const goal = goalMatch ? goalMatch[1].trim() : null;
178
+ // Extract goal if present (supports both **Goal:** and **Goal**: formats).
179
+ // #4731: multiline-aware — hard-wrapped Goals read past the line break.
180
+ const goal = roadmapParserModule.extractPhaseFieldMultiline(section, 'Goal');
176
181
  // Mode: vertical-MVP slice mode flag. Lowercased + trimmed for canonical
177
182
  // comparison; unrecognized values are preserved verbatim for forward-compat.
178
183
  const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
@@ -209,7 +214,8 @@ function searchPhaseInContent(content, escapedPhase, phaseNum, convention) {
209
214
  * Returns the phase section string if found, null if ROADMAP.md is missing,
210
215
  * or throws if ROADMAP.md read fails.
211
216
  *
212
- * Used by check-command-router (computeUiPlanGate) so ui-plan-gate uses the SAME
217
+ * Used by the shared gate phase context (lookupRoadmapPhase in gate-phase-context.cts, which
218
+ * computeUiPlanGate in gate-ui-plan.cts calls) so ui-plan-gate uses the SAME
213
219
  * phase resolution as `roadmap.get-phase` — not a milestone-only subset.
214
220
  */
215
221
  function getRoadmapPhaseWithFallback(cwd, phaseNum) {
@@ -307,6 +313,14 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
307
313
  error('Failed to read ROADMAP.md: ' + e.message);
308
314
  }
309
315
  }
316
+ /**
317
+ * #4757: the phase records whose `roadmap_complete` was READ from a ROADMAP
318
+ * checkbox. Table rows and headings with no checklist entry also report
319
+ * `roadmap_complete: false`, but there is no checkbox behind it, so they must
320
+ * not be compared against disk status by `checkbox_conflict`. A WeakSet keeps
321
+ * this out of the emitted `phases[]` shape.
322
+ */
323
+ const PHASES_WITH_CHECKBOX = new WeakSet();
310
324
  // #612 composes the convention-qualified sentinel reading with upstream's
311
325
  // canonical legacy sentinel owner. A reserved bracket milestone OR a reserved
312
326
  // phase token excludes the occurrence.
@@ -370,6 +384,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
370
384
  // The caller needs the exact occurrence identities from the same scan that
371
385
  // built `phases`; returning them together also keeps fallback rescans atomic.
372
386
  const detailKeys = new Set();
387
+ let statusError;
373
388
  while ((match = phasePattern.exec(content)) !== null) {
374
389
  const bracketId = G ? match[1] : undefined;
375
390
  const phaseNum = match[1 + G];
@@ -392,15 +407,14 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
392
407
  const nextHeader = restOfContent.match(new RegExp(`\\n {0,3}#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}[A-Za-z]?\\d[\\d.-]*`, 'i'));
393
408
  const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
394
409
  const section = content.slice(sectionStart, sectionEnd);
395
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
396
- const goal = goalMatch ? goalMatch[1].trim() : null;
410
+ const goal = roadmapParserModule.extractPhaseFieldMultiline(section, 'Goal');
397
411
  const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
398
412
  const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
399
413
  const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
400
414
  const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
401
415
  // Check completion on disk
402
416
  const normalized = normalizePhaseName(phaseNum);
403
- let diskStatus = 'no_directory';
417
+ let diskStatus = phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY;
404
418
  let planCount = 0;
405
419
  let summaryCount = 0;
406
420
  let hasContext = false;
@@ -444,33 +458,32 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
444
458
  hasResearch = counts.hasResearch;
445
459
  contextReadError = counts.contextReadError;
446
460
  contextScope = counts.scope;
447
- // ADR-3180 §7.4 (issue #3186, disk-strict, #3168 fix): route "is this
448
- // phase complete" through the canonical owner (`isPhaseComplete`),
449
- // which calls readVerificationStatus UNCONDITIONALLY — plan count is
450
- // NOT a precondition, so a zero-plan phase with a passing
451
- // `*-VERIFICATION.md` reports complete here too, not just via
452
- // `phase.complete`.
453
- // #612: `convention` (a parameter of this function, same thread as
454
- // matchPhaseDirs above) rides into completion so a bracket phase dir
455
- // resolves and scopes its verification report like its legacy twin.
456
- const completionResult = isPhaseComplete(node_path_1.default.join(phasesDir, dirMatch), { convention });
457
- if (completionResult.value.complete)
458
- diskStatus = 'complete';
459
- else if (summaryCount > 0)
460
- diskStatus = 'partial';
461
- else if (planCount > 0)
462
- diskStatus = 'planned';
463
- else if (hasResearch)
464
- diskStatus = 'researched';
465
- else if (hasContext)
466
- diskStatus = 'discussed';
467
- else
468
- diskStatus = 'empty';
461
+ // #5060: route the disk-status ladder through the Phase Status Module's
462
+ // single owner (`phaseStatus`/`toDiskStatus`) rather than a local
463
+ // if/else re-derivation. `convention` still threads through so a
464
+ // bracket phase's verification report resolves and scopes like its
465
+ // legacy twin (#612).
466
+ const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dirMatch), { convention });
467
+ statusError = firstStatusError(statusError, ps.value.statusError);
468
+ diskStatus = (0, phase_status_cjs_1.toDiskStatus)(ps.value.status, { hasResearch, hasContext });
469
469
  }
470
- // Check ROADMAP checkbox status. #3537: padding-tolerant fragment — the
471
- // heading discovered above may use a different padding than the
472
- // summary-bullet checkbox below it (mixed padding inside one ROADMAP is
473
- // legal and seen in real projects).
470
+ // Check ROADMAP checkbox status. #4965: line-anchored, label-first — the
471
+ // old fragment allowed `\s*.*` between the checkbox and the label, which
472
+ // let "Phase N" match ANYWHERE in any checklist line's free-form
473
+ // description (not just this phase's own line), and `content.match`
474
+ // returns only the first hit in the document — so an earlier, unrelated
475
+ // line whose prose merely mentioned "Phase N" shadowed that phase's own
476
+ // checkbox state (both a false negative and a false positive were
477
+ // reported against real ROADMAPs). The label must now be the first thing
478
+ // after the checkbox, tolerating only an optional `**` bold marker and
479
+ // horizontal whitespace — matching `cmdRoadmapAnalyze`'s own
480
+ // `checklistPattern` (the missing-detail-section scan a bit further down
481
+ // in this file), which already hard-requires `\*\*` immediately after the
482
+ // checkbox for the same reason. A decoration the old `.*` used to tolerate
483
+ // by accident — a blockquoted bullet, an emoji/badge before the label —
484
+ // now resolves to `roadmap_complete: false` instead of matching; this is
485
+ // the deliberate, precedent-consistent narrowing (see 10-diagnosis.md's
486
+ // "Negative space" note), not a new gap.
474
487
  //
475
488
  // ADR-3180 §7.4 (disk-strict, #2957, maintainer decision 2026-08-08):
476
489
  // `roadmapComplete` is reported below as metadata ONLY — it carries NO
@@ -481,10 +494,10 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
481
494
  // checkbox — no passing `*-VERIFICATION.md`, plans outstanding — now
482
495
  // reports incomplete; this is the deliberate Tier-2 break (ADR-3180 §7.4
483
496
  // Decision 3).
484
- const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
497
+ const checkboxPattern = new RegExp(`^[ \\t]*-[ \\t]*\\[(x| )\\][ \\t]*(?:\\*\\*)?${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'im');
485
498
  const checkboxMatch = content.match(checkboxPattern);
486
499
  const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
487
- phases.push({
500
+ const headingPhase = {
488
501
  number: phaseNum,
489
502
  name: phaseName,
490
503
  goal,
@@ -498,7 +511,10 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
498
511
  roadmap_complete: roadmapComplete,
499
512
  context_read_error: contextReadError,
500
513
  context_scope: contextScope,
501
- });
514
+ };
515
+ if (checkboxMatch)
516
+ PHASES_WITH_CHECKBOX.add(headingPhase);
517
+ phases.push(headingPhase);
502
518
  }
503
519
  // #3577: markdown-table row declarations join the enumeration — same
504
520
  // enrichment contract as headings (disk counts when the directory exists),
@@ -512,7 +528,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
512
528
  detailKeys.add(occurrenceKey(tr.id));
513
529
  if (seen.has(stripPadA(tr.id)))
514
530
  continue;
515
- const dirMatchA = matchPhaseDirs(phaseDirNames, normalizePhaseName(tr.id)).matches[0];
531
+ const dirMatchA = matchPhaseDirs(phaseDirNames, normalizePhaseName(tr.id), convention).matches[0];
516
532
  let tPlanCount = 0;
517
533
  let tSummaryCount = 0;
518
534
  let tHasContext = false;
@@ -521,8 +537,9 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
521
537
  // #4014 (epic #3473 B4): additive sibling, same default rule as the
522
538
  // heading-declared branch above.
523
539
  let tContextScope = SCOPE.COMPLETE;
540
+ let tDiskStatus = phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY;
524
541
  if (dirMatchA) {
525
- const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatchA));
542
+ const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatchA), convention);
526
543
  tPlanCount = counts.planCount;
527
544
  tSummaryCount = counts.summaryCount;
528
545
  tHasContext = node_fs_1.default.existsSync(node_path_1.default.join(phasesDir, dirMatchA, 'CONTEXT.md'));
@@ -533,6 +550,11 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
533
550
  // an unreadable phase directory is still surfaced via the sibling call.
534
551
  tContextReadError = counts.contextReadError;
535
552
  tContextScope = counts.scope;
553
+ // #5060: route through the Phase Status Module's owner rather than the
554
+ // fixed 'ok' sentinel this row used to emit.
555
+ const tps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dirMatchA), { convention });
556
+ statusError = firstStatusError(statusError, tps.value.statusError);
557
+ tDiskStatus = (0, phase_status_cjs_1.toDiskStatus)(tps.value.status, { hasResearch: tHasResearch, hasContext: tHasContext });
536
558
  }
537
559
  phases.push({
538
560
  number: tr.id,
@@ -544,13 +566,13 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
544
566
  summary_count: tSummaryCount,
545
567
  has_context: tHasContext,
546
568
  has_research: tHasResearch,
547
- disk_status: dirMatchA ? 'ok' : 'no_directory',
569
+ disk_status: tDiskStatus,
548
570
  roadmap_complete: false,
549
571
  context_read_error: tContextReadError,
550
572
  context_scope: tContextScope,
551
573
  });
552
574
  }
553
- return { phases, detailKeys };
575
+ return { phases, detailKeys, statusError };
554
576
  }
555
577
  function cmdRoadmapAnalyze(cwd, raw) {
556
578
  const roadmapPath = planningPaths(cwd).roadmap;
@@ -586,6 +608,8 @@ function cmdRoadmapAnalyze(cwd, raw) {
586
608
  let collected = collectAnalyzePhases(content, phasesDir, _phaseDirNames, convention);
587
609
  let phases = collected.phases;
588
610
  let detailKeys = collected.detailKeys;
611
+ // #5118: carried from every phaseStatus read this command makes.
612
+ let statusError = collected.statusError;
589
613
  // `effectiveContent` is what the downstream checklist scan (missing_details)
590
614
  // iterates. Defaults to the scoped window; switched to the fallback document
591
615
  // when the recovery path below fires, so a phase found via fallback is not
@@ -612,6 +636,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
612
636
  collected = fallbackCollection;
613
637
  phases = collected.phases;
614
638
  detailKeys = collected.detailKeys;
639
+ statusError = firstStatusError(statusError, collected.statusError);
615
640
  effectiveContent = fallbackContent;
616
641
  }
617
642
  }
@@ -624,12 +649,12 @@ function cmdRoadmapAnalyze(cwd, raw) {
624
649
  version: m.version,
625
650
  }));
626
651
  // Find current and next phase
627
- const currentPhase = phases.find(p => p.disk_status === 'planned' || p.disk_status === 'partial') || null;
628
- const nextPhase = phases.find(p => p.disk_status === 'empty' || p.disk_status === 'no_directory' || p.disk_status === 'discussed' || p.disk_status === 'researched') || null;
652
+ const currentPhase = phases.find(p => p.disk_status === phase_status_cjs_1.DISK_STATUS.PLANNED || p.disk_status === phase_status_cjs_1.DISK_STATUS.PARTIAL || p.disk_status === phase_status_cjs_1.DISK_STATUS.EXECUTED) || null;
653
+ const nextPhase = phases.find(p => p.disk_status === phase_status_cjs_1.DISK_STATUS.EMPTY || p.disk_status === phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY || p.disk_status === phase_status_cjs_1.DISK_STATUS.DISCUSSED || p.disk_status === phase_status_cjs_1.DISK_STATUS.RESEARCHED) || null;
629
654
  // Aggregated stats
630
655
  const totalPlans = phases.reduce((sum, p) => sum + p.plan_count, 0);
631
656
  const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
632
- const completedPhases = phases.filter(p => p.disk_status === 'complete').length;
657
+ const completedPhases = phases.filter(p => p.disk_status === phase_status_cjs_1.DISK_STATUS.COMPLETE).length;
633
658
  // Detect phases in summary list without detail sections (malformed ROADMAP).
634
659
  // The char class must allow `-` (not just `.`) so dash-separated milestone-prefixed
635
660
  // IDs (e.g. `1-01`) match the detail-heading scanner above; otherwise they truncate
@@ -658,7 +683,87 @@ function cmdRoadmapAnalyze(cwd, raw) {
658
683
  if (seenChecklistKeys.has(key))
659
684
  continue;
660
685
  seenChecklistKeys.add(key);
661
- checklistOccurrences.push({ token, bracketId });
686
+ // #4899: the checkbox state (`[x]` vs `[ ]`) isn't a capturing group in
687
+ // `checklistPattern` — adding one would shift every downstream `1 + G` /
688
+ // `G` index this loop and `missingDetails` below already depend on.
689
+ // Reading it off the full match text instead is index-safe.
690
+ checklistOccurrences.push({ token, bracketId, checked: /\[x\]/i.test(checklistMatch[0]) });
691
+ }
692
+ // #4899: `collectAnalyzePhases` only recognises `### Phase N:` headings and
693
+ // progress-table rows — the shape `templates/roadmap.md` itself emits
694
+ // (checklist entries only, no detail sections yet) produces `phases: []`
695
+ // even though `checklistOccurrences` above just proved real phases exist.
696
+ // That evidence was computed and then discarded on the way to the final
697
+ // `phases: []` output (ADR-4910 §5/Phase 4). When the heading/table scan
698
+ // found nothing but the checklist did, synthesize a minimal `AnalyzePhase`
699
+ // per non-sentinel checklist occurrence — same enrichment calls
700
+ // (`matchPhaseDirs`, `countPhasePlansAndSummaries`, `isPhaseComplete`) the
701
+ // heading branch of `collectAnalyzePhases` already uses, so a synthesized
702
+ // phase's on-disk status is derived identically, not approximated.
703
+ //
704
+ // `missing_phase_details` is intentionally left untouched by this branch:
705
+ // its contract is "this checklist token has no `### Phase N:` heading or
706
+ // progress-table row", and that remains true for every synthesized token —
707
+ // synthesis fills in `phases[]` from the checklist itself, it does not
708
+ // manufacture a detail section. Removing these tokens from
709
+ // `missing_phase_details` would hide the very malformed-ROADMAP signal
710
+ // ADR-4910 (#4899) treats as evidence to surface, not evidence to launder.
711
+ //
712
+ // Gated on `!hasPhaseListingTableHeader(effectiveContent)` (found by the
713
+ // pre-existing #4480 regression "a status table cannot hide missing phase
714
+ // details"): a `| Phase | Status |`-shaped table with no Name column
715
+ // correctly declares no phases via `collectAnalyzePhases`, but its mere
716
+ // presence signals this roadmap has already moved to table-based tracking —
717
+ // synthesizing phantom phases from checklist entries alongside a
718
+ // deliberately thin tracking table would manufacture phase_count where the
719
+ // roadmap's own structure says "not yet detailed", the same laundering this
720
+ // branch exists to avoid for `missing_phase_details`. #4899's actual target
721
+ // shape (`templates/roadmap.md`: checklist only, no Progress section at
722
+ // all) has no such table and is unaffected.
723
+ if (phases.length === 0 && checklistOccurrences.length > 0 && !hasPhaseListingTableHeader(effectiveContent)) {
724
+ for (const occ of checklistOccurrences) {
725
+ if (isSentinelPhase(occ.token, occ.bracketId))
726
+ continue;
727
+ const normalized = normalizePhaseName(occ.token);
728
+ const dirMatch = matchPhaseDirs(_phaseDirNames, normalized, convention).matches[0];
729
+ let diskStatus = 'no_directory';
730
+ let planCount = 0;
731
+ let summaryCount = 0;
732
+ let hasContext = false;
733
+ let hasResearch = false;
734
+ let contextReadError = null;
735
+ let contextScope = SCOPE.COMPLETE;
736
+ if (dirMatch) {
737
+ const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch), convention);
738
+ planCount = counts.planCount;
739
+ summaryCount = counts.summaryCount;
740
+ hasContext = counts.hasContext;
741
+ hasResearch = counts.hasResearch;
742
+ contextReadError = counts.contextReadError;
743
+ contextScope = counts.scope;
744
+ // #5060: same owner call as the heading branch above.
745
+ const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dirMatch), { convention });
746
+ statusError = firstStatusError(statusError, ps.value.statusError);
747
+ diskStatus = (0, phase_status_cjs_1.toDiskStatus)(ps.value.status, { hasResearch, hasContext });
748
+ }
749
+ const synthesized = {
750
+ number: occ.token,
751
+ name: `Phase ${occ.token}`,
752
+ goal: null,
753
+ mode: null,
754
+ depends_on: null,
755
+ plan_count: planCount,
756
+ summary_count: summaryCount,
757
+ has_context: hasContext,
758
+ has_research: hasResearch,
759
+ disk_status: diskStatus,
760
+ roadmap_complete: occ.checked,
761
+ context_read_error: contextReadError,
762
+ context_scope: contextScope,
763
+ };
764
+ PHASES_WITH_CHECKBOX.add(synthesized);
765
+ phases.push(synthesized);
766
+ }
662
767
  }
663
768
  // The EMITTED value stays the bare token, unchanged: `phases[].number` is a
664
769
  // token under every convention, and `missing_phase_details` is read against
@@ -701,6 +806,29 @@ function cmdRoadmapAnalyze(cwd, raw) {
701
806
  const progressPercent = progressScope === SCOPE.COMPLETE
702
807
  ? (0, phase_lifecycle_cjs_1.clampPercent)(scopedTotalSummaries, scopedTotalPlans)
703
808
  : null;
809
+ // #4757: `current_phase` / `next_phase` / `completed_phases` are disk-
810
+ // authoritative (ADR-3180 §7.4, #2957 — a ticked ROADMAP checkbox carries no
811
+ // machine authority), so a phase whose checkbox disagrees with its disk
812
+ // status can be handed back as the phase to work on while the operator has
813
+ // already ticked it (a backfilled phase with a SUMMARY and no PLAN reads
814
+ // `empty`; an all-summarized phase with no VERIFICATION reads `executed`), or
815
+ // withheld from `completed_phases` while ticked-complete. The selectors stay
816
+ // disk-authoritative; the disagreement is SURFACED here rather than silently
817
+ // resolved either way. Computed after the heading/table/checklist phase
818
+ // enumeration is final so synthesized phases are covered too. Only phases
819
+ // that HAVE a checkbox can conflict: a progress-table row or a heading with
820
+ // no checklist entry reports `roadmap_complete: false` because there is no
821
+ // checkbox to read, not because one is unticked.
822
+ const checkboxConflict = phases
823
+ .filter(p => PHASES_WITH_CHECKBOX.has(p)
824
+ && p.roadmap_complete !== (p.disk_status === phase_status_cjs_1.DISK_STATUS.COMPLETE))
825
+ .map(p => ({
826
+ number: p.number,
827
+ roadmap_complete: p.roadmap_complete,
828
+ disk_status: p.disk_status,
829
+ plan_count: p.plan_count,
830
+ summary_count: p.summary_count,
831
+ }));
704
832
  const result = {
705
833
  milestones,
706
834
  phases,
@@ -728,6 +856,9 @@ function cmdRoadmapAnalyze(cwd, raw) {
728
856
  progress_scope: progressScope,
729
857
  current_phase: currentPhase ? currentPhase.number : null,
730
858
  next_phase: nextPhase ? nextPhase.number : null,
859
+ // #4757: phases whose ROADMAP checkbox disagrees with `disk_status`
860
+ // (`roadmap_complete` vs `disk_status === "complete"`); always an array.
861
+ checkbox_conflict: checkboxConflict,
731
862
  missing_phase_details: missingDetails.length > 0 ? missingDetails : null,
732
863
  // #3184/#3165: distinguishes a genuinely empty milestone (`scope:
733
864
  // "complete"`, `phase_count: 0`) from a window that could not be fully
@@ -735,6 +866,10 @@ function cmdRoadmapAnalyze(cwd, raw) {
735
866
  // were previously output-identical.
736
867
  scope,
737
868
  };
869
+ // #5118: a read-only aggregate over a refused report prints nothing and
870
+ // fails with the error's own reason.
871
+ if (statusError)
872
+ failOnVerificationStatusError(statusError);
738
873
  output(result, raw, undefined);
739
874
  }
740
875
  // ─── cmdRoadmapMilestoneScope ────────────────────────────────────────────────
@@ -778,11 +913,11 @@ function cmdRoadmapMilestoneScope(cwd, raw) {
778
913
  phaseIdConvention = undefined;
779
914
  }
780
915
  if (phaseIdConvention === undefined || phaseIdConvention === null) {
781
- // Bounded per local/no-unbounded-quantifier (#2128): frontmatter is a
782
- // short header block — 4KB is orders of magnitude beyond any real one.
783
- const fmMatch = rawContent.match(/^---\r?\n([\s\S]{0,4000}?)\r?\n---/);
784
- if (fmMatch) {
785
- const kvMatch = fmMatch[1].match(/^phase_id_convention:\s*(.*)$/m);
916
+ // The block is the one the one fence owner finds (`frontmatterRegion`), the same one
917
+ // `roadmap validate` reads, so the two cannot disagree on where it ends (#3641 NEW-1).
918
+ const found = frontmatterRegion(rawContent);
919
+ if (found?.terminated) {
920
+ const kvMatch = found.region.match(/^phase_id_convention:\s*(.*)$/m);
786
921
  if (kvMatch) {
787
922
  const val = kvMatch[1].trim();
788
923
  if (val !== 'null' && val !== '') {
@@ -839,11 +974,20 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
839
974
  if (!phaseInfo) {
840
975
  error(`Phase ${phaseNum} not found`);
841
976
  }
842
- const planCount = phaseInfo.plans.length;
843
- // Count only summaries that pair with a real plan (#1988): stray non-plan
844
- // summaries (30-FIX-CR02-SUMMARY.md, 30-GAPCLOSURE-SUMMARY.md, …) must not
845
- // inflate summary_count and silently flip the phase to Complete.
846
- const summaryCount = countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries);
977
+ const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
978
+ // #5060: ONE count source for this verb — `scanPhasePlans` (the ADR-3180
979
+ // §7.5 count owner) — rather than `phaseInfo.plans.length` /
980
+ // `countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries)`. The two
981
+ // diverge when a plan's frontmatter declares `status: superseded`:
982
+ // scanPhasePlans excludes it (and its summary) from both counts (#2349),
983
+ // the phaseInfo-based counts do not. Using scanPhasePlans everywhere in
984
+ // this verb — the `planCount === 0` decline, the `Plans Complete` cell,
985
+ // the `Plans:` line, the checkbox gate, and the JSON output — keeps every
986
+ // one of those in agreement with the Status-cell ladder below, which
987
+ // already read scanPhasePlans's counts.
988
+ const coverageScan = scanPhasePlans(phaseDir);
989
+ const planCount = coverageScan.planCount;
990
+ const summaryCount = coverageScan.summaryCount;
847
991
  if (planCount === 0) {
848
992
  declineNoOp(raw, 'updated', 'No plans found', 'roadmap update-plan-progress skipped — no plans found for this phase. ROADMAP.md was left unchanged.', { plan_count: 0, summary_count: 0 });
849
993
  return;
@@ -860,12 +1004,15 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
860
1004
  // agrees with `roadmap analyze` / `init manager` / `phase complete` for
861
1005
  // the same phase (ADR-3180 §7.4's headline: one predicate for the read
862
1006
  // path and the write path).
863
- const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
864
1007
  // ADR-3180 §7.4 read/write-path symmetry with the threaded site at ~583:
865
1008
  // thread convention here too, so this write path's completion reading
866
1009
  // agrees with the read path's under the bracket convention.
867
1010
  const convention = resolvePhaseIdConvention(cwd);
868
1011
  const completionResult = isPhaseComplete(phaseDir, { convention });
1012
+ // #5118: no write before the error — a report whose `status` is outside the
1013
+ // closed set fails this writer before ROADMAP.md is touched.
1014
+ if (completionResult.value.statusError)
1015
+ failOnVerificationStatusError(completionResult.value.statusError);
869
1016
  const verificationResult = completionResult.value.verification;
870
1017
  // #2648 precedent, applied at this write site (ADR-3180 §7.4 / #3186):
871
1018
  // `isPhaseComplete` deliberately carries NO plan-count precondition — the
@@ -881,14 +1028,29 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
881
1028
  // (phase.cts:~1995, #2648: "a coverage gate that passes when it cannot
882
1029
  // read the plans is no gate at all"), composed explicitly here rather than
883
1030
  // folded into the predicate: complete AND all plans executed.
884
- const coverageScan = scanPhasePlans(phaseDir);
1031
+ // #5060: `coverageScan` (declared above, before the `planCount === 0`
1032
+ // decline) is now this verb's ONE count source — the `Plans Complete`
1033
+ // cell, the `Plans:` line, the checkbox gate, the JSON output, and the
1034
+ // Status-cell ladder below all read `planCount`/`summaryCount` from it, so
1035
+ // a phase with a `status: superseded` plan (#2349) reports the same
1036
+ // counts everywhere instead of diverging between the ladder and the rest
1037
+ // of this verb's output.
885
1038
  const unsummarizedPlans = findUnsummarizedPlans(coverageScan.planFiles, coverageScan.summaryFiles);
886
1039
  const isComplete = completionResult.value.complete && unsummarizedPlans.length === 0;
887
1040
  // #3057 B3: routing above is unchanged (an indeterminate staleness check
888
1041
  // still routes as if nothing were stale) — this only makes the fact visible
889
1042
  // to whatever reads this command's JSON output.
890
- const verificationStaleCheckIndeterminate = verificationResult.staleCheckIndeterminate === true;
891
- const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
1043
+ const verificationStaleCheckIndeterminate = 'staleCheckIndeterminate' in verificationResult && verificationResult.staleCheckIndeterminate === true;
1044
+ // #5060: route the Status-cell token through the Phase Status Module's
1045
+ // owner ladder rather than a local if/else, over the same `planCount`/
1046
+ // `summaryCount` (from `coverageScan`) every other output in this verb
1047
+ // uses.
1048
+ const status = (0, phase_status_cjs_1.toRoadmapStatusCell)((0, phase_status_cjs_1.phaseStatusFromFacts)({
1049
+ planCount,
1050
+ summaryCount,
1051
+ complete: isComplete,
1052
+ verificationStatus: verificationResult.status,
1053
+ }));
892
1054
  const today = clock_cjs_1.realClock.localToday();
893
1055
  if (!node_fs_1.default.existsSync(roadmapPath)) {
894
1056
  declineNoOp(raw, 'updated', 'ROADMAP.md not found', 'roadmap update-plan-progress skipped — ROADMAP.md not found.', { plan_count: planCount, summary_count: summaryCount });
@@ -923,10 +1085,19 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
923
1085
  let roadmapContent = originalContent;
924
1086
  const phasePattern = phaseMarkdownRegexSource(phaseNum);
925
1087
  // #4247: ONE local source for the ATX phase-heading anchor that every
926
- // section-scoped writer below (`planCountPattern`,
1088
+ // section-scoped writer below (`planSectionPattern`,
927
1089
  // `insertRowsPatternA|B`) starts with — extracted so the target-detection
928
1090
  // gate below reads the SAME grammar the writers anchor on, and a future
929
1091
  // edit to one cannot drift from the other three copies.
1092
+ // #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
1093
+ // this write-path anchor was never one of the five ADR-4910 §8 call sites
1094
+ // this phase owns (init.cts/milestone.cts's buildPhaseHeadingScanRegex
1095
+ // sites); a direct any-bracket selector call here would have added a
1096
+ // fourth uncounted ANY_BRACKET consumer to roadmap.cts's pinned
1097
+ // tests/adr-612-bracket-heading-selection.test.cjs census. Left
1098
+ // bracket-blind deliberately, matching this call's pre-#4984 behavior;
1099
+ // folding it into the census is Phase 6 work.
1100
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
930
1101
  const phaseHeadingAnchor = `#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])`;
931
1102
  // #4247: target detection runs against the ORIGINAL content's active
932
1103
  // (post-</details>) region — the same milestone scoping every writer
@@ -936,7 +1107,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
936
1107
  const gateActiveRegion = gateDetailsClose === -1
937
1108
  ? originalContent
938
1109
  : originalContent.slice(gateDetailsClose + '</details>'.length);
939
- // Heading target: the exact grammar `planCountPattern` /
1110
+ // Heading target: the exact grammar `planSectionPattern` /
940
1111
  // `insertRowsPatternA|B` anchor on (an ATX phase heading for this phase).
941
1112
  const headingTargetFound = new RegExp(phaseHeadingAnchor, 'i').test(gateActiveRegion);
942
1113
  // Checklist target: when the phase is complete, its own checklist bullet
@@ -970,7 +1141,29 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
970
1141
  text = plansResult.value;
971
1142
  tableRowFound = true;
972
1143
  }
973
- const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${status.padEnd(11)}`);
1144
+ // #4925: the verb owns the Status cell's leading status TOKEN only — the
1145
+ // same split #2853/#3584 made for the `Plans:` line below. It used to
1146
+ // overwrite the whole cell with ` ${status.padEnd(11)}` (no trailing
1147
+ // space), discarding any operator prose kept after the token. Three arms:
1148
+ // 1. empty or a dash placeholder → write the token, padded exactly as
1149
+ // phase complete writes ` Complete ` (phase.cts).
1150
+ // 2. a leading status token → rewrite the token only and keep the prose
1151
+ // after it; an unchanged token leaves the cell byte-identical.
1152
+ // 3. freeform prose with no leading token → operator-owned, untouched.
1153
+ // #5060: routed through the Phase Status Module's `matchRoadmapStatusCell`
1154
+ // owner rather than a local statusTokenRe. `ROADMAP_STATUS_TOKEN.DEFERRED`
1155
+ // is now a recognized token (the old regex never matched "deferred" at
1156
+ // all, so a Deferred cell fell through to arm 3 unchanged) — the DEFERRED
1157
+ // guard below preserves that same "leave it alone" outcome explicitly.
1158
+ const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', (current) => {
1159
+ if (current === '' || /^[-–—]$/.test(current))
1160
+ return ` ${status.padEnd(11)} `;
1161
+ const m = (0, phase_status_cjs_1.matchRoadmapStatusCell)(current);
1162
+ if (!m || m.token === phase_status_cjs_1.ROADMAP_STATUS_TOKEN.DEFERRED || m.text === status)
1163
+ return current;
1164
+ const rest = current.slice(m.text.length);
1165
+ return rest === '' ? ` ${status.padEnd(11)} ` : ` ${(0, markdown_table_cjs_1.escapeCell)(status + rest)} `;
1166
+ });
974
1167
  if (statusResult.ok) {
975
1168
  text = statusResult.value;
976
1169
  tableRowFound = true;
@@ -987,7 +1180,9 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
987
1180
  if (isComplete) {
988
1181
  return dateShape.test(current.trim()) ? current : ` ${today} `;
989
1182
  }
990
- return ' ';
1183
+ // #4925: only a stale ISO completion date — this verb's own stamp — is
1184
+ // cleared; a `-` placeholder or operator text stays byte-identical.
1185
+ return dateShape.test(current) ? ' ' : current;
991
1186
  });
992
1187
  if (completedResult.ok) {
993
1188
  text = completedResult.value;
@@ -1033,7 +1228,14 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1033
1228
  // `_match` unchanged. An untouched first line cannot orphan its own
1034
1229
  // continuation on the next line, since the pattern never spans past
1035
1230
  // `\n` in the first place.
1036
- const planCountPattern = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)(\\d+\\s*\\/\\s*\\d+\\s+plans(?:\\s+(?:complete|executed))?|\\d+\\s+plans?)?([^\\r\\n]*)`, 'i');
1231
+ // #4906 Phase 2 (#4917/ADR-4910): migrated off the one-capture-group
1232
+ // regex that replaced to end of line onto the PlanningDoc `boldField`
1233
+ // write seam. `planSectionPattern` scopes the match to phase N's OWN
1234
+ // detail section (heading + body up to the next heading) — the same
1235
+ // window `phaseHeadingAnchor`'s siblings anchor on — and the callback
1236
+ // below runs parse -> classify -> (maybe) mutate -> serialize entirely
1237
+ // within that section text, mirroring phase.cts's `writePlansField`.
1238
+ const planSectionPattern = new RegExp(`${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*`, 'i');
1037
1239
  const planCountText = isComplete
1038
1240
  ? `${summaryCount}/${planCount} plans complete`
1039
1241
  : `${summaryCount}/${planCount} plans executed`;
@@ -1042,24 +1244,107 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1042
1244
  // gsd-core/templates/roadmap.md actually ships, not to "anything in
1043
1245
  // brackets" — a bracketed human annotation like `[Deferred pending
1044
1246
  // re-scope]` is structurally bracketed too but carries none of this
1045
- // wording, so it correctly falls through to arm 3 untouched.
1247
+ // wording, so it correctly falls through to arm 3 untouched. Kept as a
1248
+ // caller-side classifier (NOT seam grammar) reused below against the
1249
+ // `boldField` node's own parsed `value`.
1046
1250
  const isTemplatePlaceholder = (value) => {
1047
1251
  const trimmed = value.trim();
1048
1252
  return /^\[\s*Number of plans\b[\s\S]*\]$/i.test(trimmed);
1049
1253
  };
1050
- roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, (_match, label, existingCount, trailing) => {
1051
- if (existingCount) {
1052
- // Arm 1: real count token — rewrite it, preserve the trailing annotation.
1053
- return `${label}${planCountText}${trailing}`;
1254
+ // Positive detectors for the two "real count token" shapes (#2853 /
1255
+ // #3584 Finding B): a fraction count (`N/M plans complete|executed`) or a
1256
+ // bare singular/plural count (`N plan`/`N plans`, the fresh single-plan
1257
+ // template shape at gsd-core/templates/roadmap.md:62). Either one is an
1258
+ // existing count token to overwrite (arm 1), never template placeholder
1259
+ // (arm 2) or freeform prose (arm 3).
1260
+ // #4906 regression fix: PREFIX match (not full-string) — a real count
1261
+ // token may have a glued-on annotation with no ` — ` separator (e.g.
1262
+ // `0/1 plans executed (11-16 are gap closure from VERIFICATION)`), which
1263
+ // `parseBoldFieldLine`'s em-dash-only trailing-content split leaves
1264
+ // entirely inside `value` (TRAILING_SEPARATOR_RE in planning-document.cts
1265
+ // is unchanged and correct — this is a caller-side classification fix,
1266
+ // not a seam fix). Returns the matched prefix length, or -1 if no match.
1267
+ const fractionCountPrefixLength = (value) => {
1268
+ const m = value.match(/^\d+\s*\/\s*\d+\s+plans(?:\s+(?:complete|executed))?/i);
1269
+ return m ? m[0].length : -1;
1270
+ };
1271
+ const bareCountPrefixLength = (value) => {
1272
+ const m = value.match(/^\d+\s+plans?/i);
1273
+ return m ? m[0].length : -1;
1274
+ };
1275
+ roadmapContent = replaceInCurrentMilestone(roadmapContent, planSectionPattern, (sectionText) => {
1276
+ const parsed = (0, planning_document_cjs_1.parsePlanningDoc)(sectionText, 'ROADMAP.md');
1277
+ if (!parsed.ok) {
1278
+ // Unreadable section (e.g. an unterminated frontmatter fence) —
1279
+ // leave it byte-identical rather than throwing.
1280
+ return sectionText;
1054
1281
  }
1055
- if (isTemplatePlaceholder(trailing)) {
1056
- // Arm 2: fresh-template placeholder — replace with the count.
1057
- return `${label}${planCountText}`;
1282
+ const fieldId = (0, planning_document_cjs_1.findField)(parsed.value, 'Plans');
1283
+ if (!fieldId) {
1284
+ // #4906 regression (#1163, caught by gsd-test): a hand-edited or
1285
+ // pre-template ROADMAP.md may carry a PLAIN (non-bold) `Plans:` line
1286
+ // rather than the canonical `**Plans**:`/`**Plans:**` bold field.
1287
+ // BOLD_FIELD_RE is deliberately bold-only (widening it to any bare
1288
+ // `Label:` would register ordinary prose like "Note: see below" as a
1289
+ // spurious field seam-wide) — this is domain knowledge about ONE
1290
+ // field's legacy tolerated shape, the same class of thing
1291
+ // `isTemplatePlaceholder` above already keeps caller-side rather
1292
+ // than seam grammar, so the fallback lives here, not in
1293
+ // planning-document.cts.
1294
+ const plainMatch = sectionText.match(/^([ \t]*)Plans:([ \t]*)([^\r\n]*)$/m);
1295
+ if (!plainMatch) {
1296
+ // No `**Plans:**`/`**Plans**:`/plain `Plans:` line in this phase's
1297
+ // own section — nothing to write; not a failure (mirrors the old
1298
+ // regex's silent no-match no-op).
1299
+ return sectionText;
1300
+ }
1301
+ const [whole, indent, spacing, plainValue] = plainMatch;
1302
+ const plainFractionLen = fractionCountPrefixLength(plainValue);
1303
+ const plainBareLen = bareCountPrefixLength(plainValue);
1304
+ const plainCountPrefixLen = plainFractionLen >= 0 ? plainFractionLen : plainBareLen;
1305
+ if (plainCountPrefixLen < 0 && !isTemplatePlaceholder(plainValue)) {
1306
+ // Arm 3: freeform prose, TBD, a bracketed human annotation, or an
1307
+ // empty value — leave the section exactly as it was.
1308
+ return sectionText;
1309
+ }
1310
+ const plainSuffix = plainCountPrefixLen >= 0 ? plainValue.slice(plainCountPrefixLen) : '';
1311
+ const newPlainLine = `${indent}Plans:${spacing}${planCountText}${plainSuffix}`;
1312
+ const start = plainMatch.index ?? sectionText.indexOf(whole);
1313
+ return sectionText.slice(0, start) + newPlainLine + sectionText.slice(start + whole.length);
1314
+ }
1315
+ const current = (0, planning_document_cjs_1.readNode)(parsed.value, fieldId);
1316
+ if (!current.ok) {
1317
+ return sectionText;
1318
+ }
1319
+ const currentValue = current.value;
1320
+ const fractionPrefixLen = fractionCountPrefixLength(currentValue);
1321
+ const barePrefixLen = bareCountPrefixLength(currentValue);
1322
+ const countPrefixLen = fractionPrefixLen >= 0 ? fractionPrefixLen : barePrefixLen;
1323
+ if (countPrefixLen < 0 && !isTemplatePlaceholder(currentValue)) {
1324
+ // Arm 3: freeform prose, TBD, a bracketed human annotation, or an
1325
+ // empty value — leave the section exactly as it was.
1326
+ return sectionText;
1058
1327
  }
1059
- // Arm 3: freeform prose, TBD, a bracketed human annotation, a wrapped
1060
- // sentence's first line, or an empty value — leave the line exactly as
1061
- // it was.
1062
- return _match;
1328
+ // Arm 1 (real count token, possibly with a glued-on no-separator
1329
+ // annotation re-attached verbatim as `suffix`) or arm 2 (fresh-template
1330
+ // placeholder, whole value replaced): write the computed count.
1331
+ // `setFieldValue`'s valueSpan/trailingSpan split additionally preserves
1332
+ // any EM-DASH-separated trailing annotation (#2853) automatically — no
1333
+ // separate "preserve trailing" branch needed for that shape.
1334
+ const suffix = countPrefixLen >= 0 ? currentValue.slice(countPrefixLen) : '';
1335
+ const newValueToWrite = planCountText + suffix;
1336
+ const staged = (0, planning_document_cjs_1.setFieldValue)(parsed.value, fieldId, newValueToWrite);
1337
+ if (!staged.ok) {
1338
+ return sectionText;
1339
+ }
1340
+ const out = (0, planning_document_cjs_1.serialize)(staged.value);
1341
+ if (!out.ok) {
1342
+ // `hasUnreadableNodes` refusal (ADR-4910 amendment) — a ragged
1343
+ // SIBLING node elsewhere in this same section refuses the whole
1344
+ // splice. Never throw; leave the section unchanged.
1345
+ return sectionText;
1346
+ }
1347
+ return out.value;
1063
1348
  });
1064
1349
  // If complete: check checkbox
1065
1350
  if (isComplete) {
@@ -1067,10 +1352,17 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1067
1352
  roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
1068
1353
  }
1069
1354
  // Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
1070
- for (const summaryFile of phaseInfo.summaries) {
1071
- const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
1072
- if (!planId)
1073
- continue;
1355
+ // #4741: tick only plans the phase's own COUNT still counts. `plans` is the
1356
+ // superseded-filtered set (#2349 via scanPhasePlans) while `summaries` is
1357
+ // the raw *-SUMMARY.md listing — a superseded plan can carry a SUMMARY
1358
+ // (e.g. `status: halted`), and ticking it read as "executed" right under a
1359
+ // count line that excludes it. The prefix match mirrors the checkbox regex
1360
+ // below (rows match by planId prefix, which the PLAN-01.md naming shape
1361
+ // relies on), so non-superseded plans tick exactly as before.
1362
+ const tickableSummaries = phaseInfo.summaries
1363
+ .map((summaryFile) => ({ summaryFile, planId: summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '') }))
1364
+ .filter(({ planId }) => planId !== '' && phaseInfo.plans.some((planFile) => planFile.startsWith(planId)));
1365
+ for (const { planId } of tickableSummaries) {
1074
1366
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
1075
1367
  const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
1076
1368
  roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
@@ -1094,9 +1386,26 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1094
1386
  // Detection is scoped to the active region so a plan that appears in an
1095
1387
  // archived <details> block is still correctly detected as missing from the
1096
1388
  // active milestone section.
1389
+ //
1390
+ // #4786: a plan row may be written WITH the `-PLAN.md` suffix (canonical
1391
+ // template form) or WITHOUT it (hand-written form: `- [x] 659-01 — desc`).
1392
+ // The tick loop above keys on the bare planId stem with a PREFIX match
1393
+ // (pre-existing, out of scope here), while detection required the full
1394
+ // `-PLAN.md` filename — so every suffix-less row was counted missing and
1395
+ // the insertion fired BESIDE the recognized list — 32 checkbox lines for
1396
+ // 16 plans, exit 0. The stem arm below accepts the bare id only up to a
1397
+ // boundary (whitespace / `:` / dashes / `-PLAN.md` / `.md` / `**` / `)`),
1398
+ // so `5-011` never satisfies `5-01`. Deliberately NOT in the boundary set:
1399
+ // `.` — a dotted sub-id (`5-01.5`) is a real distinct plan, and counting
1400
+ // its row as 5-01's presence would suppress a genuine insertion.
1097
1401
  const missingPlans = phaseInfo.plans.filter((planFile) => {
1098
1402
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planFile);
1099
- return !new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${planEscaped}`, 'i').test(activeRegion);
1403
+ if (new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${planEscaped}`, 'i').test(activeRegion))
1404
+ return false;
1405
+ const stem = planFile.replace(/-PLAN\.md$/i, '');
1406
+ const stemEscaped = (0, pattern_cjs_1.escapeRegex)(stem);
1407
+ const stemPresent = new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${stemEscaped}(?=$|\\s|:|—|–|-PLAN\\.md|\\.md|\\*\\*|\\))`, 'i').test(activeRegion);
1408
+ return !stemPresent;
1100
1409
  });
1101
1410
  if (missingPlans.length > 0) {
1102
1411
  // Insert missing plan checklist rows (#1163). We prefer to anchor to the
@@ -1139,10 +1448,9 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1139
1448
  if (withRows !== roadmapContent) {
1140
1449
  roadmapContent = withRows;
1141
1450
  // Mark any newly-inserted rows that already have summaries as complete
1142
- for (const summaryFile of phaseInfo.summaries) {
1143
- const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
1144
- if (!planId)
1145
- continue;
1451
+ // (#4741: same superseded-filtered tick list as the loop above — a
1452
+ // pre-existing superseded row must stay unchecked on this path too).
1453
+ for (const { planId } of tickableSummaries) {
1146
1454
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
1147
1455
  const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
1148
1456
  roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
@@ -1299,6 +1607,15 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
1299
1607
  // #3537: padding-tolerant fragment so the caller's resolved padded id
1300
1608
  // matches un-padded ROADMAP headings.
1301
1609
  const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
1610
+ // #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
1611
+ // this site was never one of the five ADR-4910 §8 call sites this phase
1612
+ // owns (init.cts/milestone.cts's buildPhaseHeadingScanRegex sites); a
1613
+ // direct any-bracket selector call here would have added a fifth
1614
+ // uncounted ANY_BRACKET consumer to roadmap.cts's pinned
1615
+ // tests/adr-612-bracket-heading-selection.test.cjs census. Left
1616
+ // bracket-blind deliberately, matching this call's pre-#4984 behavior;
1617
+ // folding it into the census is Phase 6 work.
1618
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
1302
1619
  const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*)`, 'i');
1303
1620
  const phaseMatch = content.match(phaseHeaderPattern);
1304
1621
  if (!phaseMatch)