@opengsd/gsd-core 1.15.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 (487) 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/agents/gsd-code-fixer.compact.md +1 -1
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-code-reviewer.compact.md +5 -3
  7. package/agents/gsd-code-reviewer.md +8 -6
  8. package/agents/gsd-debug-session-manager.compact.md +1 -1
  9. package/agents/gsd-debug-session-manager.md +1 -1
  10. package/agents/gsd-debugger.md +2 -2
  11. package/agents/gsd-eval-auditor.compact.md +1 -1
  12. package/agents/gsd-eval-auditor.md +1 -1
  13. package/agents/gsd-executor.md +4 -4
  14. package/agents/gsd-intel-updater.compact.md +1 -1
  15. package/agents/gsd-intel-updater.md +1 -1
  16. package/agents/gsd-mempalace-curator.md +2 -2
  17. package/agents/gsd-phase-researcher.md +1 -1
  18. package/agents/gsd-plan-checker.md +7 -2
  19. package/agents/gsd-planner.md +3 -3
  20. package/agents/gsd-project-researcher.compact.md +1 -1
  21. package/agents/gsd-project-researcher.md +1 -1
  22. package/agents/gsd-research-synthesizer.compact.md +1 -1
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-auditor.compact.md +21 -30
  25. package/agents/gsd-ui-auditor.md +42 -51
  26. package/agents/gsd-ui-researcher.compact.md +1 -1
  27. package/agents/gsd-ui-researcher.md +1 -1
  28. package/agents/gsd-verifier.md +31 -9
  29. package/bin/install.js +126 -196
  30. package/commands/gsd/add-tests.md +6 -1
  31. package/commands/gsd/ai-integration-phase.md +6 -1
  32. package/commands/gsd/audit-fix.md +5 -0
  33. package/commands/gsd/audit-milestone.md +6 -1
  34. package/commands/gsd/autonomous.md +5 -0
  35. package/commands/gsd/capture.md +8 -4
  36. package/commands/gsd/code-review.md +7 -2
  37. package/commands/gsd/complete-milestone.md +4 -0
  38. package/commands/gsd/config.md +7 -3
  39. package/commands/gsd/debug.md +11 -7
  40. package/commands/gsd/discuss-phase.md +7 -3
  41. package/commands/gsd/docs-update.md +12 -7
  42. package/commands/gsd/eval-review.md +6 -1
  43. package/commands/gsd/execute-phase.md +12 -7
  44. package/commands/gsd/extract-learnings.md +5 -0
  45. package/commands/gsd/fast.md +4 -0
  46. package/commands/gsd/forensics.md +5 -1
  47. package/commands/gsd/graphify.md +10 -6
  48. package/commands/gsd/health.md +5 -0
  49. package/commands/gsd/help.md +7 -2
  50. package/commands/gsd/import.md +7 -3
  51. package/commands/gsd/inbox.md +5 -0
  52. package/commands/gsd/ingest-docs.md +5 -1
  53. package/commands/gsd/manager.md +6 -1
  54. package/commands/gsd/map-codebase.md +7 -3
  55. package/commands/gsd/mempalace-capture.md +5 -1
  56. package/commands/gsd/mempalace-recall.md +5 -1
  57. package/commands/gsd/milestone-summary.md +5 -1
  58. package/commands/gsd/mvp-phase.md +8 -3
  59. package/commands/gsd/new-milestone.md +6 -1
  60. package/commands/gsd/new-project.md +5 -0
  61. package/commands/gsd/next.md +6 -1
  62. package/commands/gsd/ns-context.md +4 -0
  63. package/commands/gsd/ns-ideate.md +4 -0
  64. package/commands/gsd/ns-manage.md +4 -0
  65. package/commands/gsd/ns-project.md +4 -0
  66. package/commands/gsd/ns-review.md +4 -0
  67. package/commands/gsd/ns-workflow.md +4 -0
  68. package/commands/gsd/onboard.md +6 -1
  69. package/commands/gsd/pause-work.md +5 -1
  70. package/commands/gsd/phase.md +8 -4
  71. package/commands/gsd/plan-phase.md +6 -1
  72. package/commands/gsd/plan-review-convergence.md +5 -1
  73. package/commands/gsd/pr-branch.md +4 -0
  74. package/commands/gsd/profile-user.md +5 -1
  75. package/commands/gsd/progress.md +6 -1
  76. package/commands/gsd/quick-batch.md +20 -8
  77. package/commands/gsd/quick.md +12 -7
  78. package/commands/gsd/review.md +5 -1
  79. package/commands/gsd/secure-phase.md +6 -1
  80. package/commands/gsd/ship.md +5 -0
  81. package/commands/gsd/sketch.md +7 -2
  82. package/commands/gsd/spec-phase.md +5 -1
  83. package/commands/gsd/spike.md +8 -3
  84. package/commands/gsd/surface.md +5 -1
  85. package/commands/gsd/thread.md +4 -0
  86. package/commands/gsd/ui-phase.md +6 -1
  87. package/commands/gsd/ui-review.md +6 -1
  88. package/commands/gsd/ultraplan-phase.md +5 -1
  89. package/commands/gsd/undo.md +5 -1
  90. package/commands/gsd/update.md +6 -2
  91. package/commands/gsd/validate-phase.md +6 -1
  92. package/commands/gsd/verify-work.md +6 -1
  93. package/commands/gsd/workspace.md +7 -3
  94. package/gsd-core/bin/gsd-tools.cjs +142 -56
  95. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  96. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  97. package/gsd-core/bin/lib/audit.cjs +78 -44
  98. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  99. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  100. package/gsd-core/bin/lib/capability-registry.cjs +183 -103
  101. package/gsd-core/bin/lib/capability-validator.cjs +16 -0
  102. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  103. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  104. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  105. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  106. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  107. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  108. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  109. package/gsd-core/bin/lib/commands.cjs +173 -170
  110. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  111. package/gsd-core/bin/lib/config.cjs +43 -12
  112. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  113. package/gsd-core/bin/lib/coverage.cjs +4 -8
  114. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  115. package/gsd-core/bin/lib/drift.cjs +177 -42
  116. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  117. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  118. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  119. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  120. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  121. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  122. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  123. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  124. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  125. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  126. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  127. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  128. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  129. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  130. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  131. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  132. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  133. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  134. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  135. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  136. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  137. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  138. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  139. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  140. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  141. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  142. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  143. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  144. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  145. package/gsd-core/bin/lib/init.cjs +162 -96
  146. package/gsd-core/bin/lib/install-engine.cjs +19 -35
  147. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  148. package/gsd-core/bin/lib/io.cjs +122 -3
  149. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  150. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  151. package/gsd-core/bin/lib/milestone.cjs +19 -1
  152. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  153. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  154. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  155. package/gsd-core/bin/lib/pattern.cjs +10 -0
  156. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  157. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  158. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  159. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  160. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  161. package/gsd-core/bin/lib/phase.cjs +280 -80
  162. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  163. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  164. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  165. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  166. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  167. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  168. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  169. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  170. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  171. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  172. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  173. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  174. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  175. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  176. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  177. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  178. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  179. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
  180. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  181. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  182. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  183. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
  184. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  185. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  186. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  187. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  188. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  189. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  190. package/gsd-core/bin/lib/state-document.cjs +100 -22
  191. package/gsd-core/bin/lib/state.cjs +214 -105
  192. package/gsd-core/bin/lib/surface.cjs +2 -1
  193. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  194. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  195. package/gsd-core/bin/lib/uat.cjs +424 -7
  196. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  197. package/gsd-core/bin/lib/vendor/README.md +31 -9
  198. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  199. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  200. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  201. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  202. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  203. package/gsd-core/bin/lib/verification.cjs +1165 -314
  204. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  205. package/gsd-core/bin/lib/verify.cjs +286 -567
  206. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  207. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  208. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  209. package/gsd-core/bin/lib/workstream.cjs +11 -2
  210. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  211. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  212. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  213. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  214. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  215. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  216. package/gsd-core/references/execute-phase-response-language.md +1 -1
  217. package/gsd-core/references/gsd-run-resolver.md +1 -1
  218. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  219. package/gsd-core/references/offer-next.md +1 -1
  220. package/gsd-core/references/planning-config.md +1 -1
  221. package/gsd-core/references/spidr-splitting.md +1 -1
  222. package/gsd-core/references/tdd.md +10 -4
  223. package/gsd-core/references/verifier-phase-gates.md +5 -2
  224. package/gsd-core/references/verify-mvp-mode.md +2 -2
  225. package/gsd-core/references/workstream-flag.md +33 -3
  226. package/gsd-core/templates/README.md +1 -1
  227. package/gsd-core/templates/UAT.md +17 -1
  228. package/gsd-core/templates/config.json +2 -11
  229. package/gsd-core/templates/verification-report.md +1 -1
  230. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  231. package/gsd-core/workflows/add-backlog.md +1 -1
  232. package/gsd-core/workflows/add-phase.md +8 -7
  233. package/gsd-core/workflows/add-tests.md +3 -2
  234. package/gsd-core/workflows/add-todo.md +4 -3
  235. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  236. package/gsd-core/workflows/audit-fix.md +1 -1
  237. package/gsd-core/workflows/audit-milestone.md +4 -3
  238. package/gsd-core/workflows/audit-uat.md +1 -1
  239. package/gsd-core/workflows/autonomous.md +28 -18
  240. package/gsd-core/workflows/check-todos.md +6 -5
  241. package/gsd-core/workflows/cleanup.md +1 -1
  242. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  243. package/gsd-core/workflows/code-review-fix.md +5 -4
  244. package/gsd-core/workflows/code-review.md +84 -90
  245. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  246. package/gsd-core/workflows/complete-milestone.md +12 -7
  247. package/gsd-core/workflows/debug.md +30 -5
  248. package/gsd-core/workflows/diagnose-issues.md +3 -2
  249. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  250. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  252. package/gsd-core/workflows/discuss-phase.md +4 -3
  253. package/gsd-core/workflows/do.md +1 -1
  254. package/gsd-core/workflows/docs-update.md +4 -3
  255. package/gsd-core/workflows/edit-phase.md +4 -3
  256. package/gsd-core/workflows/eval-review.md +5 -3
  257. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  258. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  259. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  260. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  261. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  263. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  264. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  265. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  266. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  267. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  268. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  269. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  270. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  271. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  272. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  273. package/gsd-core/workflows/execute-phase.md +78 -170
  274. package/gsd-core/workflows/execute-plan.md +13 -19
  275. package/gsd-core/workflows/explore.md +2 -2
  276. package/gsd-core/workflows/extract-learnings.md +3 -2
  277. package/gsd-core/workflows/fast.md +1 -1
  278. package/gsd-core/workflows/forensics.md +1 -1
  279. package/gsd-core/workflows/graduation.md +1 -1
  280. package/gsd-core/workflows/health.md +2 -1
  281. package/gsd-core/workflows/import.md +3 -2
  282. package/gsd-core/workflows/inbox.md +1 -1
  283. package/gsd-core/workflows/ingest-docs.md +1 -1
  284. package/gsd-core/workflows/insert-phase.md +4 -3
  285. package/gsd-core/workflows/list-seeds.md +1 -1
  286. package/gsd-core/workflows/list-workspaces.md +1 -1
  287. package/gsd-core/workflows/manager.md +5 -3
  288. package/gsd-core/workflows/map-codebase.md +4 -3
  289. package/gsd-core/workflows/milestone-summary.md +3 -2
  290. package/gsd-core/workflows/mvp-phase.md +14 -14
  291. package/gsd-core/workflows/new-milestone.md +10 -10
  292. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  293. package/gsd-core/workflows/new-project.md +1 -1
  294. package/gsd-core/workflows/new-workspace.md +1 -1
  295. package/gsd-core/workflows/next.md +1 -1
  296. package/gsd-core/workflows/pause-work.md +2 -2
  297. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  298. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  299. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  300. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  301. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  302. package/gsd-core/workflows/plan-phase.md +27 -18
  303. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  304. package/gsd-core/workflows/plant-seed.md +1 -1
  305. package/gsd-core/workflows/pr-branch.md +1 -1
  306. package/gsd-core/workflows/profile-user.md +1 -1
  307. package/gsd-core/workflows/progress.md +19 -49
  308. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  309. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  310. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  311. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  312. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  313. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  314. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  315. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  316. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  317. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  318. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  319. package/gsd-core/workflows/quick-batch.md +15 -10
  320. package/gsd-core/workflows/quick.md +42 -31
  321. package/gsd-core/workflows/remove-phase.md +3 -2
  322. package/gsd-core/workflows/remove-workspace.md +1 -1
  323. package/gsd-core/workflows/resume-project.md +3 -2
  324. package/gsd-core/workflows/review.md +3 -2
  325. package/gsd-core/workflows/scan.md +3 -2
  326. package/gsd-core/workflows/secure-phase.md +11 -12
  327. package/gsd-core/workflows/settings-advanced.md +1 -1
  328. package/gsd-core/workflows/settings-integrations.md +1 -1
  329. package/gsd-core/workflows/settings.md +1 -1
  330. package/gsd-core/workflows/ship.md +12 -12
  331. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  332. package/gsd-core/workflows/sketch.md +1 -1
  333. package/gsd-core/workflows/smart-entry.md +1 -1
  334. package/gsd-core/workflows/spec-phase.md +1 -1
  335. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  336. package/gsd-core/workflows/spike.md +1 -1
  337. package/gsd-core/workflows/stats.md +1 -1
  338. package/gsd-core/workflows/sync-skills.md +1 -1
  339. package/gsd-core/workflows/thread.md +2 -2
  340. package/gsd-core/workflows/transition.md +13 -23
  341. package/gsd-core/workflows/ui-phase.md +5 -4
  342. package/gsd-core/workflows/ui-review.md +4 -3
  343. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  344. package/gsd-core/workflows/undo.md +1 -1
  345. package/gsd-core/workflows/validate-phase.md +10 -12
  346. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  347. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  348. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  349. package/gsd-core/workflows/verify-work.md +68 -59
  350. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  351. package/hooks/dist/gsd-check-update-worker.js +8 -0
  352. package/hooks/dist/gsd-check-update.js +8 -0
  353. package/hooks/dist/gsd-context-monitor.js +31 -8
  354. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  355. package/hooks/dist/gsd-secret-read-guard.js +147 -16
  356. package/hooks/dist/gsd-statusline.js +15 -7
  357. package/hooks/dist/gsd-update-banner.js +8 -0
  358. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  359. package/hooks/dist/gsd-workflow-guard.js +5 -4
  360. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  361. package/hooks/dist/lib/cli-exit.js +12 -0
  362. package/hooks/dist/lib/git-probe.js +17 -1
  363. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  364. package/hooks/gsd-agent-isolation-guard.js +8 -0
  365. package/hooks/gsd-check-update-worker.js +8 -0
  366. package/hooks/gsd-check-update.js +8 -0
  367. package/hooks/gsd-context-monitor.js +31 -8
  368. package/hooks/gsd-cursor-subagent-start.js +8 -0
  369. package/hooks/gsd-secret-read-guard.js +147 -16
  370. package/hooks/gsd-statusline.js +15 -7
  371. package/hooks/gsd-update-banner.js +8 -0
  372. package/hooks/gsd-windsurf-pre-write.js +11 -2
  373. package/hooks/gsd-workflow-guard.js +5 -4
  374. package/hooks/gsd-worktree-path-guard.js +6 -2
  375. package/hooks/hooks.json +5 -5
  376. package/hooks/lib/cli-exit.js +12 -0
  377. package/hooks/lib/git-probe.js +17 -1
  378. package/hooks/lib/isolation-sentinel.js +2 -2
  379. package/package.json +21 -4
  380. package/scripts/changeset/parse.cjs +52 -4
  381. package/scripts/ci-timeout-report.cjs +770 -4
  382. package/scripts/command-contract-helpers.cjs +12 -8
  383. package/scripts/docs-guard-registry.cjs +6 -0
  384. package/scripts/gen-features.cjs +13 -8
  385. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  386. package/scripts/gen-loop-host-contract.cjs +10 -1
  387. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  388. package/scripts/gen-plugin-skills.cjs +87 -1
  389. package/scripts/gen-research-agents.cjs +24 -31
  390. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  391. package/scripts/gen-test-timings.cjs +32 -7
  392. package/scripts/lib/cli-exit.cjs +12 -0
  393. package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
  394. package/scripts/lib/ndjson-reporter.cjs +28 -3
  395. package/scripts/lib/platform-conformance-tier.generated.cjs +34 -5
  396. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  397. package/scripts/lib/vendor-bundle.cjs +59 -0
  398. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  399. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  400. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  401. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  402. package/scripts/lint-descriptions.cjs +7 -3
  403. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  404. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  405. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  406. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  407. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  408. package/scripts/lint-phase-id-drift.cjs +29 -0
  409. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  410. package/scripts/lint-response-language-coverage.cjs +3 -0
  411. package/scripts/lint-skill-deps.cjs +7 -3
  412. package/scripts/lint-test-file-count.allowlist.json +11 -1
  413. package/scripts/lint-test-file-count.cjs +34 -1
  414. package/scripts/lint-vendored-deps.cjs +41 -5
  415. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  416. package/scripts/mutation-matrix.cjs +50 -5
  417. package/scripts/require-issue-link-policy.cjs +6 -2
  418. package/scripts/sync-runtime-launcher.cjs +184 -2
  419. package/scripts/verify-npm-publish.cjs +76 -20
  420. package/skills/gsd-add-tests/SKILL.md +6 -1
  421. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  422. package/skills/gsd-audit-fix/SKILL.md +5 -0
  423. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  424. package/skills/gsd-autonomous/SKILL.md +5 -0
  425. package/skills/gsd-capture/SKILL.md +8 -4
  426. package/skills/gsd-code-review/SKILL.md +7 -2
  427. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  428. package/skills/gsd-config/SKILL.md +8 -4
  429. package/skills/gsd-debug/SKILL.md +11 -7
  430. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  431. package/skills/gsd-docs-update/SKILL.md +12 -7
  432. package/skills/gsd-eval-review/SKILL.md +6 -1
  433. package/skills/gsd-execute-phase/SKILL.md +12 -7
  434. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  435. package/skills/gsd-fast/SKILL.md +4 -0
  436. package/skills/gsd-forensics/SKILL.md +5 -1
  437. package/skills/gsd-graphify/SKILL.md +10 -6
  438. package/skills/gsd-health/SKILL.md +5 -0
  439. package/skills/gsd-help/SKILL.md +7 -2
  440. package/skills/gsd-import/SKILL.md +7 -3
  441. package/skills/gsd-inbox/SKILL.md +5 -0
  442. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  443. package/skills/gsd-manager/SKILL.md +6 -1
  444. package/skills/gsd-map-codebase/SKILL.md +7 -3
  445. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  446. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  447. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  448. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  449. package/skills/gsd-new-milestone/SKILL.md +6 -1
  450. package/skills/gsd-new-project/SKILL.md +5 -0
  451. package/skills/gsd-next/SKILL.md +6 -1
  452. package/skills/gsd-ns-context/SKILL.md +4 -0
  453. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  454. package/skills/gsd-ns-manage/SKILL.md +4 -0
  455. package/skills/gsd-ns-project/SKILL.md +4 -0
  456. package/skills/gsd-ns-review/SKILL.md +4 -0
  457. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  458. package/skills/gsd-onboard/SKILL.md +6 -1
  459. package/skills/gsd-pause-work/SKILL.md +5 -1
  460. package/skills/gsd-phase/SKILL.md +8 -4
  461. package/skills/gsd-plan-phase/SKILL.md +6 -1
  462. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  463. package/skills/gsd-pr-branch/SKILL.md +4 -0
  464. package/skills/gsd-profile-user/SKILL.md +5 -1
  465. package/skills/gsd-progress/SKILL.md +6 -1
  466. package/skills/gsd-quick/SKILL.md +16 -10
  467. package/skills/gsd-quick-batch/SKILL.md +20 -8
  468. package/skills/gsd-review/SKILL.md +5 -1
  469. package/skills/gsd-review-backlog/SKILL.md +3 -2
  470. package/skills/gsd-secure-phase/SKILL.md +6 -1
  471. package/skills/gsd-ship/SKILL.md +5 -0
  472. package/skills/gsd-sketch/SKILL.md +7 -2
  473. package/skills/gsd-spec-phase/SKILL.md +5 -1
  474. package/skills/gsd-spike/SKILL.md +8 -3
  475. package/skills/gsd-surface/SKILL.md +5 -1
  476. package/skills/gsd-thread/SKILL.md +4 -0
  477. package/skills/gsd-ui-phase/SKILL.md +6 -1
  478. package/skills/gsd-ui-review/SKILL.md +6 -1
  479. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  480. package/skills/gsd-undo/SKILL.md +5 -1
  481. package/skills/gsd-update/SKILL.md +6 -2
  482. package/skills/gsd-validate-phase/SKILL.md +6 -1
  483. package/skills/gsd-verify-work/SKILL.md +6 -1
  484. package/skills/gsd-workspace/SKILL.md +7 -3
  485. package/skills/gsd-workstreams/SKILL.md +6 -6
  486. package/vscode/package.json +1 -1
  487. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -22,6 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
