@opengsd/gsd-core 1.18.0 → 1.20.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 (308) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -15,6 +15,7 @@ const node_os_1 = __importDefault(require("node:os"));
15
15
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
16
16
  const clock_cjs_1 = require("./clock.cjs");
17
17
  const pattern_cjs_1 = require("./pattern.cjs");
18
+ const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
18
19
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
19
20
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
20
21
  const io = require("./io.cjs");
@@ -33,6 +34,8 @@ const roadmapParser = require("./roadmap-parser.cjs");
33
34
  const coreUtils = require("./core-utils.cjs");
34
35
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
35
36
  const phaseId = require("./phase-id.cjs");
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id-display.cjs is an export= CommonJS module
38
+ const phaseIdDisplayMod = require("./phase-id-display.cjs");
36
39
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- worktree-safety.cjs is an export= CommonJS module
37
40
  const worktreeSafety = require("./worktree-safety.cjs");
38
41
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
@@ -93,12 +96,13 @@ const { harvestPriorVerifyCommands } = verifyCommandGrounding;
93
96
  const { output, error, ERROR_REASON, formatDiagnosticToken } = io;
94
97
  const { loadConfig, loadConfigResolved } = configLoader;
95
98
  const { resolveModelInternal, resolveGranularityInternal, assertValidGranularityOverride } = modelResolver;
96
- const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
99
+ const { phaseDirectoryOptions, findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs, } = phaseLocator;
97
100
  const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
98
101
  const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
99
- 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;
102
+ const { comparePhaseNum, phaseDirectoryName, customPhaseQuery, customDirectoryOwner, phaseScanTokenSource, phaseKeyFromToken, phaseDirectoryContext, normalizePhaseName, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, PHASE_DEP_REF_SOURCE, extractPhaseDependencyTokens, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName, parsePhaseId, renderPhaseId, bracketQualifiedKey, phaseHeadingPrefixSrcFor, parsePhaseChecklistLine, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseId;
103
+ const { phaseToken } = phaseIdDisplayMod;
100
104
  const { pruneOrphanedWorktrees } = worktreeSafety;
101
- const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, resolveEnvWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, resolvePhaseIdConvention, worktreesOptedOut, } = planningWorkspace;
105
+ const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, resolveEnvWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, findPhaseSpecIn, resolvePhaseIdConvention, worktreesOptedOut, } = planningWorkspace;
102
106
  const { extractFrontmatter, frontmatterBlock } = frontmatterMod;
103
107
  const { isPhaseComplete, resolveVerificationFile, resolveUatFile, VERIFICATION_STATUS, VerificationStatusError } = verificationMod;
104
108
  const { evaluateUatPassed } = uatPredicateMod;
@@ -134,8 +138,8 @@ function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
134
138
  // #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
135
139
  // with the #2056 foreign-prefix guard, so every init command gets the same
136
140
  // protection without duplicating the guard logic at each call site.
