@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -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");
@@ -52,6 +52,9 @@ const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
52
52
  // eslint-disable-next-line @typescript-eslint/no-require-imports
53
53
  const verificationMod = require("./verification.cjs");
54
54
  const { isPhaseComplete } = 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");
55
58
  // ─── coerceTruthToString ──────────────────────────────────────────────────────
56
59
  /**
57
60
  * Coerce an arbitrary YAML scalar/object into a string for cross-cutting
@@ -89,7 +92,7 @@ function coerceTruthToString(t) {
89
92
  return '';
90
93
  }
91
94
  // ─── countPhasePlansAndSummaries ──────────────────────────────────────────────
92
- function countPhasePlansAndSummaries(phaseDir) {
95
+ function countPhasePlansAndSummaries(phaseDir, convention) {
93
96
  const { planCount, summaryCount } = scanPhasePlans(phaseDir);
94
97
  // hasContext and hasResearch are not plan-scan concerns — read the directory
95
98
  // once and share the listing for all non-plan metadata that cmdRoadmapAnalyze needs.
@@ -112,7 +115,9 @@ function countPhasePlansAndSummaries(phaseDir) {
112
115
  // summaryCount above stay on scanPhasePlans's own unscoped listing since a
113
116
  // PLAN/SUMMARY leading number is a plan sequence number, not a phase
114
117
  // number. Mirrors core-utils.cts's getPhaseFileStats.
115
- const scopedFiles = scopeToPhase(phaseFiles, node_path_1.default.basename(phaseDir));
118
+ // #612: `convention` threaded from the one caller (which already threads it
119
+ // into matchPhaseDirs) so a bracket dir scopes by its real token.
120
+ const scopedFiles = scopeToPhase(phaseFiles, node_path_1.default.basename(phaseDir), convention);
116
121
  return {
117
122
  planCount,
118
123
  summaryCount,
@@ -168,9 +173,9 @@ function searchPhaseInContent(content, escapedPhase, phaseNum, convention) {
168
173
  .find((candidate) => candidate.level <= currentHeading.level);
169
174
  const sectionEnd = nextHeading ? nextHeading.offset : content.length;
170
175
  const section = content.slice(headerIndex, sectionEnd).trim();
171
- // Extract goal if present (supports both **Goal:** and **Goal**: formats)
172
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
173
- const goal = goalMatch ? goalMatch[1].trim() : null;
176
+ // Extract goal if present (supports both **Goal:** and **Goal**: formats).
177
+ // #4731: multiline-aware — hard-wrapped Goals read past the line break.
178
+ const goal = roadmapParserModule.extractPhaseFieldMultiline(section, 'Goal');
174
179
  // Mode: vertical-MVP slice mode flag. Lowercased + trimmed for canonical
175
180
  // comparison; unrecognized values are preserved verbatim for forward-compat.
176
181
  const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
@@ -349,8 +354,17 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
349
354
  // #3036: widen the id capture to accept non-numeric-leading ids (e.g. B7, P0.3-2)
350
355
  // that get-phase/execute-phase already resolve. An optional leading letter prefix
351
356
  // ([A-Za-z]?) covers letter-prefixed ids without breaking numeric-leading ones.
357
+ // #4478: line-anchored (`^ {0,3}`, `/m`) — unanchored, `#{2,4}` matched a
358
+ // `### Phase N:`-shaped mention ANYWHERE `exec()`'s scan reached: mid-sentence
359
+ // prose, inside a blockquote, inside an inline code span (backtick-quoted on
360
+ // the same line, not a fenced block `tokenizeHeadings` would exclude). Any
361
+ // such line minted a phantom phase entry, inflating phase_count and able to
362
+ // collide on a phase NUMBER with a real heading nearby. `{0,3}` leading
363
+ // spaces mirrors `tokenizeHeadings`'s own CommonMark ATX-heading tolerance
364
+ // (src/markdown-sectionizer.cts:453) so a legitimately-indented heading that
365
+ // matched before this fix still matches after it.
352
366
  // phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
353
- const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
367
+ const phasePattern = new RegExp(`^ {0,3}#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gim');
354
368
  // The capturing intro inserts the bracket id at group 1 only under the
355
369
  // bracket convention; the token and name shift by the same offset.
356
370
  const G = convention === 'bracket' ? 1 : 0;
@@ -372,11 +386,16 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
372
386
  // #3691: `\d` → `\d[\d.]*` so decimal phase headings (e.g. `### Phase 02.3:`) are
373
387
  // recognised as section boundaries. #3036: `[A-Za-z]?\d` so non-numeric-leading ids
374
388
  // (e.g. B7) are also recognised.
375
- const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}[A-Za-z]?\\d[\\d.-]*`, 'i'));
389
+ // #4478 follow-up (independent code review on this same fix): ` {0,3}` after
390
+ // the literal `\n` mirrors phasePattern's own new leading-space tolerance
391
+ // above -- without it, a legitimately-indented (1-3 space) NEXT phase
392
+ // heading was invisible to this boundary lookup, letting the PRIOR phase's
393
+ // goal/mode/depends_on extraction bleed across the section boundary into
394
+ // the next phase's own body.
395
+ 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'));
376
396
  const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
377
397
  const section = content.slice(sectionStart, sectionEnd);
378
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
379
- const goal = goalMatch ? goalMatch[1].trim() : null;
398
+ const goal = roadmapParserModule.extractPhaseFieldMultiline(section, 'Goal');
380
399
  const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
381
400
  const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
382
401
  const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
@@ -420,7 +439,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
420
439
  // convention into that owner rather than reviving the primitive `.find()`.
421
440
  const dirMatch = matchPhaseDirs(phaseDirNames, normalized, convention).matches[0];
422
441
  if (dirMatch) {
423
- const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch));
442
+ const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch), convention);
424
443
  planCount = counts.planCount;
425
444
  summaryCount = counts.summaryCount;
426
445
  hasContext = counts.hasContext;
@@ -433,7 +452,10 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
433
452
  // NOT a precondition, so a zero-plan phase with a passing
434
453
  // `*-VERIFICATION.md` reports complete here too, not just via
435
454
  // `phase.complete`.
436
- const completionResult = isPhaseComplete(node_path_1.default.join(phasesDir, dirMatch));
455
+ // #612: `convention` (a parameter of this function, same thread as
456
+ // matchPhaseDirs above) rides into completion so a bracket phase dir
457
+ // resolves and scopes its verification report like its legacy twin.
458
+ const completionResult = isPhaseComplete(node_path_1.default.join(phasesDir, dirMatch), { convention });
437
459
  if (completionResult.value.complete)
438
460
  diskStatus = 'complete';
439
461
  else if (summaryCount > 0)
@@ -638,7 +660,94 @@ function cmdRoadmapAnalyze(cwd, raw) {
638
660
  if (seenChecklistKeys.has(key))
639
661
  continue;
640
662
  seenChecklistKeys.add(key);
641
- checklistOccurrences.push({ token, bracketId });
663
+ // #4899: the checkbox state (`[x]` vs `[ ]`) isn't a capturing group in
664
+ // `checklistPattern` — adding one would shift every downstream `1 + G` /
665
+ // `G` index this loop and `missingDetails` below already depend on.
666
+ // Reading it off the full match text instead is index-safe.
667
+ checklistOccurrences.push({ token, bracketId, checked: /\[x\]/i.test(checklistMatch[0]) });
668
+ }
669
+ // #4899: `collectAnalyzePhases` only recognises `### Phase N:` headings and
670
+ // progress-table rows — the shape `templates/roadmap.md` itself emits
671
+ // (checklist entries only, no detail sections yet) produces `phases: []`
672
+ // even though `checklistOccurrences` above just proved real phases exist.
673
+ // That evidence was computed and then discarded on the way to the final
674
+ // `phases: []` output (ADR-4910 §5/Phase 4). When the heading/table scan
675
+ // found nothing but the checklist did, synthesize a minimal `AnalyzePhase`
676
+ // per non-sentinel checklist occurrence — same enrichment calls
677
+ // (`matchPhaseDirs`, `countPhasePlansAndSummaries`, `isPhaseComplete`) the
678
+ // heading branch of `collectAnalyzePhases` already uses, so a synthesized
679
+ // phase's on-disk status is derived identically, not approximated.
680
+ //
681
+ // `missing_phase_details` is intentionally left untouched by this branch:
682
+ // its contract is "this checklist token has no `### Phase N:` heading or
683
+ // progress-table row", and that remains true for every synthesized token —
684
+ // synthesis fills in `phases[]` from the checklist itself, it does not
685
+ // manufacture a detail section. Removing these tokens from
686
+ // `missing_phase_details` would hide the very malformed-ROADMAP signal
687
+ // ADR-4910 (#4899) treats as evidence to surface, not evidence to launder.
688
+ //
689
+ // Gated on `!hasPhaseListingTableHeader(effectiveContent)` (found by the
690
+ // pre-existing #4480 regression "a status table cannot hide missing phase
691
+ // details"): a `| Phase | Status |`-shaped table with no Name column
692
+ // correctly declares no phases via `collectAnalyzePhases`, but its mere
693
+ // presence signals this roadmap has already moved to table-based tracking —
694
+ // synthesizing phantom phases from checklist entries alongside a
695
+ // deliberately thin tracking table would manufacture phase_count where the
696
+ // roadmap's own structure says "not yet detailed", the same laundering this
697
+ // branch exists to avoid for `missing_phase_details`. #4899's actual target
698
+ // shape (`templates/roadmap.md`: checklist only, no Progress section at
699
+ // all) has no such table and is unaffected.
700
+ if (phases.length === 0 && checklistOccurrences.length > 0 && !hasPhaseListingTableHeader(effectiveContent)) {
701
+ for (const occ of checklistOccurrences) {
702
+ if (isSentinelPhase(occ.token, occ.bracketId))
703
+ continue;
704
+ const normalized = normalizePhaseName(occ.token);
705
+ const dirMatch = matchPhaseDirs(_phaseDirNames, normalized, convention).matches[0];
706
+ let diskStatus = 'no_directory';
707
+ let planCount = 0;
708
+ let summaryCount = 0;
709
+ let hasContext = false;
710
+ let hasResearch = false;
711
+ let contextReadError = null;
712
+ let contextScope = SCOPE.COMPLETE;
713
+ if (dirMatch) {
714
+ const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch), convention);
715
+ planCount = counts.planCount;
716
+ summaryCount = counts.summaryCount;
717
+ hasContext = counts.hasContext;
718
+ hasResearch = counts.hasResearch;
719
+ contextReadError = counts.contextReadError;
720
+ contextScope = counts.scope;
721
+ const completionResult = isPhaseComplete(node_path_1.default.join(phasesDir, dirMatch), { convention });
722
+ if (completionResult.value.complete)
723
+ diskStatus = 'complete';
724
+ else if (summaryCount > 0)
725
+ diskStatus = 'partial';
726
+ else if (planCount > 0)
727
+ diskStatus = 'planned';
728
+ else if (hasResearch)
729
+ diskStatus = 'researched';
730
+ else if (hasContext)
731
+ diskStatus = 'discussed';
732
+ else
733
+ diskStatus = 'empty';
734
+ }
735
+ phases.push({
736
+ number: occ.token,
737
+ name: `Phase ${occ.token}`,
738
+ goal: null,
739
+ mode: null,
740
+ depends_on: null,
741
+ plan_count: planCount,
742
+ summary_count: summaryCount,
743
+ has_context: hasContext,
744
+ has_research: hasResearch,
745
+ disk_status: diskStatus,
746
+ roadmap_complete: occ.checked,
747
+ context_read_error: contextReadError,
748
+ context_scope: contextScope,
749
+ });
750
+ }
642
751
  }
643
752
  // The EMITTED value stays the bare token, unchanged: `phases[].number` is a
644
753
  // token under every convention, and `missing_phase_details` is read against
@@ -841,7 +950,11 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
841
950
  // the same phase (ADR-3180 §7.4's headline: one predicate for the read
842
951
  // path and the write path).
843
952
  const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
844
- const completionResult = isPhaseComplete(phaseDir);
953
+ // ADR-3180 §7.4 read/write-path symmetry with the threaded site at ~583:
954
+ // thread convention here too, so this write path's completion reading
955
+ // agrees with the read path's under the bracket convention.
956
+ const convention = resolvePhaseIdConvention(cwd);
957
+ const completionResult = isPhaseComplete(phaseDir, { convention });
845
958
  const verificationResult = completionResult.value.verification;
846
959
  // #2648 precedent, applied at this write site (ADR-3180 §7.4 / #3186):
847
960
  // `isPhaseComplete` deliberately carries NO plan-count precondition — the
@@ -872,6 +985,23 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
872
985
  }
873
986
  // Wrap entire read-modify-write in lock to prevent concurrent corruption
874
987
  let updated = false;
988
+ // #4247: the refusal flag. The write/report decision below must be keyed to
989
+ // "a writable roadmap representation of THIS phase was found", never to "any
990
+ // byte moved". On a checklist-form ROADMAP (`- [ ] **Phase N: …**`, the
991
+ // roadmapper's own summary-checklist form) every phase-targeted grammar
992
+ // below requires an ATX `#{2,4} Phase N` heading and therefore finds
993
+ // nothing; the Progress-table row is the only other writable target, and
994
+ // when its Phase cell does not match `phaseCellRe` (e.g. a word-prefixed
995
+ // `Phase 68` cell — deliberately unrecognized on the read side too,
996
+ // `deriveProgressFromRoadmap`'s `/^\d/` data-row filter) the command used
997
+ // to fall through to unrelated byte deltas (an UN-scoped plan-checkbox mark
998
+ // anywhere in the document) and report `updated: true` while the phase's
999
+ // own row stayed untouched — with the file-global write then letting the
1000
+ // platform write seam's markdown normalization inject blank lines around
1001
+ // other phases' bullets, splitting hand-wrapped sentences mid-entry. The
1002
+ // refusal below declines with the analyzer's own `missing_phase_details`
1003
+ // vocabulary and leaves ROADMAP.md byte-identical.
1004
+ let missingPhaseDetails = false;
875
1005
  withPlanningLock(cwd, () => {
876
1006
  // #3957 (B9.4): captured BEFORE any transform runs, so the write/report
877
1007
  // decision below reflects whether the transforms actually changed
@@ -881,6 +1011,41 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
881
1011
  const originalContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
882
1012
  let roadmapContent = originalContent;
883
1013
  const phasePattern = phaseMarkdownRegexSource(phaseNum);
1014
+ // #4247: ONE local source for the ATX phase-heading anchor that every
1015
+ // section-scoped writer below (`planSectionPattern`,
1016
+ // `insertRowsPatternA|B`) starts with — extracted so the target-detection
1017
+ // gate below reads the SAME grammar the writers anchor on, and a future
1018
+ // edit to one cannot drift from the other three copies.
1019
+ // #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
1020
+ // this write-path anchor was never one of the five ADR-4910 §8 call sites
1021
+ // this phase owns (init.cts/milestone.cts's buildPhaseHeadingScanRegex
1022
+ // sites); a direct any-bracket selector call here would have added a
1023
+ // fourth uncounted ANY_BRACKET consumer to roadmap.cts's pinned
1024
+ // tests/adr-612-bracket-heading-selection.test.cjs census. Left
1025
+ // bracket-blind deliberately, matching this call's pre-#4984 behavior;
1026
+ // folding it into the census is Phase 6 work.
1027
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
1028
+ const phaseHeadingAnchor = `#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])`;
1029
+ // #4247: target detection runs against the ORIGINAL content's active
1030
+ // (post-</details>) region — the same milestone scoping every writer
1031
+ // below applies — so the gate asks "does the file carry a writable phase
1032
+ // representation" rather than "did some regex fire mid-transform".
1033
+ const gateDetailsClose = originalContent.lastIndexOf('</details>');
1034
+ const gateActiveRegion = gateDetailsClose === -1
1035
+ ? originalContent
1036
+ : originalContent.slice(gateDetailsClose + '</details>'.length);
1037
+ // Heading target: the exact grammar `planSectionPattern` /
1038
+ // `insertRowsPatternA|B` anchor on (an ATX phase heading for this phase).
1039
+ const headingTargetFound = new RegExp(phaseHeadingAnchor, 'i').test(gateActiveRegion);
1040
+ // Checklist target: when the phase is complete, its own checklist bullet
1041
+ // (`- [ ] **Phase N: …**`) IS a writable phase row — the completion
1042
+ // checkbox stamp below updates it. Same grammar as that writer, widened
1043
+ // one notch to `[ x]` so an ALREADY-checked bullet still counts as a
1044
+ // found target: an idempotent re-run then takes the honest
1045
+ // "no changes were needed" decline instead of this refusal.
1046
+ const checklistTargetFound = isComplete && new RegExp(`-\\s*\\[[ x]\\]\\s*.*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i').test(gateActiveRegion);
1047
+ // Table-row target: set by the row-scoped cell updates below.
1048
+ let tableRowFound = false;
884
1049
  // Progress table row: update Plans Complete/Status/Completed columns BY
885
1050
  // COLUMN NAME (handles 4- or 5-column RoadmapProgress tables regardless of
886
1051
  // Milestone-column presence) via the markdown-table seam (ADR-2143 §7) —
@@ -899,11 +1064,15 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
899
1064
  roadmapContent = editProgressTableSlice(roadmapContent, (scoped) => {
900
1065
  let text = scoped;
901
1066
  const plansResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Plans Complete', ` ${summaryCount}/${planCount} `);
902
- if (plansResult.ok)
1067
+ if (plansResult.ok) {
903
1068
  text = plansResult.value;
1069
+ tableRowFound = true;
1070
+ }
904
1071
  const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${status.padEnd(11)}`);
905
- if (statusResult.ok)
1072
+ if (statusResult.ok) {
906
1073
  text = statusResult.value;
1074
+ tableRowFound = true;
1075
+ }
907
1076
  // Preserve only a valid ISO date (#1161: idempotent; self-heal garbage).
908
1077
  // Ragged-tolerant (#2245 Blocker 2): probe the CURRENT Completed cell via
909
1078
  // a no-op updateTableCell write (its own tolerant row scan) rather than
@@ -918,8 +1087,10 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
918
1087
  }
919
1088
  return ' ';
920
1089
  });
921
- if (completedResult.ok)
1090
+ if (completedResult.ok) {
922
1091
  text = completedResult.value;
1092
+ tableRowFound = true;
1093
+ }
923
1094
  return text;
924
1095
  });
925
1096
  // Update plan count in phase detail section.
@@ -960,7 +1131,14 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
960
1131
  // `_match` unchanged. An untouched first line cannot orphan its own
961
1132
  // continuation on the next line, since the pattern never spans past
962
1133
  // `\n` in the first place.
963
- const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\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');
1134
+ // #4906 Phase 2 (#4917/ADR-4910): migrated off the one-capture-group
1135
+ // regex that replaced to end of line onto the PlanningDoc `boldField`
1136
+ // write seam. `planSectionPattern` scopes the match to phase N's OWN
1137
+ // detail section (heading + body up to the next heading) — the same
1138
+ // window `phaseHeadingAnchor`'s siblings anchor on — and the callback
1139
+ // below runs parse -> classify -> (maybe) mutate -> serialize entirely
1140
+ // within that section text, mirroring phase.cts's `writePlansField`.
1141
+ const planSectionPattern = new RegExp(`${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*`, 'i');
964
1142
  const planCountText = isComplete
965
1143
  ? `${summaryCount}/${planCount} plans complete`
966
1144
  : `${summaryCount}/${planCount} plans executed`;
@@ -969,24 +1147,107 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
969
1147
  // gsd-core/templates/roadmap.md actually ships, not to "anything in
970
1148
  // brackets" — a bracketed human annotation like `[Deferred pending
971
1149
  // re-scope]` is structurally bracketed too but carries none of this
972
- // wording, so it correctly falls through to arm 3 untouched.
1150
+ // wording, so it correctly falls through to arm 3 untouched. Kept as a
1151
+ // caller-side classifier (NOT seam grammar) reused below against the
1152
+ // `boldField` node's own parsed `value`.
973
1153
  const isTemplatePlaceholder = (value) => {
974
1154
  const trimmed = value.trim();
975
1155
  return /^\[\s*Number of plans\b[\s\S]*\]$/i.test(trimmed);
976
1156
  };
977
- roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, (_match, label, existingCount, trailing) => {
978
- if (existingCount) {
979
- // Arm 1: real count token — rewrite it, preserve the trailing annotation.
980
- return `${label}${planCountText}${trailing}`;
1157
+ // Positive detectors for the two "real count token" shapes (#2853 /
1158
+ // #3584 Finding B): a fraction count (`N/M plans complete|executed`) or a
1159
+ // bare singular/plural count (`N plan`/`N plans`, the fresh single-plan
1160
+ // template shape at gsd-core/templates/roadmap.md:62). Either one is an
1161
+ // existing count token to overwrite (arm 1), never template placeholder
1162
+ // (arm 2) or freeform prose (arm 3).
1163
+ // #4906 regression fix: PREFIX match (not full-string) — a real count
1164
+ // token may have a glued-on annotation with no ` — ` separator (e.g.
1165
+ // `0/1 plans executed (11-16 are gap closure from VERIFICATION)`), which
1166
+ // `parseBoldFieldLine`'s em-dash-only trailing-content split leaves
1167
+ // entirely inside `value` (TRAILING_SEPARATOR_RE in planning-document.cts
1168
+ // is unchanged and correct — this is a caller-side classification fix,
1169
+ // not a seam fix). Returns the matched prefix length, or -1 if no match.
1170
+ const fractionCountPrefixLength = (value) => {
1171
+ const m = value.match(/^\d+\s*\/\s*\d+\s+plans(?:\s+(?:complete|executed))?/i);
1172
+ return m ? m[0].length : -1;
1173
+ };
1174
+ const bareCountPrefixLength = (value) => {
1175
+ const m = value.match(/^\d+\s+plans?/i);
1176
+ return m ? m[0].length : -1;
1177
+ };
1178
+ roadmapContent = replaceInCurrentMilestone(roadmapContent, planSectionPattern, (sectionText) => {
1179
+ const parsed = (0, planning_document_cjs_1.parsePlanningDoc)(sectionText, 'ROADMAP.md');
1180
+ if (!parsed.ok) {
1181
+ // Unreadable section (e.g. an unterminated frontmatter fence) —
1182
+ // leave it byte-identical rather than throwing.
1183
+ return sectionText;
1184
+ }
1185
+ const fieldId = (0, planning_document_cjs_1.findField)(parsed.value, 'Plans');
1186
+ if (!fieldId) {
1187
+ // #4906 regression (#1163, caught by gsd-test): a hand-edited or
1188
+ // pre-template ROADMAP.md may carry a PLAIN (non-bold) `Plans:` line
1189
+ // rather than the canonical `**Plans**:`/`**Plans:**` bold field.
1190
+ // BOLD_FIELD_RE is deliberately bold-only (widening it to any bare
1191
+ // `Label:` would register ordinary prose like "Note: see below" as a
1192
+ // spurious field seam-wide) — this is domain knowledge about ONE
1193
+ // field's legacy tolerated shape, the same class of thing
1194
+ // `isTemplatePlaceholder` above already keeps caller-side rather
1195
+ // than seam grammar, so the fallback lives here, not in
1196
+ // planning-document.cts.
1197
+ const plainMatch = sectionText.match(/^([ \t]*)Plans:([ \t]*)([^\r\n]*)$/m);
1198
+ if (!plainMatch) {
1199
+ // No `**Plans:**`/`**Plans**:`/plain `Plans:` line in this phase's
1200
+ // own section — nothing to write; not a failure (mirrors the old
1201
+ // regex's silent no-match no-op).
1202
+ return sectionText;
1203
+ }
1204
+ const [whole, indent, spacing, plainValue] = plainMatch;
1205
+ const plainFractionLen = fractionCountPrefixLength(plainValue);
1206
+ const plainBareLen = bareCountPrefixLength(plainValue);
1207
+ const plainCountPrefixLen = plainFractionLen >= 0 ? plainFractionLen : plainBareLen;
1208
+ if (plainCountPrefixLen < 0 && !isTemplatePlaceholder(plainValue)) {
1209
+ // Arm 3: freeform prose, TBD, a bracketed human annotation, or an
1210
+ // empty value — leave the section exactly as it was.
1211
+ return sectionText;
1212
+ }
1213
+ const plainSuffix = plainCountPrefixLen >= 0 ? plainValue.slice(plainCountPrefixLen) : '';
1214
+ const newPlainLine = `${indent}Plans:${spacing}${planCountText}${plainSuffix}`;
1215
+ const start = plainMatch.index ?? sectionText.indexOf(whole);
1216
+ return sectionText.slice(0, start) + newPlainLine + sectionText.slice(start + whole.length);
1217
+ }
1218
+ const current = (0, planning_document_cjs_1.readNode)(parsed.value, fieldId);
1219
+ if (!current.ok) {
1220
+ return sectionText;
1221
+ }
1222
+ const currentValue = current.value;
1223
+ const fractionPrefixLen = fractionCountPrefixLength(currentValue);
1224
+ const barePrefixLen = bareCountPrefixLength(currentValue);
1225
+ const countPrefixLen = fractionPrefixLen >= 0 ? fractionPrefixLen : barePrefixLen;
1226
+ if (countPrefixLen < 0 && !isTemplatePlaceholder(currentValue)) {
1227
+ // Arm 3: freeform prose, TBD, a bracketed human annotation, or an
1228
+ // empty value — leave the section exactly as it was.
1229
+ return sectionText;
981
1230
  }
982
- if (isTemplatePlaceholder(trailing)) {
983
- // Arm 2: fresh-template placeholder — replace with the count.
984
- return `${label}${planCountText}`;
1231
+ // Arm 1 (real count token, possibly with a glued-on no-separator
1232
+ // annotation re-attached verbatim as `suffix`) or arm 2 (fresh-template
1233
+ // placeholder, whole value replaced): write the computed count.
1234
+ // `setFieldValue`'s valueSpan/trailingSpan split additionally preserves
1235
+ // any EM-DASH-separated trailing annotation (#2853) automatically — no
1236
+ // separate "preserve trailing" branch needed for that shape.
1237
+ const suffix = countPrefixLen >= 0 ? currentValue.slice(countPrefixLen) : '';
1238
+ const newValueToWrite = planCountText + suffix;
1239
+ const staged = (0, planning_document_cjs_1.setFieldValue)(parsed.value, fieldId, newValueToWrite);
1240
+ if (!staged.ok) {
1241
+ return sectionText;
985
1242
  }
986
- // Arm 3: freeform prose, TBD, a bracketed human annotation, a wrapped
987
- // sentence's first line, or an empty value — leave the line exactly as
988
- // it was.
989
- return _match;
1243
+ const out = (0, planning_document_cjs_1.serialize)(staged.value);
1244
+ if (!out.ok) {
1245
+ // `hasUnreadableNodes` refusal (ADR-4910 amendment) — a ragged
1246
+ // SIBLING node elsewhere in this same section refuses the whole
1247
+ // splice. Never throw; leave the section unchanged.
1248
+ return sectionText;
1249
+ }
1250
+ return out.value;
990
1251
  });
991
1252
  // If complete: check checkbox
992
1253
  if (isComplete) {
@@ -994,10 +1255,17 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
994
1255
  roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
995
1256
  }
996
1257
  // Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
997
- for (const summaryFile of phaseInfo.summaries) {
998
- const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
999
- if (!planId)
1000
- continue;
1258
+ // #4741: tick only plans the phase's own COUNT still counts. `plans` is the
1259
+ // superseded-filtered set (#2349 via scanPhasePlans) while `summaries` is
1260
+ // the raw *-SUMMARY.md listing — a superseded plan can carry a SUMMARY
1261
+ // (e.g. `status: halted`), and ticking it read as "executed" right under a
1262
+ // count line that excludes it. The prefix match mirrors the checkbox regex
1263
+ // below (rows match by planId prefix, which the PLAN-01.md naming shape
1264
+ // relies on), so non-superseded plans tick exactly as before.
1265
+ const tickableSummaries = phaseInfo.summaries
1266
+ .map((summaryFile) => ({ summaryFile, planId: summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '') }))
1267
+ .filter(({ planId }) => planId !== '' && phaseInfo.plans.some((planFile) => planFile.startsWith(planId)));
1268
+ for (const { planId } of tickableSummaries) {
1001
1269
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
1002
1270
  const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
1003
1271
  roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
@@ -1021,9 +1289,26 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1021
1289
  // Detection is scoped to the active region so a plan that appears in an
1022
1290
  // archived <details> block is still correctly detected as missing from the
1023
1291
  // active milestone section.
1292
+ //
1293
+ // #4786: a plan row may be written WITH the `-PLAN.md` suffix (canonical
1294
+ // template form) or WITHOUT it (hand-written form: `- [x] 659-01 — desc`).
1295
+ // The tick loop above keys on the bare planId stem with a PREFIX match
1296
+ // (pre-existing, out of scope here), while detection required the full
1297
+ // `-PLAN.md` filename — so every suffix-less row was counted missing and
1298
+ // the insertion fired BESIDE the recognized list — 32 checkbox lines for
1299
+ // 16 plans, exit 0. The stem arm below accepts the bare id only up to a
1300
+ // boundary (whitespace / `:` / dashes / `-PLAN.md` / `.md` / `**` / `)`),
1301
+ // so `5-011` never satisfies `5-01`. Deliberately NOT in the boundary set:
1302
+ // `.` — a dotted sub-id (`5-01.5`) is a real distinct plan, and counting
1303
+ // its row as 5-01's presence would suppress a genuine insertion.
1024
1304
  const missingPlans = phaseInfo.plans.filter((planFile) => {
1025
1305
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planFile);
1026
- return !new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${planEscaped}`, 'i').test(activeRegion);
1306
+ if (new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${planEscaped}`, 'i').test(activeRegion))
1307
+ return false;
1308
+ const stem = planFile.replace(/-PLAN\.md$/i, '');
1309
+ const stemEscaped = (0, pattern_cjs_1.escapeRegex)(stem);
1310
+ const stemPresent = new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${stemEscaped}(?=$|\\s|:|—|–|-PLAN\\.md|\\.md|\\*\\*|\\))`, 'i').test(activeRegion);
1311
+ return !stemPresent;
1027
1312
  });
1028
1313
  if (missingPlans.length > 0) {
1029
1314
  // Insert missing plan checklist rows (#1163). We prefer to anchor to the
@@ -1040,8 +1325,8 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1040
1325
  //
1041
1326
  // Pattern A: anchor to bare `Plans:` header (preferred).
1042
1327
  // Pattern B: fallback to bold summary when no bare header exists.
1043
- const insertRowsPatternA = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
1044
- const insertRowsPatternB = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
1328
+ const insertRowsPatternA = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
1329
+ const insertRowsPatternB = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
1045
1330
  const sortedMissing = [...missingPlans].sort();
1046
1331
  const newRows = sortedMissing.map(p => `- [ ] ${p}`).join('\n');
1047
1332
  const inserter = (match) => `${match}\n${newRows}`;
@@ -1066,10 +1351,9 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1066
1351
  if (withRows !== roadmapContent) {
1067
1352
  roadmapContent = withRows;
1068
1353
  // Mark any newly-inserted rows that already have summaries as complete
1069
- for (const summaryFile of phaseInfo.summaries) {
1070
- const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
1071
- if (!planId)
1072
- continue;
1354
+ // (#4741: same superseded-filtered tick list as the loop above — a
1355
+ // pre-existing superseded row must stay unchecked on this path too).
1356
+ for (const { planId } of tickableSummaries) {
1073
1357
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
1074
1358
  const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
1075
1359
  roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
@@ -1081,10 +1365,24 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1081
1365
  // `cmdRoadmapAnnotateDependencies`'s existing `nextContent !== content`
1082
1366
  // gate. Previously this wrote and reported `updated: true`
1083
1367
  // unconditionally, even on an idempotent re-run that changed nothing.
1084
- if (roadmapContent !== originalContent) {
1368
+ //
1369
+ // #4247: ...but ONLY when a writable representation of THIS phase was
1370
+ // found. Without the target gate, a byte delta from an unrelated
1371
+ // transform (the un-scoped plan-checkbox mark) satisfied the #3957 gate
1372
+ // and produced a success-shaped `updated: true` while the phase's own
1373
+ // row stayed untouched — and the file-global write let the platform
1374
+ // write seam's markdown normalization reflow unrelated entries. When no
1375
+ // target exists the command refuses: no write at all, so ROADMAP.md is
1376
+ // left byte-identical, and the caller gets a typed
1377
+ // `missing_phase_details` decline instead of a false green.
1378
+ const phaseRepresentationFound = tableRowFound || headingTargetFound || checklistTargetFound;
1379
+ if (phaseRepresentationFound && roadmapContent !== originalContent) {
1085
1380
  (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, roadmapContent);
1086
1381
  updated = true;
1087
1382
  }
1383
+ if (!phaseRepresentationFound) {
1384
+ missingPhaseDetails = true;
1385
+ }
1088
1386
  });
1089
1387
  const computed = {
1090
1388
  phase: phaseNum,
@@ -1097,6 +1395,13 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
1097
1395
  if (updated) {
1098
1396
  output({ updated: true, ...computed }, raw, `${summaryCount}/${planCount} ${status}`);
1099
1397
  }
1398
+ else if (missingPhaseDetails) {
1399
+ // #4247: honest refusal — the reason names the real condition (the
1400
+ // analyzer's `missing_phase_details` vocabulary), never "already
1401
+ // reflects", which was false: the ROADMAP was never able to record this
1402
+ // phase's progress in the first place.
1403
+ declineNoOp(raw, 'updated', 'missing_phase_details', `roadmap update-plan-progress skipped — ROADMAP.md has no writable entry for phase ${formatDiagnosticToken(String(phaseNum))} (no matching Progress-table row, no phase detail section, and no checklist entry this command can update). ROADMAP.md was left unchanged.`, computed);
1404
+ }
1100
1405
  else {
1101
1406
  declineNoOp(raw, 'updated', "no changes were needed — ROADMAP.md already reflects this phase's plan/summary counts and status", "roadmap update-plan-progress skipped — no changes were needed; ROADMAP.md already reflects this phase's plan/summary counts and status.", computed);
1102
1407
  }
@@ -1205,6 +1510,15 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
1205
1510
  // #3537: padding-tolerant fragment so the caller's resolved padded id
1206
1511
  // matches un-padded ROADMAP headings.
1207
1512
  const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
1513
+ // #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
1514
+ // this site was never one of the five ADR-4910 §8 call sites this phase
1515
+ // owns (init.cts/milestone.cts's buildPhaseHeadingScanRegex sites); a
1516
+ // direct any-bracket selector call here would have added a fifth
1517
+ // uncounted ANY_BRACKET consumer to roadmap.cts's pinned
1518
+ // tests/adr-612-bracket-heading-selection.test.cjs census. Left
1519
+ // bracket-blind deliberately, matching this call's pre-#4984 behavior;
1520
+ // folding it into the census is Phase 6 work.
1521
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
1208
1522
  const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*)`, 'i');
1209
1523
  const phaseMatch = content.match(phaseHeaderPattern);
1210
1524
  if (!phaseMatch)