@pareto4/pp-core 1.5.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 (618) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/GEMINI.md +53 -0
  3. package/LICENSE +22 -0
  4. package/README.md +100 -0
  5. package/agents/pp-advisor-researcher.md +108 -0
  6. package/agents/pp-ai-researcher.md +114 -0
  7. package/agents/pp-assumptions-analyzer.md +105 -0
  8. package/agents/pp-code-fixer.md +669 -0
  9. package/agents/pp-code-reviewer.md +387 -0
  10. package/agents/pp-codebase-mapper.md +853 -0
  11. package/agents/pp-debug-session-manager.md +315 -0
  12. package/agents/pp-debugger.md +1453 -0
  13. package/agents/pp-doc-classifier.md +168 -0
  14. package/agents/pp-doc-synthesizer.md +204 -0
  15. package/agents/pp-doc-verifier.md +217 -0
  16. package/agents/pp-doc-writer.md +616 -0
  17. package/agents/pp-domain-researcher.md +147 -0
  18. package/agents/pp-eval-auditor.md +191 -0
  19. package/agents/pp-eval-planner.md +154 -0
  20. package/agents/pp-executor.md +808 -0
  21. package/agents/pp-framework-selector.md +160 -0
  22. package/agents/pp-integration-checker.md +470 -0
  23. package/agents/pp-intel-updater.md +339 -0
  24. package/agents/pp-mempalace-curator.md +47 -0
  25. package/agents/pp-nyquist-auditor.md +204 -0
  26. package/agents/pp-pattern-mapper.md +335 -0
  27. package/agents/pp-phase-researcher.md +867 -0
  28. package/agents/pp-plan-checker.md +988 -0
  29. package/agents/pp-planner.md +1052 -0
  30. package/agents/pp-project-researcher.md +612 -0
  31. package/agents/pp-research-synthesizer.md +260 -0
  32. package/agents/pp-roadmapper.md +741 -0
  33. package/agents/pp-security-auditor.md +156 -0
  34. package/agents/pp-ui-auditor.md +495 -0
  35. package/agents/pp-ui-checker.md +309 -0
  36. package/agents/pp-ui-researcher.md +375 -0
  37. package/agents/pp-user-profiler.md +171 -0
  38. package/agents/pp-verifier.md +967 -0
  39. package/assets/gsd-logo-2000-transparent.png +0 -0
  40. package/assets/gsd-logo-2000-transparent.svg +17 -0
  41. package/assets/gsd-logo-2000.png +0 -0
  42. package/assets/gsd-logo-2000.svg +21 -0
  43. package/assets/terminal.svg +68 -0
  44. package/bin/install.js +12370 -0
  45. package/bin/lib/ui-safety-gate.cjs +107 -0
  46. package/commands/pp-add-tests.md +42 -0
  47. package/commands/pp-ai-integration-phase.md +37 -0
  48. package/commands/pp-audit-fix.md +34 -0
  49. package/commands/pp-audit-milestone.md +37 -0
  50. package/commands/pp-audit-uat.md +24 -0
  51. package/commands/pp-autonomous.md +51 -0
  52. package/commands/pp-capture.md +62 -0
  53. package/commands/pp-cleanup.md +24 -0
  54. package/commands/pp-code-review.md +59 -0
  55. package/commands/pp-complete-milestone.md +143 -0
  56. package/commands/pp-config.md +56 -0
  57. package/commands/pp-debug.md +52 -0
  58. package/commands/pp-discovery.md +34 -0
  59. package/commands/pp-discuss-phase.md +77 -0
  60. package/commands/pp-docs-update.md +49 -0
  61. package/commands/pp-eval-review.md +33 -0
  62. package/commands/pp-execute-phase.md +65 -0
  63. package/commands/pp-explore.md +27 -0
  64. package/commands/pp-extract-learnings.md +23 -0
  65. package/commands/pp-fast.md +31 -0
  66. package/commands/pp-forensics.md +57 -0
  67. package/commands/pp-graphify.md +204 -0
  68. package/commands/pp-health.md +31 -0
  69. package/commands/pp-help.md +28 -0
  70. package/commands/pp-import.md +45 -0
  71. package/commands/pp-inbox.md +39 -0
  72. package/commands/pp-ingest-docs.md +42 -0
  73. package/commands/pp-manager.md +45 -0
  74. package/commands/pp-map-codebase.md +83 -0
  75. package/commands/pp-mempalace-capture.md +71 -0
  76. package/commands/pp-mempalace-recall.md +102 -0
  77. package/commands/pp-milestone-summary.md +51 -0
  78. package/commands/pp-mvp-phase.md +45 -0
  79. package/commands/pp-new-milestone.md +45 -0
  80. package/commands/pp-new-project.md +47 -0
  81. package/commands/pp-ns-context.md +25 -0
  82. package/commands/pp-ns-ideate.md +24 -0
  83. package/commands/pp-ns-manage.md +36 -0
  84. package/commands/pp-ns-project.md +27 -0
  85. package/commands/pp-ns-review.md +29 -0
  86. package/commands/pp-ns-workflow.md +35 -0
  87. package/commands/pp-pause-work.md +43 -0
  88. package/commands/pp-phase.md +56 -0
  89. package/commands/pp-plan-phase.md +63 -0
  90. package/commands/pp-plan-review-convergence.md +60 -0
  91. package/commands/pp-pr-branch.md +26 -0
  92. package/commands/pp-profile-user.md +46 -0
  93. package/commands/pp-progress.md +49 -0
  94. package/commands/pp-quick.md +174 -0
  95. package/commands/pp-resume-work.md +30 -0
  96. package/commands/pp-review-backlog.md +63 -0
  97. package/commands/pp-review.md +42 -0
  98. package/commands/pp-secure-phase.md +36 -0
  99. package/commands/pp-settings.md +29 -0
  100. package/commands/pp-ship.md +24 -0
  101. package/commands/pp-sketch.md +60 -0
  102. package/commands/pp-spec-phase.md +63 -0
  103. package/commands/pp-spike.md +57 -0
  104. package/commands/pp-stats.md +20 -0
  105. package/commands/pp-surface.md +162 -0
  106. package/commands/pp-thread.md +24 -0
  107. package/commands/pp-ui-phase.md +35 -0
  108. package/commands/pp-ui-review.md +33 -0
  109. package/commands/pp-ultraplan-phase.md +34 -0
  110. package/commands/pp-undo.md +35 -0
  111. package/commands/pp-update.md +49 -0
  112. package/commands/pp-validate-phase.md +36 -0
  113. package/commands/pp-verify-work.md +39 -0
  114. package/commands/pp-workspace.md +52 -0
  115. package/commands/pp-workstreams.md +70 -0
  116. package/gemini-extension.json +6 -0
  117. package/hooks/dist/lib/git-cmd.js +150 -0
  118. package/hooks/dist/lib/pp-graphify-rebuild.sh +65 -0
  119. package/hooks/dist/managed-hooks-registry.cjs +39 -0
  120. package/hooks/dist/pp-check-update-worker.js +108 -0
  121. package/hooks/dist/pp-check-update.js +66 -0
  122. package/hooks/dist/pp-config-reload.js +133 -0
  123. package/hooks/dist/pp-context-monitor.js +196 -0
  124. package/hooks/dist/pp-cursor-post-tool.js +75 -0
  125. package/hooks/dist/pp-cursor-session-start.js +52 -0
  126. package/hooks/dist/pp-ensure-canonical-path.js +305 -0
  127. package/hooks/dist/pp-graphify-update.sh +158 -0
  128. package/hooks/dist/pp-phase-boundary.sh +47 -0
  129. package/hooks/dist/pp-prompt-guard.js +97 -0
  130. package/hooks/dist/pp-read-guard.js +101 -0
  131. package/hooks/dist/pp-read-injection-scanner.js +203 -0
  132. package/hooks/dist/pp-session-state.sh +59 -0
  133. package/hooks/dist/pp-statusline.js +566 -0
  134. package/hooks/dist/pp-update-banner.js +138 -0
  135. package/hooks/dist/pp-validate-commit.sh +57 -0
  136. package/hooks/dist/pp-workflow-guard.js +167 -0
  137. package/hooks/dist/pp-worktree-path-guard.js +185 -0
  138. package/hooks/hooks.json +70 -0
  139. package/hooks/lib/git-cmd.js +150 -0
  140. package/hooks/lib/pp-graphify-rebuild.sh +65 -0
  141. package/hooks/managed-hooks-registry.cjs +39 -0
  142. package/hooks/pp-check-update-worker.js +108 -0
  143. package/hooks/pp-check-update.js +66 -0
  144. package/hooks/pp-config-reload.js +133 -0
  145. package/hooks/pp-context-monitor.js +196 -0
  146. package/hooks/pp-cursor-post-tool.js +75 -0
  147. package/hooks/pp-cursor-session-start.js +52 -0
  148. package/hooks/pp-ensure-canonical-path.js +305 -0
  149. package/hooks/pp-graphify-update.sh +158 -0
  150. package/hooks/pp-phase-boundary.sh +47 -0
  151. package/hooks/pp-prompt-guard.js +97 -0
  152. package/hooks/pp-read-guard.js +101 -0
  153. package/hooks/pp-read-injection-scanner.js +203 -0
  154. package/hooks/pp-session-state.sh +59 -0
  155. package/hooks/pp-statusline.js +566 -0
  156. package/hooks/pp-update-banner.js +138 -0
  157. package/hooks/pp-validate-commit.sh +57 -0
  158. package/hooks/pp-workflow-guard.js +167 -0
  159. package/hooks/pp-worktree-path-guard.js +185 -0
  160. package/package.json +125 -0
  161. package/pp-core/bin/check-latest-version.cjs +161 -0
  162. package/pp-core/bin/lib/active-workstream-store.cjs +297 -0
  163. package/pp-core/bin/lib/adr-parser.cjs +399 -0
  164. package/pp-core/bin/lib/agent-command-router.cjs +68 -0
  165. package/pp-core/bin/lib/agent-install-check.cjs +143 -0
  166. package/pp-core/bin/lib/artifacts.cjs +51 -0
  167. package/pp-core/bin/lib/audit-command-router.cjs +61 -0
  168. package/pp-core/bin/lib/audit.cjs +743 -0
  169. package/pp-core/bin/lib/capability-activation.cjs +113 -0
  170. package/pp-core/bin/lib/capability-registry.cjs +3727 -0
  171. package/pp-core/bin/lib/capability-state.cjs +497 -0
  172. package/pp-core/bin/lib/capability-writer.cjs +355 -0
  173. package/pp-core/bin/lib/check-command-router.cjs +800 -0
  174. package/pp-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
  175. package/pp-core/bin/lib/cli-exit.cjs +61 -0
  176. package/pp-core/bin/lib/clock.cjs +95 -0
  177. package/pp-core/bin/lib/clusters.cjs +131 -0
  178. package/pp-core/bin/lib/code-review-flags.cjs +59 -0
  179. package/pp-core/bin/lib/command-aliases.cjs +809 -0
  180. package/pp-core/bin/lib/command-arg-projection.cjs +55 -0
  181. package/pp-core/bin/lib/command-roster.cjs +19 -0
  182. package/pp-core/bin/lib/command-routing-hub.cjs +300 -0
  183. package/pp-core/bin/lib/commands.cjs +1244 -0
  184. package/pp-core/bin/lib/config-loader.cjs +714 -0
  185. package/pp-core/bin/lib/config-schema.cjs +58 -0
  186. package/pp-core/bin/lib/config-types.cjs +20 -0
  187. package/pp-core/bin/lib/config.cjs +796 -0
  188. package/pp-core/bin/lib/configuration.cjs +209 -0
  189. package/pp-core/bin/lib/context-utilization.cjs +48 -0
  190. package/pp-core/bin/lib/core-utils.cjs +198 -0
  191. package/pp-core/bin/lib/decisions.cjs +136 -0
  192. package/pp-core/bin/lib/docs.cjs +264 -0
  193. package/pp-core/bin/lib/drift.cjs +364 -0
  194. package/pp-core/bin/lib/edge-probe.cjs +196 -0
  195. package/pp-core/bin/lib/fallow-runner.cjs +153 -0
  196. package/pp-core/bin/lib/federated-config.cjs +182 -0
  197. package/pp-core/bin/lib/frontmatter.cjs +494 -0
  198. package/pp-core/bin/lib/gap-checker.cjs +260 -0
  199. package/pp-core/bin/lib/git-base-branch.cjs +220 -0
  200. package/pp-core/bin/lib/graphify-command-router.cjs +72 -0
  201. package/pp-core/bin/lib/graphify.cjs +472 -0
  202. package/pp-core/bin/lib/gsd2-import.cjs +456 -0
  203. package/pp-core/bin/lib/init-command-router.cjs +62 -0
  204. package/pp-core/bin/lib/init.cjs +1912 -0
  205. package/pp-core/bin/lib/install-profiles.cjs +814 -0
  206. package/pp-core/bin/lib/installer-migration-authoring.cjs +122 -0
  207. package/pp-core/bin/lib/installer-migration-report.cjs +351 -0
  208. package/pp-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +225 -0
  209. package/pp-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
  210. package/pp-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
  211. package/pp-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
  212. package/pp-core/bin/lib/installer-migrations/004-prune-stale-pristine-snapshots.cjs +123 -0
  213. package/pp-core/bin/lib/installer-migrations/005-rename-gsd-core-to-pp-core.cjs +109 -0
  214. package/pp-core/bin/lib/installer-migrations/006-rename-gsd-command-namespace.cjs +135 -0
  215. package/pp-core/bin/lib/installer-migrations.cjs +823 -0
  216. package/pp-core/bin/lib/intel-command-router.cjs +119 -0
  217. package/pp-core/bin/lib/intel.cjs +584 -0
  218. package/pp-core/bin/lib/io.cjs +220 -0
  219. package/pp-core/bin/lib/learnings.cjs +270 -0
  220. package/pp-core/bin/lib/legacy-cleanup.cjs +253 -0
  221. package/pp-core/bin/lib/loop-host-contract.cjs +105 -0
  222. package/pp-core/bin/lib/loop-resolver.cjs +479 -0
  223. package/pp-core/bin/lib/milestone.cjs +381 -0
  224. package/pp-core/bin/lib/model-catalog.cjs +154 -0
  225. package/pp-core/bin/lib/model-profiles.cjs +24 -0
  226. package/pp-core/bin/lib/model-resolver.cjs +467 -0
  227. package/pp-core/bin/lib/observability/event.cjs +51 -0
  228. package/pp-core/bin/lib/observability/logger.cjs +146 -0
  229. package/pp-core/bin/lib/observability/redaction.cjs +48 -0
  230. package/pp-core/bin/lib/package-identity.cjs +35 -0
  231. package/pp-core/bin/lib/package-legitimacy.cjs +368 -0
  232. package/pp-core/bin/lib/phase-command-router.cjs +209 -0
  233. package/pp-core/bin/lib/phase-id.cjs +213 -0
  234. package/pp-core/bin/lib/phase-lifecycle.cjs +74 -0
  235. package/pp-core/bin/lib/phase-locator.cjs +145 -0
  236. package/pp-core/bin/lib/phase.cjs +1446 -0
  237. package/pp-core/bin/lib/phases-command-router.cjs +43 -0
  238. package/pp-core/bin/lib/plan-drift-guard.cjs +117 -0
  239. package/pp-core/bin/lib/plan-scan.cjs +91 -0
  240. package/pp-core/bin/lib/planning-workspace.cjs +245 -0
  241. package/pp-core/bin/lib/probe-core.cjs +402 -0
  242. package/pp-core/bin/lib/profile-output.cjs +1164 -0
  243. package/pp-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
  244. package/pp-core/bin/lib/profile-pipeline.cjs +519 -0
  245. package/pp-core/bin/lib/prohibition-enforcement.cjs +660 -0
  246. package/pp-core/bin/lib/project-root.cjs +119 -0
  247. package/pp-core/bin/lib/prompt-budget.cjs +305 -0
  248. package/pp-core/bin/lib/research-provider.cjs +137 -0
  249. package/pp-core/bin/lib/research-store.cjs +167 -0
  250. package/pp-core/bin/lib/review-reviewer-selection.cjs +121 -0
  251. package/pp-core/bin/lib/roadmap-command-router.cjs +166 -0
  252. package/pp-core/bin/lib/roadmap-parser.cjs +456 -0
  253. package/pp-core/bin/lib/roadmap-upgrade.cjs +476 -0
  254. package/pp-core/bin/lib/roadmap.cjs +738 -0
  255. package/pp-core/bin/lib/runtime-artifact-conversion.cjs +1966 -0
  256. package/pp-core/bin/lib/runtime-artifact-layout.cjs +342 -0
  257. package/pp-core/bin/lib/runtime-config-adapter-registry.cjs +103 -0
  258. package/pp-core/bin/lib/runtime-homes.cjs +272 -0
  259. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1460 -0
  260. package/pp-core/bin/lib/runtime-name-policy.cjs +97 -0
  261. package/pp-core/bin/lib/runtime-slash.cjs +137 -0
  262. package/pp-core/bin/lib/schema-detect.cjs +159 -0
  263. package/pp-core/bin/lib/secrets.cjs +34 -0
  264. package/pp-core/bin/lib/security.cjs +480 -0
  265. package/pp-core/bin/lib/semver-compare.cjs +42 -0
  266. package/pp-core/bin/lib/shell-command-projection.cjs +550 -0
  267. package/pp-core/bin/lib/state-command-router.cjs +172 -0
  268. package/pp-core/bin/lib/state-document.cjs +312 -0
  269. package/pp-core/bin/lib/state.cjs +2587 -0
  270. package/pp-core/bin/lib/surface.cjs +505 -0
  271. package/pp-core/bin/lib/task-command-router.cjs +85 -0
  272. package/pp-core/bin/lib/teams-status.cjs +74 -0
  273. package/pp-core/bin/lib/template.cjs +246 -0
  274. package/pp-core/bin/lib/uat-predicate.cjs +329 -0
  275. package/pp-core/bin/lib/uat.cjs +359 -0
  276. package/pp-core/bin/lib/ui-safety-gate.cjs +98 -0
  277. package/pp-core/bin/lib/update-context.cjs +221 -0
  278. package/pp-core/bin/lib/validate-command-router.cjs +91 -0
  279. package/pp-core/bin/lib/validate.cjs +122 -0
  280. package/pp-core/bin/lib/verification-command-router.cjs +31 -0
  281. package/pp-core/bin/lib/verification.cjs +196 -0
  282. package/pp-core/bin/lib/verify-command-router.cjs +44 -0
  283. package/pp-core/bin/lib/verify.cjs +1932 -0
  284. package/pp-core/bin/lib/workstream-inventory-builder.cjs +81 -0
  285. package/pp-core/bin/lib/workstream-inventory.cjs +147 -0
  286. package/pp-core/bin/lib/workstream-name-policy.cjs +91 -0
  287. package/pp-core/bin/lib/workstream.cjs +386 -0
  288. package/pp-core/bin/lib/worktree-base-ref.cjs +350 -0
  289. package/pp-core/bin/lib/worktree-safety.cjs +985 -0
  290. package/pp-core/bin/pp-tools.cjs +2239 -0
  291. package/pp-core/bin/pp_run +20 -0
  292. package/pp-core/bin/shared/config-defaults.manifest.json +97 -0
  293. package/pp-core/bin/shared/config-schema.manifest.json +176 -0
  294. package/pp-core/bin/shared/model-catalog.json +160 -0
  295. package/pp-core/bin/shared/runtime-aliases.manifest.json +79 -0
  296. package/pp-core/bin/verify-reapply-patches.cjs +400 -0
  297. package/pp-core/contexts/dev.md +21 -0
  298. package/pp-core/contexts/research.md +22 -0
  299. package/pp-core/contexts/review.md +23 -0
  300. package/pp-core/references/agent-contracts.md +79 -0
  301. package/pp-core/references/ai-evals.md +156 -0
  302. package/pp-core/references/ai-frameworks.md +186 -0
  303. package/pp-core/references/artifact-types.md +131 -0
  304. package/pp-core/references/autonomous-smart-discuss.md +277 -0
  305. package/pp-core/references/checkpoints.md +814 -0
  306. package/pp-core/references/common-bug-patterns.md +114 -0
  307. package/pp-core/references/context-budget.md +85 -0
  308. package/pp-core/references/continuation-format.md +253 -0
  309. package/pp-core/references/debugger-philosophy.md +76 -0
  310. package/pp-core/references/decimal-phase-calculation.md +64 -0
  311. package/pp-core/references/doc-conflict-engine.md +91 -0
  312. package/pp-core/references/domain-probes.md +125 -0
  313. package/pp-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  314. package/pp-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  315. package/pp-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  316. package/pp-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  317. package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  318. package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  319. package/pp-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  320. package/pp-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  321. package/pp-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  322. package/pp-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  323. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  324. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  325. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  326. package/pp-core/references/edge-probe.md +272 -0
  327. package/pp-core/references/execute-mvp-tdd.md +81 -0
  328. package/pp-core/references/executor-examples.md +110 -0
  329. package/pp-core/references/few-shot-examples/plan-checker.md +73 -0
  330. package/pp-core/references/few-shot-examples/verifier.md +109 -0
  331. package/pp-core/references/gate-prompts.md +100 -0
  332. package/pp-core/references/gates.md +70 -0
  333. package/pp-core/references/git-integration.md +298 -0
  334. package/pp-core/references/git-planning-commit.md +40 -0
  335. package/pp-core/references/ios-scaffold.md +123 -0
  336. package/pp-core/references/loop-hook-dispatch.md +61 -0
  337. package/pp-core/references/mandatory-initial-read.md +2 -0
  338. package/pp-core/references/model-profile-resolution.md +38 -0
  339. package/pp-core/references/model-profiles.md +245 -0
  340. package/pp-core/references/mvp-concepts.md +49 -0
  341. package/pp-core/references/phase-argument-parsing.md +61 -0
  342. package/pp-core/references/planner-antipatterns.md +176 -0
  343. package/pp-core/references/planner-chunked.md +49 -0
  344. package/pp-core/references/planner-gap-closure.md +62 -0
  345. package/pp-core/references/planner-graphify-auto-update.md +67 -0
  346. package/pp-core/references/planner-guidance.md +186 -0
  347. package/pp-core/references/planner-human-verify-mode.md +57 -0
  348. package/pp-core/references/planner-interface-context.md +62 -0
  349. package/pp-core/references/planner-load-graph-context.md +36 -0
  350. package/pp-core/references/planner-mvp-mode.md +53 -0
  351. package/pp-core/references/planner-reviews.md +42 -0
  352. package/pp-core/references/planner-revision.md +87 -0
  353. package/pp-core/references/planner-source-audit.md +73 -0
  354. package/pp-core/references/planning-config.md +477 -0
  355. package/pp-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
  356. package/pp-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
  357. package/pp-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
  358. package/pp-core/references/prohibition-probe.md +326 -0
  359. package/pp-core/references/project-skills-discovery.md +19 -0
  360. package/pp-core/references/questioning.md +162 -0
  361. package/pp-core/references/research-documentation-lookup.md +29 -0
  362. package/pp-core/references/research-philosophy.md +29 -0
  363. package/pp-core/references/research-verification-protocol.md +27 -0
  364. package/pp-core/references/revision-loop.md +97 -0
  365. package/pp-core/references/scout-codebase.md +51 -0
  366. package/pp-core/references/skeleton-template.md +48 -0
  367. package/pp-core/references/sketch-interactivity.md +41 -0
  368. package/pp-core/references/sketch-theme-system.md +94 -0
  369. package/pp-core/references/sketch-tooling.md +45 -0
  370. package/pp-core/references/sketch-variant-patterns.md +81 -0
  371. package/pp-core/references/spidr-splitting.md +69 -0
  372. package/pp-core/references/tdd.md +330 -0
  373. package/pp-core/references/thinking-models-debug.md +44 -0
  374. package/pp-core/references/thinking-models-execution.md +50 -0
  375. package/pp-core/references/thinking-models-planning.md +62 -0
  376. package/pp-core/references/thinking-models-research.md +50 -0
  377. package/pp-core/references/thinking-models-verification.md +55 -0
  378. package/pp-core/references/thinking-partner.md +96 -0
  379. package/pp-core/references/ui-brand.md +162 -0
  380. package/pp-core/references/universal-anti-patterns.md +63 -0
  381. package/pp-core/references/user-profiling.md +681 -0
  382. package/pp-core/references/user-story-template.md +58 -0
  383. package/pp-core/references/verification-overrides.md +227 -0
  384. package/pp-core/references/verification-patterns.md +612 -0
  385. package/pp-core/references/verify-mvp-mode.md +85 -0
  386. package/pp-core/references/workstream-flag.md +111 -0
  387. package/pp-core/references/worktree-branch-check.md +44 -0
  388. package/pp-core/references/worktree-path-safety.md +67 -0
  389. package/pp-core/templates/AI-SPEC.md +246 -0
  390. package/pp-core/templates/DEBUG.md +169 -0
  391. package/pp-core/templates/README.md +77 -0
  392. package/pp-core/templates/SECURITY.md +61 -0
  393. package/pp-core/templates/UAT.md +265 -0
  394. package/pp-core/templates/UI-SPEC.md +100 -0
  395. package/pp-core/templates/VALIDATION.md +76 -0
  396. package/pp-core/templates/claude-md.md +145 -0
  397. package/pp-core/templates/codebase/architecture.md +255 -0
  398. package/pp-core/templates/codebase/concerns.md +310 -0
  399. package/pp-core/templates/codebase/conventions.md +307 -0
  400. package/pp-core/templates/codebase/integrations.md +280 -0
  401. package/pp-core/templates/codebase/stack.md +186 -0
  402. package/pp-core/templates/codebase/structure.md +285 -0
  403. package/pp-core/templates/codebase/testing.md +480 -0
  404. package/pp-core/templates/config.json +62 -0
  405. package/pp-core/templates/context.md +352 -0
  406. package/pp-core/templates/continue-here.md +78 -0
  407. package/pp-core/templates/copilot-instructions.md +7 -0
  408. package/pp-core/templates/debug-subagent-prompt.md +91 -0
  409. package/pp-core/templates/dev-preferences.md +21 -0
  410. package/pp-core/templates/discovery.md +146 -0
  411. package/pp-core/templates/discussion-log.md +63 -0
  412. package/pp-core/templates/milestone-archive.md +123 -0
  413. package/pp-core/templates/milestone.md +115 -0
  414. package/pp-core/templates/phase-prompt.md +610 -0
  415. package/pp-core/templates/planner-subagent-prompt.md +117 -0
  416. package/pp-core/templates/project.md +203 -0
  417. package/pp-core/templates/requirements.md +231 -0
  418. package/pp-core/templates/research-project/ARCHITECTURE.md +204 -0
  419. package/pp-core/templates/research-project/FEATURES.md +147 -0
  420. package/pp-core/templates/research-project/PITFALLS.md +200 -0
  421. package/pp-core/templates/research-project/STACK.md +120 -0
  422. package/pp-core/templates/research-project/SUMMARY.md +170 -0
  423. package/pp-core/templates/research.md +592 -0
  424. package/pp-core/templates/retrospective.md +54 -0
  425. package/pp-core/templates/roadmap.md +202 -0
  426. package/pp-core/templates/spec.md +333 -0
  427. package/pp-core/templates/state.md +195 -0
  428. package/pp-core/templates/summary-complex.md +60 -0
  429. package/pp-core/templates/summary-minimal.md +42 -0
  430. package/pp-core/templates/summary-standard.md +49 -0
  431. package/pp-core/templates/summary.md +249 -0
  432. package/pp-core/templates/user-profile.md +146 -0
  433. package/pp-core/templates/user-setup.md +311 -0
  434. package/pp-core/templates/verification-report.md +335 -0
  435. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -0
  436. package/pp-core/workflows/add-backlog.md +91 -0
  437. package/pp-core/workflows/add-phase.md +113 -0
  438. package/pp-core/workflows/add-tests.md +355 -0
  439. package/pp-core/workflows/add-todo.md +161 -0
  440. package/pp-core/workflows/ai-integration-phase.md +295 -0
  441. package/pp-core/workflows/analyze-dependencies.md +96 -0
  442. package/pp-core/workflows/audit-fix.md +178 -0
  443. package/pp-core/workflows/audit-milestone.md +362 -0
  444. package/pp-core/workflows/audit-uat.md +110 -0
  445. package/pp-core/workflows/autonomous.md +886 -0
  446. package/pp-core/workflows/check-todos.md +180 -0
  447. package/pp-core/workflows/cleanup.md +195 -0
  448. package/pp-core/workflows/code-review-fix.md +506 -0
  449. package/pp-core/workflows/code-review.md +696 -0
  450. package/pp-core/workflows/complete-milestone.md +857 -0
  451. package/pp-core/workflows/debug.md +237 -0
  452. package/pp-core/workflows/diagnose-issues.md +245 -0
  453. package/pp-core/workflows/discovery-phase.md +291 -0
  454. package/pp-core/workflows/discovery-sprint.md +1546 -0
  455. package/pp-core/workflows/discuss-phase/modes/advisor.md +176 -0
  456. package/pp-core/workflows/discuss-phase/modes/all.md +28 -0
  457. package/pp-core/workflows/discuss-phase/modes/analyze.md +44 -0
  458. package/pp-core/workflows/discuss-phase/modes/auto.md +57 -0
  459. package/pp-core/workflows/discuss-phase/modes/batch.md +52 -0
  460. package/pp-core/workflows/discuss-phase/modes/chain.md +98 -0
  461. package/pp-core/workflows/discuss-phase/modes/default.md +141 -0
  462. package/pp-core/workflows/discuss-phase/modes/power.md +44 -0
  463. package/pp-core/workflows/discuss-phase/modes/text.md +55 -0
  464. package/pp-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
  465. package/pp-core/workflows/discuss-phase/templates/context.md +136 -0
  466. package/pp-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
  467. package/pp-core/workflows/discuss-phase-assumptions.md +675 -0
  468. package/pp-core/workflows/discuss-phase-power.md +291 -0
  469. package/pp-core/workflows/discuss-phase.md +520 -0
  470. package/pp-core/workflows/do.md +111 -0
  471. package/pp-core/workflows/docs-update.md +1168 -0
  472. package/pp-core/workflows/edit-phase.md +295 -0
  473. package/pp-core/workflows/eval-review.md +159 -0
  474. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
  475. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  476. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
  477. package/pp-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
  478. package/pp-core/workflows/execute-phase.md +1707 -0
  479. package/pp-core/workflows/execute-plan.md +541 -0
  480. package/pp-core/workflows/explore.md +146 -0
  481. package/pp-core/workflows/extract-learnings.md +243 -0
  482. package/pp-core/workflows/fast.md +124 -0
  483. package/pp-core/workflows/forensics.md +279 -0
  484. package/pp-core/workflows/graduation.md +196 -0
  485. package/pp-core/workflows/health.md +224 -0
  486. package/pp-core/workflows/help/modes/brief.md +22 -0
  487. package/pp-core/workflows/help/modes/default.md +50 -0
  488. package/pp-core/workflows/help/modes/full.md +791 -0
  489. package/pp-core/workflows/help/modes/topic.md +74 -0
  490. package/pp-core/workflows/help.md +24 -0
  491. package/pp-core/workflows/import.md +256 -0
  492. package/pp-core/workflows/inbox.md +387 -0
  493. package/pp-core/workflows/ingest-docs.md +340 -0
  494. package/pp-core/workflows/insert-phase.md +152 -0
  495. package/pp-core/workflows/list-phase-assumptions.md +178 -0
  496. package/pp-core/workflows/list-workspaces.md +57 -0
  497. package/pp-core/workflows/manager.md +435 -0
  498. package/pp-core/workflows/map-codebase.md +444 -0
  499. package/pp-core/workflows/milestone-summary.md +224 -0
  500. package/pp-core/workflows/mvp-phase.md +222 -0
  501. package/pp-core/workflows/new-milestone.md +643 -0
  502. package/pp-core/workflows/new-project.md +1563 -0
  503. package/pp-core/workflows/new-workspace.md +240 -0
  504. package/pp-core/workflows/next.md +347 -0
  505. package/pp-core/workflows/node-repair.md +92 -0
  506. package/pp-core/workflows/note.md +158 -0
  507. package/pp-core/workflows/pause-work.md +250 -0
  508. package/pp-core/workflows/plan-milestone-gaps.md +281 -0
  509. package/pp-core/workflows/plan-phase.md +1755 -0
  510. package/pp-core/workflows/plan-review-convergence.md +369 -0
  511. package/pp-core/workflows/plant-seed.md +230 -0
  512. package/pp-core/workflows/pr-branch.md +159 -0
  513. package/pp-core/workflows/profile-user.md +455 -0
  514. package/pp-core/workflows/progress.md +756 -0
  515. package/pp-core/workflows/quick.md +1045 -0
  516. package/pp-core/workflows/reapply-patches.md +443 -0
  517. package/pp-core/workflows/remove-phase.md +156 -0
  518. package/pp-core/workflows/remove-workspace.md +108 -0
  519. package/pp-core/workflows/resume-project.md +348 -0
  520. package/pp-core/workflows/review.md +765 -0
  521. package/pp-core/workflows/scan.md +107 -0
  522. package/pp-core/workflows/secure-phase.md +184 -0
  523. package/pp-core/workflows/session-report.md +146 -0
  524. package/pp-core/workflows/settings-advanced.md +816 -0
  525. package/pp-core/workflows/settings-integrations.md +312 -0
  526. package/pp-core/workflows/settings.md +592 -0
  527. package/pp-core/workflows/ship.md +465 -0
  528. package/pp-core/workflows/sketch-wrap-up.md +286 -0
  529. package/pp-core/workflows/sketch.md +361 -0
  530. package/pp-core/workflows/spec-phase.md +495 -0
  531. package/pp-core/workflows/spike-wrap-up.md +307 -0
  532. package/pp-core/workflows/spike.md +453 -0
  533. package/pp-core/workflows/stats.md +80 -0
  534. package/pp-core/workflows/sync-skills.md +182 -0
  535. package/pp-core/workflows/thread.md +222 -0
  536. package/pp-core/workflows/transition.md +694 -0
  537. package/pp-core/workflows/ui-phase.md +328 -0
  538. package/pp-core/workflows/ui-review.md +193 -0
  539. package/pp-core/workflows/ultraplan-phase.md +199 -0
  540. package/pp-core/workflows/undo.md +314 -0
  541. package/pp-core/workflows/update.md +501 -0
  542. package/pp-core/workflows/validate-phase.md +183 -0
  543. package/pp-core/workflows/verify-phase.md +569 -0
  544. package/pp-core/workflows/verify-work.md +804 -0
  545. package/scripts/affected-tests-lib.cjs +554 -0
  546. package/scripts/audit-workflow-script-paths.cjs +73 -0
  547. package/scripts/base64-scan.sh +351 -0
  548. package/scripts/build-hooks.js +254 -0
  549. package/scripts/changeset/README.md +129 -0
  550. package/scripts/changeset/cli.cjs +597 -0
  551. package/scripts/changeset/github-release-notes.cjs +199 -0
  552. package/scripts/changeset/lint.cjs +147 -0
  553. package/scripts/changeset/new.cjs +151 -0
  554. package/scripts/changeset/parse.cjs +114 -0
  555. package/scripts/changeset/render.cjs +34 -0
  556. package/scripts/changeset/serialize.cjs +130 -0
  557. package/scripts/check-alias-drift.cjs +114 -0
  558. package/scripts/check-env.cjs +312 -0
  559. package/scripts/check-npm-integrity.cjs +215 -0
  560. package/scripts/ci-guard-runner.cjs +22 -0
  561. package/scripts/ci-prepare-test-scope.cjs +93 -0
  562. package/scripts/ci-rebase-check.cjs +86 -0
  563. package/scripts/ci-test-scope.cjs +441 -0
  564. package/scripts/command-contract-helpers.cjs +64 -0
  565. package/scripts/diff-touches-shipped-paths.cjs +149 -0
  566. package/scripts/fix-slash-commands.cjs +161 -0
  567. package/scripts/gen-capability-registry.cjs +2646 -0
  568. package/scripts/gen-commands-reference.cjs +139 -0
  569. package/scripts/gen-inventory-manifest.cjs +114 -0
  570. package/scripts/gen-loop-host-contract.cjs +526 -0
  571. package/scripts/gen-research-agents.cjs +276 -0
  572. package/scripts/generate-package-identity.cjs +134 -0
  573. package/scripts/issue-dedupe.cjs +278 -0
  574. package/scripts/issue-version-gate.cjs +140 -0
  575. package/scripts/lib/allowlist-ratchet.cjs +236 -0
  576. package/scripts/lib/cli-exit.cjs +56 -0
  577. package/scripts/lint-allow-test-rule-refs.allowlist.json +328 -0
  578. package/scripts/lint-allow-test-rule-refs.cjs +162 -0
  579. package/scripts/lint-command-contract.cjs +118 -0
  580. package/scripts/lint-descriptions.cjs +87 -0
  581. package/scripts/lint-docs-required.cjs +222 -0
  582. package/scripts/lint-legacy-dir-name.cjs +166 -0
  583. package/scripts/lint-package-identity-drift.cjs +141 -0
  584. package/scripts/lint-pr-check-project-dir.cjs +99 -0
  585. package/scripts/lint-regression-test-names.allowlist.json +269 -0
  586. package/scripts/lint-regression-test-names.cjs +117 -0
  587. package/scripts/lint-shell-command-projection-drift.cjs +62 -0
  588. package/scripts/lint-skill-deps.cjs +185 -0
  589. package/scripts/lint-test-file-count.allowlist.json +181 -0
  590. package/scripts/lint-test-file-count.cjs +246 -0
  591. package/scripts/lint-windows-test-portability.cjs +178 -0
  592. package/scripts/mutation-matrix.cjs +323 -0
  593. package/scripts/pr-target-policy.cjs +63 -0
  594. package/scripts/pr-template-policy.cjs +268 -0
  595. package/scripts/prompt-injection-scan.sh +207 -0
  596. package/scripts/registry-smoke.cjs +337 -0
  597. package/scripts/release-notes/discord-release-summary.cjs +373 -0
  598. package/scripts/release-notes/format-github-release-notes.cjs +261 -0
  599. package/scripts/release-tarball-smoke.cjs +763 -0
  600. package/scripts/research-profiles.cjs +149 -0
  601. package/scripts/run-affected-tests.cjs +7 -0
  602. package/scripts/run-tests.cjs +589 -0
  603. package/scripts/secret-scan-lint.sh +231 -0
  604. package/scripts/secret-scan.sh +358 -0
  605. package/scripts/setup-branch-protection.sh +234 -0
  606. package/scripts/strip-prose-atrefs.cjs +106 -0
  607. package/scripts/sync-manifest-versions.cjs +119 -0
  608. package/scripts/sync-next-version.cjs +133 -0
  609. package/scripts/sync-rulesets.sh +34 -0
  610. package/scripts/sync-runtime-launcher.cjs +425 -0
  611. package/scripts/test-failure-reasons.cjs +34 -0
  612. package/scripts/update-size-baseline.cjs +68 -0
  613. package/scripts/verify-npm-publish.cjs +240 -0
  614. package/scripts/verify-opengsd-sweep.cjs +288 -0
  615. package/scripts/verify-phase-02-rename.sh +258 -0
  616. package/scripts/verify-phase-03-rename.sh +376 -0
  617. package/scripts/workflow-policy.cjs +483 -0
  618. package/scripts/workflow-size.cjs +90 -0