137
- function guardedFindPhase(cwd, phase, projectCode) {
138
- let phaseInfo = findPhaseInternal(cwd, phase);
141
+ function guardedFindPhase(cwd, phase, projectCode, phaseNaming) {
142
+ let phaseInfo = findPhaseInternal(cwd, phase, undefined, phaseDirectoryOptions(cwd, { phase_naming: phaseNaming, project_code: projectCode }));
139
143
  if (isForeignPrefixedPhaseQuery(phase, projectCode) && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
140
144
  phaseInfo = null;
141
145
  }
@@ -177,6 +181,28 @@ function resolvePhaseDisplayName(phaseInfo, roadmapPhase) {
177
181
  || phaseInfo?.['phase_name']
178
182
  || null;
179
183
  }
184
+ /**
185
+ * #5629: the phase SPEC as init facts, for every init payload a SPEC-consuming workflow parses
186
+ * (discuss-phase via `phase-op`, discuss-phase-assumptions, plan-phase). `has_spec` is always present;
187
+ * `spec_path` (absolute, posix) only when a plain `NN-SPEC.md` exists. One predicate (`findPhaseSpecIn`, shared
188
+ * with the context-drift gate) decides, so a UI-SPEC or AI-SPEC is never mistaken for the requirements lock and
189
+ * no workflow globs for it. A phase with no directory yet, or one that cannot be read, has no SPEC.
190
+ */
191
+ function phaseSpecFacts(phaseDirAbs) {
192
+ if (!phaseDirAbs)
193
+ return { has_spec: false };
194
+ let files;
195
+ try {
196
+ files = node_fs_1.default.readdirSync(phaseDirAbs);
197
+ }
198
+ catch {
199
+ return { has_spec: false };
200
+ }
201
+ // Scoped to THIS phase's own artifacts first (#3511), like the sibling artifact lookups: a misfiled
202
+ // `02-SPEC.md` in phase 03's directory must not win (the context-drift gate scopes the same way).
203
+ const spec = findPhaseSpecIn(scopeToPhase(files, node_path_1.default.basename(phaseDirAbs)));
204
+ return spec ? { has_spec: true, spec_path: toPosixPath(node_path_1.default.join(phaseDirAbs, spec)) } : { has_spec: false };
205
+ }
180
206
  /**
181
207
  * #2994 (review finding, DEFECT.GENERATIVE-FIX): shared archived/not-found
182
208
  * fallback applied identically by `cmdInitExecutePhase`, `cmdInitPlanPhase`,
@@ -600,6 +626,28 @@ function detectFallowConfig(cwd) {
600
626
  function detectGitCreateTag(cwd) {
601
627
  return readConfigJsonValue(cwd, ['git', 'create_tag']) !== false;
602
628
  }
629
+ /**
630
+ * `git.repo_tagging` ground truth (#5706, epic #3628): the milestone tagging
631
+ * mode exposed to `complete-milestone.md`. Only the documented `per_repo`
632
+ * opts in; an absent key or any other stored value reads as the default
633
+ * `single` (today's behavior), matching `loadConfig`'s resolution.
634
+ */
635
+ function detectRepoTagging(cwd) {
636
+ return readConfigJsonValue(cwd, ['git', 'repo_tagging']) === 'per_repo' ? 'per_repo' : 'single';
637
+ }
638
+ /**
639
+ * The configured `planning.sub_repos` (#5706): an array of non-empty repo
640
+ * names, possibly empty. A legacy top-level `sub_repos` is honored when the
641
+ * nested key is absent, the same flat-then-nested order `loadConfig` applies.
642
+ * Non-string members are dropped; any degraded read yields `[]`.
643
+ */
644
+ function detectSubRepos(cwd) {
645
+ const nested = readConfigJsonValue(cwd, ['planning', 'sub_repos']);
646
+ const raw = nested !== undefined ? nested : readConfigJsonValue(cwd, ['sub_repos']);
647
+ if (!Array.isArray(raw))
648
+ return [];
649
+ return raw.filter((name) => typeof name === 'string' && name.trim() !== '');
650
+ }
603
651
  /**
604
652
  * `state:phase-mvp-mode` ground truth (design doc §Behavior table: ROADMAP.md
605
653
  * `**Mode:** mvp` for the CURRENT phase). Bounded, non-throwing — an absent
@@ -858,7 +906,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
858
906
  error('phase required for init execute-phase');
859
907
  }
860
908
  const config = loadConfig(cwd);
861
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
909
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
862
910
  // #3216: getMilestoneInfo now returns a ScopedResult — `.value` carries the
863
911
  // MilestoneInfo (or null on any non-COMPLETE scope). NOT display-only: when
864
912
  // `branching_strategy === 'milestone'`, `milestone['version']`/`['name']`
@@ -1033,7 +1081,7 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
1033
1081
  }
1034
1082
  const config = loadConfig(cwd);
1035
1083
  // #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
1036
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
1084
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1037
1085
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
1038
1086
  phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
1039
1087
  const phaseName = rp['phase_name'];
@@ -1067,12 +1115,14 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
1067
1115
  const phaseNamePlan = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
1068
1116
  const rawProjectCodePlan = config.project_code || '';
1069
1117
  let expectedPhaseDirPlan = null;
1070
- if (!phaseDirPlan && phaseNumberPlan && phaseNamePlan) {
1118
+ const namingContextPlan = phaseDirectoryContext(config);
1119
+ const customNamingPlan = customPhaseQuery(phase, namingContextPlan) !== null;
1120
+ if ((!phaseDirPlan || customNamingPlan) && phaseNumberPlan && phaseNamePlan) {
1071
1121
  const paddedNum = normalizePhaseName(phaseNumberPlan);
1072
- const slug = (generateSlugInternal(phaseNamePlan) || '').substring(0, 60);
1122
+ const expectedName = roadmapPhase?.['phase_name'] || phaseNamePlan;
1123
+ const slug = (generateSlugInternal(expectedName) || '').substring(0, 60);
1073
1124
  if (slug) {
1074
- const prefix = rawProjectCodePlan ? `${rawProjectCodePlan}-` : '';
1075
- const dirName = `${prefix}${paddedNum}-${slug}`;
1125
+ const dirName = phaseDirectoryName(customPhaseQuery(phaseNumberPlan, namingContextPlan) ? phaseNumberPlan : paddedNum, slug, rawProjectCodePlan);
1076
1126
  // #2376: absolute — see comment on phase_dir below.
1077
1127
  expectedPhaseDirPlan = toPosixPath(node_path_1.default.join(planningPaths(cwd).phases, dirName));
1078
1128
  }
@@ -1139,6 +1189,8 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
1139
1189
  state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
1140
1190
  roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
1141
1191
  requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
1192
+ // #5629: the phase SPEC (plan-phase lifts its Edge Coverage from it; no workflow globs for it).
1193
+ ...phaseSpecFacts(phaseInfo?.['directory'] ? node_path_1.default.join(cwd, phaseInfo['directory']) : null),
1142
1194
  patterns_path: null,
1143
1195
  };
1144
1196
  if (phaseInfo?.['directory']) {
@@ -1661,7 +1713,7 @@ function cmdInitVerifyWork(cwd, phase, raw) {
1661
1713
  }
1662
1714
  const config = loadConfig(cwd);
1663
1715
  const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
1664
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
1716
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1665
1717
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
1666
1718
  phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
1667
1719
  const phaseName = rp['phase_name'];
@@ -1768,7 +1820,7 @@ function cmdInitVerifyWork(cwd, phase, raw) {
1768
1820
  */
1769
1821
  function cmdInitCodeReview(cwd, phase, raw, options = {}) {
1770
1822
  const config = loadConfig(cwd);
1771
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
1823
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1772
1824
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
1773
1825
  phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
1774
1826
  const rpName = rp['phase_name'];
@@ -1834,7 +1886,7 @@ function cmdInitCodeReview(cwd, phase, raw, options = {}) {
1834
1886
  */
1835
1887
  function cmdInitReview(cwd, phase, raw, options = {}) {
1836
1888
  const config = loadConfig(cwd);
1837
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
1889
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1838
1890
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
1839
1891
  phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => ({
1840
1892
  found: true,
@@ -1893,7 +1945,7 @@ function cmdInitReview(cwd, phase, raw, options = {}) {
1893
1945
  */
1894
1946
  function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
1895
1947
  const config = loadConfig(cwd);
1896
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
1948
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1897
1949
  const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
1898
1950
  phaseInfo = applyRoadmapFallback(phaseInfo, roadmapPhase, (rp) => {
1899
1951
  const rpName = rp['phase_name'];
@@ -1934,6 +1986,8 @@ function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
1934
1986
  plan_count: phaseInfo?.['plans']?.length || 0,
1935
1987
  roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
1936
1988
  planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
1989
+ // #5629: the phase SPEC, so assumptions mode honours it like the default flow does.
1990
+ ...phaseSpecFacts(phaseDir ? node_path_1.default.join(cwd, phaseDir) : null),
1937
1991
  };
1938
1992
  // #2994 (Phase 6.3): additive, optional field — degrades to null, never throws.
1939
1993
  const sectionManifestOptions = {
@@ -1947,7 +2001,7 @@ function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
1947
2001
  }
1948
2002
  function cmdInitPhaseOp(cwd, phase, raw) {
1949
2003
  const config = loadConfig(cwd);
1950
- let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
2004
+ let phaseInfo = guardedFindPhase(cwd, phase, config.project_code, config.phase_naming);
1951
2005
  // #2237: surface ambiguous phase-directory collisions instead of silently
1952
2006
  // taking the first match when unrelated projects share a .planning/phases/ tree.
1953
2007
  if (phaseInfo?.['ambiguous_matches']) {
@@ -2065,6 +2119,8 @@ function cmdInitPhaseOp(cwd, phase, raw) {
2065
2119
  state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
2066
2120
  roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
2067
2121
  requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
2122
+ // #5629: the phase SPEC (discuss-phase reads it from here; no workflow globs for it).
2123
+ ...phaseSpecFacts(phaseDir ? node_path_1.default.join(cwd, phaseDir) : null),
2068
2124
  };
2069
2125
  if (phaseInfo?.['directory']) {
2070
2126
  const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
@@ -2548,37 +2604,37 @@ function cmdInitManager(cwd, raw) {
2548
2604
  }
2549
2605
  const rawContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf-8');
2550
2606
  const content = extractCurrentMilestone(rawContent, cwd);
2607
+ const directoryContext = phaseDirectoryOptions(cwd, config, rawContent);
2551
2608
  // #3185 (ADR-3180 Decision 1): "which phase directories belong to the
2552
2609
  // CURRENT milestone" is the scoped question listMilestonePhaseDirs owns —
2553
2610
  // routed through it instead of a hand-rolled readdirSync + a separate
2554
2611
  // getMilestonePhaseFilter window check (which also never excluded
2555
2612
  // sentinels, unlike the owner).
2556
2613
  const _checkboxStates = new Map();
2557
- // #4982: anchored (`^`, `m`) so the phase label must be the FIRST thing
2558
- // after the checkbox (tolerating only an optional `**` bold marker) —
2559
- // the prior unanchored `\s*.*` let the greedy `.*` bind to the LAST
2560
- // "Phase N" mentioned anywhere in the line's prose instead of the line's
2561
- // own phase. First-match-wins (`if (!_checkboxStates.has(...))` below)
2614
+ // #4982: parsePhaseChecklistLine is line-anchored, so the phase label must
2615
+ // be the FIRST thing after the checkbox (tolerating only an optional `**`
2616
+ // bold marker), and a later "Phase N" in the line's prose cannot bind the
2617
+ // checkbox. First-match-wins (`if (!_checkboxStates.has(...))` below)
2562
2618
  // additionally guards against a later anchored line (e.g. a per-plan
2563
2619
  // sub-entry self-titled "Phase N ...") overwriting an already-recorded
2564
- // phase's own (first) checkbox — anchoring alone does not fully close
2565
- // that "last-match-wins" composition defect.
2566
- const _cbPattern = new RegExp(`^[ \\t]*-[ \\t]*\\[(x| )\\][ \\t]*(?:\\*\\*)?${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gim');
2567
- let _cbMatch;
2568
- while ((_cbMatch = _cbPattern.exec(content)) !== null) {
2569
- const phaseGroup = capturesBracketId ? 3 : 2;
2570
- const _cbKey = _cbMatch[phaseGroup];
2571
- if (!_checkboxStates.has(_cbKey)) {
2572
- _checkboxStates.set(_cbKey, _cbMatch[1].toLowerCase() === 'x');
2620
+ // phase's own (first) checkbox; anchoring alone does not fully close that
2621
+ // "last-match-wins" composition defect.
2622
+ for (const line of content.split(/\r?\n/)) {
2623
+ const checkbox = parsePhaseChecklistLine(line, phaseIdConvention, directoryContext);
2624
+ if (checkbox && !_checkboxStates.has(checkbox.phaseToken)) {
2625
+ _checkboxStates.set(checkbox.phaseToken, checkbox.checked);
2573
2626
  }
2574
2627
  }
2575
2628
  // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
2576
- const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${PHASE_NUMBER_TOKEN_SOURCE})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
2629
+ const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefix}(${phaseScanTokenSource(directoryContext)})(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
2577
2630
  const phases = [];
2631
+ const bracketIdsByPhaseNumber = new Map();
2578
2632
  let match;
2579
2633
  while ((match = phasePattern.exec(content)) !== null) {
2580
2634
  const bracketId = capturesBracketId ? match[1] : undefined;
2581
2635
  const phaseNum = capturesBracketId ? match[2] : match[1];
2636
+ if (bracketId)
2637
+ bracketIdsByPhaseNumber.set(phaseNum, bracketId);
2582
2638
  const phaseName = (capturesBracketId ? match[3] : match[2])
2583
2639
  .replace(/\(INSERTED\)/i, '')
2584
2640
  .trim();
@@ -2594,7 +2650,7 @@ function cmdInitManager(cwd, raw) {
2594
2650
  }
2595
2651
  const sectionStart = match.index;
2596
2652
  const restOfContent = content.slice(sectionStart);
2597
- const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}\\d[\\d.]*`, 'i'));
2653
+ const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixNoCapture}${phaseScanTokenSource(directoryContext)}`, 'i'));
2598
2654
  const sectionEnd = nextHeader
2599
2655
  ? sectionStart + nextHeader.index
2600
2656
  : content.length;
@@ -2624,7 +2680,7 @@ function cmdInitManager(cwd, raw) {
2624
2680
  // of reporting no_directory/phase_complete:false. Five other init
2625
2681
  // commands already route through this same primitive; this was the
2626
2682
  // holdout private copy (#4793-family declared-owner drift).
2627
- const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention);
2683
+ const located = findPhaseInternal(cwd, phaseNum, phaseIdConvention, directoryContext);
2628
2684
  const dirMatch = located && located['found']
2629
2685
  ? node_path_1.default.posix.basename(String(located['directory']))
2630
2686
  : null;
@@ -2735,15 +2791,83 @@ function cmdInitManager(cwd, raw) {
2735
2791
  .filter((p) => p['phase_complete'] === true)
2736
2792
  .map((p) => normalizePhaseNumber(p['number'])));
2737
2793
  const phaseMap = new Map(phases.map((p) => [normalizePhaseNumber(p['number']), p]));
2738
- const _allCompletedPattern = new RegExp(`-\\s*\\[x\\]\\s*.*${phaseHeadingPrefixNoCapture}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
2739
- let _allMatch;
2740
- while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
2741
- const phaseNum = normalizePhaseNumber(_allMatch[1]);
2742
- const phase = phaseMap.get(phaseNum);
2743
- if (!phase || phase['phase_complete'] === true) {
2744
- completedNums.add(phaseNum);
2794
+ const bracketIdentityKey = (value) => {
2795
+ try {
2796
+ const id = parsePhaseId(value);
2797
+ const subphase = id.subphase ? `.${id.subphase}` : '';
2798
+ return bracketQualifiedKey(`${id.project}.${id.milestone}-${id.phase}${subphase}`, 'bracket');
2799
+ }
2800
+ catch {
2801
+ return null;
2802
+ }
2803
+ };
2804
+ const phaseByBracketIdentity = new Map();
2805
+ const bracketIdentityByPhaseNumber = new Map();
2806
+ const completedBracketIdentities = new Set();
2807
+ if (phaseIdConvention === 'bracket') {
2808
+ for (const phase of phases) {
2809
+ const phaseNumber = phase['number'];
2810
+ const bracketId = bracketIdsByPhaseNumber.get(phaseNumber);
2811
+ const canonicalPhaseToken = phaseToken(phaseNumber);
2812
+ // #4304: key each heading on the reader's canonical
2813
+ // token, not on a strict parse of its rendered display. An accepted
2814
+ // unpadded heading (`### [CK.02] 1:`) gets no display_id, because
2815
+ // parsePhaseId rejects `CK.02-1`, so it had no identity at all and a
2816
+ // checked `[CK.02] 01` row completed it while phase_complete was false.
2817
+ // phaseToken pads the number to `01`, and `CK.02-01` is the form
2818
+ // parsePhaseId accepts; canonical headings key exactly as before.
2819
+ const key = bracketId && canonicalPhaseToken
2820
+ ? bracketIdentityKey(`${bracketId}-${canonicalPhaseToken}`)
2821
+ : null;
2822
+ if (!key)
2823
+ continue;
2824
+ bracketIdentityByPhaseNumber.set(normalizePhaseNumber(phaseNumber), key);
2825
+ phaseByBracketIdentity.set(key, phase);
2826
+ if (phase['phase_complete'] === true)
2827
+ completedBracketIdentities.add(key);
2828
+ }
2829
+ }
2830
+ if (phaseIdConvention === 'bracket') {
2831
+ // A checked historical row satisfies only its own qualified identity. In
2832
+ // particular, `[CK.01] 01` must not complete bare/current `[CK.02] 01`.
2833
+ for (const line of rawContent.split(/\r?\n/)) {
2834
+ const checkbox = parsePhaseChecklistLine(line, phaseIdConvention);
2835
+ if (!checkbox?.checked || !checkbox.bracketId)
2836
+ continue;
2837
+ const identity = `[${checkbox.bracketId}] ${checkbox.phaseToken}`;
2838
+ const key = bracketIdentityKey(identity);
2839
+ if (!key)
2840
+ continue;
2841
+ const current = phaseMap.get(normalizePhaseNumber(checkbox.phaseToken));
2842
+ const currentKey = current
2843
+ ? bracketIdentityByPhaseNumber.get(normalizePhaseNumber(current['number'])) ?? null
2844
+ : null;
2845
+ if (!current || currentKey !== key || current['phase_complete'] === true) {
2846
+ completedBracketIdentities.add(key);
2847
+ }
2848
+ }
2849
+ }
2850
+ else {
2851
+ // Preserve the historical greedy non-bracket fallback byte-for-byte.
2852
+ const _allCompletedPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`.*${phaseHeadingPrefixNoCapture}(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gi');
2853
+ let _allMatch;
2854
+ while ((_allMatch = _allCompletedPattern.exec(rawContent)) !== null) {
2855
+ if (!(0, phase_entry_grammar_cjs_1.isPhaseEntryChecked)(_allMatch[0]))
2856
+ continue;
2857
+ const phaseNum = normalizePhaseNumber(_allMatch[1]);
2858
+ const phase = phaseMap.get(phaseNum);
2859
+ if (!phase || phase['phase_complete'] === true) {
2860
+ completedNums.add(phaseNum);
2861
+ }
2745
2862
  }
2746
2863
  }
2864
+ const currentDependencyNumber = (dependency) => {
2865
+ const identity = bracketIdentityKey(dependency);
2866
+ if (!identity)
2867
+ return normalizePhaseNumber(dependency);
2868
+ const current = phaseByBracketIdentity.get(identity);
2869
+ return current ? normalizePhaseNumber(current['number']) : null;
2870
+ };
2747
2871
  function reaches(from, to, visited = new Set()) {
2748
2872
  const normalizedFrom = normalizePhaseNumber(from);
2749
2873
  const normalizedTo = normalizePhaseNumber(to);
@@ -2753,15 +2877,18 @@ function cmdInitManager(cwd, raw) {
2753
2877
  const p = phaseMap.get(normalizedFrom);
2754
2878
  if (!p || !p['dep_phases'] || p['dep_phases'].length === 0)
2755
2879
  return false;
2756
- if (p['dep_phases'].some((dep) => normalizePhaseNumber(dep) === normalizedTo)) {
2880
+ if (p['dep_phases'].some((dep) => currentDependencyNumber(dep) === normalizedTo)) {
2757
2881
  return true;
2758
2882
  }
2759
- return p['dep_phases'].some((dep) => reaches(dep, to, visited));
2883
+ return p['dep_phases'].some((dep) => {
2884
+ const dependencyNumber = currentDependencyNumber(dep);
2885
+ return dependencyNumber === null ? false : reaches(dependencyNumber, to, visited);
2886
+ });
2760
2887
  }
2761
2888
  function hasDepRelationship(numA, numB) {
2762
2889
  return reaches(numA, numB) || reaches(numB, numA);
2763
2890
  }
2764
- // #4764: a phase reference in depends_on prose is a PHASE-SHAPED token in
2891
+ // #4764/#4304: a phase reference in depends_on prose is a PHASE-SHAPED token in
2765
2892
  // context — directly following "Phase"/"Phases" — never a bare digit run.
2766
2893
  // The previous whole-field scrape matched the token grammar against every
2767
2894
  // digit run, so calendar dates ("2026-09-14" → 2026, 09, 14), git shas
@@ -2774,9 +2901,27 @@ function cmdInitManager(cwd, raw) {
2774
2901
  // "Phase 1-3") — silently dropping a REAL dependency would clear
2775
2902
  // deps_satisfied prematurely, the dangerous direction. Negation prose
2776
2903
  // ("dropped the dependency on Phase 654") is NOT detected: the issue's own
2777
- // minimum keeps such tokens.
2778
- const depPhaseRefRe = new RegExp(`${PHASE_DEP_REF_SOURCE}`, 'gi');
2779
- const depTokenRe = new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi');
2904
+ // minimum keeps such tokens. Bracket repositories route through the same
2905
+ // phase-id owner so `[CK.02] 01` retains its complete qualified identity;
2906
+ // bare `01` still resolves against the active milestone. This prevents a
2907
+ // checked historical `[CK.01] 01` from satisfying incomplete `[CK.02] 01`.
2908
+ // Non-bracket extraction remains this manager surface's exact #4764 code
2909
+ // path below, including its case-insensitive token regex. Planning-inspect's
2910
+ // old token regex was case-sensitive, so forcing both through one widened
2911
+ // helper changed its `Phase 1a` bytes from `1` to `1a`.
2912
+ const legacyManagerDependencyTokens = (prose) => {
2913
+ const depPhaseRefRe = new RegExp(`${PHASE_DEP_REF_SOURCE}`, 'gi');
2914
+ const depTokenRe = new RegExp(`${PHASE_NUMBER_TOKEN_SOURCE}`, 'gi');
2915
+ const tokens = [];
2916
+ let refMatch;
2917
+ while ((refMatch = depPhaseRefRe.exec(prose)) !== null) {
2918
+ let tokenMatch;
2919
+ depTokenRe.lastIndex = 0;
2920
+ while ((tokenMatch = depTokenRe.exec(refMatch[1])) !== null)
2921
+ tokens.push(tokenMatch[0]);
2922
+ }
2923
+ return tokens;
2924
+ };
2780
2925
  for (const phase of phases) {
2781
2926
  if (!phase['depends_on'] ||
2782
2927
  /^none$/i.test(phase['depends_on'].trim())) {
@@ -2785,24 +2930,39 @@ function cmdInitManager(cwd, raw) {
2785
2930
  else {
2786
2931
  const prose = phase['depends_on'];
2787
2932
  const ownNumber = normalizePhaseNumber(phase['number']);
2933
+ const ownIdentity = typeof phase['display_id'] === 'string'
2934
+ ? bracketIdentityKey(phase['display_id'])
2935
+ : null;
2788
2936
  const depNums = [];
2789
2937
  const seen = new Set();
2790
- let refMatch;
2791
- depPhaseRefRe.lastIndex = 0;
2792
- while ((refMatch = depPhaseRefRe.exec(prose)) !== null) {
2793
- let tok;
2794
- depTokenRe.lastIndex = 0;
2795
- while ((tok = depTokenRe.exec(refMatch[1])) !== null) {
2796
- const normalized = normalizePhaseNumber(tok[0]);
2797
- if (normalized === ownNumber)
2798
- continue; // #4764: never the row's own phase
2799
- if (seen.has(normalized))
2938
+ const dependencyTokens = phaseIdConvention === 'bracket'
2939
+ ? extractPhaseDependencyTokens(prose, phaseIdConvention)
2940
+ : legacyManagerDependencyTokens(prose);
2941
+ for (const token of dependencyTokens) {
2942
+ const identity = bracketIdentityKey(token);
2943
+ if (identity) {
2944
+ if (identity === ownIdentity)
2800
2945
  continue;
2801
- seen.add(normalized);
2802
- depNums.push(tok[0]);
2946
+ if (seen.has(identity))
2947
+ continue;
2948
+ seen.add(identity);
2949
+ depNums.push(token);
2950
+ continue;
2803
2951
  }
2952
+ const normalized = normalizePhaseNumber(token);
2953
+ if (normalized === ownNumber)
2954
+ continue; // #4764: never the row's own phase
2955
+ if (seen.has(normalized))
2956
+ continue;
2957
+ seen.add(normalized);
2958
+ depNums.push(token);
2804
2959
  }
2805
- phase['deps_satisfied'] = depNums.every((n) => completedNums.has(normalizePhaseNumber(n)));
2960
+ phase['deps_satisfied'] = depNums.every((dependency) => {
2961
+ const identity = bracketIdentityKey(dependency);
2962
+ return identity
2963
+ ? completedBracketIdentities.has(identity)
2964
+ : completedNums.has(normalizePhaseNumber(dependency));
2965
+ });
2806
2966
  phase['dep_phases'] = depNums;
2807
2967
  }
2808
2968
  }
@@ -3016,9 +3176,23 @@ function cmdInitCompleteMilestone(cwd, raw, options = {}) {
3016
3176
  milestones_path: node_fs_1.default.existsSync(milestonesPath) ? toPosixPath(milestonesPath) : null,
3017
3177
  project_path: node_fs_1.default.existsSync(projectPath) ? toPosixPath(projectPath) : null,
3018
3178
  requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
3179
+ // #5706 (epic #3628): the repositories configured for this project and the
3180
+ // milestone tagging mode, so the workflow reads both from one init call.
3181
+ sub_repos: detectSubRepos(cwd),
3182
+ repo_tagging: detectRepoTagging(cwd),
3019
3183
  };
3184
+ // #5195: `state:flat-mode` gates the PROJECT.md evolution review. PROJECT.md
3185
+ // is shared across workstreams (the `projectPath` comment above), so a
3186
+ // workstream's milestone close must not rewrite or stage it — the same
3187
+ // decision `cmdInitNewMilestone` applies to its Part A (#2308), resolved from
3188
+ // the same authority (`GSD_WORKSTREAM` env, else the stored pointer) so the
3189
+ // gate cannot disagree with the workstream-scoped paths returned above.
3190
+ // Read-only peek: no self-heal side effect from building a report field (#3579).
3191
+ const workstreamActive = !!(resolveEnvWorkstream() ?? peekActiveWorkstream(cwd));
3020
3192
  result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'complete-milestone', {
3021
3193
  gitCreateTag,
3194
+ workstreamActive,
3195
+ flatMode: !workstreamActive,
3022
3196
  });
3023
3197
  output(withProjectRoot(cwd, result), raw);
3024
3198
  }
@@ -3252,6 +3426,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3252
3426
  `Available workstreams: ${_availableWorkstreams.join(', ')}`, ERROR_REASON.WORKSTREAM_MODE_NONE_ACTIVE);
3253
3427
  }
3254
3428
  const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
3429
+ const directoryContext = phaseDirectoryOptions(cwd, config);
3255
3430
  const phases = [];
3256
3431
  let currentPhase = null;
3257
3432
  let nextPhase = null;
@@ -3264,7 +3439,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3264
3439
  // #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag, owned by
3265
3440
  // buildPhaseHeadingScanRegex (phase-id.cts) so this scan also recognizes
3266
3441
  // bracket-convention headings instead of hand-rolling a literal `Phase\s+`.
3267
- const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention);
3442
+ const { regex: headingPattern, phaseNumGroup: hNumGroup, phaseNameGroup: hNameGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.ANY_BRACKET, progressConvention, directoryContext);
3268
3443
  let hm;
3269
3444
  while ((hm = headingPattern.exec(roadmapContent)) !== null) {
3270
3445
  roadmapPhaseNums.add(hm[hNumGroup]);
@@ -3297,12 +3472,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
3297
3472
  // matched (by absorbing the bracket text). This does NOT migrate the site
3298
3473
  // onto phaseHeadingPrefixSrcFor / the bracket convention — that stays
3299
3474
  // out of scope per the comment above (#4984 revert, pinned census).
3300
- const cbPattern = new RegExp(`^[ \\t]*-[ \\t]*\\[(x| )\\][ \\t]*(?:\\*\\*)?(?:\\[[^\\]\\n]{0,200}\\]\\s*)?Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})[:\\s]`, 'gim');
3475
+ const cbPattern = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`(?:\\[[^\\]\\n]{0,200}\\]\\s*)?Phase\\s+(${phaseScanTokenSource(directoryContext)})[:\\s]`, 'gim', { state: 'captured' });
3301
3476
  let cbm;
3302
3477
  while ((cbm = cbPattern.exec(roadmapContent)) !== null) {
3303
3478
  const cbKey = cbm[2];
3304
3479
  if (!roadmapCheckboxStates.has(cbKey)) {
3305
- roadmapCheckboxStates.set(cbKey, cbm[1].toLowerCase() === 'x');
3480
+ roadmapCheckboxStates.set(cbKey, (0, phase_entry_grammar_cjs_1.isBoxCharChecked)(cbm[1]));
3306
3481
  }
3307
3482
  }
3308
3483
  }
@@ -3319,10 +3494,12 @@ function cmdInitProgress(cwd, raw, options = {}) {
3319
3494
  // behavior-preserving).
3320
3495
  const dirs = listMilestonePhaseDirs(phasesDir, { cwd }).value;
3321
3496
  for (const dir of dirs) {
3322
- const dirMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})-?(.*)`, 'i'));
3323
- const phaseNumber = dirMatch ? dirMatch[1] : dir;
3324
- const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
3325
- seenPhaseNums.add(phaseNumber.replace(/^0+/, '') || '0');
3497
+ const dirMatch = dir.match(new RegExp(`^(${phaseScanTokenSource(directoryContext)})-?(.*)`, 'i'));
3498
+ const query = customPhaseQuery(dir, directoryContext);
3499
+ const owner = query && customDirectoryOwner(dir, query);
3500
+ const phaseNumber = owner ? owner.id : dirMatch ? dirMatch[1] : dir;
3501
+ const phaseName = owner ? owner.slug : dirMatch && dirMatch[2] ? dirMatch[2] : null;
3502
+ seenPhaseNums.add((phaseKeyFromToken(phaseNumber, directoryContext) ?? phaseNumber).toLowerCase());
3326
3503
  const phasePath = node_path_1.default.join(phasesDir, dir);
3327
3504
  const phaseFiles = node_fs_1.default.readdirSync(phasePath);
3328
3505
  // #5060: Phase Status Module — the single owner of "what state is
@@ -3367,7 +3544,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3367
3544
  /* otherwise intentionally empty */
3368
3545
  }
3369
3546
  for (const [num, name] of roadmapPhaseNames) {
3370
- const stripped = num.replace(/^0+/, '') || '0';
3547
+ const stripped = (phaseKeyFromToken(num, directoryContext) ?? num).toLowerCase();
3371
3548
  if (!seenPhaseNums.has(stripped)) {
3372
3549
  const checkboxComplete = roadmapCheckboxStates.get(num) === true ||
3373
3550
  roadmapCheckboxStates.get(stripped) === true;
@@ -3397,7 +3574,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
3397
3574
  }
3398
3575
  }
3399
3576
  }
3400
- phases.sort((a, b) => comparePhaseNum(a['number'], b['number']));
3577
+ phases.sort((a, b) => comparePhaseNum(a['number'], b['number'], directoryContext));
3401
3578
  // #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
3402
3579
  // above could claim nextPhase from a stray out-of-order artifact directory
3403
3580
  // (a phase-9 UAT evidence dir while roadmap phase 8 was pending and