@opengsd/gsd-core 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (551) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/README.ja-JP.md +3 -3
  5. package/README.ko-KR.md +3 -3
  6. package/README.pt-BR.md +3 -3
  7. package/README.zh-CN.md +3 -3
  8. package/agents/gsd-code-fixer.compact.md +7 -6
  9. package/agents/gsd-code-fixer.md +9 -8
  10. package/agents/gsd-code-reviewer.compact.md +5 -3
  11. package/agents/gsd-code-reviewer.md +8 -6
  12. package/agents/gsd-debug-session-manager.compact.md +17 -2
  13. package/agents/gsd-debug-session-manager.md +17 -2
  14. package/agents/gsd-debugger.md +3 -3
  15. package/agents/gsd-eval-auditor.compact.md +1 -1
  16. package/agents/gsd-eval-auditor.md +1 -1
  17. package/agents/gsd-executor.md +17 -12
  18. package/agents/gsd-intel-updater.compact.md +1 -1
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +2 -2
  21. package/agents/gsd-phase-researcher.md +19 -11
  22. package/agents/gsd-plan-checker.md +15 -9
  23. package/agents/gsd-planner.md +15 -11
  24. package/agents/gsd-project-researcher.compact.md +1 -1
  25. package/agents/gsd-project-researcher.md +1 -1
  26. package/agents/gsd-research-synthesizer.compact.md +1 -1
  27. package/agents/gsd-research-synthesizer.md +1 -1
  28. package/agents/gsd-ui-auditor.compact.md +21 -30
  29. package/agents/gsd-ui-auditor.md +166 -37
  30. package/agents/gsd-ui-researcher.compact.md +1 -1
  31. package/agents/gsd-ui-researcher.md +1 -1
  32. package/agents/gsd-verifier.md +37 -14
  33. package/bin/install.js +764 -287
  34. package/commands/gsd/add-tests.md +6 -1
  35. package/commands/gsd/ai-integration-phase.md +6 -1
  36. package/commands/gsd/audit-fix.md +5 -0
  37. package/commands/gsd/audit-milestone.md +6 -1
  38. package/commands/gsd/autonomous.md +7 -2
  39. package/commands/gsd/capture.md +9 -5
  40. package/commands/gsd/code-review.md +7 -2
  41. package/commands/gsd/complete-milestone.md +4 -0
  42. package/commands/gsd/config.md +7 -3
  43. package/commands/gsd/debug.md +11 -7
  44. package/commands/gsd/discuss-phase.md +7 -3
  45. package/commands/gsd/docs-update.md +12 -7
  46. package/commands/gsd/eval-review.md +6 -1
  47. package/commands/gsd/execute-phase.md +12 -7
  48. package/commands/gsd/extract-learnings.md +5 -0
  49. package/commands/gsd/fast.md +4 -0
  50. package/commands/gsd/forensics.md +5 -1
  51. package/commands/gsd/graphify.md +10 -6
  52. package/commands/gsd/health.md +5 -0
  53. package/commands/gsd/help.md +7 -2
  54. package/commands/gsd/import.md +7 -3
  55. package/commands/gsd/inbox.md +5 -0
  56. package/commands/gsd/ingest-docs.md +5 -1
  57. package/commands/gsd/manager.md +6 -1
  58. package/commands/gsd/map-codebase.md +7 -3
  59. package/commands/gsd/mempalace-capture.md +12 -4
  60. package/commands/gsd/mempalace-recall.md +5 -1
  61. package/commands/gsd/milestone-summary.md +5 -1
  62. package/commands/gsd/mvp-phase.md +8 -3
  63. package/commands/gsd/new-milestone.md +6 -1
  64. package/commands/gsd/new-project.md +5 -0
  65. package/commands/gsd/next.md +6 -1
  66. package/commands/gsd/ns-context.md +4 -0
  67. package/commands/gsd/ns-ideate.md +4 -0
  68. package/commands/gsd/ns-manage.md +4 -0
  69. package/commands/gsd/ns-project.md +4 -0
  70. package/commands/gsd/ns-review.md +4 -0
  71. package/commands/gsd/ns-workflow.md +4 -0
  72. package/commands/gsd/onboard.md +6 -1
  73. package/commands/gsd/pause-work.md +5 -1
  74. package/commands/gsd/phase.md +8 -4
  75. package/commands/gsd/plan-phase.md +6 -1
  76. package/commands/gsd/plan-review-convergence.md +11 -7
  77. package/commands/gsd/pr-branch.md +4 -0
  78. package/commands/gsd/profile-user.md +5 -1
  79. package/commands/gsd/progress.md +7 -2
  80. package/commands/gsd/quick-batch.md +21 -9
  81. package/commands/gsd/quick.md +12 -7
  82. package/commands/gsd/review.md +7 -4
  83. package/commands/gsd/secure-phase.md +6 -1
  84. package/commands/gsd/ship.md +5 -0
  85. package/commands/gsd/sketch.md +7 -2
  86. package/commands/gsd/spec-phase.md +5 -1
  87. package/commands/gsd/spike.md +8 -3
  88. package/commands/gsd/surface.md +5 -1
  89. package/commands/gsd/thread.md +4 -0
  90. package/commands/gsd/ui-phase.md +6 -1
  91. package/commands/gsd/ui-review.md +6 -1
  92. package/commands/gsd/ultraplan-phase.md +5 -1
  93. package/commands/gsd/undo.md +5 -1
  94. package/commands/gsd/update.md +6 -2
  95. package/commands/gsd/validate-phase.md +6 -1
  96. package/commands/gsd/verify-work.md +6 -1
  97. package/commands/gsd/workspace.md +7 -3
  98. package/gsd-core/bin/gsd-tools.cjs +477 -78
  99. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  100. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  101. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  102. package/gsd-core/bin/lib/audit.cjs +144 -42
  103. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  104. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  105. package/gsd-core/bin/lib/capability-registry.cjs +197 -222
  106. package/gsd-core/bin/lib/capability-validator.cjs +16 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +343 -207
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  117. package/gsd-core/bin/lib/config.cjs +76 -19
  118. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  119. package/gsd-core/bin/lib/coverage.cjs +4 -8
  120. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  121. package/gsd-core/bin/lib/decisions.cjs +30 -14
  122. package/gsd-core/bin/lib/drift.cjs +177 -42
  123. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  124. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  125. package/gsd-core/bin/lib/frontmatter.cjs +426 -234
  126. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  127. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  128. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  129. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  130. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  131. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  132. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  133. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  134. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  135. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  136. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  137. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  138. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  139. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  140. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  142. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  143. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  144. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  145. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  146. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  147. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  148. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  149. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  150. package/gsd-core/bin/lib/graphify.cjs +10 -2
  151. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  152. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  153. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  154. package/gsd-core/bin/lib/init.cjs +364 -132
  155. package/gsd-core/bin/lib/install-engine.cjs +30 -33
  156. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  157. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  158. package/gsd-core/bin/lib/io.cjs +122 -3
  159. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  160. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  161. package/gsd-core/bin/lib/milestone.cjs +37 -6
  162. package/gsd-core/bin/lib/model-resolver.cjs +171 -62
  163. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  164. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  165. package/gsd-core/bin/lib/pattern.cjs +10 -0
  166. package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
  167. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  168. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  169. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  170. package/gsd-core/bin/lib/phase-id.cjs +110 -8
  171. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  172. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  173. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  174. package/gsd-core/bin/lib/phase.cjs +489 -88
  175. package/gsd-core/bin/lib/plan-document.cjs +142 -20
  176. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  177. package/gsd-core/bin/lib/planning-document.cjs +692 -0
  178. package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
  179. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  180. package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
  181. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  184. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  185. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  186. package/gsd-core/bin/lib/project-root.cjs +41 -2
  187. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  188. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  189. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  190. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  191. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  192. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  193. package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
  194. package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
  195. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
  196. package/gsd-core/bin/lib/roadmap.cjs +405 -88
  197. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
  198. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
  199. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  200. package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
  201. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
  202. package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
  203. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  204. package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
  205. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  206. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  207. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  208. package/gsd-core/bin/lib/state-document.cjs +100 -22
  209. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  210. package/gsd-core/bin/lib/state.cjs +256 -105
  211. package/gsd-core/bin/lib/surface.cjs +19 -2
  212. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
  213. package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
  214. package/gsd-core/bin/lib/uat.cjs +432 -7
  215. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  216. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
  217. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  218. package/gsd-core/bin/lib/vendor/README.md +31 -9
  219. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  220. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  221. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  222. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  223. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  224. package/gsd-core/bin/lib/verification.cjs +1378 -274
  225. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  226. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  227. package/gsd-core/bin/lib/verify.cjs +357 -531
  228. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  229. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  230. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  231. package/gsd-core/bin/lib/workstream.cjs +11 -2
  232. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  233. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  234. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
  235. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  236. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  237. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  238. package/gsd-core/references/checkpoints.md +5 -3
  239. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  240. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  241. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  242. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  243. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  244. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  245. package/gsd-core/references/edge-probe.md +195 -21
  246. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  247. package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
  248. package/gsd-core/references/execute-phase-response-language.md +1 -1
  249. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  250. package/gsd-core/references/gsd-run-resolver.md +1 -1
  251. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  252. package/gsd-core/references/model-profiles.md +1 -1
  253. package/gsd-core/references/offer-next.md +1 -1
  254. package/gsd-core/references/phase-argument-parsing.md +9 -7
  255. package/gsd-core/references/phase-id-convention.md +28 -0
  256. package/gsd-core/references/planner-gap-closure.md +2 -0
  257. package/gsd-core/references/planner-load-graph-context.md +24 -13
  258. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  259. package/gsd-core/references/planning-config.md +12 -3
  260. package/gsd-core/references/spidr-splitting.md +1 -1
  261. package/gsd-core/references/tdd.md +37 -8
  262. package/gsd-core/references/ui-consideration-probe.md +10 -5
  263. package/gsd-core/references/verifier-phase-gates.md +5 -2
  264. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  265. package/gsd-core/references/verify-mvp-mode.md +2 -2
  266. package/gsd-core/references/workstream-flag.md +33 -3
  267. package/gsd-core/references/worktree-path-safety.md +321 -0
  268. package/gsd-core/templates/README.md +1 -1
  269. package/gsd-core/templates/UAT.md +17 -1
  270. package/gsd-core/templates/config.json +2 -11
  271. package/gsd-core/templates/verification-report.md +1 -1
  272. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  273. package/gsd-core/workflows/add-backlog.md +1 -1
  274. package/gsd-core/workflows/add-phase.md +8 -7
  275. package/gsd-core/workflows/add-tests.md +4 -3
  276. package/gsd-core/workflows/add-todo.md +6 -5
  277. package/gsd-core/workflows/ai-integration-phase.md +13 -4
  278. package/gsd-core/workflows/audit-fix.md +1 -1
  279. package/gsd-core/workflows/audit-milestone.md +4 -3
  280. package/gsd-core/workflows/audit-uat.md +1 -1
  281. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  282. package/gsd-core/workflows/autonomous.md +43 -23
  283. package/gsd-core/workflows/check-todos.md +7 -6
  284. package/gsd-core/workflows/cleanup.md +2 -2
  285. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  286. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  287. package/gsd-core/workflows/code-review-fix.md +112 -25
  288. package/gsd-core/workflows/code-review.md +146 -135
  289. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  290. package/gsd-core/workflows/complete-milestone.md +13 -8
  291. package/gsd-core/workflows/debug.md +32 -7
  292. package/gsd-core/workflows/diagnose-issues.md +3 -2
  293. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  294. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  295. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  296. package/gsd-core/workflows/discuss-phase.md +4 -3
  297. package/gsd-core/workflows/do.md +2 -2
  298. package/gsd-core/workflows/docs-update.md +6 -5
  299. package/gsd-core/workflows/edit-phase.md +4 -3
  300. package/gsd-core/workflows/eval-review.md +14 -5
  301. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  302. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
  303. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  304. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  305. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  306. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  307. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  308. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  309. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  310. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  311. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  312. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  313. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  314. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  315. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  316. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  317. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  318. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  319. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  320. package/gsd-core/workflows/execute-phase.md +96 -178
  321. package/gsd-core/workflows/execute-plan.md +18 -23
  322. package/gsd-core/workflows/explore.md +4 -4
  323. package/gsd-core/workflows/extract-learnings.md +4 -2
  324. package/gsd-core/workflows/fast.md +1 -1
  325. package/gsd-core/workflows/forensics.md +1 -1
  326. package/gsd-core/workflows/graduation.md +1 -1
  327. package/gsd-core/workflows/health.md +3 -2
  328. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  329. package/gsd-core/workflows/help/modes/full.md +5 -5
  330. package/gsd-core/workflows/help/modes/topic.md +15 -5
  331. package/gsd-core/workflows/import.md +4 -3
  332. package/gsd-core/workflows/inbox.md +2 -2
  333. package/gsd-core/workflows/ingest-docs.md +3 -3
  334. package/gsd-core/workflows/insert-phase.md +4 -3
  335. package/gsd-core/workflows/list-seeds.md +1 -1
  336. package/gsd-core/workflows/list-workspaces.md +1 -1
  337. package/gsd-core/workflows/manager.md +6 -4
  338. package/gsd-core/workflows/map-codebase.md +5 -4
  339. package/gsd-core/workflows/milestone-summary.md +3 -2
  340. package/gsd-core/workflows/mvp-phase.md +14 -14
  341. package/gsd-core/workflows/new-milestone.md +11 -11
  342. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  343. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  344. package/gsd-core/workflows/new-project.md +7 -7
  345. package/gsd-core/workflows/new-workspace.md +2 -2
  346. package/gsd-core/workflows/next.md +1 -1
  347. package/gsd-core/workflows/note.md +1 -1
  348. package/gsd-core/workflows/onboard.md +1 -1
  349. package/gsd-core/workflows/pause-work.md +2 -2
  350. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  351. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  352. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  353. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  354. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  355. package/gsd-core/workflows/plan-phase.md +50 -24
  356. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  357. package/gsd-core/workflows/plant-seed.md +62 -20
  358. package/gsd-core/workflows/pr-branch.md +113 -13
  359. package/gsd-core/workflows/profile-user.md +2 -2
  360. package/gsd-core/workflows/progress.md +19 -49
  361. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
  362. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  363. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  364. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  365. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  366. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  367. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  368. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  369. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  370. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  371. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  372. package/gsd-core/workflows/quick-batch.md +15 -10
  373. package/gsd-core/workflows/quick.md +62 -39
  374. package/gsd-core/workflows/reapply-patches.md +9 -3
  375. package/gsd-core/workflows/remove-phase.md +3 -2
  376. package/gsd-core/workflows/remove-workspace.md +2 -2
  377. package/gsd-core/workflows/resume-project.md +3 -2
  378. package/gsd-core/workflows/review.md +33 -17
  379. package/gsd-core/workflows/scan.md +3 -2
  380. package/gsd-core/workflows/secure-phase.md +13 -13
  381. package/gsd-core/workflows/settings-advanced.md +30 -10
  382. package/gsd-core/workflows/settings-integrations.md +2 -3
  383. package/gsd-core/workflows/settings.md +4 -4
  384. package/gsd-core/workflows/ship.md +14 -13
  385. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  386. package/gsd-core/workflows/sketch.md +1 -1
  387. package/gsd-core/workflows/smart-entry.md +2 -2
  388. package/gsd-core/workflows/spec-phase.md +15 -5
  389. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  390. package/gsd-core/workflows/spike.md +1 -1
  391. package/gsd-core/workflows/stats.md +1 -1
  392. package/gsd-core/workflows/sync-skills.md +5 -5
  393. package/gsd-core/workflows/thread.md +2 -2
  394. package/gsd-core/workflows/transition.md +13 -23
  395. package/gsd-core/workflows/ui-phase.md +48 -11
  396. package/gsd-core/workflows/ui-review.md +21 -6
  397. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  398. package/gsd-core/workflows/undo.md +339 -20
  399. package/gsd-core/workflows/update.md +7 -7
  400. package/gsd-core/workflows/validate-phase.md +12 -13
  401. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  402. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  403. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  404. package/gsd-core/workflows/verify-work.md +129 -55
  405. package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
  406. package/hooks/dist/gsd-check-update-worker.js +8 -0
  407. package/hooks/dist/gsd-check-update.js +8 -0
  408. package/hooks/dist/gsd-context-monitor.js +31 -8
  409. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  410. package/hooks/dist/gsd-secret-read-guard.js +161 -4
  411. package/hooks/dist/gsd-statusline.js +85 -20
  412. package/hooks/dist/gsd-update-banner.js +8 -0
  413. package/hooks/dist/gsd-validate-commit.sh +63 -4
  414. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  415. package/hooks/dist/gsd-workflow-guard.js +5 -4
  416. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  417. package/hooks/dist/lib/cli-exit.js +12 -0
  418. package/hooks/dist/lib/git-probe.js +17 -1
  419. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  420. package/hooks/gsd-agent-isolation-guard.js +32 -0
  421. package/hooks/gsd-check-update-worker.js +8 -0
  422. package/hooks/gsd-check-update.js +8 -0
  423. package/hooks/gsd-context-monitor.js +31 -8
  424. package/hooks/gsd-cursor-subagent-start.js +8 -0
  425. package/hooks/gsd-secret-read-guard.js +161 -4
  426. package/hooks/gsd-statusline.js +85 -20
  427. package/hooks/gsd-update-banner.js +8 -0
  428. package/hooks/gsd-validate-commit.sh +63 -4
  429. package/hooks/gsd-windsurf-pre-write.js +11 -2
  430. package/hooks/gsd-workflow-guard.js +5 -4
  431. package/hooks/gsd-worktree-path-guard.js +6 -2
  432. package/hooks/hooks.json +5 -5
  433. package/hooks/lib/cli-exit.js +12 -0
  434. package/hooks/lib/git-probe.js +17 -1
  435. package/hooks/lib/isolation-sentinel.js +2 -2
  436. package/package.json +22 -4
  437. package/scripts/build-hooks.js +15 -6
  438. package/scripts/changeset/parse.cjs +52 -4
  439. package/scripts/check-contract-drift.cjs +127 -11
  440. package/scripts/ci-timeout-report.cjs +770 -4
  441. package/scripts/command-contract-helpers.cjs +15 -8
  442. package/scripts/docs-guard-registry.cjs +34 -0
  443. package/scripts/gen-features.cjs +13 -8
  444. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  445. package/scripts/gen-loop-host-contract.cjs +79 -1
  446. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  447. package/scripts/gen-plugin-skills.cjs +87 -1
  448. package/scripts/gen-research-agents.cjs +24 -31
  449. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  450. package/scripts/gen-test-timings.cjs +32 -7
  451. package/scripts/lib/cli-exit.cjs +12 -0
  452. package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
  453. package/scripts/lib/ndjson-reporter.cjs +31 -5
  454. package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
  455. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  456. package/scripts/lib/vendor-bundle.cjs +59 -0
  457. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  458. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  459. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  460. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  461. package/scripts/lint-descriptions.cjs +7 -3
  462. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
  463. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  464. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  465. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  466. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  467. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  468. package/scripts/lint-phase-id-drift.cjs +319 -5
  469. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  470. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  471. package/scripts/lint-response-language-coverage.cjs +3 -0
  472. package/scripts/lint-retired-runtime-name.cjs +619 -0
  473. package/scripts/lint-skill-deps.cjs +7 -3
  474. package/scripts/lint-state-write-path-drift.cjs +93 -0
  475. package/scripts/lint-test-file-count.allowlist.json +38 -9
  476. package/scripts/lint-test-file-count.cjs +34 -1
  477. package/scripts/lint-vendored-deps.cjs +41 -5
  478. package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
  479. package/scripts/mutation-matrix.cjs +50 -5
  480. package/scripts/prompt-injection-scan.sh +4 -0
  481. package/scripts/release-tarball-smoke.cjs +194 -1
  482. package/scripts/require-issue-link-policy.cjs +6 -2
  483. package/scripts/sync-runtime-launcher.cjs +184 -2
  484. package/scripts/verify-npm-publish.cjs +76 -20
  485. package/skills/gsd-add-tests/SKILL.md +6 -1
  486. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  487. package/skills/gsd-audit-fix/SKILL.md +5 -0
  488. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  489. package/skills/gsd-autonomous/SKILL.md +7 -2
  490. package/skills/gsd-capture/SKILL.md +9 -5
  491. package/skills/gsd-code-review/SKILL.md +7 -2
  492. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  493. package/skills/gsd-config/SKILL.md +8 -4
  494. package/skills/gsd-debug/SKILL.md +11 -7
  495. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  496. package/skills/gsd-docs-update/SKILL.md +12 -7
  497. package/skills/gsd-eval-review/SKILL.md +6 -1
  498. package/skills/gsd-execute-phase/SKILL.md +12 -7
  499. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  500. package/skills/gsd-fast/SKILL.md +4 -0
  501. package/skills/gsd-forensics/SKILL.md +5 -1
  502. package/skills/gsd-graphify/SKILL.md +10 -6
  503. package/skills/gsd-health/SKILL.md +5 -0
  504. package/skills/gsd-help/SKILL.md +7 -2
  505. package/skills/gsd-import/SKILL.md +7 -3
  506. package/skills/gsd-inbox/SKILL.md +5 -0
  507. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  508. package/skills/gsd-manager/SKILL.md +6 -1
  509. package/skills/gsd-map-codebase/SKILL.md +7 -3
  510. package/skills/gsd-mempalace-capture/SKILL.md +12 -4
  511. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  512. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  513. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  514. package/skills/gsd-new-milestone/SKILL.md +6 -1
  515. package/skills/gsd-new-project/SKILL.md +5 -0
  516. package/skills/gsd-next/SKILL.md +6 -1
  517. package/skills/gsd-ns-context/SKILL.md +4 -0
  518. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  519. package/skills/gsd-ns-manage/SKILL.md +4 -0
  520. package/skills/gsd-ns-project/SKILL.md +4 -0
  521. package/skills/gsd-ns-review/SKILL.md +4 -0
  522. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  523. package/skills/gsd-onboard/SKILL.md +6 -1
  524. package/skills/gsd-pause-work/SKILL.md +5 -1
  525. package/skills/gsd-phase/SKILL.md +8 -4
  526. package/skills/gsd-plan-phase/SKILL.md +6 -1
  527. package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
  528. package/skills/gsd-pr-branch/SKILL.md +4 -0
  529. package/skills/gsd-profile-user/SKILL.md +5 -1
  530. package/skills/gsd-progress/SKILL.md +7 -2
  531. package/skills/gsd-quick/SKILL.md +16 -10
  532. package/skills/gsd-quick-batch/SKILL.md +21 -9
  533. package/skills/gsd-review/SKILL.md +7 -4
  534. package/skills/gsd-review-backlog/SKILL.md +3 -2
  535. package/skills/gsd-secure-phase/SKILL.md +6 -1
  536. package/skills/gsd-ship/SKILL.md +5 -0
  537. package/skills/gsd-sketch/SKILL.md +7 -2
  538. package/skills/gsd-spec-phase/SKILL.md +5 -1
  539. package/skills/gsd-spike/SKILL.md +8 -3
  540. package/skills/gsd-surface/SKILL.md +5 -1
  541. package/skills/gsd-thread/SKILL.md +4 -0
  542. package/skills/gsd-ui-phase/SKILL.md +6 -1
  543. package/skills/gsd-ui-review/SKILL.md +6 -1
  544. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  545. package/skills/gsd-undo/SKILL.md +5 -1
  546. package/skills/gsd-update/SKILL.md +6 -2
  547. package/skills/gsd-validate-phase/SKILL.md +6 -1
  548. package/skills/gsd-verify-work/SKILL.md +6 -1
  549. package/skills/gsd-workspace/SKILL.md +7 -3
  550. package/skills/gsd-workstreams/SKILL.md +6 -6
  551. package/vscode/package.json +1 -1