24
  const node_child_process_1 = require("node:child_process");
25
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
25
26
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
26
27
  const ioMod = require("./io.cjs");
27
28
  const { output, error, ERROR_REASON, formatDiagnosticToken } = ioMod;
@@ -38,11 +39,13 @@ const coreUtilsMod = require("./core-utils.cjs");
38
39
  // generative-fix divergence CLAUDE.md warns about. Collapsed onto core-utils'
39
40
  // copy, which was already the leaf owner, so there is no second surface left to
40
41
  // drift and no parity test needed to police one.
41
- const { toPosixPath, generateSlugInternal, readSubdirectories, extractCanonicalPlanId, findUnsummarizedPlans, normalizeLineEndings, } = coreUtilsMod;
42
+ const { toPosixPath, generateSlugInternal, readSubdirectories, extractCanonicalPlanId, findUnsummarizedPlans, } = coreUtilsMod;
42
43
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
43
44
  const phaseIdMod = require("./phase-id.cjs");
44
- const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, isSentinelPhaseId, scopeToPhase, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, } = phaseIdMod;
45
+ const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, isSentinelPhaseId, scopeToPhase, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseIdMod;
45
46
  const pattern_cjs_1 = require("./pattern.cjs");
47
+ const gate_exit_cjs_1 = require("./gate-exit.cjs");
48
+ const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
46
49
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
47
50
  const phaseLocatorMod = require("./phase-locator.cjs");