@@ -0,0 +1,1446 @@
1
+ "use strict";
2
+ /**
3
+ * Phase — Phase CRUD, query, and lifecycle operations
4
+ *
5
+ * ADR-457 build-at-publish: the hand-written bin/lib/phase.cjs collapsed to
6
+ * a TypeScript source of truth, compiled by tsc to a gitignored .cjs at the
7
+ * same require() path. Behaviour preserved byte-for-behaviour; only types are added.
8
+ *
9
+ * Re-export shim note (issue #4 / ADR-3524):
10
+ * The phase lifecycle pure-computation helpers live in phase-lifecycle.cjs.
11
+ * cmdPhaseComplete uses
12
+ * deriveProgressFromRoadmap + clampPercent from that module to fix the
13
+ * non-idempotent Completed Phases blind-increment bug.
14
+ *
15
+ * The async mutation handlers (phaseAdd, phaseInsert, phaseRemove, phaseComplete)
16
+ * in phase-lifecycle.ts are I/O-bound and remain per-side per ADR-3524 Section 4.
17
+ * This file provides the CJS (sync) implementations of those handlers.
18
+ */
19
+ var __importDefault = (this && this.__importDefault) || function (mod) {
20
+ return (mod && mod.__esModule) ? mod : { "default": mod };
21
+ };
22
+ const node_fs_1 = __importDefault(require("node:fs"));
23
+ const node_path_1 = __importDefault(require("node:path"));
24
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
25
+ const ioMod = require("./io.cjs");
26
+ const { output, error, ERROR_REASON } = ioMod;
27
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
28
+ const configLoaderMod = require("./config-loader.cjs");
29
+ const { loadConfig } = configLoaderMod;
30
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
31
+ const coreUtilsMod = require("./core-utils.cjs");
32
+ const { toPosixPath, generateSlugInternal, readSubdirectories } = coreUtilsMod;
33
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
34
+ const phaseIdMod = require("./phase-id.cjs");
35
+ const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, phaseTokenMatches } = phaseIdMod;
36
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
37
+ const phaseLocatorMod = require("./phase-locator.cjs");
38
+ const { findPhaseInternal, getArchivedPhaseDirs } = phaseLocatorMod;
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
40
+ const roadmapParserMod = require("./roadmap-parser.cjs");
41
+ const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter } = roadmapParserMod;
42
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
43
+ const planningWorkspace = require("./planning-workspace.cjs");
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
45
+ const frontmatterMod = require("./frontmatter.cjs");
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- state.cjs is an export= CommonJS module
47
+ const stateMod = require("./state.cjs");
48
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
49
+ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
50
+ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
51
+ const clock_cjs_1 = require("./clock.cjs");
52
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
53
+ const uatPredicate = require("./uat-predicate.cjs");
54
+ const { evaluateUatPassed } = uatPredicate;
55
+ const { planningDir, withPlanningLock } = planningWorkspace;
56
+ const { extractFrontmatter } = frontmatterMod;
57
+ const { readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, syncStateFrontmatter, withStateLock, updatePerformanceMetricsSection, } = stateMod;
58
+ // #2893 — strict canonical filter: `{padded_phase}-{NN}-PLAN.md` or `PLAN.md`.
59
+ const isCanonicalPlanFile = (f) => f.endsWith('-PLAN.md') || f === 'PLAN.md';
60
+ // Any .md file with PLAN anywhere in the basename — diagnostic net
61
+ const PLAN_OUTLINE_RE = /-PLAN-OUTLINE\.md$/i;
62
+ const PLAN_PRE_BOUNCE_RE = /-PLAN.*\.pre-bounce\.md$/i;
63
+ const looksLikePlanFile = (f) => /\.md$/i.test(f) &&
64
+ /PLAN/i.test(f) &&
65
+ !PLAN_OUTLINE_RE.test(f) &&
66
+ !PLAN_PRE_BOUNCE_RE.test(f);
67
+ function describeNonCanonicalPlans(dirFiles, matchedFiles) {
68
+ const matched = new Set(matchedFiles);
69
+ const offenders = dirFiles.filter((f) => looksLikePlanFile(f) && !matched.has(f));
70
+ if (offenders.length === 0)
71
+ return null;
72
+ return (`Found ${offenders.length} plan-shaped file(s) in this phase that don't match the canonical ` +
73
+ `naming convention "{padded_phase}-{NN}-PLAN.md" (or bare "PLAN.md") and were skipped: ` +
74
+ offenders.map((f) => `"${f}"`).join(', ') +
75
+ `. Rename to the canonical form (e.g. "01-01-PLAN.md") so the executor can detect them. ` +
76
+ `See agents/gsd-planner.md write_phase_prompt step for the full contract.`);
77
+ }
78
+ function extractCanonicalPlanId(filename) {
79
+ const base = filename
80
+ .replace(/-PLAN\.md$/i, '')
81
+ .replace(/-SUMMARY\.md$/i, '')
82
+ .replace(/\.md$/i, '');
83
+ const parts = base.split('-').filter(Boolean);
84
+ const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
85
+ const phaseIdx = parts.findIndex((p) => tokenRe.test(p));
86
+ if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
87
+ return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
88
+ }
89
+ return base;
90
+ }
91
+ function cmdPhasesList(cwd, options, raw) {
92
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
93
+ const { type, phase, includeArchived } = options;
94
+ if (!node_fs_1.default.existsSync(phasesDir)) {
95
+ if (type) {
96
+ output({ files: [], count: 0 }, raw, '');
97
+ }
98
+ else {
99
+ output({ directories: [], count: 0 }, raw, '');
100
+ }
101
+ return;
102
+ }
103
+ try {
104
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
105
+ let dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
106
+ if (includeArchived) {
107
+ const archived = getArchivedPhaseDirs(cwd);
108
+ for (const a of archived) {
109
+ dirs.push(`${a.name} [${a.milestone}]`);
110
+ }
111
+ }
112
+ dirs.sort((a, b) => comparePhaseNum(a, b));
113
+ if (phase) {
114
+ const normalized = normalizePhaseName(phase);
115
+ const match = dirs.find((d) => phaseTokenMatches(d, normalized));
116
+ if (!match) {
117
+ output({ files: [], count: 0, phase_dir: null, error: 'Phase not found' }, raw, '');
118
+ return;
119
+ }
120
+ dirs = [match];
121
+ }
122
+ if (type) {
123
+ const files = [];
124
+ const warnings = [];
125
+ for (const dir of dirs) {
126
+ const dirPath = node_path_1.default.join(phasesDir, dir);
127
+ const dirFiles = node_fs_1.default.readdirSync(dirPath);
128
+ let filtered;
129
+ if (type === 'plans') {
130
+ filtered = dirFiles.filter(isCanonicalPlanFile);
131
+ const w = describeNonCanonicalPlans(dirFiles, filtered);
132
+ if (w)
133
+ warnings.push(`${dir}: ${w}`);
134
+ }
135
+ else if (type === 'summaries') {
136
+ filtered = dirFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
137
+ }
138
+ else {
139
+ filtered = dirFiles;
140
+ }
141
+ files.push(...filtered.sort());
142
+ }
143
+ const result = {
144
+ files,
145
+ count: files.length,
146
+ phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)*-?/, '') : null,
147
+ };
148
+ if (warnings.length)
149
+ result['warning'] = warnings.join(' | ');
150
+ output(result, raw, files.join('\n'));
151
+ return;
152
+ }
153
+ output({ directories: dirs, count: dirs.length }, raw, dirs.join('\n'));
154
+ }
155
+ catch (e) {
156
+ const msg = e instanceof Error ? e.message : String(e);
157
+ error('Failed to list phases: ' + msg);
158
+ }
159
+ }
160
+ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
161
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
162
+ const normalized = normalizePhaseName(basePhase);
163
+ try {
164
+ let baseExists = false;
165
+ const decimalSet = new Set();
166
+ if (node_fs_1.default.existsSync(phasesDir)) {
167
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
168
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
169
+ baseExists = dirs.some((d) => phaseTokenMatches(d, normalized));
170
+ const dirPattern = new RegExp(`^(?:[A-Z]{1,6}-)?${escapeRegex(normalized)}\\.(\\d+)`);
171
+ for (const dir of dirs) {
172
+ const match = dir.match(dirPattern);
173
+ if (match)
174
+ decimalSet.add(parseInt(match[1], 10));
175
+ }
176
+ }
177
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
178
+ if (node_fs_1.default.existsSync(roadmapPath)) {
179
+ try {
180
+ const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
181
+ const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${phaseMarkdownRegexSource(normalized)}\\.(\\d+)\\s*:`, 'gi');
182
+ let pm;
183
+ while ((pm = phasePattern.exec(roadmapContent)) !== null) {
184
+ decimalSet.add(parseInt(pm[1], 10));
185
+ }
186
+ }
187
+ catch {
188
+ /* ROADMAP.md read failure is non-fatal */
189
+ }
190
+ }
191
+ const existingDecimals = Array.from(decimalSet)
192
+ .sort((a, b) => a - b)
193
+ .map((n) => `${normalized}.${n}`);
194
+ let nextDecimal;
195
+ if (decimalSet.size === 0) {
196
+ nextDecimal = `${normalized}.1`;
197
+ }
198
+ else {
199
+ nextDecimal = `${normalized}.${Math.max(...decimalSet) + 1}`;
200
+ }
201
+ output({
202
+ found: baseExists,
203
+ base_phase: normalized,
204
+ next: nextDecimal,
205
+ existing: existingDecimals,
206
+ }, raw, nextDecimal);
207
+ }
208
+ catch (e) {
209
+ const msg = e instanceof Error ? e.message : String(e);
210
+ error('Failed to calculate next decimal phase: ' + msg);
211
+ }
212
+ }
213
+ function getRoadmapModeForPhase(cwd, phaseNum) {
214
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
215
+ if (!node_fs_1.default.existsSync(roadmapPath))
216
+ return null;
217
+ const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
218
+ const milestoneContent = extractCurrentMilestone(rawContent, cwd);
219
+ const fullContent = stripShippedMilestones(rawContent);
220
+ const escapedPhase = phaseMarkdownRegexSource(phaseNum);
221
+ const phaseHeader = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}\\s*:`, 'i');
222
+ for (const content of [milestoneContent, fullContent]) {
223
+ const headerMatch = content.match(phaseHeader);
224
+ if (!headerMatch || headerMatch.index === undefined)
225
+ continue;
226
+ const sectionStart = headerMatch.index;
227
+ const rest = content.slice(sectionStart);
228
+ const nextHeader = rest.slice(headerMatch[0].length).match(/\n#{2,4}\s+Phase\s+\S/i);
229
+ const sectionEnd = nextHeader
230
+ ? sectionStart + headerMatch[0].length + nextHeader.index
231
+ : content.length;
232
+ const section = content.slice(sectionStart, sectionEnd);
233
+ const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
234
+ if (modeMatch)
235
+ return modeMatch[1].trim().toLowerCase();
236
+ }
237
+ return null;
238
+ }
239
+ function cmdPhaseMvpMode(cwd, args, raw) {
240
+ const phaseNum = args[0];
241
+ if (!phaseNum) {
242
+ error('Usage: phase.mvp-mode <phase-number> [--cli-flag]', ERROR_REASON.USAGE);
243
+ }
244
+ const cliFlagPresent = args.includes('--cli-flag');
245
+ const roadmapMode = getRoadmapModeForPhase(cwd, phaseNum);
246
+ const config = loadConfig(cwd);
247
+ const configMvpMode = Boolean(config.mvp_mode);
248
+ let active = false;
249
+ let source = 'none';
250
+ if (cliFlagPresent) {
251
+ active = true;
252
+ source = 'cli_flag';
253
+ }
254
+ else if (roadmapMode === 'mvp') {
255
+ active = true;
256
+ source = 'roadmap';
257
+ }
258
+ else if (configMvpMode) {
259
+ active = true;
260
+ source = 'config';
261
+ }
262
+ output({
263
+ active,
264
+ source,
265
+ roadmap_mode: roadmapMode,
266
+ config_mvp_mode: configMvpMode,
267
+ cli_flag_present: cliFlagPresent,
268
+ }, raw);
269
+ }
270
+ function cmdFindPhase(cwd, phase, raw) {
271
+ if (!phase) {
272
+ error('phase identifier required');
273
+ }
274
+ const planBase = planningDir(cwd);
275
+ const normalized = normalizePhaseName(phase);
276
+ const notFound = {
277
+ found: false,
278
+ directory: null,
279
+ phase_number: null,
280
+ phase_name: null,
281
+ plans: [],
282
+ summaries: [],
283
+ searched_directories: [],
284
+ };
285
+ const searchDirs = [];
286
+ const flatPhasesDir = node_path_1.default.join(planBase, 'phases');
287
+ if (node_fs_1.default.existsSync(flatPhasesDir))
288
+ searchDirs.push(flatPhasesDir);
289
+ try {
290
+ const milestonesDir = node_path_1.default.join(planBase, 'milestones');
291
+ const entries = node_fs_1.default
292
+ .readdirSync(milestonesDir, { withFileTypes: true })
293
+ .filter((e) => e.isDirectory() && /^v\d+.*-phases$/.test(e.name))
294
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
295
+ for (const e of entries) {
296
+ searchDirs.push(node_path_1.default.join(milestonesDir, e.name));
297
+ }
298
+ }
299
+ catch {
300
+ /* no milestones dir */
301
+ }
302
+ notFound.searched_directories = searchDirs.map((searchDir) => toPosixPath(node_path_1.default.join(node_path_1.default.relative(cwd, planBase), node_path_1.default.relative(planBase, searchDir))));
303
+ for (const searchDir of searchDirs) {
304
+ try {
305
+ const entries = node_fs_1.default.readdirSync(searchDir, { withFileTypes: true });
306
+ const dirs = entries
307
+ .filter((e) => e.isDirectory())
308
+ .map((e) => e.name)
309
+ .sort((a, b) => comparePhaseNum(a, b));
310
+ const match = dirs.find((d) => phaseTokenMatches(d, normalized));
311
+ if (!match)
312
+ continue;
313
+ const dirMatch = match.match(/^(?:[A-Z]{1,6}-)(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i) ||
314
+ match.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);
315
+ const phaseNumber = dirMatch ? dirMatch[1] : normalized;
316
+ const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
317
+ const phaseDir = node_path_1.default.join(searchDir, match);
318
+ const phaseFiles = node_fs_1.default.readdirSync(phaseDir);
319
+ const plans = phaseFiles.filter(isCanonicalPlanFile).sort();
320
+ const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').sort();
321
+ const planNamingWarning = describeNonCanonicalPlans(phaseFiles, plans);
322
+ const result = {
323
+ found: true,
324
+ directory: toPosixPath(node_path_1.default.join(node_path_1.default.relative(cwd, planBase), node_path_1.default.relative(planBase, searchDir), match)),
325
+ phase_number: phaseNumber,
326
+ phase_name: phaseName,
327
+ plans,
328
+ summaries,
329
+ };
330
+ if (planNamingWarning)
331
+ result['warning'] = planNamingWarning;
332
+ output(result, raw, result['directory']);
333
+ return;
334
+ }
335
+ catch {
336
+ continue;
337
+ }
338
+ }
339
+ output(notFound, raw, '');
340
+ }
341
+ function extractObjective(content) {
342
+ const m = content.match(/<objective>\s*\n?\s*(.+)/);
343
+ return m ? m[1].trim() : null;
344
+ }
345
+ // O(V + E). Assigns each in-phase plan its longest-path topological level over the
346
+ // in-phase dependsOn DAG (Kahn's algorithm). Returns { level: Map<id,number>, visited: number }.
347
+ // visited < rawPlans.length signals a dependency cycle.
348
+ function computeDependencyLevels(rawPlans, planMap, canonicalToId) {
349
+ const level = new Map();
350
+ const inDeg = new Map();
351
+ const adj = new Map();
352
+ for (const p of rawPlans) {
353
+ if (!inDeg.has(p.id))
354
+ inDeg.set(p.id, 0);
355
+ if (!adj.has(p.id))
356
+ adj.set(p.id, []);
357
+ for (const dep of p.dependsOn) {
358
+ const depLower = dep.toLowerCase();
359
+ const resolvedDep = planMap.has(depLower)
360
+ ? planMap.get(depLower).id
361
+ : canonicalToId.get(depLower);
362
+ if (!resolvedDep)
363
+ continue;
364
+ if (!adj.has(resolvedDep))
365
+ adj.set(resolvedDep, []);
366
+ adj.get(resolvedDep).push(p.id);
367
+ inDeg.set(p.id, (inDeg.get(p.id) ?? 0) + 1);
368
+ }
369
+ }
370
+ const queue = [];
371
+ for (const p of rawPlans) {
372
+ if ((inDeg.get(p.id) ?? 0) === 0) {
373
+ queue.push(p.id);
374
+ level.set(p.id, 0);
375
+ }
376
+ }
377
+ // Dequeue by head index (queue[head++]), NOT Array.shift(): shift() is O(n) per
378
+ // call in V8. Head-index dequeue is O(1) amortized -> O(V+E) overall. (#307)
379
+ let head = 0;
380
+ let visited = 0;
381
+ while (head < queue.length) {
382
+ const cur = queue[head++];
383
+ visited++;
384
+ const curLevel = level.get(cur);
385
+ for (const dep of adj.get(cur) ?? []) {
386
+ const newLevel = curLevel + 1;
387
+ if (newLevel > (level.get(dep) ?? -1)) {
388
+ level.set(dep, newLevel);
389
+ }
390
+ inDeg.set(dep, inDeg.get(dep) - 1);
391
+ if (inDeg.get(dep) === 0) {
392
+ queue.push(dep);
393
+ }
394
+ }
395
+ }
396
+ return { level, visited };
397
+ }
398
+ function cmdPhasePlanIndex(cwd, phase, raw) {
399
+ if (!phase) {
400
+ error('phase required for phase-plan-index');
401
+ }
402
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
403
+ const normalized = normalizePhaseName(phase);
404
+ let phaseDir = null;
405
+ let phaseDirName = null;
406
+ try {
407
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
408
+ const dirs = entries
409
+ .filter((e) => e.isDirectory())
410
+ .map((e) => e.name)
411
+ .sort((a, b) => comparePhaseNum(a, b));
412
+ const match = dirs.find((d) => phaseTokenMatches(d, normalized));
413
+ if (match) {
414
+ phaseDir = node_path_1.default.join(phasesDir, match);
415
+ phaseDirName = match;
416
+ }
417
+ }
418
+ catch {
419
+ // phases dir doesn't exist
420
+ }
421
+ if (!phaseDir) {
422
+ output({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], has_checkpoints: false }, raw);
423
+ return;
424
+ }
425
+ void phaseDirName; // used only to set phaseDir above
426
+ const phaseFiles = node_fs_1.default.readdirSync(phaseDir);
427
+ const planFiles = phaseFiles.filter(isCanonicalPlanFile).sort();
428
+ const summaryFiles = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
429
+ const planNamingWarning = describeNonCanonicalPlans(phaseFiles, planFiles);
430
+ const completedPlanIds = new Set(summaryFiles.flatMap((s) => {
431
+ const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
432
+ const canonical = extractCanonicalPlanId(s);
433
+ return canonical === exact ? [exact] : [exact, canonical];
434
+ }));
435
+ // ── Pass 1: parse each plan file ─────────────────────────────────────────
436
+ const rawPlans = [];
437
+ for (const planFile of planFiles) {
438
+ const planId = planFile.replace('-PLAN.md', '').replace('PLAN.md', '');
439
+ const planPath = node_path_1.default.join(phaseDir, planFile);
440
+ const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
441
+ const fm = extractFrontmatter(content);
442
+ const xmlTasks = content.match(/<task[\s>]/gi) || [];
443
+ const mdTasks = content.match(/##\s*Task\s*\d+/gi) || [];
444
+ const taskCount = xmlTasks.length || mdTasks.length;
445
+ const parsedWave = parseInt(fm['wave'], 10);
446
+ const declaredWave = Number.isNaN(parsedWave) ? null : parsedWave;
447
+ let dependsOn = [];
448
+ const fmDeps = fm['depends_on'];
449
+ if (Array.isArray(fmDeps)) {
450
+ dependsOn = fmDeps.map(String);
451
+ }
452
+ else if (typeof fmDeps === 'string' && fmDeps.trim() !== '') {
453
+ dependsOn = [fmDeps];
454
+ }
455
+ let autonomous = true;
456
+ if (fm['autonomous'] !== undefined) {
457
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue comparison
458
+ autonomous = fm['autonomous'] === 'true' || String(fm['autonomous']) === 'true';
459
+ }
460
+ let filesModified = [];
461
+ const fmFiles = fm['files_modified'] || fm['files-modified'];
462
+ if (fmFiles) {
463
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
464
+ filesModified = Array.isArray(fmFiles) ? fmFiles.map(String) : [String(fmFiles)];
465
+ }
466
+ const hasSummary = completedPlanIds.has(planId) || completedPlanIds.has(extractCanonicalPlanId(planFile));
467
+ rawPlans.push({
468
+ id: planId,
469
+ declaredWave,
470
+ dependsOn,
471
+ autonomous,
472
+ objective: extractObjective(content) || fm['objective'] || null,
473
+ filesModified,
474
+ taskCount,
475
+ hasSummary,
476
+ });
477
+ }
478
+ // ── Pass 2: topological level assignment via depends_on DAG ──────────────
479
+ const seenLower = new Map();
480
+ for (const p of rawPlans) {
481
+ const lower = p.id.toLowerCase();
482
+ const existing = seenLower.get(lower);
483
+ if (existing !== undefined) {
484
+ error(`depends_on index collision in phase ${normalized}: plan IDs '${existing}' and '${p.id}' are identical when case-folded. Rename one file to avoid ambiguous dependency resolution.`);
485
+ return;
486
+ }
487
+ seenLower.set(lower, p.id);
488
+ }
489
+ const planMap = new Map(rawPlans.map((p) => [p.id.toLowerCase(), p]));
490
+ const canonicalToId = new Map(rawPlans.map((p) => [extractCanonicalPlanId(p.id).toLowerCase(), p.id]));
491
+ const { level, visited } = computeDependencyLevels(rawPlans, planMap, canonicalToId);
492
+ if (visited < rawPlans.length) {
493
+ const cycleNodes = rawPlans.filter((p) => !level.has(p.id)).map((p) => p.id);
494
+ error(`depends_on cycle detected in phase ${normalized} — cycle involves: ${cycleNodes.join(', ')}`);
495
+ return;
496
+ }
497
+ // ── Pass 3: determine lowest bucket key and build output ─────────────────
498
+ const anyWaveZero = rawPlans.some((p) => p.declaredWave === 0);
499
+ const levelOffset = anyWaveZero ? 0 : 1;
500
+ const plans = [];
501
+ const waves = {};
502
+ const incomplete = [];
503
+ let hasCheckpoints = false;
504
+ const warnings = [];
505
+ for (const rawPlan of rawPlans) {
506
+ if (!rawPlan.autonomous) {
507
+ hasCheckpoints = true;
508
+ }
509
+ if (!rawPlan.hasSummary) {
510
+ incomplete.push(rawPlan.id);
511
+ }
512
+ const computedWave = (level.get(rawPlan.id) ?? 0) + levelOffset;
513
+ const effectiveWave = computedWave;
514
+ if (rawPlan.declaredWave !== null && rawPlan.declaredWave !== computedWave) {
515
+ warnings.push(`Plan ${rawPlan.id}: declared wave: ${rawPlan.declaredWave} but depends_on DAG places it in wave ${computedWave}`);
516
+ }
517
+ const plan = {
518
+ id: rawPlan.id,
519
+ wave: effectiveWave,
520
+ depends_on: rawPlan.dependsOn.map((dep) => {
521
+ const lower = String(dep).toLowerCase();
522
+ return planMap.has(lower) ? planMap.get(lower).id : dep;
523
+ }),
524
+ autonomous: rawPlan.autonomous,
525
+ objective: rawPlan.objective,
526
+ files_modified: rawPlan.filesModified,
527
+ task_count: rawPlan.taskCount,
528
+ has_summary: rawPlan.hasSummary,
529
+ };
530
+ plans.push(plan);
531
+ const waveKey = String(effectiveWave);
532
+ if (!waves[waveKey]) {
533
+ waves[waveKey] = [];
534
+ }
535
+ waves[waveKey].push(rawPlan.id);
536
+ }
537
+ const result = {
538
+ phase: normalized,
539
+ plans,
540
+ waves,
541
+ incomplete,
542
+ has_checkpoints: hasCheckpoints,
543
+ };
544
+ if (planNamingWarning)
545
+ result['warning'] = planNamingWarning;
546
+ if (warnings.length > 0)
547
+ result['warnings'] = warnings;
548
+ output(result, raw);
549
+ }
550
+ function cmdPhaseAdd(cwd, description, raw, customId) {
551
+ if (!description) {
552
+ error('description required for phase add');
553
+ }
554
+ const config = loadConfig(cwd);
555
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
556
+ if (!node_fs_1.default.existsSync(roadmapPath)) {
557
+ error('ROADMAP.md not found');
558
+ }
559
+ const slug = generateSlugInternal(description) || '';
560
+ const { newPhaseId, dirName } = withPlanningLock(cwd, () => {
561
+ const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
562
+ const content = extractCurrentMilestone(rawContent, cwd);
563
+ const projectCode = config.project_code || '';
564
+ const prefix = projectCode ? `${projectCode}-` : '';
565
+ let _newPhaseId;
566
+ let _dirName;
567
+ if (customId || config.phase_naming === 'custom') {
568
+ _newPhaseId = customId || slug.toUpperCase();
569
+ if (!_newPhaseId)
570
+ error('--id required when phase_naming is "custom"');
571
+ _dirName = `${prefix}${_newPhaseId}-${slug}`;
572
+ }
573
+ else {
574
+ // Collect all phase numbers visible in the current-milestone content.
575
+ // Three sources are scanned so that a phase in ANY representation
576
+ // (section header, roadmap bullet, or on-disk directory) is counted:
577
+ // 1) Section headers: ### Phase N: / ## Phase N: / #### Phase N:
578
+ const headerPattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
579
+ // 2) Roadmap bullet entries: - [ ] **Phase N: ...** (all checkbox variants)
580
+ // The lookahead accepts colon, decimal-dot, whitespace, bold-close asterisk,
581
+ // or end-of-line so titleless forms ("- [ ] **Phase 11**", "- [ ] Phase 11")
582
+ // are counted and cannot collide with a freshly-added phase. (#1229)
583
+ const bulletPattern = /^[ \t]*-[ \t]*\[[^\]]*\][ \t]*\*{0,2}Phase[ \t]+(\d+)(?=[:.\s*]|$)/gim;
584
+ const usedPhaseNums = new Set();
585
+ let m;
586
+ while ((m = headerPattern.exec(content)) !== null) {
587
+ const num = parseInt(m[1], 10);
588
+ if (num !== 999)
589
+ usedPhaseNums.add(num);
590
+ }
591
+ while ((m = bulletPattern.exec(content)) !== null) {
592
+ const num = parseInt(m[1], 10);
593
+ if (num !== 999)
594
+ usedPhaseNums.add(num);
595
+ }
596
+ // 3) On-disk phase directories (e.g. phases/11-foo/ with no header yet)
597
+ const phasesOnDisk = node_path_1.default.join(planningDir(cwd), 'phases');
598
+ if (node_fs_1.default.existsSync(phasesOnDisk)) {
599
+ const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
600
+ for (const entry of node_fs_1.default.readdirSync(phasesOnDisk)) {
601
+ const match = entry.match(dirNumPattern);
602
+ if (!match)
603
+ continue;
604
+ const num = parseInt(match[1], 10);
605
+ if (num !== 999)
606
+ usedPhaseNums.add(num);
607
+ }
608
+ }
609
+ // phase.add appends after the highest *used* number. Collecting numbers from
610
+ // section headers, roadmap bullets, AND on-disk dirs above is what prevents the
611
+ // #1229 collision (a bullet-only Phase N is now counted), so max+1 cannot reuse
612
+ // an existing number.
613
+ const maxUsed = usedPhaseNums.size > 0 ? Math.max(...usedPhaseNums) : 0;
614
+ _newPhaseId = maxUsed + 1;
615
+ const paddedNum = String(_newPhaseId).padStart(2, '0');
616
+ _dirName = `${prefix}${paddedNum}-${slug}`;
617
+ }
618
+ const dirPath = node_path_1.default.join(planningDir(cwd), 'phases', _dirName);
619
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
620
+ (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(dirPath, '.gitkeep'), '');
621
+ const dependsOn = config.phase_naming === 'custom'
622
+ ? ''
623
+ : `\n**Depends on:** Phase ${typeof _newPhaseId === 'number' ? _newPhaseId - 1 : 'TBD'}`;
624
+ const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${_newPhaseId} to break down)\n`;
625
+ let updatedContent;
626
+ const lastSeparator = rawContent.lastIndexOf('\n---');
627
+ if (lastSeparator > 0) {
628
+ updatedContent = rawContent.slice(0, lastSeparator) + phaseEntry + rawContent.slice(lastSeparator);
629
+ }
630
+ else {
631
+ updatedContent = rawContent + phaseEntry;
632
+ }
633
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, updatedContent);
634
+ return { newPhaseId: _newPhaseId, dirName: _dirName };
635
+ });
636
+ const result = {
637
+ phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
638
+ padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
639
+ name: description,
640
+ slug,
641
+ directory: toPosixPath(node_path_1.default.join(node_path_1.default.relative(cwd, planningDir(cwd)), 'phases', dirName)),
642
+ naming_mode: config.phase_naming,
643
+ };
644
+ output(result, raw, result.padded);
645
+ }
646
+ function cmdPhaseAddBatch(cwd, descriptions, raw) {
647
+ if (!Array.isArray(descriptions) || descriptions.length === 0) {
648
+ error('descriptions array required for phase add-batch');
649
+ }
650
+ const config = loadConfig(cwd);
651
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
652
+ if (!node_fs_1.default.existsSync(roadmapPath)) {
653
+ error('ROADMAP.md not found');
654
+ }
655
+ const projectCode = config.project_code || '';
656
+ const prefix = projectCode ? `${projectCode}-` : '';
657
+ const results = withPlanningLock(cwd, () => {
658
+ let rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
659
+ const content = extractCurrentMilestone(rawContent, cwd);
660
+ let maxPhase = 0;
661
+ if (config.phase_naming !== 'custom') {
662
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
663
+ let m;
664
+ while ((m = phasePattern.exec(content)) !== null) {
665
+ const num = parseInt(m[1], 10);
666
+ if (num === 999)
667
+ continue;
668
+ if (num > maxPhase)
669
+ maxPhase = num;
670
+ }
671
+ const phasesOnDisk = node_path_1.default.join(planningDir(cwd), 'phases');
672
+ if (node_fs_1.default.existsSync(phasesOnDisk)) {
673
+ const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
674
+ for (const entry of node_fs_1.default.readdirSync(phasesOnDisk)) {
675
+ const match = entry.match(dirNumPattern);
676
+ if (!match)
677
+ continue;
678
+ const num = parseInt(match[1], 10);
679
+ if (num === 999)
680
+ continue;
681
+ if (num > maxPhase)
682
+ maxPhase = num;
683
+ }
684
+ }
685
+ }
686
+ const added = [];
687
+ for (const description of descriptions) {
688
+ const slug = generateSlugInternal(description) || '';
689
+ let newPhaseId;
690
+ let dirName;
691
+ if (config.phase_naming === 'custom') {
692
+ newPhaseId = slug.toUpperCase();
693
+ dirName = `${prefix}${newPhaseId}-${slug}`;
694
+ }
695
+ else {
696
+ maxPhase += 1;
697
+ newPhaseId = maxPhase;
698
+ dirName = `${prefix}${String(newPhaseId).padStart(2, '0')}-${slug}`;
699
+ }
700
+ const dirPath = node_path_1.default.join(planningDir(cwd), 'phases', dirName);
701
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
702
+ (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(dirPath, '.gitkeep'), '');
703
+ const dependsOn = config.phase_naming === 'custom'
704
+ ? ''
705
+ : `\n**Depends on:** Phase ${typeof newPhaseId === 'number' ? newPhaseId - 1 : 'TBD'}`;
706
+ const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${newPhaseId} to break down)\n`;
707
+ const lastSeparator = rawContent.lastIndexOf('\n---');
708
+ rawContent =
709
+ lastSeparator > 0
710
+ ? rawContent.slice(0, lastSeparator) + phaseEntry + rawContent.slice(lastSeparator)
711
+ : rawContent + phaseEntry;
712
+ added.push({
713
+ phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
714
+ padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
715
+ name: description,
716
+ slug,
717
+ directory: toPosixPath(node_path_1.default.join(node_path_1.default.relative(cwd, planningDir(cwd)), 'phases', dirName)),
718
+ naming_mode: config.phase_naming,
719
+ });
720
+ }
721
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, rawContent);
722
+ return added;
723
+ });
724
+ output({ phases: results, count: results.length }, raw);
725
+ }
726
+ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
727
+ if (!afterPhase || !description) {
728
+ error('after-phase and description required for phase insert');
729
+ }
730
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
731
+ if (!node_fs_1.default.existsSync(roadmapPath)) {
732
+ error('ROADMAP.md not found');
733
+ }
734
+ const slug = generateSlugInternal(description) || '';
735
+ const { decimalPhase, dirName } = withPlanningLock(cwd, () => {
736
+ const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
737
+ const content = extractCurrentMilestone(rawContent, cwd);
738
+ const normalizedAfter = normalizePhaseName(afterPhase);
739
+ const afterPhaseEscaped = phaseMarkdownRegexSource(normalizedAfter);
740
+ const targetPattern = new RegExp(`#{2,4}\\s*Phase\\s+${afterPhaseEscaped}:`, 'i');
741
+ const headingMatch = targetPattern.test(content);
742
+ const bulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${afterPhaseEscaped}[:\\s]`, 'i');
743
+ const anyHeadingPattern = /#{2,4}\s*Phase\s+\d/i;
744
+ const roadmapHasHeadingPhases = anyHeadingPattern.test(content);
745
+ const isBulletStyle = !headingMatch && bulletPattern.test(content) && !roadmapHasHeadingPhases;
746
+ if (!headingMatch && !isBulletStyle) {
747
+ const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${afterPhaseEscaped}[:\\s]`, 'i');
748
+ if (checklistPattern.test(content)) {
749
+ error(`Phase ${afterPhase} exists in roadmap summary but is missing a detail section (### Phase ${afterPhase}: ...).`);
750
+ }
751
+ error(`Phase ${afterPhase} not found in ROADMAP.md`);
752
+ }
753
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
754
+ const normalizedBase = normalizePhaseName(afterPhase);
755
+ const decimalSet = new Set();
756
+ try {
757
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
758
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
759
+ const decimalPattern = new RegExp(`^(?:[A-Z]{1,6}-)?${escapeRegex(normalizedBase)}\\.(\\d+)`);
760
+ for (const dir of dirs) {
761
+ const dm = dir.match(decimalPattern);
762
+ if (dm)
763
+ decimalSet.add(parseInt(dm[1], 10));
764
+ }
765
+ }
766
+ catch {
767
+ /* intentionally empty */
768
+ }
769
+ const rmPhasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${phaseMarkdownRegexSource(normalizedBase)}\\.(\\d+)\\s*:`, 'gi');
770
+ let rmMatch;
771
+ while ((rmMatch = rmPhasePattern.exec(rawContent)) !== null) {
772
+ decimalSet.add(parseInt(rmMatch[1], 10));
773
+ }
774
+ const nextDecimal = decimalSet.size === 0 ? 1 : Math.max(...decimalSet) + 1;
775
+ const _decimalPhase = `${normalizedBase}.${nextDecimal}`;
776
+ const insertConfig = loadConfig(cwd);
777
+ const projectCode = insertConfig.project_code || '';
778
+ const pfx = projectCode ? `${projectCode}-` : '';
779
+ const _dirName = `${pfx}${_decimalPhase}-${slug}`;
780
+ const dirPath = node_path_1.default.join(planningDir(cwd), 'phases', _dirName);
781
+ (0, shell_command_projection_cjs_1.platformEnsureDir)(dirPath);
782
+ (0, shell_command_projection_cjs_1.platformWriteSync)(node_path_1.default.join(dirPath, '.gitkeep'), '');
783
+ let updatedContent;
784
+ if (isBulletStyle) {
785
+ const boldBulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${afterPhaseEscaped}:`, 'i');
786
+ const useBold = boldBulletPattern.test(content);
787
+ const phaseLabel = useBold
788
+ ? `**Phase ${_decimalPhase}: ${description}**`
789
+ : `Phase ${_decimalPhase}: ${description}`;
790
+ const bulletEntry = `\n- [ ] ${phaseLabel}`;
791
+ const targetBulletPattern = new RegExp(`(-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${afterPhaseEscaped}[:\\s][^\\n]*)`, 'i');
792
+ const bulletMatchResult = rawContent.match(targetBulletPattern);
793
+ if (!bulletMatchResult) {
794
+ error(`Could not find Phase ${afterPhase} bullet line`);
795
+ }
796
+ const bulletLineEnd = rawContent.indexOf(bulletMatchResult[0]) + bulletMatchResult[0].length;
797
+ const afterBullet = rawContent.slice(bulletLineEnd);
798
+ const nextBulletMatch = afterBullet.match(/\n-\s*\[[ x]\]\s*(?:\*\*)?Phase\s+\d/i);
799
+ let insertIdx;
800
+ if (nextBulletMatch) {
801
+ insertIdx = bulletLineEnd + nextBulletMatch.index;
802
+ }
803
+ else {
804
+ insertIdx = bulletLineEnd;
805
+ }
806
+ updatedContent =
807
+ rawContent.slice(0, insertIdx) + bulletEntry + rawContent.slice(insertIdx);
808
+ }
809
+ else {
810
+ const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${_decimalPhase} to break down)\n`;
811
+ const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
812
+ const headerMatch = rawContent.match(headerPattern);
813
+ if (!headerMatch) {
814
+ error(`Could not find Phase ${afterPhase} header`);
815
+ }
816
+ const headerIdx = rawContent.indexOf(headerMatch[0]);
817
+ const afterHeader = rawContent.slice(headerIdx + headerMatch[0].length);
818
+ const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d[\d.]*/i);
819
+ let insertIdx;
820
+ if (nextPhaseMatch) {
821
+ insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
822
+ }
823
+ else {
824
+ insertIdx = rawContent.length;
825
+ }
826
+ updatedContent =
827
+ rawContent.slice(0, insertIdx) + phaseEntry + rawContent.slice(insertIdx);
828
+ }
829
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, updatedContent);
830
+ return { decimalPhase: _decimalPhase, dirName: _dirName };
831
+ });
832
+ const result = {
833
+ phase_number: decimalPhase,
834
+ after_phase: afterPhase,
835
+ name: description,
836
+ slug,
837
+ directory: toPosixPath(node_path_1.default.join(node_path_1.default.relative(cwd, planningDir(cwd)), 'phases', dirName)),
838
+ };
839
+ output(result, raw, decimalPhase);
840
+ }
841
+ function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
842
+ const renamedDirs = [];
843
+ const renamedFiles = [];
844
+ const decPattern = new RegExp(`^(0*${baseInt})\\.(\\d+)-(.+)$`);
845
+ const dirs = readSubdirectories(phasesDir, true);
846
+ const toRename = dirs
847
+ .map((dir) => {
848
+ const m = dir.match(decPattern);
849
+ return m
850
+ ? { dir, prefix: m[1], oldDecimal: parseInt(m[2], 10), slug: m[3] }
851
+ : null;
852
+ })
853
+ .filter((item) => item !== null && item.oldDecimal > removedDecimal)
854
+ .sort((a, b) => b.oldDecimal - a.oldDecimal);
855
+ for (const item of toRename) {
856
+ const newDecimal = item.oldDecimal - 1;
857
+ const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
858
+ const newPhaseId = `${baseInt}.${newDecimal}`;
859
+ const newDirName = `${item.prefix}.${newDecimal}-${item.slug}`;
860
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, item.dir), node_path_1.default.join(phasesDir, newDirName));
861
+ renamedDirs.push({ from: item.dir, to: newDirName });
862
+ for (const f of node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, newDirName))) {
863
+ if (f.includes(oldPhaseId)) {
864
+ const newFileName = f.replace(oldPhaseId, newPhaseId);
865
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, newDirName, f), node_path_1.default.join(phasesDir, newDirName, newFileName));
866
+ renamedFiles.push({ from: f, to: newFileName });
867
+ }
868
+ }
869
+ }
870
+ return { renamedDirs, renamedFiles };
871
+ }
872
+ function renameIntegerPhases(phasesDir, removedInt) {
873
+ const renamedDirs = [];
874
+ const renamedFiles = [];
875
+ const dirs = readSubdirectories(phasesDir, true);
876
+ const toRename = dirs
877
+ .map((dir) => {
878
+ const m = dir.match(/^(\d+)([A-Z])?(?:\.(\d+))?-(.+)$/i);
879
+ if (!m)
880
+ return null;
881
+ const dirInt = parseInt(m[1], 10);
882
+ return dirInt > removedInt && dirInt !== 999
883
+ ? {
884
+ dir,
885
+ oldInt: dirInt,
886
+ letter: m[2] ? m[2].toUpperCase() : '',
887
+ decimal: m[3] ? parseInt(m[3], 10) : null,
888
+ slug: m[4],
889
+ }
890
+ : null;
891
+ })
892
+ .filter((item) => item !== null)
893
+ .sort((a, b) => a.oldInt !== b.oldInt ? b.oldInt - a.oldInt : (b.decimal || 0) - (a.decimal || 0));
894
+ for (const item of toRename) {
895
+ const newInt = item.oldInt - 1;
896
+ const newPadded = String(newInt).padStart(2, '0');
897
+ const oldPadded = String(item.oldInt).padStart(2, '0');
898
+ const letterSuffix = item.letter || '';
899
+ const decimalSuffix = item.decimal !== null ? `.${item.decimal}` : '';
900
+ const oldPrefix = `${oldPadded}${letterSuffix}${decimalSuffix}`;
901
+ const newPrefix = `${newPadded}${letterSuffix}${decimalSuffix}`;
902
+ const newDirName = `${newPrefix}-${item.slug}`;
903
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, item.dir), node_path_1.default.join(phasesDir, newDirName));
904
+ renamedDirs.push({ from: item.dir, to: newDirName });
905
+ for (const f of node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, newDirName))) {
906
+ if (f.startsWith(oldPrefix)) {
907
+ const newFileName = newPrefix + f.slice(oldPrefix.length);
908
+ node_fs_1.default.renameSync(node_path_1.default.join(phasesDir, newDirName, f), node_path_1.default.join(phasesDir, newDirName, newFileName));
909
+ renamedFiles.push({ from: f, to: newFileName });
910
+ }
911
+ }
912
+ }
913
+ return { renamedDirs, renamedFiles };
914
+ }
915
+ function decrementRoadmapPhaseNumber(raw, removedInt) {
916
+ const num = parseInt(raw, 10);
917
+ if (!Number.isInteger(num) || num <= removedInt || num === 999)
918
+ return raw;
919
+ return String(num - 1);
920
+ }
921
+ function decrementRoadmapPhaseToken(raw, removedInt) {
922
+ const match = String(raw).match(/^(\d+)(\.\d+)?$/);
923
+ if (!match)
924
+ return raw;
925
+ const num = parseInt(match[1], 10);
926
+ if (!Number.isInteger(num) || num <= removedInt || num === 999)
927
+ return raw;
928
+ return `${num - 1}${match[2] || ''}`;
929
+ }
930
+ function decrementRoadmapPaddedPhaseNumber(raw, removedInt) {
931
+ const num = parseInt(raw, 10);
932
+ if (!Number.isInteger(num) || num <= removedInt || num === 999)
933
+ return raw;
934
+ return String(num - 1).padStart(raw.length, '0');
935
+ }
936
+ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, removedInt, cwd) {
937
+ withPlanningLock(cwd, () => {
938
+ let content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
939
+ const escaped = escapeRegex(targetPhase);
940
+ content = content.replace(new RegExp(`\\n?(?<h>#{2,4})\\s*Phase\\s+${escaped}\\s*:[\\s\\S]*?(?=\\n\\k<h>(?!#)\\s+Phase\\s+[^\\n:]+\\s*:|$)`, 'i'), '');
941
+ content = content.replace(new RegExp(`\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${escaped}[:\\s][^\\n]*`, 'gi'), '');
942
+ content = content.replace(new RegExp(`\\n?\\|\\s*${escaped}\\.?\\s[^|]*\\|[^\\n]*`, 'gi'), '');
943
+ if (!isDecimal) {
944
+ content = content.replace(/(#{2,4}\s*Phase\s+)(\d+(?:\.\d+)?)(\s*:)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}${suffix}`);
945
+ content = content.replace(/(-\s*\[[ x]\]\s*.*?Phase\s+)(\d+)(\s*:|\s+)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
946
+ content = content.replace(/(\|\s*)(\d+)(\.\s)/g, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
947
+ content = content.replace(/(?<![0-9-])(\d{2})-(\d{2})(?=(?:(?:-[A-Za-z][A-Za-z0-9-]*)?-(?:PLAN|SUMMARY)\.md)|(?![0-9-]))/g, (_match, phaseNum, planNum) => `${decrementRoadmapPaddedPhaseNumber(phaseNum, removedInt)}-${planNum}`);
948
+ content = content.replace(/(\*\*Depends on\*\*\s*:\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
949
+ content = content.replace(/(Depends on:\*\*\s*Phase\s+)(\d+(?:\.\d+)?)\b/gi, (_match, prefix, num) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}`);
950
+ }
951
+ (0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, content);
952
+ });
953
+ }
954
+ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
955
+ if (!targetPhase)
956
+ error('phase number required for phase remove');
957
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
958
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
959
+ if (!node_fs_1.default.existsSync(roadmapPath))
960
+ error('ROADMAP.md not found');
961
+ const normalized = normalizePhaseName(targetPhase);
962
+ const isDecimal = targetPhase.includes('.');
963
+ const force = options.force || false;
964
+ const subdirs = readSubdirectories(phasesDir, true);
965
+ const targetDir = subdirs.find((d) => phaseTokenMatches(d, normalized)) || null;
966
+ if (targetDir && !force) {
967
+ const files = node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, targetDir));
968
+ const summaries = files.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
969
+ if (summaries.length > 0) {
970
+ error(`Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`);
971
+ }
972
+ }
973
+ if (targetDir)
974
+ node_fs_1.default.rmSync(node_path_1.default.join(phasesDir, targetDir), { recursive: true, force: true });
975
+ let renamedDirs = [];
976
+ let renamedFiles = [];
977
+ try {
978
+ const renamed = isDecimal
979
+ ? renameDecimalPhases(phasesDir, parseInt(normalized.split('.')[0], 10), parseInt(normalized.split('.')[1], 10))
980
+ : renameIntegerPhases(phasesDir, parseInt(normalized, 10));
981
+ renamedDirs = renamed.renamedDirs;
982
+ renamedFiles = renamed.renamedFiles;
983
+ }
984
+ catch {
985
+ /* intentionally empty */
986
+ }
987
+ updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, parseInt(normalized, 10), cwd);
988
+ const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
989
+ if (node_fs_1.default.existsSync(statePath)) {
990
+ readModifyWriteStateMd(statePath, (stateContent) => {
991
+ const totalRaw = stateExtractField(stateContent, 'Total Phases');
992
+ if (totalRaw) {
993
+ stateContent =
994
+ stateReplaceField(stateContent, 'Total Phases', String(parseInt(totalRaw, 10) - 1)) ||
995
+ stateContent;
996
+ }
997
+ const ofMatch = stateContent.match(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i);
998
+ if (ofMatch) {
999
+ stateContent = stateContent.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i, `$1${parseInt(ofMatch[2], 10) - 1}$3`);
1000
+ }
1001
+ return stateContent;
1002
+ }, cwd);
1003
+ }
1004
+ output({
1005
+ removed: targetPhase,
1006
+ directory_deleted: targetDir,
1007
+ renamed_directories: renamedDirs,
1008
+ renamed_files: renamedFiles,
1009
+ roadmap_updated: true,
1010
+ state_updated: node_fs_1.default.existsSync(statePath),
1011
+ }, raw);
1012
+ }
1013
+ function writePlanningFileSet(writes) {
1014
+ const applied = [];
1015
+ try {
1016
+ for (const write of writes) {
1017
+ if (write.before === write.after)
1018
+ continue;
1019
+ (0, shell_command_projection_cjs_1.platformWriteSync)(write.filePath, write.after);
1020
+ applied.push(write);
1021
+ }
1022
+ }
1023
+ catch (err) {
1024
+ for (const write of applied.reverse()) {
1025
+ try {
1026
+ (0, shell_command_projection_cjs_1.platformWriteSync)(write.filePath, write.before);
1027
+ }
1028
+ catch (rollbackErr) {
1029
+ const errObj = err;
1030
+ errObj.rollbackError = rollbackErr;
1031
+ const rollbackMsg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
1032
+ errObj.message +=
1033
+ `\nWARNING: rollback failed while restoring ${write.filePath} ` +
1034
+ `(${rollbackMsg}). Planning files under .planning/ may be left in an ` +
1035
+ `inconsistent, partially rolled back state. Inspect ROADMAP.md / REQUIREMENTS.md / ` +
1036
+ `STATE.md before re-running phase complete.`;
1037
+ break;
1038
+ }
1039
+ }
1040
+ throw err;
1041
+ }
1042
+ }
1043
+ function phaseDisplayNameFromRoadmap(roadmapContent, phaseNum) {
1044
+ if (!roadmapContent || !phaseNum)
1045
+ return null;
1046
+ const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
1047
+ const heading = roadmapContent.match(new RegExp(`^#{2,4}\\s*Phase\\s+${phaseEscaped}\\s*:\\s*([^\\n]+)`, 'im'));
1048
+ if (!heading)
1049
+ return null;
1050
+ const name = heading[1].replace(/\(INSERTED\)/i, '').trim();
1051
+ return name || null;
1052
+ }
1053
+ function phaseDisplayNameFromSlug(slug) {
1054
+ if (!slug)
1055
+ return null;
1056
+ const name = slug.replace(/-/g, ' ').trim();
1057
+ return name || null;
1058
+ }
1059
+ function cmdPhaseComplete(cwd, phaseNum, raw) {
1060
+ if (!phaseNum) {
1061
+ error('phase number required for phase complete');
1062
+ }
1063
+ const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
1064
+ const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
1065
+ const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
1066
+ const today = clock_cjs_1.realClock.today();
1067
+ const phaseInfoRaw = findPhaseInternal(cwd, phaseNum);
1068
+ if (!phaseInfoRaw) {
1069
+ error(`Phase ${phaseNum} not found`);
1070
+ }
1071
+ const phaseInfo = phaseInfoRaw;
1072
+ const planCount = phaseInfo['plans']
1073
+ ? phaseInfo['plans'].length
1074
+ : 0;
1075
+ const summaryCount = phaseInfo['summaries']
1076
+ ? phaseInfo['summaries'].length
1077
+ : 0;
1078
+ let requirementsUpdated = false;
1079
+ const warnings = [];
1080
+ try {
1081
+ const phaseFullDir = node_path_1.default.join(cwd, phaseInfo['directory']);
1082
+ const phaseFiles = node_fs_1.default.readdirSync(phaseFullDir);
1083
+ for (const file of phaseFiles.filter((f) => f.includes('-UAT') && f.endsWith('.md'))) {
1084
+ const content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseFullDir, file), 'utf-8');
1085
+ if (/result: pending/.test(content))
1086
+ warnings.push(`${file}: has pending tests`);
1087
+ if (/result: blocked/.test(content))
1088
+ warnings.push(`${file}: has blocked tests`);
1089
+ if (/status: partial/.test(content))
1090
+ warnings.push(`${file}: testing incomplete (partial)`);
1091
+ if (/status: diagnosed/.test(content))
1092
+ warnings.push(`${file}: has diagnosed gaps`);
1093
+ }
1094
+ for (const file of phaseFiles.filter((f) => f.includes('-VERIFICATION') && f.endsWith('.md'))) {
1095
+ const content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseFullDir, file), 'utf-8');
1096
+ // #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false positives
1097
+ // from historical metadata in the file body (e.g. `previous_status: gaps_found`).
1098
+ // A full-text regex like /status: gaps_found/ matches the substring inside
1099
+ // `previous_status: gaps_found`, producing spurious warnings even when the
1100
+ // current frontmatter status is `passed`.
1101
+ const verFm = extractFrontmatter(content);
1102
+ // Normalise to lower-case so `status: Passed` (title-case) is not missed.
1103
+ const verStatus = typeof verFm['status'] === 'string' ? verFm['status'].trim().toLowerCase() : '';
1104
+ if (verStatus === 'human_needed')
1105
+ warnings.push(`${file}: needs human verification`);
1106
+ if (verStatus === 'gaps_found')
1107
+ warnings.push(`${file}: has unresolved gaps`);
1108
+ }
1109
+ }
1110
+ catch {
1111
+ /* intentionally empty */
1112
+ }
1113
+ let nextPhaseNum = null;
1114
+ let nextPhaseName = null;
1115
+ let isLastPhase = true;
1116
+ withPlanningLock(cwd, () => {
1117
+ const runPhaseCompleteTransaction = () => {
1118
+ const writes = [];
1119
+ let roadmapContent = null;
1120
+ if (node_fs_1.default.existsSync(roadmapPath)) {
1121
+ const originalRoadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
1122
+ roadmapContent = originalRoadmapContent;
1123
+ const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
1124
+ const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`, 'i');
1125
+ roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
1126
+ const tableRowPattern = new RegExp(`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`, 'im');
1127
+ roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
1128
+ const cells = fullRow.split('|').slice(1, -1);
1129
+ const dateShape = /^\d{4}-\d{2}-\d{2}$/;
1130
+ if (cells.length === 5) {
1131
+ cells[2] = ` ${summaryCount}/${planCount} `;
1132
+ cells[3] = ' Complete ';
1133
+ // Preserve only a valid ISO date (#1161: idempotent; self-heal garbage)
1134
+ const existingDate5 = cells[4].trim();
1135
+ cells[4] = dateShape.test(existingDate5) ? cells[4] : ` ${today} `;
1136
+ }
1137
+ else if (cells.length === 4) {
1138
+ cells[1] = ` ${summaryCount}/${planCount} `;
1139
+ cells[2] = ' Complete ';
1140
+ // Preserve only a valid ISO date (#1161: idempotent; self-heal garbage)
1141
+ const existingDate4 = cells[3].trim();
1142
+ cells[3] = dateShape.test(existingDate4) ? cells[3] : ` ${today} `;
1143
+ }
1144
+ return '|' + cells.join('|') + '|';
1145
+ });
1146
+ const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`, 'i');
1147
+ roadmapContent = roadmapContent.replace(planCountPattern, `$1${summaryCount}/${planCount} plans complete`);
1148
+ const phaseInfoSummaries = phaseInfo['summaries'];
1149
+ for (const summaryFile of phaseInfoSummaries) {
1150
+ const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
1151
+ if (!planId)
1152
+ continue;
1153
+ const planEscaped = escapeRegex(planId);
1154
+ const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
1155
+ roadmapContent = (roadmapContent).replace(planCheckboxPattern, '$1x$2');
1156
+ }
1157
+ writes.push({
1158
+ filePath: roadmapPath,
1159
+ before: originalRoadmapContent,
1160
+ after: roadmapContent,
1161
+ });
1162
+ const reqPath = node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md');
1163
+ if (node_fs_1.default.existsSync(reqPath)) {
1164
+ const phaseEsc = phaseMarkdownRegexSource(phaseNum);
1165
+ const currentMilestoneRoadmap = extractCurrentMilestone(roadmapContent, cwd);
1166
+ const phaseSectionMatch = currentMilestoneRoadmap.match(new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEsc}[:\\s][\\s\\S]*?)(?=#{2,4}\\s*Phase\\s+|$)`, 'i'));
1167
+ const sectionText = phaseSectionMatch ? phaseSectionMatch[1] : '';
1168
+ const reqMatch = sectionText.match(/\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]+)/i);
1169
+ const originalReqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
1170
+ let reqContent = originalReqContent;
1171
+ if (reqMatch) {
1172
+ const reqIds = reqMatch[1]
1173
+ .replace(/[\[\]]/g, '')
1174
+ .split(/[,\s]+/)
1175
+ .map((r) => r.trim())
1176
+ .filter(Boolean);
1177
+ for (const reqId of reqIds) {
1178
+ const reqEscaped = escapeRegex(reqId);
1179
+ reqContent = reqContent.replace(new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi'), '$1x$2');
1180
+ reqContent = reqContent.replace(new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*(?:Pending|In Progress)\\s*(\\|)`, 'gi'), '$1 Complete $2');
1181
+ }
1182
+ }
1183
+ // #1159 (Defect B): collect requirement IDs only from ACTIVE sections.
1184
+ // Requirements under headings whose text contains "deferred", "backlog",
1185
+ // "future", or "v2" (case-insensitive) are explicitly out of current scope
1186
+ // and must not be flagged as missing from the Traceability table.
1187
+ //
1188
+ // Strategy: walk lines, track heading depth, and toggle a "deferred" flag
1189
+ // when a heading matching the pattern is encountered. A sub-heading (higher
1190
+ // depth) that is ITSELF in a deferred parent remains deferred unless it
1191
+ // opens a same-or-shallower heading that does NOT match the pattern.
1192
+ // Lines inside fenced code blocks (``` or ~~~) are treated as content, not
1193
+ // headings, to avoid false deferred-section detection from code examples.
1194
+ const DEFERRED_HEADING_RE = /\b(?:deferred|backlog|future|v\d+)\b/i;
1195
+ const bodyReqIds = [];
1196
+ // deferredDepth: the heading level that opened the current deferred block,
1197
+ // or 0 when we are in an active section.
1198
+ let deferredDepth = 0;
1199
+ let inFence = false;
1200
+ for (const line of reqContent.split(/\r?\n/)) {
1201
+ // Track fenced code blocks (``` or ~~~).
1202
+ if (/^\s*(?:```|~~~)/.test(line)) {
1203
+ inFence = !inFence;
1204
+ continue;
1205
+ }
1206
+ if (inFence)
1207
+ continue; // ignore content inside a code fence
1208
+ const headingM = line.match(/^(#{1,6})\s+(.*)/);
1209
+ if (headingM) {
1210
+ const depth = headingM[1].length;
1211
+ const text = headingM[2];
1212
+ if (deferredDepth > 0 && depth > deferredDepth) {
1213
+ // Sub-heading inside a deferred block: stays deferred regardless of name.
1214
+ continue;
1215
+ }
1216
+ // Heading at same level or shallower than current deferred opener,
1217
+ // or no active deferred block yet.
1218
+ if (DEFERRED_HEADING_RE.test(text)) {
1219
+ deferredDepth = depth; // enter a deferred block
1220
+ }
1221
+ else {
1222
+ deferredDepth = 0; // back in an active section
1223
+ }
1224
+ continue;
1225
+ }
1226
+ if (deferredDepth > 0)
1227
+ continue; // skip content in deferred sections
1228
+ // Collect bold REQ-ID patterns from active-section lines.
1229
+ const reqPat = /\*\*([A-Z][A-Z0-9]*-\d+)\*\*/g;
1230
+ let bodyMatch;
1231
+ while ((bodyMatch = reqPat.exec(line)) !== null) {
1232
+ const id = bodyMatch[1];
1233
+ if (!bodyReqIds.includes(id))
1234
+ bodyReqIds.push(id);
1235
+ }
1236
+ }
1237
+ const traceabilityHeadingMatch = reqContent.match(/^#{1,6}\s+Traceability\b/im);
1238
+ const traceabilitySection = traceabilityHeadingMatch
1239
+ ? reqContent.slice(traceabilityHeadingMatch.index)
1240
+ : '';
1241
+ const tableReqIds = new Set();
1242
+ const tableRowPat = /^\|\s*([A-Z][A-Z0-9]*-\d+)\s*\|/gm;
1243
+ let tableMatch;
1244
+ while ((tableMatch = tableRowPat.exec(traceabilitySection)) !== null) {
1245
+ tableReqIds.add(tableMatch[1]);
1246
+ }
1247
+ const unregistered = bodyReqIds.filter((id) => !tableReqIds.has(id));
1248
+ if (unregistered.length > 0) {
1249
+ warnings.push(`REQUIREMENTS.md: ${unregistered.length} REQ-ID(s) found in body but missing from Traceability table: ${unregistered.join(', ')} — add them manually to keep traceability in sync`);
1250
+ }
1251
+ writes.push({ filePath: reqPath, before: originalReqContent, after: reqContent });
1252
+ requirementsUpdated = true;
1253
+ }
1254
+ }
1255
+ try {
1256
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
1257
+ const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
1258
+ const dirs = entries
1259
+ .filter((e) => e.isDirectory())
1260
+ .map((e) => e.name)
1261
+ .filter(isDirInMilestone)
1262
+ .sort((a, b) => comparePhaseNum(a, b));
1263
+ for (const dir of dirs) {
1264
+ const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);
1265
+ if (dm) {
1266
+ if (/^999(?:\.|$)/.test(dm[1]))
1267
+ continue;
1268
+ if (comparePhaseNum(dm[1], phaseNum) > 0) {
1269
+ nextPhaseNum = dm[1];
1270
+ nextPhaseName = dm[2] || null;
1271
+ isLastPhase = false;
1272
+ break;
1273
+ }
1274
+ }
1275
+ }
1276
+ }
1277
+ catch {
1278
+ /* intentionally empty */
1279
+ }
1280
+ if (isLastPhase && roadmapContent !== null) {
1281
+ try {
1282
+ const roadmapForPhases = extractCurrentMilestone(roadmapContent, cwd);
1283
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
1284
+ let pm;
1285
+ while ((pm = phasePattern.exec(roadmapForPhases)) !== null) {
1286
+ if (comparePhaseNum(pm[1], phaseNum) > 0) {
1287
+ nextPhaseNum = pm[1];
1288
+ nextPhaseName = pm[2]
1289
+ .replace(/\(INSERTED\)/i, '')
1290
+ .trim()
1291
+ .toLowerCase()
1292
+ .replace(/\s+/g, '-');
1293
+ isLastPhase = false;
1294
+ break;
1295
+ }
1296
+ }
1297
+ }
1298
+ catch {
1299
+ /* intentionally empty */
1300
+ }
1301
+ }
1302
+ if (node_fs_1.default.existsSync(statePath)) {
1303
+ const originalStateContent = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
1304
+ let stateContent = originalStateContent;
1305
+ const phaseValue = nextPhaseNum || phaseNum;
1306
+ const nextPhaseDisplayName = phaseDisplayNameFromRoadmap(roadmapContent, nextPhaseNum) ??
1307
+ phaseDisplayNameFromSlug(nextPhaseName);
1308
+ const existingPhaseField = stateExtractField(stateContent, 'Current Phase') ||
1309
+ stateExtractField(stateContent, 'Phase');
1310
+ let newPhaseValue = String(phaseValue);
1311
+ if (existingPhaseField) {
1312
+ const totalMatch = existingPhaseField.match(/of\s+(\d+)/);
1313
+ const nameMatch = existingPhaseField.match(/\(([^)]+)\)/);
1314
+ if (totalMatch) {
1315
+ const total = totalMatch[1];
1316
+ const nameStr = nextPhaseDisplayName
1317
+ ? ` (${nextPhaseDisplayName})`
1318
+ : nameMatch
1319
+ ? ` (${nameMatch[1]})`
1320
+ : '';
1321
+ newPhaseValue = `${phaseValue} of ${total}${nameStr}`;
1322
+ }
1323
+ else if (nextPhaseDisplayName) {
1324
+ newPhaseValue = `${phaseValue} — ${nextPhaseDisplayName}`;
1325
+ }
1326
+ }
1327
+ stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Phase', 'Phase', newPhaseValue);
1328
+ if (nextPhaseDisplayName) {
1329
+ stateContent =
1330
+ stateReplaceField(stateContent, 'Current Phase Name', nextPhaseDisplayName) ||
1331
+ stateContent;
1332
+ }
1333
+ stateContent = stateReplaceFieldWithFallback(stateContent, 'Status', null, isLastPhase ? 'Milestone complete' : 'Ready to plan');
1334
+ stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Plan', 'Plan', 'Not started');
1335
+ const lastActivityDescription = `Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`;
1336
+ if (/^Last activity:/m.test(stateContent)) {
1337
+ stateContent =
1338
+ stateReplaceField(stateContent, 'Last activity', `${today} — ${lastActivityDescription}`) ||
1339
+ stateContent;
1340
+ }
1341
+ else {
1342
+ stateContent =
1343
+ stateReplaceField(stateContent, 'Last Activity', today) ||
1344
+ stateContent;
1345
+ }
1346
+ stateContent =
1347
+ stateReplaceField(stateContent, 'Last Activity Description', lastActivityDescription) ||
1348
+ stateContent;
1349
+ const completedRaw = stateExtractField(stateContent, 'Completed Phases');
1350
+ if (completedRaw !== null) {
1351
+ let newCompleted = parseInt(completedRaw, 10);
1352
+ let derivedTotalPhases = null;
1353
+ if (roadmapContent !== null) {
1354
+ const derived = (0, phase_lifecycle_cjs_1.deriveProgressFromRoadmap)(roadmapContent);
1355
+ if (derived.completedPhases !== null)
1356
+ newCompleted = derived.completedPhases;
1357
+ if (derived.totalPhases !== null)
1358
+ derivedTotalPhases = derived.totalPhases;
1359
+ }
1360
+ stateContent =
1361
+ stateReplaceField(stateContent, 'Completed Phases', String(newCompleted)) ||
1362
+ stateContent;
1363
+ const totalRaw = stateExtractField(stateContent, 'Total Phases');
1364
+ const totalPhases = derivedTotalPhases || (totalRaw ? parseInt(totalRaw, 10) : null);
1365
+ if (totalPhases && totalPhases > 0) {
1366
+ const newPercent = (0, phase_lifecycle_cjs_1.clampPercent)(newCompleted, totalPhases);
1367
+ stateContent =
1368
+ stateReplaceField(stateContent, 'Progress', `${newPercent}%`) || stateContent;
1369
+ stateContent = stateContent.replace(/(percent:\s*)\d+/, `$1${newPercent}`);
1370
+ }
1371
+ }
1372
+ stateContent = updatePerformanceMetricsSection(stateContent, cwd, phaseNum, planCount, summaryCount);
1373
+ stateContent = syncStateFrontmatter(stateContent, cwd);
1374
+ writes.push({ filePath: statePath, before: originalStateContent, after: stateContent });
1375
+ }
1376
+ writePlanningFileSet(writes);
1377
+ };
1378
+ if (node_fs_1.default.existsSync(statePath)) {
1379
+ withStateLock(statePath, runPhaseCompleteTransaction);
1380
+ }
1381
+ else {
1382
+ runPhaseCompleteTransaction();
1383
+ }
1384
+ });
1385
+ let autoPruned = false;
1386
+ try {
1387
+ const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
1388
+ if (node_fs_1.default.existsSync(configPath)) {
1389
+ const rawConfig = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
1390
+ const workflow = rawConfig['workflow'];
1391
+ const autoPruneEnabled = workflow && workflow['auto_prune_state'] === true;
1392
+ if (autoPruneEnabled && node_fs_1.default.existsSync(statePath)) {
1393
+ // Non-hoisted: load-order matters (stateMod must be fully resolved first).
1394
+ const { cmdStatePrune } = stateMod;
1395
+ cmdStatePrune(cwd, { keepRecent: '3', dryRun: false, silent: true }, true);
1396
+ autoPruned = true;
1397
+ }
1398
+ }
1399
+ }
1400
+ catch {
1401
+ /* intentionally empty — auto-prune is best-effort */
1402
+ }
1403
+ const result = {
1404
+ completed_phase: phaseNum,
1405
+ phase_name: phaseInfo['phase_name'],
1406
+ plans_executed: `${summaryCount}/${planCount}`,
1407
+ next_phase: nextPhaseNum,
1408
+ next_phase_name: nextPhaseName,
1409
+ is_last_phase: isLastPhase,
1410
+ date: today,
1411
+ roadmap_updated: node_fs_1.default.existsSync(roadmapPath),
1412
+ state_updated: node_fs_1.default.existsSync(statePath),
1413
+ requirements_updated: requirementsUpdated,
1414
+ auto_pruned: autoPruned,
1415
+ warnings,
1416
+ has_warnings: warnings.length > 0,
1417
+ };
1418
+ output(result, raw);
1419
+ }
1420
+ function cmdPhaseUatPassed(cwd, phaseNum, raw, opts = {}) {
1421
+ if (!phaseNum) {
1422
+ error('phase number required for phase uat-passed');
1423
+ }
1424
+ const phaseInfoRaw = findPhaseInternal(cwd, phaseNum);
1425
+ if (!phaseInfoRaw) {
1426
+ error(`Phase ${phaseNum} not found`);
1427
+ }
1428
+ const phaseInfo = phaseInfoRaw;
1429
+ const phaseFullDir = node_path_1.default.join(cwd, phaseInfo['directory']);
1430
+ const report = evaluateUatPassed(phaseFullDir, { policy: opts.policy });
1431
+ output({ phase: phaseNum, ...report }, raw);
1432
+ }
1433
+ module.exports = {
1434
+ cmdPhasesList,
1435
+ cmdPhaseNextDecimal,
1436
+ cmdFindPhase,
1437
+ cmdPhasePlanIndex,
1438
+ cmdPhaseAdd,
1439
+ cmdPhaseAddBatch,
1440
+ cmdPhaseMvpMode,
1441
+ cmdPhaseInsert,
1442
+ cmdPhaseRemove,
1443
+ cmdPhaseComplete,
1444
+ cmdPhaseUatPassed,
1445
+ computeDependencyLevels,
1446
+ };