@@ -41,17 +41,24 @@ const { SCOPE } = planningScopeMod;
41
41
  const secrets_cjs_1 = require("./secrets.cjs");
42
42
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
43
43
  const scanPhasePlans = require("./plan-scan.cjs");
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-document.cjs is an export= CommonJS module
45
+ const planDocument = require("./plan-document.cjs");
44
46
  const state_document_cjs_1 = require("./state-document.cjs");
45
47
  const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
46
48
  const host_runtime_detection_cjs_1 = require("./host-runtime-detection.cjs");
47
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- commands.cjs is an export= CommonJS module
48
- const commandsMod = require("./commands.cjs");
49
49
  const security_cjs_1 = require("./security.cjs");
50
50
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
51
51
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
52
52
  const frontmatterMod = require("./frontmatter.cjs");
53
53
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
54
54
  const verificationMod = require("./verification.cjs");
55
+ // #5060: Phase Status Module — the single owner of "what state is phase P
56
+ // in?" (ADR-5057 §1/§2). Replaces this file's own determinePhaseStatus/
57
+ // projectCompletionStatus derivations, and cmdInitMilestoneOp's
58
+ // summary-count-only completion check (the former listPhaseSummaryFiles
59
+ // helper, since removed). listPhasePlanFiles (below) is unrelated — it backs
60
+ // only the STATE.md disk-plan-count validation, not a completion verdict.
61
+ const phase_status_cjs_1 = require("./phase-status.cjs");
55
62
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
56
63
  const uatPredicateMod = require("./uat-predicate.cjs");