48
51
  const { findPhaseInternal, getArchivedPhaseDirs, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
@@ -70,7 +73,12 @@ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
70
73
  const clock_cjs_1 = require("./clock.cjs");
71
74
  const state_transition_cjs_1 = require("./state-transition.cjs");
72
75
  const markdown_table_cjs_1 = require("./markdown-table.cjs");
76
+ // #5060: the Phase Status Module owns the ROADMAP Status-cell token vocabulary.
77
+ const phase_status_cjs_1 = require("./phase-status.cjs");
73
78
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
79
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap.cjs is an export= CommonJS module
80
+ const roadmapMod = require("./roadmap.cjs");
81
+ const { buildPhaseHeadingRegex } = roadmapMod;
74
82
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
75
83
  const uatPredicate = require("./uat-predicate.cjs");
76
84
  const { evaluateUatPassed } = uatPredicate;
@@ -81,7 +89,7 @@ const verificationMod = require("./verification.cjs");
81
89
  // cycle (the reverse edge, `state.cts → verify.cjs`, would).
82
90
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- verify.cjs is an export= CommonJS module
83
91
  const verifyMod = require("./verify.cjs");
84
- const { readVerificationStatus } = verificationMod;
92
+ const { readVerificationStatus, VERIFICATION_STATUS, findVerificationStatusError } = verificationMod;
85
93
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-dependency-graph.cjs is an export= CommonJS module
86
94
  const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
87
95
  const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted, isSummaryFileBlocked } = planDependencyGraphMod;
@@ -93,8 +101,7 @@ const milestoneLockMod = require("./milestone-lock.cjs");
93
101
  // eslint-disable-next-line @typescript-eslint/no-require-imports
94
102
  const planDocumentMod = require("./plan-document.cjs");
95
103
  const { parsePlanDocument, planIdFromFile } = planDocumentMod;
96
- const { extractFrontmatter } = frontmatterMod;
97
- const { readModifyWriteStateMd, stateExtractField, stateReplaceField, syncAndPreserveStateMd, withStateLock, updatePerformanceMetricsSection, } = stateMod;
104
+ const { readModifyWriteStateMd, stateExtractField, stateReplaceField, syncAndPreserveStateMd, assertVerificationReportsReadable, withStateLock, updatePerformanceMetricsSection, } = stateMod;
98
105
  // Any .md file with PLAN anywhere in the basename — diagnostic net
99
106
  const PLAN_OUTLINE_RE = /-PLAN-OUTLINE\.md$/i;
100
107
  const PLAN_PRE_BOUNCE_RE = /-PLAN.*\.pre-bounce\.md$/i;
@@ -349,17 +356,25 @@ function getRoadmapModeForPhase(cwd, phaseNum) {
349
356
  const milestoneContent = extractCurrentMilestone(rawContent, cwd);
350
357
  const fullContent = stripShippedMilestones(rawContent);
351
358
  const escapedPhase = phaseMarkdownRegexSource(phaseNum);
352
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
353
- const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'i');
359
+ // #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts) is
360
+ // anchored `^...$` with no 'm' flag — it is designed to test ONE
361
+ // already-tokenized heading LINE (mirroring searchPhaseInContent's own
362
+ // usage), not to `.match()` against multi-line content directly. Routing
363
+ // through tokenizeHeadings first is therefore required here, not a pure
364
+ // regex-literal swap (the design doc's "direct swap" plan does not hold
365
+ // once the anchor is accounted for).
366
+ const phaseHeader = buildPhaseHeadingRegex(escapedPhase);
354
367
  for (const content of [milestoneContent, fullContent]) {
355
- const headerMatch = content.match(phaseHeader);
356
- if (!headerMatch || headerMatch.index === undefined)
368
+ const heading = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content).find((h) => phaseHeader.test(h.text));
369
+ if (!heading)
357
370
  continue;
358
- const sectionStart = headerMatch.index;
371
+ const headingLineEnd = content.indexOf('\n', heading.offset);
372
+ const headerMatchLength = (headingLineEnd === -1 ? content.length : headingLineEnd) - heading.offset;
373
+ const sectionStart = heading.offset;
359
374
  const rest = content.slice(sectionStart);
360
- const nextHeader = rest.slice(headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\S/i);
375
+ const nextHeader = rest.slice(headerMatchLength).match(/\n#{2,4}\s+Phase\s+\S/i);
361
376
  const sectionEnd = nextHeader
362
- ? sectionStart + headerMatch[0].length + nextHeader.index
377
+ ? sectionStart + headerMatchLength + nextHeader.index
363
378
  : content.length;
364
379
  const section = content.slice(sectionStart, sectionEnd);
365
380
  const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
@@ -846,6 +861,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
846
861
  filesModified: planDoc.filesModified,
847
862
  filesDeleted: planDoc.filesDeleted,
848
863
  agentHint: planDoc.agentHint,
864
+ gapClosure: planDoc.gapClosure,
849
865
  taskCount: planDoc.taskCount,
850
866
  hasSummary,
851
867
  halted,
@@ -981,6 +997,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
981
997
  return planMap.has(lower) ? planMap.get(lower).id : dep;
982
998
  }),
983
999
  autonomous: rawPlan.autonomous,
1000
+ // #4924: execute-phase's `--gaps-only` filter reads this field off the
1001
+ // index. Before it was emitted, every plan read as non-gap-closure and the
1002
+ // filter selected nothing — a silent, successful-looking empty run.
1003
+ gap_closure: rawPlan.gapClosure,
984
1004
  objective: rawPlan.objective,
985
1005
  files_modified: rawPlan.filesModified,
986
1006
  files_deleted: rawPlan.filesDeleted,