57
64
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
@@ -87,12 +94,11 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
87
94
  const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
88
95
  const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
89
96
  const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
90
- const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName } = phaseId;
97
+ const { comparePhaseNum, normalizePhaseName, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, PHASE_DEP_REF_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName, parsePhaseId, renderPhaseId, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseId;
91
98
  const { pruneOrphanedWorktrees } = worktreeSafety;
92
- const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
93
- const { determinePhaseStatus } = commandsMod;
94
- const { extractFrontmatter } = frontmatterMod;
95
- const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
99
+ const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, resolveEnvWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, resolvePhaseIdConvention, worktreesOptedOut, } = planningWorkspace;
100
+ const { extractFrontmatter, frontmatterBlock } = frontmatterMod;
101
+ const { isPhaseComplete, resolveVerificationFile, resolveUatFile, VERIFICATION_STATUS, VerificationStatusError } = verificationMod;
96
102
  const { evaluateUatPassed } = uatPredicateMod;
97
103
  const { resolveLoopHooks } = loopResolverMod;
98
104
  const { loadRegistry } = capabilityLoaderMod;
@@ -100,7 +106,6 @@ const { resolveCapabilityRuntimeState } = capabilityStateMod;
100
106
  // Unused but imported for structural parity
101
107
  void stripShippedMilestones;
102
108
  // Accept all bold/colon variants of the Requirements header (#2769)
103
- const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
104
109
  // #2056/#2104: isForeignPrefixedPhaseQuery is imported from phase-id.cts
105
110
  // (the canonical predicate). parsePhasePrefix is no longer needed locally.
106
111
  // phaseInfoMatchesExactPrefix and roadmapPhaseMatchesExactPrefix are local
@@ -110,9 +115,18 @@ function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
110
115
  const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
111
116
  return numStr.toUpperCase() === phase.toUpperCase();
112
117
  }
118
+ // #4906 Phase 5 (#4984): NOT migrated onto the heading-baseline selector —
119
+ // this site was never one of the five ADR-4910 §8 call sites this phase owns
120
+ // (init milestone/progress heading scans + milestone.cts's unstarted-phase
121
+ // guard, all via buildPhaseHeadingScanRegex), and adding a NEW direct
122
+ // any-bracket selector consumer here inflated init.cts's
123
+ // tests/adr-612-bracket-heading-selection.test.cjs census past its pinned
124
+ // count. Left bracket-blind deliberately, matching this call's pre-#4984
125
+ // behavior; folding it into the census is Phase 6 work.
113
126
  function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
114
127
  const sectionRaw = roadmapPhase?.['section'];
115
128
  const section = typeof sectionRaw === 'string' ? sectionRaw : '';
129
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above the function.
116
130
  return new RegExp(`^#{2,4}\\s*Phase\\s+${(0, pattern_cjs_1.escapeRegex)(phase)}(?:\\b|\\s|:)`, 'i').test(section);
117
131
  }
118
132
  // #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
@@ -176,20 +190,10 @@ function applyRoadmapFallback(phaseInfo, roadmapPhase, buildFallback) {
176
190
  }
177
191
  return phaseInfo;
178
192
  }
179
- function listPhaseSummaryFiles(phaseDir) {
180
- return scanPhasePlans(phaseDir)['summaryFiles'];
181
- }
182
193
  function listPhasePlanFiles(phaseDir) {
183
194
  return scanPhasePlans(phaseDir)['planFiles'];
184
195
  }