@@ -1163,8 +1183,14 @@ function collectSiblingWorktreePhaseNums(cwd, used) {
1163
1183
  const siblingPlanningDir = (wt) => planningDir(wt, ws);
1164
1184
  const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
1165
1185
  // Same header shape the allocators scan locally (#1729 tag tolerance).
1166
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1167
- const headerPattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*(?:\s*\([^)\n]{0,200}\))?:/gi;
1186
+ // #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
1187
+ // (src/phase-id.cjs), the shared "which phases exist in this content" scan
1188
+ // owner. LABEL_ONLY baseline with no convention argument reproduces the
1189
+ // prior literal `Phase\s+` (no bracket tolerance) byte-for-byte — this is a
1190
+ // counter, matching the LABEL_ONLY docstring guidance exactly, so it is
1191
+ // deliberately NOT widened onto the bracket grammar the way the real-heading
1192
+ // readers (getRoadmapModeForPhase, phaseDisplayNameFromRoadmap) are.
1193
+ const { regex: headerPattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
1168
1194
  for (const line of porcelain.split('\n')) {
1169
1195
  if (!line.startsWith('worktree '))
1170
1196
  continue;
@@ -1189,7 +1215,7 @@ function collectSiblingWorktreePhaseNums(cwd, used) {
1189
1215
  let m;
1190
1216
  headerPattern.lastIndex = 0;
1191
1217
  while ((m = headerPattern.exec(content)) !== null) {
1192
- const num = parseInt(m[1], 10);
1218
+ const num = parseInt(m[phaseNumGroup], 10);
1193
1219
  if (!isSentinelPhaseId(num))
1194
1220
  used.add(num);
1195
1221
  }
@@ -1229,8 +1255,13 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
1229
1255
  // (section header, roadmap bullet, or on-disk directory) is counted:
1230
1256
  // 1) Section headers: ### Phase N: / ## Phase N: / #### Phase N:
1231
1257
  // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
1232
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1233
- const headerPattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*(?:\s*\([^)\n]{0,200}\))?:/gi;
1258
+ // #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
1259
+ // (src/phase-id.cjs) with the LABEL_ONLY baseline and no convention
1260
+ // argument — a counter site, matching phase-id.cts's own LABEL_ONLY
1261
+ // docstring guidance, reproducing the prior `Phase\s+` literal (no
1262
+ // bracket tolerance) byte-for-byte. See collectSiblingWorktreePhaseNums
1263
+ // (above) for the identical migration on the same regex shape.
1264
+ const { regex: headerPattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
1234
1265
  // 2) Roadmap bullet entries: - [ ] **Phase N: ...** (all checkbox variants)
1235
1266
  // The lookahead accepts colon, decimal-dot, whitespace, bold-close asterisk,
1236
1267
  // or end-of-line so titleless forms ("- [ ] **Phase 11**", "- [ ] Phase 11")
@@ -1239,7 +1270,7 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
1239
1270
  const usedPhaseNums = new Set();
1240
1271
  let m;
1241
1272
  while ((m = headerPattern.exec(content)) !== null) {
1242
- const num = parseInt(m[1], 10);
1273
+ const num = parseInt(m[phaseNumGroup], 10);
1243
1274
  // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
1244
1275
  if (!isSentinelPhaseId(num))
1245
1276
  usedPhaseNums.add(num);
@@ -1339,12 +1370,15 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) {
1339
1370
  // bullets, on-disk dirs. The bullet scan was missing here — a bullet-only
1340
1371
  // `Phase N` row was invisible to batch allocation (#3849 secondary).
1341
1372
  // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
1342
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1343
- const phasePattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*(?:\s*\([^)\n]{0,200}\))?:/gi;
1373
+ // #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
1374
+ // (src/phase-id.cjs) with the LABEL_ONLY baseline and no convention
1375
+ // argument — same counter-site migration as cmdPhaseAdd's identical
1376
+ // regex above.
1377
+ const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
1344
1378
  const bulletPattern = /^[ \t]*-[ \t]*\[[^\]]{0,200}\][ \t]*\*{0,2}Phase[ \t]+(\d+)(?=[:.\s*]|$)/gim;
1345
1379
  let m;
1346
1380
  while ((m = phasePattern.exec(content)) !== null) {
1347
- const num = parseInt(m[1], 10);
1381
+ const num = parseInt(m[phaseNumGroup], 10);
1348
1382
  // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
1349
1383
  if (isSentinelPhaseId(num))
1350
1384
  continue;
@@ -1467,9 +1501,19 @@ function scanExistingDecimalPhaseNumbers(phasesDir, rawContent, base) {
1467
1501
  decimalSet.add(parseInt(dm[1], 10));
1468
1502
  }
1469
1503
  }
1470
- const rmPhasePattern = new RegExp(
1471
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1472
- `#{2,4}\\s*Phase\\s+${phaseMarkdownRegexSource(base)}\\.(\\d+)${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'gi');
1504
+ // #5007 (Phase 6 / ADR-4910 §8): composes phaseHeadingPrefixSrcFor directly
1505
+ // rather than routing through buildPhaseHeadingScanRegex — the owner has no
1506
+ // decimal-subphase capture group. The design doc's suggested composition
1507
+ // passed `capturing=true` to phaseHeadingPrefixSrcFor, but this site has no
1508
+ // use for the bracket-id capture it would add, and doing so would silently
1509
+ // shift `rmMatch[1]` (the decimal digits) to group 2 — a real correctness
1510
+ // hazard. `capturing` is left at its default (false), and the `#{2,4}\s*`
1511
+ // heading marker (also omitted from the design's snippet) is kept explicit,
1512
+ // since phaseHeadingPrefixSrcFor never includes it — callers always add it
1513
+ // themselves (see buildPhaseHeadingScanRegex's own usage). No convention
1514
+ // argument: LABEL_ONLY with convention=undefined reproduces the prior
1515
+ // `Phase\s+` literal byte-for-byte.
1516
+ const rmPhasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)}${phaseMarkdownRegexSource(base)}\\.(\\d+)${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'gi');
1473
1517
  let rmMatch;
1474
1518
  while ((rmMatch = rmPhasePattern.exec(rawContent)) !== null) {
1475
1519
  decimalSet.add(parseInt(rmMatch[1], 10));
@@ -1496,12 +1540,27 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
1496
1540
  const content = extractCurrentMilestone(rawContent, cwd);
1497
1541
  const normalizedAfter = normalizePhaseName(afterPhase);
1498
1542
  const afterPhaseEscaped = phaseMarkdownRegexSource(normalizedAfter);
1499
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1500
- const targetPattern = new RegExp(`#{2,4}\\s*Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:`, 'i');
1501
- const headingMatch = targetPattern.test(content);
1543
+ const insertConvention = resolvePhaseIdConvention(cwd);
1544
+ // #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts)
1545
+ // is anchored `^...$` with no 'm' flag — designed to test ONE
1546
+ // already-tokenized heading LINE, not to `.test()` against multi-line
1547
+ // `content` directly (the design doc's "no anchoring change needed"
1548
+ // claim for this site does not hold once the anchor is accounted for —
1549
+ // same discrepancy as getRoadmapModeForPhase and phaseDisplayNameFromRoadmap
1550
+ // above/below). Routed through tokenizeHeadings, mirroring
1551
+ // searchPhaseInContent's own usage.
1552
+ const targetPattern = buildPhaseHeadingRegex(afterPhaseEscaped, insertConvention);
1553
+ const headingMatch = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content).some((h) => targetPattern.test(h.text));
1502
1554
  const bulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
1503
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1504
- const anyHeadingPattern = /#{2,4}\s*Phase\s+\d/i;
1555
+ // #5007 (Phase 6 / ADR-4910 §8): a pure "does ANY phase heading exist"
1556
+ // boolean — neither shared owner exposes this shape (buildPhaseHeadingRegex
1557
+ // is phase-N-specific; buildPhaseHeadingScanRegex always captures a phase
1558
+ // number). Composed directly from phaseHeadingPrefixSrcFor with the
1559
+ // `#{2,4}\s*` marker kept explicit (phaseHeadingPrefixSrcFor never
1560
+ // includes it). Unanchored `.test()` on multi-line content, same as the
1561
+ // pre-migration literal — no tokenize-first refactor needed here since
1562
+ // there is no anchor in this source.
1563
+ const anyHeadingPattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}\\d`, 'i');
1505
1564
  const roadmapHasHeadingPhases = anyHeadingPattern.test(content);
1506
1565
  const isBulletStyle = !headingMatch && bulletPattern.test(content) && !roadmapHasHeadingPhases;
1507
1566
  if (!headingMatch && !isBulletStyle) {
@@ -1577,16 +1636,42 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
1577
1636
  }
1578
1637
  else {
1579
1638
  const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${_decimalPhase} to break down)\n`;
1580
- const headerPattern = new RegExp(
1581
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1582
- `(#{2,4}\\s*Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*\\n)`, 'i');
1639
+ // #5007 (Phase 6 / ADR-4910 §8): this site needs the WHOLE matched
1640
+ // heading line plus its trailing newline (for splicing the new entry
1641
+ // in immediately after it) — neither shared owner returns that shape
1642
+ // (buildPhaseHeadingRegex captures only the title). Composed directly
1643
+ // from phaseHeadingPrefixSrcFor, keeping this site's own full-line
1644
+ // capture; audited together with the targetPattern/anyHeadingPattern
1645
+ // migration above (same describe block, same afterPhaseEscaped/
1646
+ // insertConvention inputs).
1647
+ const headerPattern = new RegExp(`(#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*\\n)`, 'i');
1583
1648
  const headerMatch = rawContent.match(headerPattern);
1584
1649
  if (!headerMatch) {
1585
1650
  error(`Could not find Phase ${afterPhase} header`);
1586
1651
  }
1587
1652
  const headerIdx = rawContent.indexOf(headerMatch[0]);
1588
1653
  const afterHeader = rawContent.slice(headerIdx + headerMatch[0].length);
1589
- const nextPhaseMatch = afterHeader.match(/\r?\n#{2,4}\s+Phase\s+\d[\d.]*/i);
1654
+ // #5007 (Phase 6 / ADR-4910 §8): this next-phase-boundary lookup was NOT
1655
+ // one of the 15 originally-marked grandfathered sites, but it hand-rolls
1656
+ // the identical `#{2,4}...Phase\s+` heading-marker shape and sits right
1657
+ // next to headerPattern above, which this same commit series just
1658
+ // widened (via insertConvention) to recognize a bracket-tagged target
1659
+ // heading. Left bracket-blind, this regex would silently defeat that
1660
+ // widening: under the bracket convention a freshly-recognized
1661
+ // bracket-tagged target could still fail to find the NEXT heading as a
1662
+ // boundary (`[CODE.MM] Phase N+1:` never matches a bare `Phase\s+`
1663
+ // literal), so the newly inserted entry falls through to the
1664
+ // rawContent.length branch and lands appended at end-of-file instead of
1665
+ // directly after the target section. Found while migrating the
1666
+ // neighboring sites, not deferred: this is the same defect class the
1667
+ // whole Phase 6 effort exists to drain, just an unmarked instance of it.
1668
+ // Composed with the SAME baseline/convention as headerPattern above so
1669
+ // target-discovery and next-boundary-discovery agree. The base
1670
+ // `#{2,4}\s+` (mandatory whitespace after the hashes, not `\s*`) is
1671
+ // preserved exactly as the prior literal had it — a pre-existing,
1672
+ // deliberately-unchanged asymmetry with the other sites in this file.
1673
+ const nextPhaseHeadingPrefix = `#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}`;
1674
+ const nextPhaseMatch = afterHeader.match(new RegExp(`\\r?\\n${nextPhaseHeadingPrefix}\\d[\\d.]*`, 'i'));
1590
1675
  let insertIdx;
1591
1676
  if (nextPhaseMatch) {
1592
1677
  insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
@@ -1901,9 +1986,30 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
1901
1986
  if (!isDecimal) {
1902
1987
  // #1729: fold an optional pre-colon ( ) tag into the suffix capture so it
1903
1988
  // is re-emitted verbatim — a tagged later phase still gets renumbered.
1904
- content = content.replace(
1905
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
1906
- /(#{2,4}\s*Phase\s+)(\d+(?:\.\d+)?)((?:\s*\([^)\r\n]{0,200}\))?\s*:)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}${suffix}`);
1989
+ // #5007 (Phase 6 / ADR-4910 §8): composes phaseHeadingPrefixSrcFor
1990
+ // directly (needs prefix/number/suffix as three separate capture groups
1991
+ // for a verbatim in-place rewrite — neither shared owner returns that
1992
+ // shape). Two deliberate departures from the design doc's suggested
1993
+ // composition, both kept for zero behavior change on this
1994
+ // highest-blast-radius site (feeds phase remove's renumber path):
1995
+ // 1. The `#{2,4}\s*` heading marker is kept explicit and folded INTO
1996
+ // the prefix capture group — the design's snippet omitted it
1997
+ // entirely (phaseHeadingPrefixSrcFor never includes it; every
1998
+ // caller adds it, e.g. buildPhaseHeadingScanRegex), and the
1999
+ // original group 1 here captured the marker too.
2000
+ // 2. No convention argument is threaded (LABEL_ONLY with
2001
+ // convention=undefined), even though phaseHeadingPrefixSrcFor
2002
+ // COULD accept one — this keeps the composed source byte-identical
2003
+ // to the prior `Phase\s+` literal, a deliberate non-widening for
2004
+ // this specific site (unlike cmdPhaseInsert's targetPattern).
2005
+ // The number group widens from `\d+(?:\.\d+)?` to the full
2006
+ // PHASE_NUMBER_TOKEN_SOURCE (letter suffix + multi-segment decimals) —
2007
+ // this is SAFE, not a behavior change: decrementRoadmapPhaseToken's own
2008
+ // regex (`^(\d+)(\.\d+)?$`) rejects anything wider and returns the
2009
+ // token unchanged, so a heading this widening newly matches (e.g.
2010
+ // "Phase 12A:") is written back byte-for-byte unchanged, identical to
2011
+ // the pre-migration outcome of the whole regex simply not matching it.
2012
+ content = content.replace(new RegExp(`(#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)})(${PHASE_NUMBER_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE}\\s*:)`, 'gi'), (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}${suffix}`);
1907
2013
  content = content.replace(/(-\s*\[[ x]\]\s*.*?Phase\s+)(\d+)(\s*:|\s+)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
1908
2014
  // ORDINAL-RENUMBER — CELL EDIT (not row-deletion) — migrated onto
1909
2015
  // updateTableCell (ADR-2143 §7, sibling of the deleteTableRow scoping
@@ -1992,8 +2098,52 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
1992
2098
  content = ordinalBefore + ordinalSection + ordinalRest;
1993
2099
  }
1994
2100
  content = content.replace(/(?<![0-9-])(\d{2})-(\d{2})(?=(?:(?:-[A-Za-z][A-Za-z0-9-]*)?-(?:PLAN|SUMMARY)\.md)|(?![0-9-]))/g, (_match, phaseNum, planNum) => `${decrementRoadmapPaddedPhaseNumber(phaseNum, removedInt)}-${planNum}`);
1995
- content = content.replace(/(\*\*Depends on\*\*\s*:\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
1996
- content = content.replace(/(Depends on:\*\*\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
2101
+ // #5007 (Phase 6 / ADR-4910): migrated the "Depends on" bold-field
2102
+ // decrement off two hand-rolled regexes — one per bold-placement
2103
+ // variant, `**Depends on**: Phase N` and `**Depends on:** Phase N` —
2104
+ // onto the PlanningDoc `boldField` read/write seam (`writePlansField`
2105
+ // above is the established precedent for this migration shape).
2106
+ // `BOLD_FIELD_RE` (planning-document.cts) recognizes BOTH placements
2107
+ // as the SAME field label ("Depends on") — `parseBoldFieldLine` strips
2108
+ // the token down to its inner text regardless of which side of `**`
2109
+ // the colon lands on — so this single loop naturally replaces both
2110
+ // regexes with one call site; no variant-specific branching remains.
2111
+ // Unlike `writePlansField`'s single `findField` lookup (scoped to one
2112
+ // already-isolated phase section), a whole ROADMAP.md can carry a
2113
+ // "Depends on" field in EVERY phase's detail section, so this walks
2114
+ // every `boldField` node in the whole parsed document whose label is
2115
+ // "Depends on" and rewrites each one independently — a
2116
+ // representability refusal or absent match on one field leaves that
2117
+ // field untouched and never blocks any other field, mirroring the
2118
+ // prior regexes' per-occurrence independence. A parse failure or an
2119
+ // unreadable-nodes refusal on `serialize` leaves `content` byte-for-
2120
+ // byte untouched, the same silent no-op the prior regexes had for any
2121
+ // input they didn't match (neither ever threw or warned).
2122
+ const dependsOnParsed = (0, planning_document_cjs_1.parsePlanningDoc)(content, 'ROADMAP.md');
2123
+ if (dependsOnParsed.ok) {
2124
+ let dependsOnDoc = dependsOnParsed.value;
2125
+ const dependsOnFieldIds = dependsOnDoc.nodes
2126
+ .filter((n) => n.kind === 'boldField' && n.label === 'Depends on')
2127
+ .map((n) => n.id);
2128
+ for (const fieldId of dependsOnFieldIds) {
2129
+ const currentRead = (0, planning_document_cjs_1.readNode)(dependsOnDoc, fieldId);
2130
+ if (!currentRead.ok)
2131
+ continue;
2132
+ const depMatch = /^(Phase\s+)(\d+(?:\.\d+)?)\b/i.exec(currentRead.value);
2133
+ if (!depMatch)
2134
+ continue;
2135
+ const [wholeMatch, prefix, num] = depMatch;
2136
+ const newValue = `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}` + currentRead.value.slice(wholeMatch.length);
2137
+ if (newValue === currentRead.value)
2138
+ continue;
2139
+ const staged = (0, planning_document_cjs_1.setFieldValue)(dependsOnDoc, fieldId, newValue);
2140
+ if (staged.ok)
2141
+ dependsOnDoc = staged.value;
2142
+ }
2143
+ const dependsOnOut = (0, planning_document_cjs_1.serialize)(dependsOnDoc);
2144
+ if (dependsOnOut.ok)
2145
+ content = dependsOnOut.value;
2146
+ }
1997
2147
  }
1998
2148
  (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, content);
1999
2149
  // #3685 / #3691: compare NORMALIZED bytes (what platformWriteSync actually
@@ -2017,21 +2167,22 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
2017
2167
  * phase, a stray 'Total Phases: 0' between fences). A file with no leading
2018
2168
  * frontmatter is all body: the field goes to content start, preserving the
2019
2169
  * former behavior for that shape.
2170
+ *
2171
+ * The block is the one `locateFrontmatterFence` finds (the one fence owner), so this writer
2172
+ * and every STATE.md reader agree on where the body starts. The blank line and the field go
2173
+ * right after the closing fence line, ended by that line's own line ending — so a CRLF file
2174
+ * gains CRLF lines only (#3572 review: a blanket re-join on '\r\n' doubled every carriage
2175
+ * return; joining the new lines on a bare '\n' mixed line endings).
2020
2176
  */
2021
2177
  function insertStateBodyFieldAtTop(content, fieldLine) {
2022
- // Split AND join on bare '\n' so CRLF line endings stay attached to their
2023
- // own lines — each '\r' remains the tail of the line it terminated, where
2024
- // the trimmed fence compare still matches it. (#3572 review: splitting on
2025
- // '\n' but re-joining on a detected '\r\n' doubled every carriage return.)
2026
- const lines = content.split('\n');
2027
- if ((lines[0] ?? '').trim() === '---') {
2028
- const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === '---');
2029
- if (closeIdx !== -1) {
2030
- lines.splice(closeIdx + 1, 0, '', fieldLine);
2031
- return lines.join('\n');
2032
- }
2178
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
2179
+ if (fence?.closed) {
2180
+ const nl = content[fence.closingFenceEnd] === '\r' ? '\r\n' : '\n';
2181
+ return `${content.slice(0, fence.closingFenceEnd)}${nl}${nl}${fieldLine}${content.slice(fence.closingFenceEnd)}`;
2033
2182
  }
2034
- return fieldLine + '\n' + content;
2183
+ // All body: the field is the new first line, ended like the document's first line.
2184
+ const firstNewline = content.indexOf('\n');
2185
+ return fieldLine + (firstNewline > 0 && content[firstNewline - 1] === '\r' ? '\r\n' : '\n') + content;
2035
2186
  }
2036
2187
  function cmdPhaseRemove(cwd, targetPhase, options, raw) {
2037
2188
  if (!targetPhase)
@@ -2076,6 +2227,21 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
2076
2227
  error(`Phase ${targetPhase} has ${summaryCount} executed plan(s). Use --force to remove anyway.`);
2077
2228
  }
2078
2229
  }
2230
+ // #5118 (no write before the error): the STATE.md rewrite below rebuilds the
2231
+ // frontmatter from EVERY surviving phase's report (buildStateFrontmatter →
2232
+ // isPhaseComplete), and it runs AFTER the directory removal, the sibling
2233
+ // renames and the ROADMAP rewrite. Validate every surviving phase's report
2234
+ // here, BEFORE the first write, so a report whose `status` is outside the
2235
+ // closed set fails this command having written nothing (the removed phase's
2236
+ // own report is not read afterwards — it is excluded). The set is the one the
2237
+ // rebuild scans (`statePhaseDirsToScan`: milestone-scoped, deduped), so a
2238
+ // survivor OUTSIDE that set — another milestone's phase directory — does not
2239
+ // block the remove.
2240
+ if (node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md'))) {
2241
+ const survivorStatusError = findVerificationStatusError(stateMod.statePhaseDirsToScan(cwd).filter((d) => d !== targetDir).map((d) => node_path_1.default.join(phasesDir, d)), { convention: resolvePhaseIdConvention(cwd) });
2242
+ if (survivorStatusError)
2243
+ throw survivorStatusError;
2244
+ }
2079
2245
  if (targetDir)
2080
2246
  node_fs_1.default.rmSync(node_path_1.default.join(phasesDir, targetDir), { recursive: true, force: true });
2081
2247
  let renamedDirs = [];
@@ -2236,8 +2402,20 @@ function phaseDisplayNameFromRoadmap(roadmapContent, phaseNum) {
2236
2402
  if (!roadmapContent || !phaseNum)
2237
2403
  return null;
2238
2404
  const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
2239
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
2240
- const heading = roadmapContent.match(new RegExp(`^#{2,4}\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:\\s*([^\\n]+)`, 'im'));
2405
+ // #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts) is
2406
+ // anchored `^...$` with no 'm' flag by design — it expects one
2407
+ // already-tokenized heading LINE, not a `^`/`m` scan against multi-line
2408
+ // content directly. Routed through tokenizeHeadings first (the same real
2409
+ // refactor searchPhaseInContent's own usage already requires), not a pure
2410
+ // regex-literal swap. Adopts buildPhaseHeadingRegex's ANY_BRACKET baseline —
2411
+ // a deliberate widening, matching getRoadmapModeForPhase's identical choice
2412
+ // above: this is a real-heading reader, so it inherits the same bracket-tag
2413
+ // tolerance searchPhaseInContent already applies everywhere else.
2414
+ const headingPattern = buildPhaseHeadingRegex(phaseEscaped);
2415
+ const matchedHeading = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(roadmapContent).find((h) => headingPattern.test(h.text));
2416
+ if (!matchedHeading)
2417
+ return null;
2418
+ const heading = matchedHeading.text.match(headingPattern);
2241
2419
  if (!heading)
2242
2420
  return null;
2243
2421
  const name = heading[1].replace(/\(INSERTED\)/i, '').trim();
@@ -3045,27 +3223,12 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3045
3223
  if (/status: diagnosed/.test(content))
3046
3224
  warnings.push(`${file}: has diagnosed gaps`);
3047
3225
  }
3048
- for (const file of scopeToPhase(phaseFiles.filter((f) => f.includes('-VERIFICATION') && f.endsWith('.md')), phaseFullDirBaseName)) {
3049
- const verificationFilePath = node_path_1.default.join(phaseFullDir, file);
3050
- // #3707-CR follow-up MINOR: normalize line endings at this read boundary
3051
- // (same fix as src/verification.cts's readVerificationStatus) so a
3052
- // lone-CR VERIFICATION.md's `---\r...\r---` frontmatter fence still
3053
- // matches extractFrontmatter's byte-0 check instead of silently
3054
- // dropping the human_needed/gaps_found advisory warning below.
3055
- const content = normalizeLineEndings(node_fs_1.default.readFileSync(verificationFilePath, 'utf-8'));
3056
- // #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false positives
3057
- // from historical metadata in the file body (e.g. `previous_status: gaps_found`).
3058
- // A full-text regex like /status: gaps_found/ matches the substring inside
3059
- // `previous_status: gaps_found`, producing spurious warnings even when the
3060
- // current frontmatter status is `passed`.
3061
- const verFm = extractFrontmatter(content, verificationFilePath);
3062
- // Normalise to lower-case so `status: Passed` (title-case) is not missed.
3063
- const verStatus = typeof verFm['status'] === 'string' ? verFm['status'].trim().toLowerCase() : '';
3064
- if (verStatus === 'human_needed')
3065
- warnings.push(`${file}: needs human verification`);
3066
- if (verStatus === 'gaps_found')
3067
- warnings.push(`${file}: has unresolved gaps`);
3068
- }
3226
+ // #5118 (ADR-5057 Phase 4): the VERIFICATION report's `status` is no
3227
+ // longer read here. This pre-scan used to read each report's frontmatter
3228
+ // itself (case-folded, bypassing the owner) to warn on human_needed /
3229
+ // gaps_found — but the completion GATE below (readVerificationStatus)
3230
+ // already refuses those statuses and names the route, so the advisory
3231
+ // could only ever duplicate the gate's own answer. One reader: the gate.
3069
3232
  }
3070
3233
  catch {
3071
3234
  /* best-effort (#2245 audit): this is an ADVISORY pre-scan of UAT/
@@ -3144,6 +3307,9 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3144
3307
  // #2617: pass the project's runtime so the blocked-completion error below
3145
3308
  // suggests the command surface this runtime actually installs
3146
3309
  // ($gsd-… on Codex) rather than a hard-coded Claude-style string.
3310
+ // #5118: an out-of-set report status THROWS VerificationStatusError out
3311
+ // of here; withPlanningLock releases the lock on the way out and the CLI
3312
+ // seam reports `verification_status_invalid`.
3147
3313
  const verificationStatus = readVerificationStatus(phaseFullDir, {
3148
3314
  runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd),
3149
3315
  convention: resolvePhaseIdConvention(cwd),
@@ -3161,9 +3327,22 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3161
3327
  staleCheckIndeterminate = true;
3162
3328
  warnings.push(`verification staleness check could not complete for phase ${phaseNum} — routed as not-stale, but this was not actually verified (#3057)`);
3163
3329
  }
3164
- if (verificationStatus.status !== 'passed') {
3330
+ if (verificationStatus.status !== VERIFICATION_STATUS.PASSED) {
3165
3331
  return verificationStatus;
3166
3332
  }
3333
+ // #5118 (no write before the error): the transaction below writes ROADMAP,
3334
+ // REQUIREMENTS and STATE, and the STATE frontmatter rebuild reads EVERY
3335
+ // phase's report (buildStateFrontmatter → isPhaseComplete). Validate it
3336
+ // here, BEFORE the first write, over EXACTLY the set that rebuild scans
3337
+ // (the milestone-scoped, deduped phase set — the owner runs
3338
+ // buildStateFrontmatter itself, so `state sync` and this command refuse
3339
+ // for the same phases), so a report whose `status` is outside the closed
3340
+ // set fails this command having written nothing (withPlanningLock
3341
+ // releases the lock).
3342
+ const preflightStatePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
3343
+ if (node_fs_1.default.existsSync(preflightStatePath)) {
3344
+ assertVerificationReportsReadable(node_fs_1.default.readFileSync(preflightStatePath, 'utf-8'), cwd);
3345
+ }
3167
3346
  const runPhaseCompleteTransaction = () => {
3168
3347
  const writes = [];
3169
3348
  let roadmapContent = null;
@@ -3357,7 +3536,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3357
3536
  const plansResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Plans Complete', ` ${summaryCount}/${planCount} `);
3358
3537
  if (plansResult.ok)
3359
3538
  text = plansResult.value;
3360
- const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ' Complete ');
3539
+ const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${(0, phase_status_cjs_1.toRoadmapStatusCell)(phase_status_cjs_1.PHASE_STATUS.COMPLETE).padEnd(11)} `);
3361
3540
  if (statusResult.ok)
3362
3541
  text = statusResult.value;
3363
3542
  // Preserve only a valid ISO date (#1161: idempotent; self-heal
@@ -3384,8 +3563,12 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3384
3563
  if (!planId)
3385
3564
  continue;
3386
3565
  const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
3387
- const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
3388
- b = b.replace(planCheckboxPattern, '$1x$2');
3566
+ // ADR-5057 §6 (Phase 13, #5217): the per-plan flip is a bullet
3567
+ // mutation, so it goes through `updateBullet` like the phase
3568
+ // checkbox flip above — first matching bullet only, never a
3569
+ // fenced line, never a mid-line occurrence.
3570
+ const planCheckboxPattern = new RegExp(`^(\\s*-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
3571
+ b = (0, markdown_sectionizer_cjs_1.updateBullet)(b, (_bulletText, rawLine) => planCheckboxPattern.test(rawLine), (rawLine) => rawLine.replace(planCheckboxPattern, '$1x$2'));
3389
3572
  }
3390
3573
  return b;
3391
3574
  });
@@ -3423,9 +3606,23 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
3423
3606
  if (node_fs_1.default.existsSync(reqPath)) {
3424
3607
  const phaseEsc = phaseMarkdownRegexSource(phaseNum);
3425
3608
  const currentMilestoneRoadmap = extractCurrentMilestone(roadmapContent, cwd);
3426
- const phaseSectionMatch = currentMilestoneRoadmap.match(new RegExp(
3427
- // phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
3428
- `(#{2,4}\\s*Phase\\s+${phaseEsc}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][\\s\\S]*?)(?=#{2,4}\\s*Phase\\s+|$)`, 'i'));
3609
+ // #5007 (Phase 6 / ADR-4910 §8): this site captures the entire
3610
+ // section BODY through to the next heading (via its own
3611
+ // `[\s\S]*?` lookahead-to-next-heading structure) — neither shared
3612
+ // owner returns that shape, so phaseHeadingPrefixSrcFor is composed
3613
+ // directly, keeping this site's own lookahead. The prior hand-rolled
3614
+ // heading-marker-plus-label literal appeared TWICE in this source
3615
+ // (the main capture prefix AND the lookahead) — both occurrences
3616
+ // were the literal-drift detector's target
3617
+ // (findPhaseHeadingScanLiteralDrift is a pure literal-text match,
3618
+ // blind to what surrounds it), so both are replaced with the same
3619
+ // composed prefix, built once via phaseHeadingPrefixSrcFor and
3620
+ // reused, so the two halves cannot drift from each other. No
3621
+ // convention argument: LABEL_ONLY with convention=undefined
3622
+ // reproduces the prior `Phase\s+` literal byte-for-byte — this
3623
+ // feeds cmdPhaseComplete, so no widening was introduced.
3624
+ const phaseSectionHeadingPrefix = `#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)}`;
3625
+ const phaseSectionMatch = currentMilestoneRoadmap.match(new RegExp(`(${phaseSectionHeadingPrefix}${phaseEsc}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][\\s\\S]*?)(?=${phaseSectionHeadingPrefix}|$)`, 'i'));
3429
3626
  const sectionText = phaseSectionMatch ? phaseSectionMatch[1] : '';
3430
3627
  // #4731: multiline-aware — hard-wrapped Requirements read past the
3431
3628
  // line break before the ID scan. The shared extractor also stops at
@@ -4209,6 +4406,9 @@ function cmdPhaseUatPassed(cwd, phaseNum, raw, opts = {}) {
4209
4406
  const phaseFullDir = node_path_1.default.join(cwd, phaseInfo['directory']);
4210
4407
  const report = evaluateUatPassed(phaseFullDir, { policy: opts.policy });
4211
4408
  output({ phase: phaseNum, ...report }, raw);
4409
+ // #5170: the exit status follows the verdict (status mode: a failing verdict is exit 1). Declared
4410
+ // AFTER output(), which rewrites the pending-outcome cell. The JSON above is unchanged.
4411
+ (0, gate_exit_cjs_1.declareGateExit)((0, gate_verdict_cjs_1.gateVerdict)(report.passed ? 'pass' : 'block', !report.passed, { phase: phaseNum, ...report }), 'status');
4212
4412
  }
4213
4413
  // #1437 — phase.list-plans: list plan files for a given phase number.
4214
4414
  // Returns the full scan result from scanPhasePlans so callers can read plan