185
- function projectCompletionStatus(implementationComplete, phaseComplete) {
186
- if (phaseComplete)
187
- return 'complete';
188
- if (implementationComplete)
189
- return 'executed';
190
- return 'incomplete';
191
- }
192
- function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, summaryCount, slashRuntime) {
196
+ function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, summaryCount, slashRuntime, convention) {
193
197
  // ADR-3180 §7.4 (issue #3186) / DO-NOT-MIGRATE exemption
194
198
  // (scripts/lint-completion-predicate-drift.cjs FUNCTION_SCOPED_EXEMPTIONS,
195
199
  // declared deviation): `implementation_complete` answers "are the plans
@@ -209,20 +213,35 @@ function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, s
209
213
  // #2617: the router still owns both the message content and the runtime
210
214
  // projection; init passes the phase number it already knows (its phaseDir
211
215
  // is unresolved in some branches, where the router could not derive one).
212
- const completionResult = isPhaseComplete(phaseFullDir, { runtime: slashRuntime, phaseNumber });
216
+ const completionResult = isPhaseComplete(phaseFullDir, { runtime: slashRuntime, phaseNumber, convention });
217
+ // #5118: the owner carried an out-of-set report status in its result
218
+ // (`statusError`, `verification.status: null`); an init bundle is never
219
+ // assembled over a report the owner refused — the carried error is raised
220
+ // here and every `init *` surface built on this projection fails with the
221
+ // error's own reason (the CLI entry seam), printing nothing.
222
+ if (completionResult.value.statusError)
223
+ throw completionResult.value.statusError;
213
224
  const verificationStatus = completionResult.value.verification;
214
225
  const projectedVerificationStatus = verificationStatus.status;
215
226
  const projectedVerificationAction = verificationStatus.next_action;
216
- const verificationPassed = projectedVerificationStatus === 'passed';
227
+ const verificationPassed = projectedVerificationStatus === VERIFICATION_STATUS.PASSED;
217
228
  const phaseComplete = completionResult.value.complete;
218
229
  return {
219
230
  implementation_complete: implementationComplete,
220
231
  verification_status: projectedVerificationStatus,
221
232
  verification_passed: verificationPassed,
222
233
  phase_complete: phaseComplete,
223
- completion_status: projectCompletionStatus(implementationComplete, phaseComplete),
234
+ // #5060: Phase Status Module — the single owner of "what state is phase P
235
+ // in?" (ADR-5057 §1/§2).
236
+ completion_status: (0, phase_status_cjs_1.toCompletionStatus)((0, phase_status_cjs_1.phaseStatusFromFacts)({
237
+ planCount,
238
+ summaryCount,
239
+ complete: phaseComplete,
240
+ verificationStatus: projectedVerificationStatus,
241
+ })),
224
242
  verification_next_action: projectedVerificationAction,
225
243
  verification_next_command: verificationStatus.next_command,
244
+ verification_route: verificationStatus.route,
226
245
  // #3057 B3: readVerificationStatus's result carries this flag when its
227
246
  // internal staleness check could not run to completion.
228
247
  verification_stale_check_indeterminate: 'staleCheckIndeterminate' in verificationStatus
@@ -469,7 +488,8 @@ function detectHasPriorPhases(cwd, phaseInfo) {
469
488
  * `false` — strict `=== true`, never coerced, mirrors `detectHasPriorPhases`'s
470
489
  * degrade-to-false discipline. `keyPath` is always a fixed literal supplied
471
490
  * by this module, never attacker/user input, so a plain bracket traversal
472
- * carries no prototype hazard here.
491
+ * carries no prototype hazard here. Only for keys whose default is OFF — a
492
+ * default-ON key read here silently inverts its unset value (#4977).
473
493
  */
474
494
  function readConfigJsonBoolean(cwd, keyPath) {
475
495
  try {
@@ -584,6 +604,13 @@ function detectPhaseMvpMode(cwd, phaseNumber) {
584
604
  const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
585
605
  const content = extractCurrentMilestone(rawContent, cwd);
586
606
  const escapedPhase = (0, pattern_cjs_1.escapeRegex)(phaseNumber);
607
+ // #4906 Phase 5 (#4984): NOT migrated onto phaseHeadingPrefixSrcFor — same
608
+ // out-of-scope reasoning as roadmapPhaseMatchesExactPrefix above (only the
609
+ // five buildPhaseHeadingScanRegex sites in init.cts/milestone.cts are this
610
+ // phase's owned migration); a direct selector call here would have added
611
+ // a fourth uncounted ANY_BRACKET consumer to init.cts's pinned
612
+ // tests/adr-612-bracket-heading-selection.test.cjs census.
613
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
587
614
  const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
588
615
  const headerMatch = content.match(phaseHeader);
589
616
  if (!headerMatch || headerMatch.index === undefined)
@@ -710,7 +737,13 @@ function buildSectionManifestField(cwd, phaseInfo, options, workflow, overrides
710
737
  flags,
711
738
  phaseNumber,
712
739
  hasPriorPhases: detectHasPriorPhases(cwd, phaseInfo),
713
- worktreesEnabled: readConfigJsonBoolean(cwd, ['workflow', 'use_worktrees']),
740
+ // #4977: `workflow.use_worktrees` defaults ON, so this fact must come from
741
+ // the #3972 owner `query dispatch-isolation` and the isolation guard
742
+ // already share — not `readConfigJsonBoolean`, whose absent ⇒ false
743
+ // polarity (right for the opt-in keys around it) excluded the quick
744
+ // pre-dispatch plan commit while the executor was still dispatched
745
+ // isolated. The ladder also brings root→workstream inheritance along.
746
+ worktreesEnabled: !worktreesOptedOut(cwd),
714
747
  phaseMvpMode: detectPhaseMvpMode(cwd, phaseNumber),
715
748
  needsCodebaseMap: overrides.needsCodebaseMap,
716
749
  chunkedMode,
@@ -751,6 +784,54 @@ function buildSectionManifestField(cwd, phaseInfo, options, workflow, overrides
751
784
  function milestoneRecord(cwd) {
752
785
  return (getMilestoneInfo(cwd).value ?? {});
753
786
  }
787
+ /**
788
+ * #4683 — threat IDs claimed by more than one of the phase's live PLAN files.
789
+ * `scanPhasePlans().planFiles` is the right input set twice over: it excludes
790
+ * derivative files (OUTLINE / PLAN-REVIEW / pre-bounce) AND `status: superseded`
791
+ * plans (#2349) — a superseded plan's IDs were deliberately reassigned to its
792
+ * replacement, so they must not hold against it. The per-document row parse is
793
+ * planDocument.extractThreatRegisterIds; only `<threat_model>` register rows
794
+ * count, and the reserved `T-{phase}-SC` shape is excluded there by grammar
795
+ * (every plan keeps that row by design). A missing/unreadable phase directory
796
+ * degrades to "no duplicates" — planning a brand-new phase has nothing to
797
+ * collide with.
798
+ */
799
+ function findDuplicateThreatIds(cwd, phaseDirRel) {
800
+ if (!phaseDirRel)
801
+ return [];
802
+ const phaseDir = node_path_1.default.join(cwd, phaseDirRel);
803
+ let planFiles;
804
+ try {
805
+ planFiles = scanPhasePlans(phaseDir).planFiles;
806
+ }
807
+ catch {
808
+ return [];
809
+ }
810
+ const owners = new Map();
811
+ for (const planFile of planFiles) {
812
+ let content;
813
+ try {
814
+ content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, planFile), 'utf-8');
815
+ }
816
+ catch {
817
+ continue;
818
+ }
819
+ for (const id of planDocument.extractThreatRegisterIds(content)) {
820
+ const claimed = owners.get(id);
821
+ if (claimed) {
822
+ if (!claimed.includes(planFile))
823
+ claimed.push(planFile);
824
+ }
825
+ else {
826
+ owners.set(id, [planFile]);
827
+ }
828
+ }
829
+ }
830
+ return [...owners.entries()]
831
+ .filter(([, claims]) => claims.length > 1)
832
+ .map(([id, plans]) => ({ id, plans: [...plans].sort() }))
833
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
834
+ }
754
835
  function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
755
836
  if (!phase) {
756
837
  error('phase required for init execute-phase');
@@ -788,9 +869,14 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
788
869
  has_reviews: false,
789
870
  };
790
871
  });
791
- const reqMatch = roadmapPhase?.['section']?.match(REQUIREMENTS_HEADER_RE);
792
- const reqExtracted = reqMatch
793
- ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
872
+ // #4731: multiline-aware — the Requirements field may hard-wrap, so the
873
+ // value is extracted past the line break before the ID scan.
874
+ const phaseSection = roadmapPhase?.['section'];
875
+ const reqLine = phaseSection
876
+ ? roadmapParser.extractPhaseFieldMultiline(phaseSection, 'Requirements')
877
+ : null;
878
+ const reqExtracted = reqLine
879
+ ? reqLine.replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
794
880
  : null;
795
881
  const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
796
882
  // #3188: these paths are null when the file is absent, matching the contract
@@ -800,6 +886,8 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
800
886
  const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
801
887
  const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
802
888
  const requirementsPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
889
+ // #4683: computed once — see the threat_id_duplicates fields in the payload.
890
+ const threatIdDuplicates = findDuplicateThreatIds(cwd, phaseInfo?.['directory']);
803
891
  const result = {
804
892
  executor_model: resolveModelInternal(cwd, 'gsd-executor'),
805
893
  verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
@@ -819,6 +907,13 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
819
907
  ? toPosixPath(node_path_1.default.join(cwd, phaseInfo['directory']))
820
908
  : null,
821
909
  phase_number: phaseInfo?.['phase_number'] || null,
910
+ // #4748: the disk path hands back the directory's padded number (`03A`)
911
+ // but the ROADMAP fallback above hands back the heading's bare one (`3A`),
912
+ // and execute-phase.md's review lookup needs the padded form for
913
+ // `{PADDED}-REVIEW.md`. It used to re-pad in shell with `printf "%02d"`,
914
+ // which cannot pad a letter id and reads an already-padded `08` as octal.
915
+ // Emit the canonical normalization, as the plan-phase/code-review inits do.
916
+ padded_phase: phaseInfo?.['phase_number'] ? normalizePhaseName(phaseInfo['phase_number']) : null,
822
917
  // #3171: prefer the ROADMAP's curated display name for `phase_name`. When
823
918
  // the phase directory already exists on disk, the disk-lookup path
824
919
  // (searchPhaseInDir) derives phase_name from the directory-name remainder
@@ -837,6 +932,12 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
837
932
  incomplete_plans: phaseInfo?.['incomplete_plans'] || [],
838
933
  plan_count: phaseInfo?.['plans']?.length || 0,
839
934
  incomplete_count: phaseInfo?.['incomplete_plans']?.length || 0,
935
+ // #4683: cross-plan threat-ID collisions (gap-closure plans renumbering
936
+ // from T-{phase}-01 again). execute-phase.md hard-stops on a non-empty
937
+ // list BEFORE any dispatch — SECURITY.md rows and VALIDATION.md's Threat
938
+ // Ref column key on this ID, so a reused ID is ambiguous downstream.
939
+ threat_id_duplicates: threatIdDuplicates,
940
+ threat_id_duplicate_count: threatIdDuplicates.length,
840
941
  // #2830: the halt-aware view, forwarded from the shared computation in
841
942
  // phase-locator. Additive — `incomplete_plans`/`incomplete_count` above keep
842
943
  // their exact name, type and semantics. Without this passthrough the shared
@@ -930,9 +1031,14 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
930
1031
  has_reviews: false,
931
1032
  };
932
1033
  });
933
- const reqMatch = roadmapPhase?.['section']?.match(REQUIREMENTS_HEADER_RE);
934
- const reqExtracted = reqMatch
935
- ? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
1034
+ // #4731: multiline-aware — the Requirements field may hard-wrap, so the
1035
+ // value is extracted past the line break before the ID scan.
1036
+ const phaseSection = roadmapPhase?.['section'];
1037
+ const reqLine = phaseSection
1038
+ ? roadmapParser.extractPhaseFieldMultiline(phaseSection, 'Requirements')
1039
+ : null;
1040
+ const reqExtracted = reqLine
1041
+ ? reqLine.replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
936
1042
  : null;
937
1043
  const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
938
1044
  const phaseDirPlan = phaseInfo?.['directory'] || null;
@@ -952,6 +1058,8 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
952
1058
  }
953
1059
  const granularityOverride = options['granularity'];
954
1060
  assertValidGranularityOverride(granularityOverride, error);
1061
+ // #4683: computed once — see the threat_id_duplicates fields in the payload.
1062
+ const threatIdDuplicatesPlan = findDuplicateThreatIds(cwd, phaseDirPlan);
955
1063
  const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
956
1064
  // #3188: see cmdInitExecutePhase — null when absent, parity with the
957
1065
  // conditional sibling fields in this same result object.
@@ -983,8 +1091,10 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
983
1091
  phase_slug: phaseInfo?.['phase_slug'] || null,
984
1092
  padded_phase: phaseNumberPlan ? normalizePhaseName(phaseNumberPlan) : null,
985
1093
  phase_req_ids,
1094
+ // #5060: Phase Status Module — the single owner of "what state is phase P
1095
+ // in?" (ADR-5057 §1/§2).
986
1096
  phase_status: phaseDirPlan
987
- ? determinePhaseStatus(phaseInfo?.['plans']?.length || 0, phaseInfo?.['summaries']?.length || 0, node_path_1.default.join(cwd, phaseDirPlan), 'Pending')
1097
+ ? (0, phase_status_cjs_1.toDisplayLabel)((0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(cwd, phaseDirPlan), { convention: resolvePhaseIdConvention(cwd) }).value.status, { pendingWord: 'Pending' })
988
1098
  : 'Pending',
989
1099
  has_research: phaseInfo?.['has_research'] || false,
990
1100
  has_context: phaseInfo?.['has_context'] || false,
@@ -996,6 +1106,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
996
1106
  has_reviews: phaseInfo?.['has_reviews'] || false,
997
1107
  has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
998
1108
  plan_count: phaseInfo?.['plans']?.length || 0,
1109
+ // #4683: the same cross-plan threat-ID duplicate list execute-phase gates
1110
+ // on, surfaced at PLAN time so the checker/reviewer catches the collision
1111
+ // before the plans are approved — not just before execution.
1112
+ threat_id_duplicates: threatIdDuplicatesPlan,
1113
+ threat_id_duplicate_count: threatIdDuplicatesPlan.length,
999
1114
  planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
1000
1115
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
1001
1116
  // #2376: absolute — see comment on phase_dir above.
@@ -1176,12 +1291,15 @@ function buildInitCompletenessFields(cwd) {
1176
1291
  }
1177
1292
  function cmdInitNewProject(cwd, raw, options = {}) {
1178
1293
  const config = loadConfig(cwd);
1179
- const homedir = node_os_1.default.homedir();
1180
- const braveKeyFile = node_path_1.default.join(homedir, '.gsd', 'brave_api_key');
1294
+ // #4976: probe the GSD-owned store the config loader resolves
1295
+ // (`GSD_HOME || homedir()`), so these flags agree with config-new-project's
1296
+ // key probes in the same /gsd-new-project run.
1297
+ const gsdHome = process.env['GSD_HOME'] || node_os_1.default.homedir();
1298
+ const braveKeyFile = node_path_1.default.join(gsdHome, '.gsd', 'brave_api_key');
1181
1299
  const hasBraveSearch = !!(process.env['BRAVE_API_KEY'] || node_fs_1.default.existsSync(braveKeyFile));
1182
- const firecrawlKeyFile = node_path_1.default.join(homedir, '.gsd', 'firecrawl_api_key');
1300
+ const firecrawlKeyFile = node_path_1.default.join(gsdHome, '.gsd', 'firecrawl_api_key');
1183
1301
  const hasFirecrawl = !!(process.env['FIRECRAWL_API_KEY'] || node_fs_1.default.existsSync(firecrawlKeyFile));
1184
- const exaKeyFile = node_path_1.default.join(homedir, '.gsd', 'exa_api_key');
1302
+ const exaKeyFile = node_path_1.default.join(gsdHome, '.gsd', 'exa_api_key');
1185
1303
  const hasExaSearch = !!(process.env['EXA_API_KEY'] || node_fs_1.default.existsSync(exaKeyFile));
1186
1304
  const hasCode = hasCodeFilesInternal(cwd);
1187
1305
  const hasPackageFile = hasPackageFileInternal(cwd);
@@ -1336,7 +1454,7 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
1336
1454
  // would otherwise silently delete a stale/invalid pointer as a side effect
1337
1455
  // of building a JSON report field, and (per #3579) could change what a
1338
1456
  // LATER resolution in the same process observes.
1339
- const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
1457
+ const resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
1340
1458
  const workstreamActive = !!resolvedWorkstream;
1341
1459
  const flatMode = !workstreamActive;
1342
1460
  // #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
@@ -1541,9 +1659,21 @@ function cmdInitVerifyWork(cwd, phase, raw) {
1541
1659
  };
1542
1660
  });
1543
1661
  const phaseDir = phaseInfo?.['directory'] || null;
1544
- const planCount = phaseInfo?.['plans']?.length || 0;
1545
- const summaryCount = phaseInfo?.['summaries']?.length || 0;
1546
- const completion = buildPhaseCompletionProjection(cwd, phaseInfo?.['phase_number'] || phase, phaseDir, planCount, summaryCount, _slashRuntime);
1662
+ // #5060: Phase Status Module — matched counts from the §7.5 owner
1663
+ // (scanPhasePlans via phaseStatus), not phaseInfo's raw plans/summaries
1664
+ // arrays, so this projection's counts agree with buildPhaseCompletionProjection's
1665
+ // own isPhaseComplete call below (see the convention thread there).
1666
+ const verifyWorkConvention = resolvePhaseIdConvention(cwd);
1667
+ const verifyWorkPs = phaseDir
1668
+ ? (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(cwd, phaseDir), { convention: verifyWorkConvention })
1669
+ : null;
1670
+ const planCount = verifyWorkPs
1671
+ ? verifyWorkPs.value.planCount
1672
+ : phaseInfo?.['plans']?.length || 0;
1673
+ const summaryCount = verifyWorkPs
1674
+ ? verifyWorkPs.value.summaryCount
1675
+ : phaseInfo?.['summaries']?.length || 0;
1676
+ const completion = buildPhaseCompletionProjection(cwd, phaseInfo?.['phase_number'] || phase, phaseDir, planCount, summaryCount, _slashRuntime, verifyWorkConvention);
1547
1677
  const uatReport = phaseDir
1548
1678
  ? evaluateUatPassed(node_path_1.default.join(cwd, phaseDir), {
1549
1679
  policy: { requireVerification: true },
@@ -2217,6 +2347,12 @@ function cmdInitTodos(cwd, area, raw) {
2217
2347
  function cmdInitMilestoneOp(cwd, raw) {
2218
2348
  const config = loadConfig(cwd);
2219
2349
  const milestone = milestoneRecord(cwd);
2350
+ // #5060: Phase Status Module — the single owner of "what state is phase P
2351
+ // in?" (ADR-5057 §1/§2). A phase counts as completed only when the ladder
2352
+ // itself says COMPLETE, not "has any *-SUMMARY.md" (a counts-only verdict
2353
+ // that bypasses isPhaseComplete and disagrees with every other completion
2354
+ // consumer on a stale `passed` report).
2355
+ const milestoneOpConvention = resolvePhaseIdConvention(cwd);
2220
2356
  let phaseCount = 0;
2221
2357
  let completedPhases = 0;
2222
2358
  const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
@@ -2225,14 +2361,16 @@ function cmdInitMilestoneOp(cwd, raw) {
2225
2361
  const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
2226
2362
  const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
2227
2363
  const currentSection = extractCurrentMilestone(roadmapRaw, cwd);
2228
- // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
2229
- const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'gi');
2364
+ // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
2365
+ // buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
2366
+ // bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
2367
+ const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, milestoneOpConvention);
2230
2368
  let m;
2231
2369
  while ((m = phasePattern.exec(currentSection)) !== null) {
2232
2370
  // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
2233
- if (isSentinelPhaseId(m[1]))
2371
+ if (isSentinelPhaseId(m[phaseNumGroup]))
2234
2372
  continue;
2235
- roadmapPhaseNumbers.push(m[1]);
2373
+ roadmapPhaseNumbers.push(m[phaseNumGroup]);
2236
2374
  }
2237
2375
  }
2238
2376
  catch {
@@ -2267,8 +2405,8 @@ function cmdInitMilestoneOp(cwd, raw) {
2267
2405
  if (!dirName)
2268
2406
  continue;
2269
2407
  try {
2270
- const hasSummary = listPhaseSummaryFiles(node_path_1.default.join(phasesDir, dirName)).length > 0;
2271
- if (hasSummary)
2408
+ const isComplete = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dirName), { convention: milestoneOpConvention }).value.status === phase_status_cjs_1.PHASE_STATUS.COMPLETE;
2409
+ if (isComplete)
2272
2410
  completedPhases++;
2273
2411
  }
2274
2412
  catch {
@@ -2287,8 +2425,8 @@ function cmdInitMilestoneOp(cwd, raw) {
2287
2425
  phaseCount = dirs.length;
2288
2426
  for (const dir of dirs) {
2289
2427
  try {
2290
- const hasSummary = listPhaseSummaryFiles(node_path_1.default.join(phasesDir, dir)).length > 0;
2291
- if (hasSummary)
2428
+ const isComplete = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dir), { convention: milestoneOpConvention }).value.status === phase_status_cjs_1.PHASE_STATUS.COMPLETE;
2429
+ if (isComplete)
2292
2430
  completedPhases++;
2293
2431
  }
2294
2432
  catch {
@@ -2375,6 +2513,10 @@ function cmdInitManager(cwd, raw) {
2375
2513
  const config = loadConfig(cwd);
2376
2514
  const milestone = milestoneRecord(cwd);
2377
2515
  const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
2516
+ const phaseIdConvention = resolvePhaseIdConvention(cwd);
2517
+ const capturesBracketId = phaseIdConvention === 'bracket';
2518
+ const phaseHeadingPrefix = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, phaseIdConvention, capturesBracketId);
2519
+ const phaseHeadingPrefixNoCapture = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, phaseIdConvention);
2378
2520
  const paths = planningPaths(cwd);
2379
2521
  if (!node_fs_1.default.existsSync(paths.roadmap)) {
2380
2522
  error(`No ROADMAP.md found. Run ${(0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', _slashRuntime)} first.`);
@@ -2384,39 +2526,61 @@ function cmdInitManager(cwd, raw) {
2384
2526
  }
2385
2527
  const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
2386
2528
  const content = extractCurrentMilestone(rawContent, cwd);
2387
- const phasesDir = paths.phases;
2388
2529
  // #3185 (ADR-3180 Decision 1): "which phase directories belong to the
2389
2530
  // CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
2390
2531
  // routed through it instead of a hand-rolled readdirSync + a separate
2391
2532
  // getMilestonePhaseFilter window check (which also never excluded
2392
2533
  // sentinels, unlike the owner).
2393
- const _phaseDirEntries = listMilestonePhaseDirs(phasesDir, { cwd }).value;
2394
2534
  const _checkboxStates = new Map();
2395
- const _cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
2535
+ // #4982: anchored (`^`, `m`) so the phase label must be the FIRST thing
2536
+ // after the checkbox (tolerating only an optional `**` bold marker) —
2537
+ // the prior unanchored `\s*.*` let the greedy `.*` bind to the LAST
2538
+ // "Phase N" mentioned anywhere in the line's prose instead of the line's
2539
+ // own phase. First-match-wins (`if (!_checkboxStates.has(...))` below)
2540
+ // additionally guards against a later anchored line (e.g. a per-plan
2541
+ // sub-entry self-titled "Phase N ...") overwriting an already-recorded
2542
+ // phase's own (first) checkbox — anchoring alone does not fully close
2543
+ // that "last-match-wins" composition defect.
2544
+ const _cbPattern = new RegExp(`^[ \\t]*-[ \\t]*\\[(x| )\\][ \\t]*(?:\\*\\*)?${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gim');
2396
2545
  let _cbMatch;
2397
2546
  while ((_cbMatch = _cbPattern.exec(content)) !== null) {
2398
- _checkboxStates.set(_cbMatch[2], _cbMatch[1].toLowerCase() === 'x');
2547
+ const phaseGroup = capturesBracketId ? 3 : 2;
2548
+ const _cbKey = _cbMatch[phaseGroup];
2549
+ if (!_checkboxStates.has(_cbKey)) {
2550
+ _checkboxStates.set(_cbKey, _cbMatch[1].toLowerCase() === 'x');
2551
+ }
2399
2552
  }
2400
2553
  // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
2401
- const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
2554
+ const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
2402
2555
  const phases = [];
2403
2556
  let match;
2404
2557
  while ((match = phasePattern.exec(content)) !== null) {
2405
- const phaseNum = match[1];
2406
- const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
2558
+ const bracketId = capturesBracketId ? match[1] : undefined;
2559
+ const phaseNum = capturesBracketId ? match[2] : match[1];
2560
+ const phaseName = (capturesBracketId ? match[3] : match[2])
2561
+ .replace(/\(INSERTED\)/i, '')
2562
+ .trim();
2563
+ let displayId;
2564
+ if (bracketId) {
2565
+ try {
2566
+ displayId = renderPhaseId(parsePhaseId(`${bracketId}-${phaseNum}`));
2567
+ }
2568
+ catch {
2569
+ // The bracket selector is deliberately read-tolerant. If a heading is
2570
+ // non-canonical, retain the manager row without fabricating display_id.
2571
+ }
2572
+ }
2407
2573
  const sectionStart = match.index;
2408
2574
  const restOfContent = content.slice(sectionStart);
2409
- const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d[\d.]*/i);
2575
+ const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}\\d[\\d.]*`, 'i'));
2410
2576
  const sectionEnd = nextHeader
2411
2577
  ? sectionStart + nextHeader.index
2412
2578
  : content.length;
2413
2579
  const section = content.slice(sectionStart, sectionEnd);
2414
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
2415
- const goal = goalMatch ? goalMatch[1].trim() : null;
2580
+ const goal = roadmapParser.extractPhaseFieldMultiline(section, 'Goal');
2416
2581
  const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
2417
2582
  const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
2418
- const normalized = normalizePhaseName(phaseNum);
2419
- let diskStatus = 'no_directory';
2583
+ let diskStatus = phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY;
2420
2584
  let planCount = 0;
2421
2585
  let summaryCount = 0;
2422
2586
  let hasContext = false;
@@ -2426,16 +2590,25 @@ function cmdInitManager(cwd, raw) {
2426
2590
  // #4014 (epic #3473 B4-unreadable): default COMPLETE — no directory at
2427
2591
  // all (dirMatch not found) is a genuine, not-unreadable answer.
2428
2592
  let contextScope = SCOPE.COMPLETE;
2429
- let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
2593
+ let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime, phaseIdConvention);
2430
2594
  try {
2431
- // #3185 (ADR-3180 Decision 2) moved this lookup off the
2432
- // milestone-scoped set and onto the physical one; that scope choice is
2433
- // kept. Only the matcher is this PR's: matchPhaseDirs resolves
2434
- // digit-leading directory names the token predicate cannot (#2528).
2435
- const dirMatch = matchPhaseDirs(_phaseDirEntries, normalized).matches[0];
2595
+ // #4801: resolve through the canonical locator instead of a private
2596
+ // current-milestone-only scan. findPhaseInternal searches the live
2597
+ // .planning/phases directory FIRST (same set the retired
2598
+ // matchPhaseDirs/_phaseDirEntries pair scanned — the #3185 physical-dir
2599
+ // scope choice is inherited by the locator's live arm) and then falls
2600
+ // back through listArchiveVersionDirs (workstream-scoped, #2855), so an
2601
+ // ARCHIVED phase directory with a passing verification resolves instead
2602
+ // of reporting no_directory/phase_complete:false. Five other init
2603
+ // commands already route through this same primitive; this was the
2604
+ // holdout private copy (#4793-family declared-owner drift).
2605
+ const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention);
2606
+ const dirMatch = located && located['found']
2607
+ ? node_path_1.default.posix.basename(String(located['directory']))
2608
+ : null;
2436
2609
  if (dirMatch) {
2437
- const fullDir = node_path_1.default.join(phasesDir, dirMatch);
2438
- const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, fullDir));
2610
+ const fullDir = node_path_1.default.join(cwd, String(located['directory']));
2611
+ const phaseDirRel = String(located['directory']);
2439
2612
  // #4014 (epic #3473 B4-unreadable): this whole block used to swallow
2440
2613
  // ANY readdirSync failure below into the bare `catch { /* empty */ }`
2441
2614
  // at the bottom — an unreadable phase directory reported the exact
@@ -2448,8 +2621,11 @@ function cmdInitManager(cwd, raw) {
2448
2621
  // only, the failure control-flow is untouched.
2449
2622
  contextScope = findContextMdIn(fullDir).scope;
2450
2623
  const phaseFiles = node_fs_1.default.readdirSync(fullDir);
2451
- planCount = listPhasePlanFiles(fullDir).length;
2452
- summaryCount = listPhaseSummaryFiles(fullDir).length;
2624
+ // #5060: Phase Status Module — the single owner of "what state is
2625
+ // phase P in?" (ADR-5057 §1/§2).
2626
+ const ps = (0, phase_status_cjs_1.phaseStatus)(fullDir, { convention: phaseIdConvention });
2627
+ planCount = ps.value.planCount;
2628
+ summaryCount = ps.value.summaryCount;
2453
2629
  // #3511-class: scope the raw listing to THIS phase's own artifacts
2454
2630
  // before the hasContext/hasResearch predicates run, so a stray
2455
2631
  // cross-phase `-CONTEXT.md`/`-RESEARCH.md` sitting in this directory
@@ -2457,21 +2633,8 @@ function cmdInitManager(cwd, raw) {
2457
2633
  const scopedFiles = scopeToPhase(phaseFiles, dirMatch);
2458
2634
  hasContext = findContextMdIn(scopedFiles) !== null;
2459
2635
  hasResearch = scopedFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
2460
- completion = buildPhaseCompletionProjection(cwd, phaseNum, phaseDirRel, planCount, summaryCount, _slashRuntime);
2461
- if (completion.phase_complete)
2462
- diskStatus = 'complete';
2463
- else if (completion.implementation_complete)
2464
- diskStatus = 'executed';
2465
- else if (summaryCount > 0)
2466
- diskStatus = 'partial';
2467
- else if (planCount > 0)
2468
- diskStatus = 'planned';
2469
- else if (hasResearch)
2470
- diskStatus = 'researched';
2471
- else if (hasContext)
2472
- diskStatus = 'discussed';
2473
- else
2474
- diskStatus = 'empty';
2636
+ completion = buildPhaseCompletionProjection(cwd, phaseNum, phaseDirRel, planCount, summaryCount, _slashRuntime, phaseIdConvention);
2637
+ diskStatus = (0, phase_status_cjs_1.toDiskStatus)(ps.value.status, { hasResearch, hasContext });
2475
2638
  const nowMs = clock_cjs_1.realClock.now();
2476
2639
  let newestMtime = 0;
2477
2640
  for (const f of phaseFiles) {
@@ -2509,6 +2672,7 @@ function cmdInitManager(cwd, raw) {
2509
2672
  const roadmapComplete = _checkboxStates.get(phaseNum) || false;
2510
2673
  phases.push({
2511
2674
  number: phaseNum,
2675
+ ...(displayId ? { display_id: displayId } : {}),
2512
2676
  name: phaseName,
2513
2677
  goal,
2514
2678
  depends_on,
@@ -2549,7 +2713,7 @@ function cmdInitManager(cwd, raw) {
2549
2713
  .filter((p) => p['phase_complete'] === true)
2550
2714
  .map((p) => normalizePhaseNumber(p['number'])));
2551
2715
  const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
2552
- const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
2716
+ const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*${phaseHeadingPrefixNoCapture}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
2553
2717
  let _allMatch;
2554
2718
  while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
2555
2719
  const phaseNum = normalizePhaseNumber(_allMatch[1]);
@@ -2575,13 +2739,47 @@ function cmdInitManager(cwd, raw) {
2575
2739
  function hasDepRelationship(numA, numB) {
2576
2740
  return reaches(numA, numB) || reaches(numB, numA);
2577
2741
  }
2742
+ // #4764: a phase reference in depends_on prose is a PHASE-SHAPED token in
2743
+ // context — directly following "Phase"/"Phases" — never a bare digit run.
2744
+ // The previous whole-field scrape matched the token grammar against every
2745
+ // digit run, so calendar dates ("2026-09-14" → 2026, 09, 14), git shas
2746
+ // ("8bf403100d" → 8b, 403100d, …), bracketed ledger ids (WINDOWS #1843) and
2747
+ // the row's OWN number all became "dependencies", and deps_satisfied came
2748
+ // back false for phases whose prose declares none (50 of 92 phases in the
2749
+ // reporter's milestone). The anchored grammar (owned by phase-id.cts as
2750
+ // PHASE_DEP_REF_SOURCE, shared with planning-inspect's dependencies) keeps
2751
+ // lists fully extracted ("Phases 601 and 602", "Phase 601, 602, and 603",
2752
+ // "Phase 1-3") — silently dropping a REAL dependency would clear
2753
+ // deps_satisfied prematurely, the dangerous direction. Negation prose
2754
+ // ("dropped the dependency on Phase 654") is NOT detected: the issue's own
2755
+ // minimum keeps such tokens.
2756
+ const depPhaseRefRe = new RegExp(`${PHASE_DEP_REF_SOURCE}`, 'gi');
2757
+ const depTokenRe = new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi');
2578
2758
  for (const phase of phases) {
2579
2759
  if (!phase['depends_on'] ||
2580
2760
  /^none$/i.test(phase['depends_on'].trim())) {
2581
2761
  phase['deps_satisfied'] = true;
2582
2762
  }
2583
2763
  else {
2584
- const depNums = phase['depends_on'].match(new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi')) || [];
2764
+ const prose = phase['depends_on'];
2765
+ const ownNumber = normalizePhaseNumber(phase['number']);
2766
+ const depNums = [];
2767
+ const seen = new Set();
2768
+ let refMatch;
2769
+ depPhaseRefRe.lastIndex = 0;
2770
+ while ((refMatch = depPhaseRefRe.exec(prose)) !== null) {
2771
+ let tok;
2772
+ depTokenRe.lastIndex = 0;
2773
+ while ((tok = depTokenRe.exec(refMatch[1])) !== null) {
2774
+ const normalized = normalizePhaseNumber(tok[0]);
2775
+ if (normalized === ownNumber)
2776
+ continue; // #4764: never the row's own phase
2777
+ if (seen.has(normalized))
2778
+ continue;
2779
+ seen.add(normalized);
2780
+ depNums.push(tok[0]);
2781
+ }
2782
+ }
2585
2783
  phase['deps_satisfied'] = depNums.every((n) => completedNums.has(normalizePhaseNumber(n)));
2586
2784
  phase['dep_phases'] = depNums;
2587
2785
  }
@@ -2594,7 +2792,7 @@ function cmdInitManager(cwd, raw) {
2594
2792
  }
2595
2793
  for (const phase of phases) {
2596
2794
  phase['is_next_to_discuss'] =
2597
- (phase['disk_status'] === 'empty' || phase['disk_status'] === 'no_directory') &&
2795
+ (phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.EMPTY || phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY) &&
2598
2796
  phase['deps_satisfied'];
2599
2797
  }
2600
2798
  let waitingSignal = null;
@@ -2616,12 +2814,12 @@ function cmdInitManager(cwd, raw) {
2616
2814
  }
2617
2815
  const recommendedActions = [];
2618
2816
  for (const phase of phases) {
2619
- if (phase['disk_status'] === 'complete')
2817
+ if (phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.COMPLETE)
2620
2818
  continue;
2621
2819
  // #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
2622
2820
  if (isSentinelPhaseId(phase['number']))
2623
2821
  continue;
2624
- if (phase['disk_status'] === 'executed') {
2822
+ if (phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.EXECUTED) {
2625
2823
  recommendedActions.push({
2626
2824
  phase: phase['number'],
2627
2825
  phase_name: phase['name'],
@@ -2630,7 +2828,7 @@ function cmdInitManager(cwd, raw) {
2630
2828
  command: phase['verification_next_command'],
2631
2829
  });
2632
2830
  }
2633
- else if (phase['disk_status'] === 'planned' && phase['deps_satisfied']) {
2831
+ else if (phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.PLANNED && phase['deps_satisfied']) {
2634
2832
  recommendedActions.push({
2635
2833
  phase: phase['number'],
2636
2834
  phase_name: phase['name'],
@@ -2639,8 +2837,8 @@ function cmdInitManager(cwd, raw) {
2639
2837
  command: `${(0, runtime_slash_cjs_1.formatGsdSlash)('execute-phase', _slashRuntime)} ${phase['number']}`,
2640
2838
  });
2641
2839
  }
2642
- else if (phase['disk_status'] === 'discussed' ||
2643
- phase['disk_status'] === 'researched') {
2840
+ else if (phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.DISCUSSED ||
2841
+ phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.RESEARCHED) {
2644
2842
  recommendedActions.push({
2645
2843
  phase: phase['number'],
2646
2844
  phase_name: phase['name'],
@@ -2649,7 +2847,7 @@ function cmdInitManager(cwd, raw) {
2649
2847
  command: `${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', _slashRuntime)} ${phase['number']}`,
2650
2848
  });
2651
2849
  }
2652
- else if ((phase['disk_status'] === 'empty' || phase['disk_status'] === 'no_directory') &&
2850
+ else if ((phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.EMPTY || phase['disk_status'] === phase_status_cjs_1.DISK_STATUS.NO_DIRECTORY) &&
2653
2851
  phase['is_next_to_discuss']) {
2654
2852
  recommendedActions.push({
2655
2853
  phase: phase['number'],
@@ -2660,10 +2858,10 @@ function cmdInitManager(cwd, raw) {
2660
2858
  });
2661
2859
  }
2662
2860
  }
2663
- const activeExecuting = phases.filter((p) => p['disk_status'] === 'partial' ||
2664
- (p['disk_status'] === 'planned' && p['is_active']));
2861
+ const activeExecuting = phases.filter((p) => p['disk_status'] === phase_status_cjs_1.DISK_STATUS.PARTIAL ||
2862
+ (p['disk_status'] === phase_status_cjs_1.DISK_STATUS.PLANNED && p['is_active']));
2665
2863
  const activePlanning = phases.filter((p) => p['is_active'] &&
2666
- (p['disk_status'] === 'discussed' || p['disk_status'] === 'researched'));
2864
+ (p['disk_status'] === phase_status_cjs_1.DISK_STATUS.DISCUSSED || p['disk_status'] === phase_status_cjs_1.DISK_STATUS.RESEARCHED));
2667
2865
  const filteredActions = recommendedActions.filter((action) => {
2668
2866
  if (action['action'] === 'execute' && activeExecuting.length > 0) {
2669
2867
  return activeExecuting.every((active) => !hasDepRelationship(action['phase'], active['number']));
@@ -2706,7 +2904,7 @@ function cmdInitManager(cwd, raw) {
2706
2904
  phases,
2707
2905
  phase_count: phases.length,
2708
2906
  completed_count: completedCount,
2709
- in_progress_count: phases.filter((p) => ['executed', 'partial', 'planned', 'discussed', 'researched'].includes(p['disk_status'])).length,
2907
+ in_progress_count: phases.filter((p) => [phase_status_cjs_1.DISK_STATUS.EXECUTED, phase_status_cjs_1.DISK_STATUS.PARTIAL, phase_status_cjs_1.DISK_STATUS.PLANNED, phase_status_cjs_1.DISK_STATUS.DISCUSSED, phase_status_cjs_1.DISK_STATUS.RESEARCHED].includes(p['disk_status'])).length,
2710
2908
  recommended_actions: filteredActions,
2711
2909
  waiting_signal: waitingSignal,
2712
2910
  all_complete: completedCount === nonBacklogPhases.length && nonBacklogPhases.length > 0,
@@ -2921,7 +3119,7 @@ function cmdInitUpdate(cwd, raw, options = {}) {
2921
3119
  function cmdInitTransition(cwd, raw, options = {}) {
2922
3120
  // #3579 root-cause fix: read-only informational field — peek, don't
2923
3121
  // self-heal (see cmdInitNewMilestone's identical rationale above).
2924
- const resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
3122
+ const resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
2925
3123
  const workstreamActive = !!resolvedWorkstream;
2926
3124
  const result = {
2927
3125
  other_active_workstreams: workstreamActive
@@ -3013,7 +3211,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3013
3211
  // non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
3014
3212
  // here and then found "absent" by diagnoseUnresolvedActiveWorkstream below,
3015
3213
  // which would misreport a present-but-bad marker as no marker at all.
3016
- const _resolvedWorkstream = process.env['GSD_WORKSTREAM'] || peekActiveWorkstream(cwd);
3214
+ const _resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
3017
3215
  if (_availableWorkstreams.length > 0 && !_resolvedWorkstream) {
3018
3216
  // #3579: getActiveWorkstream now inherits a pointer-less session's read
3019
3217
  // from the shared .planning/active-workstream marker, so reaching this
@@ -3040,17 +3238,50 @@ function cmdInitProgress(cwd, raw, options = {}) {
3040
3238
  const roadmapCheckboxStates = new Map();
3041
3239
  try {
3042
3240
  const roadmapContent = extractCurrentMilestone(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md'), 'utf-8'), cwd);
3043
- // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
3044
- const headingPattern = new RegExp(`#{2,4}\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
3241
+ const progressConvention = resolvePhaseIdConvention(cwd);
3242
+ // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
3243
+ // buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
3244
+ // bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
3245
+ const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention);
3045
3246
  let hm;
3046
3247
  while ((hm = headingPattern.exec(roadmapContent)) !== null) {
3047
- roadmapPhaseNums.add(hm[1]);
3048
- roadmapPhaseNames.set(hm[1], hm[2].replace(/\(INSERTED\)/i, '').trim());
3248
+ roadmapPhaseNums.add(hm[hNumGroup]);
3249
+ roadmapPhaseNames.set(hm[hNumGroup], hm[hNameGroup].replace(/\(INSERTED\)/i, '').trim());
3049
3250
  }
3050
- const cbPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
3251
+ // #4906 Phase 5 (#4984): NOT migrated onto phaseHeadingPrefixSrcFor — same
3252
+ // out-of-scope reasoning as roadmapPhaseMatchesExactPrefix/detectPhaseMvpMode
3253
+ // above; a direct LABEL_ONLY selector call here would have added a third
3254
+ // uncounted LABEL_ONLY consumer to init.cts's pinned
3255
+ // tests/adr-612-bracket-heading-selection.test.cjs census.
3256
+ // phase-id-owner: deliberately unmigrated (#4984 revert) — see comment above.
3257
+ // #4982: anchored (`^`, `m`) — the phase label must be the FIRST thing
3258
+ // after the checkbox (tolerating only an optional `**` bold marker) — so
3259
+ // the greedy `.*` this replaces can no longer bind a line's checkbox to
3260
+ // the LAST "Phase N" mentioned anywhere in that line's prose. This value
3261
+ // feeds `roadmap_complete` AND directly gates next_phase/current_phase
3262
+ // selection below, so getting the wrong phase here is load-bearing, not
3263
+ // metadata-only (contrast the `_cbPattern` site in cmdInitManager).
3264
+ // First-match-wins (`if (!roadmapCheckboxStates.has(...))`) additionally
3265
+ // guards against a later anchored line (e.g. a per-plan sub-entry
3266
+ // self-titled "Phase N ...") overwriting an already-recorded phase's own
3267
+ // (first) checkbox — anchoring alone does not fully close that
3268
+ // "last-match-wins" composition defect.
3269
+ // `(?:\[[^\]\n]{0,200}\]\s*)?` tolerates an optional leading bracket tag
3270
+ // (literal mirror of BASE_ANY_BRACKET_HEADING_PREFIX_SRC, phase-id.cts)
3271
+ // BEFORE the literal "Phase" word — decoration tolerance only, no capture
3272
+ // group, no convention gating. Without it the anchor fix above regresses
3273
+ // a checklist line combining a bracket tag with literal "Phase N" wording
3274
+ // (e.g. `**[GSD.02] Phase 3: ...**`), which the old unanchored `.*`
3275
+ // matched (by absorbing the bracket text). This does NOT migrate the site
3276
+ // onto phaseHeadingPrefixSrcFor / the bracket convention — that stays
3277
+ // out of scope per the comment above (#4984 revert, pinned census).
3278
+ const cbPattern = new RegExp(`^[ \\t]*-[ \\t]*\\[(x| )\\][ \\t]*(?:\\*\\*)?(?:\\[[^\\]\\n]{0,200}\\]\\s*)?Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gim');
3051
3279
  let cbm;
3052
3280
  while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
3053
- roadmapCheckboxStates.set(cbm[2], cbm[1].toLowerCase() === 'x');
3281
+ const cbKey = cbm[2];
3282
+ if (!roadmapCheckboxStates.has(cbKey)) {
3283
+ roadmapCheckboxStates.set(cbKey, cbm[1].toLowerCase() === 'x');
3284
+ }
3054
3285
  }
3055
3286
  }
3056
3287
  catch {
@@ -3072,8 +3303,9 @@ function cmdInitProgress(cwd, raw, options = {}) {
3072
3303
  seenPhaseNums.add(phaseNumber.replace(/^0+/, '') || '0');
3073
3304
  const phasePath = node_path_1.default.join(phasesDir, dir);
3074
3305
  const phaseFiles = node_fs_1.default.readdirSync(phasePath);
3075
- const plans = listPhasePlanFiles(phasePath);
3076
- const summaries = listPhaseSummaryFiles(phasePath);
3306
+ // #5060: Phase Status Module — the single owner of "what state is
3307
+ // phase P in?" (ADR-5057 §1/§2).
3308
+ const ps = (0, phase_status_cjs_1.phaseStatus)(phasePath, { convention: resolvePhaseIdConvention(cwd) });
3077
3309
  // #3511-class: scope the raw listing to THIS phase's own artifacts
3078
3310
  // before the hasResearch predicate runs, so a stray cross-phase
3079
3311
  // `-RESEARCH.md` sitting in this directory cannot win this phase's
@@ -3081,16 +3313,8 @@ function cmdInitProgress(cwd, raw, options = {}) {
3081
3313
  const scopedPhaseFiles = scopeToPhase(phaseFiles, dir);
3082
3314
  const hasResearch = scopedPhaseFiles.some((f) => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
3083
3315
  const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd), 'phases', dir)));
3084
- const completion = buildPhaseCompletionProjection(cwd, phaseNumber, phaseDirRel, plans.length, summaries.length, _slashRuntime);
3085
- const status = completion.phase_complete
3086
- ? 'complete'
3087
- : completion.implementation_complete
3088
- ? 'executed'
3089
- : plans.length > 0
3090
- ? 'in_progress'
3091
- : hasResearch
3092
- ? 'researched'
3093
- : 'pending';
3316
+ const completion = buildPhaseCompletionProjection(cwd, phaseNumber, phaseDirRel, ps.value.planCount, ps.value.summaryCount, _slashRuntime, resolvePhaseIdConvention(cwd));
3317
+ const status = (0, phase_status_cjs_1.toProgressStatus)(ps.value.status, { hasResearch });
3094
3318
  const phaseInfo = {
3095
3319
  number: phaseNumber,
3096
3320
  name: phaseName,
@@ -3099,30 +3323,37 @@ function cmdInitProgress(cwd, raw, options = {}) {
3099
3323
  // above still joins it against cwd.
3100
3324
  directory: toPosixPath(node_path_1.default.join(cwd, phaseDirRel)),
3101
3325
  status,
3102
- plan_count: plans.length,
3103
- summary_count: summaries.length,
3326
+ plan_count: ps.value.planCount,
3327
+ summary_count: ps.value.summaryCount,
3104
3328
  has_research: hasResearch,
3105
3329
  ...completion,
3106
3330
  };
3107
3331
  phases.push(phaseInfo);
3108
- if (!currentPhase && (status === 'executed' || status === 'in_progress' || status === 'researched')) {
3332
+ if (!currentPhase && (status === phase_status_cjs_1.PROGRESS_STATUS.EXECUTED || status === phase_status_cjs_1.PROGRESS_STATUS.IN_PROGRESS || status === phase_status_cjs_1.PROGRESS_STATUS.RESEARCHED)) {
3109
3333
  currentPhase = phaseInfo;
3110
3334
  }
3111
- if (!nextPhase && status === 'pending') {
3335
+ if (!nextPhase && status === phase_status_cjs_1.PROGRESS_STATUS.PENDING) {
3112
3336
  nextPhase = phaseInfo;
3113
3337
  }
3114
3338
  }
3115
3339
  }
3116
- catch {
3117
- /* intentionally empty */
3340
+ catch (err) {
3341
+ // #5118: the owner's out-of-set report error is not a scan failure to
3342
+ // degrade over — the bundle is never assembled over a refused report.
3343
+ if (err instanceof VerificationStatusError)
3344
+ throw err;
3345
+ /* otherwise intentionally empty */
3118
3346
  }
3119
3347
  for (const [num, name] of roadmapPhaseNames) {
3120
3348
  const stripped = num.replace(/^0+/, '') || '0';
3121
3349
  if (!seenPhaseNums.has(stripped)) {
3122
3350
  const checkboxComplete = roadmapCheckboxStates.get(num) === true ||
3123
3351
  roadmapCheckboxStates.get(stripped) === true;
3124
- const completion = buildPhaseCompletionProjection(cwd, num, null, 0, 0, _slashRuntime);
3125
- const status = 'not_started';
3352
+ const completion = buildPhaseCompletionProjection(cwd, num, null, 0, 0, _slashRuntime, resolvePhaseIdConvention(cwd));
3353
+ // #5060: this synthetic row has no phase directory at all — the
3354
+ // roadmap heading exists but no `seenPhaseNums` disk match was found —
3355
+ // so it is the ladder's own NOT_STARTED word, not a derived guess.
3356
+ const status = phase_status_cjs_1.PROGRESS_STATUS.NOT_STARTED;
3126
3357
  const phaseInfo = {
3127
3358
  number: num,
3128
3359
  // #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
@@ -3159,7 +3390,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3159
3390
  {
3160
3391
  const frontier = phases.find((p) => {
3161
3392
  const st = p['status'];
3162
- return (st === 'pending' || st === 'not_started') && p['roadmap_complete'] !== true;
3393
+ return (st === phase_status_cjs_1.PROGRESS_STATUS.PENDING || st === phase_status_cjs_1.PROGRESS_STATUS.NOT_STARTED) && p['roadmap_complete'] !== true;
3163
3394
  });
3164
3395
  if (frontier)
3165
3396
  nextPhase = frontier;
@@ -3196,8 +3427,8 @@ function cmdInitProgress(cwd, raw, options = {}) {
3196
3427
  milestone_name: milestone['name'] ?? null,
3197
3428
  phases,
3198
3429
  phase_count: phases.length,
3199
- completed_count: phases.filter((p) => p['status'] === 'complete').length,
3200
- in_progress_count: phases.filter((p) => ['executed', 'in_progress'].includes(p['status'])).length,
3430
+ completed_count: phases.filter((p) => p['status'] === phase_status_cjs_1.PROGRESS_STATUS.COMPLETE).length,
3431
+ in_progress_count: phases.filter((p) => [phase_status_cjs_1.PROGRESS_STATUS.EXECUTED, phase_status_cjs_1.PROGRESS_STATUS.IN_PROGRESS].includes(p['status'])).length,
3201
3432
  current_phase: currentPhase,
3202
3433
  next_phase: nextPhase,
3203
3434
  paused_at: pausedAt,
@@ -3789,10 +4020,11 @@ function buildSkillManifest(cwd, skillsDir = null) {
3789
4020
  seenNamesInRoot.add(name);
3790
4021
  const description = frontmatter['description'] || '';
3791
4022
  const triggers = [];
3792
- const bodyMatch = content.match(/^---[\s\S]*?---\s*\r?\n([\s\S]*)$/);
3793
- if (bodyMatch) {
3794
- const body = bodyMatch[1];
3795
- const triggerLines = body.match(/^TRIGGER\s+when:\s*(.+)$/gmi);
4023
+ // TRIGGER lines are read from the body after the block `extractFrontmatter` read (the
4024
+ // one fence owner), so a `---` inside a frontmatter value cannot start the body early.
4025
+ const block = frontmatterBlock(content);
4026
+ if (block) {
4027
+ const triggerLines = block.rest.match(/^TRIGGER\s+when:\s*(.+)$/gmi);
3796
4028
  if (triggerLines) {
3797
4029
  for (const line of triggerLines) {
3798
4030
  const m = line.match(/^TRIGGER\s+when:\s*(.+)$/i);