@jokerized/getresearchdone 0.4.1

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 (711) hide show
  1. package/.claude-plugin/plugin.json +103 -0
  2. package/README.md +211 -0
  3. package/agents/grd-baseline-assessor.md +684 -0
  4. package/agents/grd-code-reviewer.md +300 -0
  5. package/agents/grd-codebase-mapper.md +355 -0
  6. package/agents/grd-critique-agent.md +119 -0
  7. package/agents/grd-debugger.md +519 -0
  8. package/agents/grd-deep-diver.md +737 -0
  9. package/agents/grd-eval-planner.md +913 -0
  10. package/agents/grd-eval-reporter.md +717 -0
  11. package/agents/grd-executor.md +683 -0
  12. package/agents/grd-feasibility-analyst.md +624 -0
  13. package/agents/grd-integration-checker.md +367 -0
  14. package/agents/grd-knowledge-miner.md +81 -0
  15. package/agents/grd-migrator.md +88 -0
  16. package/agents/grd-phase-researcher.md +697 -0
  17. package/agents/grd-plan-checker.md +443 -0
  18. package/agents/grd-planner.md +1532 -0
  19. package/agents/grd-product-owner.md +562 -0
  20. package/agents/grd-project-researcher.md +513 -0
  21. package/agents/grd-research-synthesizer.md +273 -0
  22. package/agents/grd-roadmapper.md +798 -0
  23. package/agents/grd-surveyor.md +566 -0
  24. package/agents/grd-verifier.md +893 -0
  25. package/bin/gd.js +4 -0
  26. package/bin/gd.ts +227 -0
  27. package/bin/grd-manifest.js +4 -0
  28. package/bin/grd-manifest.ts +286 -0
  29. package/bin/grd-mcp-server.js +4 -0
  30. package/bin/grd-mcp-server.ts +124 -0
  31. package/bin/grd-tools.js +4 -0
  32. package/bin/grd-tools.ts +2471 -0
  33. package/bin/postinstall.js +4 -0
  34. package/bin/postinstall.ts +80 -0
  35. package/commands/add-phase.md +123 -0
  36. package/commands/add-todo.md +87 -0
  37. package/commands/assess-baseline.md +289 -0
  38. package/commands/autopilot.md +100 -0
  39. package/commands/autoplan.md +55 -0
  40. package/commands/check-todos.md +87 -0
  41. package/commands/compare-methods.md +262 -0
  42. package/commands/complete-milestone.md +225 -0
  43. package/commands/debug.md +372 -0
  44. package/commands/deep-dive.md +288 -0
  45. package/commands/discover.md +281 -0
  46. package/commands/discuss-phase.md +188 -0
  47. package/commands/discuss.md +55 -0
  48. package/commands/eval-report.md +310 -0
  49. package/commands/evolve.md +79 -0
  50. package/commands/execute-phase.md +1017 -0
  51. package/commands/feasibility.md +292 -0
  52. package/commands/help.md +407 -0
  53. package/commands/init.md +1508 -0
  54. package/commands/insert-phase.md +113 -0
  55. package/commands/iterate.md +327 -0
  56. package/commands/list-phase-assumptions.md +217 -0
  57. package/commands/long-term-roadmap.md +202 -0
  58. package/commands/map-codebase.md +111 -0
  59. package/commands/migrate.md +159 -0
  60. package/commands/new-milestone.md +169 -0
  61. package/commands/pause-work.md +83 -0
  62. package/commands/plan-milestone-gaps.md +373 -0
  63. package/commands/plan-phase.md +655 -0
  64. package/commands/principles.md +328 -0
  65. package/commands/product-plan.md +319 -0
  66. package/commands/progress.md +481 -0
  67. package/commands/quick.md +167 -0
  68. package/commands/reapply-patches.md +154 -0
  69. package/commands/remove-phase.md +97 -0
  70. package/commands/requirement.md +96 -0
  71. package/commands/resume-project.md +113 -0
  72. package/commands/settings.md +1144 -0
  73. package/commands/survey.md +242 -0
  74. package/commands/sync.md +246 -0
  75. package/commands/tracker-setup.md +322 -0
  76. package/commands/update.md +202 -0
  77. package/commands/verify-phase.md +335 -0
  78. package/commands/verify-work.md +701 -0
  79. package/commands/wireup.md +29 -0
  80. package/dist/bin/gd.d.ts +3 -0
  81. package/dist/bin/gd.d.ts.map +1 -0
  82. package/dist/bin/gd.js +178 -0
  83. package/dist/bin/gd.js.map +1 -0
  84. package/dist/bin/grd-manifest.d.ts +3 -0
  85. package/dist/bin/grd-manifest.d.ts.map +1 -0
  86. package/dist/bin/grd-manifest.js +202 -0
  87. package/dist/bin/grd-manifest.js.map +1 -0
  88. package/dist/bin/grd-mcp-server.d.ts +3 -0
  89. package/dist/bin/grd-mcp-server.d.ts.map +1 -0
  90. package/dist/bin/grd-mcp-server.js +71 -0
  91. package/dist/bin/grd-mcp-server.js.map +1 -0
  92. package/dist/bin/grd-tools.d.ts +3 -0
  93. package/dist/bin/grd-tools.d.ts.map +1 -0
  94. package/dist/bin/grd-tools.js +1680 -0
  95. package/dist/bin/grd-tools.js.map +1 -0
  96. package/dist/bin/postinstall.d.ts +3 -0
  97. package/dist/bin/postinstall.d.ts.map +1 -0
  98. package/dist/bin/postinstall.js +61 -0
  99. package/dist/bin/postinstall.js.map +1 -0
  100. package/dist/lib/autopilot-milestone.d.ts +2 -0
  101. package/dist/lib/autopilot-milestone.d.ts.map +1 -0
  102. package/dist/lib/autopilot-milestone.js +94 -0
  103. package/dist/lib/autopilot-milestone.js.map +1 -0
  104. package/dist/lib/autopilot-pipeline.d.ts +2 -0
  105. package/dist/lib/autopilot-pipeline.d.ts.map +1 -0
  106. package/dist/lib/autopilot-pipeline.js +830 -0
  107. package/dist/lib/autopilot-pipeline.js.map +1 -0
  108. package/dist/lib/autopilot-waves.d.ts +2 -0
  109. package/dist/lib/autopilot-waves.d.ts.map +1 -0
  110. package/dist/lib/autopilot-waves.js +266 -0
  111. package/dist/lib/autopilot-waves.js.map +1 -0
  112. package/dist/lib/autopilot.d.ts +2 -0
  113. package/dist/lib/autopilot.d.ts.map +1 -0
  114. package/dist/lib/autopilot.js +1314 -0
  115. package/dist/lib/autopilot.js.map +1 -0
  116. package/dist/lib/autoplan.d.ts +2 -0
  117. package/dist/lib/autoplan.d.ts.map +1 -0
  118. package/dist/lib/autoplan.js +198 -0
  119. package/dist/lib/autoplan.js.map +1 -0
  120. package/dist/lib/autoresearch.d.ts +2 -0
  121. package/dist/lib/autoresearch.d.ts.map +1 -0
  122. package/dist/lib/autoresearch.js +626 -0
  123. package/dist/lib/autoresearch.js.map +1 -0
  124. package/dist/lib/backend.d.ts +2 -0
  125. package/dist/lib/backend.d.ts.map +1 -0
  126. package/dist/lib/backend.js +1036 -0
  127. package/dist/lib/backend.js.map +1 -0
  128. package/dist/lib/benchmark.d.ts +99 -0
  129. package/dist/lib/benchmark.d.ts.map +1 -0
  130. package/dist/lib/benchmark.js +278 -0
  131. package/dist/lib/benchmark.js.map +1 -0
  132. package/dist/lib/citations.d.ts +2 -0
  133. package/dist/lib/citations.d.ts.map +1 -0
  134. package/dist/lib/citations.js +642 -0
  135. package/dist/lib/citations.js.map +1 -0
  136. package/dist/lib/cleanup.d.ts +2 -0
  137. package/dist/lib/cleanup.d.ts.map +1 -0
  138. package/dist/lib/cleanup.js +1222 -0
  139. package/dist/lib/cleanup.js.map +1 -0
  140. package/dist/lib/cli/adapters.d.ts +10 -0
  141. package/dist/lib/cli/adapters.d.ts.map +1 -0
  142. package/dist/lib/cli/adapters.js +27 -0
  143. package/dist/lib/cli/adapters.js.map +1 -0
  144. package/dist/lib/cli/agent.d.ts +17 -0
  145. package/dist/lib/cli/agent.d.ts.map +1 -0
  146. package/dist/lib/cli/agent.js +53 -0
  147. package/dist/lib/cli/agent.js.map +1 -0
  148. package/dist/lib/cli/index.d.ts +21 -0
  149. package/dist/lib/cli/index.d.ts.map +1 -0
  150. package/dist/lib/cli/index.js +264 -0
  151. package/dist/lib/cli/index.js.map +1 -0
  152. package/dist/lib/cli/output.d.ts +20 -0
  153. package/dist/lib/cli/output.d.ts.map +1 -0
  154. package/dist/lib/cli/output.js +22 -0
  155. package/dist/lib/cli/output.js.map +1 -0
  156. package/dist/lib/cli/scan-dispatch.d.ts +9 -0
  157. package/dist/lib/cli/scan-dispatch.d.ts.map +1 -0
  158. package/dist/lib/cli/scan-dispatch.js +107 -0
  159. package/dist/lib/cli/scan-dispatch.js.map +1 -0
  160. package/dist/lib/cli/tools.d.ts +16 -0
  161. package/dist/lib/cli/tools.d.ts.map +1 -0
  162. package/dist/lib/cli/tools.js +168 -0
  163. package/dist/lib/cli/tools.js.map +1 -0
  164. package/dist/lib/commands/_dashboard-parsers.d.ts +2 -0
  165. package/dist/lib/commands/_dashboard-parsers.d.ts.map +1 -0
  166. package/dist/lib/commands/_dashboard-parsers.js +192 -0
  167. package/dist/lib/commands/_dashboard-parsers.js.map +1 -0
  168. package/dist/lib/commands/analysis.d.ts +2 -0
  169. package/dist/lib/commands/analysis.d.ts.map +1 -0
  170. package/dist/lib/commands/analysis.js +1418 -0
  171. package/dist/lib/commands/analysis.js.map +1 -0
  172. package/dist/lib/commands/assumptions.d.ts +2 -0
  173. package/dist/lib/commands/assumptions.d.ts.map +1 -0
  174. package/dist/lib/commands/assumptions.js +166 -0
  175. package/dist/lib/commands/assumptions.js.map +1 -0
  176. package/dist/lib/commands/blame.d.ts +2 -0
  177. package/dist/lib/commands/blame.d.ts.map +1 -0
  178. package/dist/lib/commands/blame.js +133 -0
  179. package/dist/lib/commands/blame.js.map +1 -0
  180. package/dist/lib/commands/budget.d.ts +2 -0
  181. package/dist/lib/commands/budget.d.ts.map +1 -0
  182. package/dist/lib/commands/budget.js +100 -0
  183. package/dist/lib/commands/budget.js.map +1 -0
  184. package/dist/lib/commands/check-plans.d.ts +2 -0
  185. package/dist/lib/commands/check-plans.d.ts.map +1 -0
  186. package/dist/lib/commands/check-plans.js +190 -0
  187. package/dist/lib/commands/check-plans.js.map +1 -0
  188. package/dist/lib/commands/config.d.ts +2 -0
  189. package/dist/lib/commands/config.d.ts.map +1 -0
  190. package/dist/lib/commands/config.js +188 -0
  191. package/dist/lib/commands/config.js.map +1 -0
  192. package/dist/lib/commands/dashboard.d.ts +2 -0
  193. package/dist/lib/commands/dashboard.d.ts.map +1 -0
  194. package/dist/lib/commands/dashboard.js +466 -0
  195. package/dist/lib/commands/dashboard.js.map +1 -0
  196. package/dist/lib/commands/estimate.d.ts +2 -0
  197. package/dist/lib/commands/estimate.d.ts.map +1 -0
  198. package/dist/lib/commands/estimate.js +148 -0
  199. package/dist/lib/commands/estimate.js.map +1 -0
  200. package/dist/lib/commands/eval-diff.d.ts +2 -0
  201. package/dist/lib/commands/eval-diff.d.ts.map +1 -0
  202. package/dist/lib/commands/eval-diff.js +213 -0
  203. package/dist/lib/commands/eval-diff.js.map +1 -0
  204. package/dist/lib/commands/freshness.d.ts +2 -0
  205. package/dist/lib/commands/freshness.d.ts.map +1 -0
  206. package/dist/lib/commands/freshness.js +163 -0
  207. package/dist/lib/commands/freshness.js.map +1 -0
  208. package/dist/lib/commands/health.d.ts +2 -0
  209. package/dist/lib/commands/health.d.ts.map +1 -0
  210. package/dist/lib/commands/health.js +435 -0
  211. package/dist/lib/commands/health.js.map +1 -0
  212. package/dist/lib/commands/index.d.ts +2 -0
  213. package/dist/lib/commands/index.d.ts.map +1 -0
  214. package/dist/lib/commands/index.js +128 -0
  215. package/dist/lib/commands/index.js.map +1 -0
  216. package/dist/lib/commands/install.d.ts +56 -0
  217. package/dist/lib/commands/install.d.ts.map +1 -0
  218. package/dist/lib/commands/install.js +214 -0
  219. package/dist/lib/commands/install.js.map +1 -0
  220. package/dist/lib/commands/knowhow-aggregator.d.ts +2 -0
  221. package/dist/lib/commands/knowhow-aggregator.d.ts.map +1 -0
  222. package/dist/lib/commands/knowhow-aggregator.js +279 -0
  223. package/dist/lib/commands/knowhow-aggregator.js.map +1 -0
  224. package/dist/lib/commands/knowledge-search.d.ts +2 -0
  225. package/dist/lib/commands/knowledge-search.d.ts.map +1 -0
  226. package/dist/lib/commands/knowledge-search.js +113 -0
  227. package/dist/lib/commands/knowledge-search.js.map +1 -0
  228. package/dist/lib/commands/long-term-roadmap.d.ts +2 -0
  229. package/dist/lib/commands/long-term-roadmap.d.ts.map +1 -0
  230. package/dist/lib/commands/long-term-roadmap.js +272 -0
  231. package/dist/lib/commands/long-term-roadmap.js.map +1 -0
  232. package/dist/lib/commands/patterns.d.ts +91 -0
  233. package/dist/lib/commands/patterns.d.ts.map +1 -0
  234. package/dist/lib/commands/patterns.js +391 -0
  235. package/dist/lib/commands/patterns.js.map +1 -0
  236. package/dist/lib/commands/phase-info.d.ts +2 -0
  237. package/dist/lib/commands/phase-info.d.ts.map +1 -0
  238. package/dist/lib/commands/phase-info.js +509 -0
  239. package/dist/lib/commands/phase-info.js.map +1 -0
  240. package/dist/lib/commands/plan-lint.d.ts +56 -0
  241. package/dist/lib/commands/plan-lint.d.ts.map +1 -0
  242. package/dist/lib/commands/plan-lint.js +481 -0
  243. package/dist/lib/commands/plan-lint.js.map +1 -0
  244. package/dist/lib/commands/plan-phase.d.ts +53 -0
  245. package/dist/lib/commands/plan-phase.d.ts.map +1 -0
  246. package/dist/lib/commands/plan-phase.js +288 -0
  247. package/dist/lib/commands/plan-phase.js.map +1 -0
  248. package/dist/lib/commands/progress.d.ts +2 -0
  249. package/dist/lib/commands/progress.d.ts.map +1 -0
  250. package/dist/lib/commands/progress.js +266 -0
  251. package/dist/lib/commands/progress.js.map +1 -0
  252. package/dist/lib/commands/quality.d.ts +2 -0
  253. package/dist/lib/commands/quality.d.ts.map +1 -0
  254. package/dist/lib/commands/quality.js +80 -0
  255. package/dist/lib/commands/quality.js.map +1 -0
  256. package/dist/lib/commands/rollback.d.ts +2 -0
  257. package/dist/lib/commands/rollback.d.ts.map +1 -0
  258. package/dist/lib/commands/rollback.js +145 -0
  259. package/dist/lib/commands/rollback.js.map +1 -0
  260. package/dist/lib/commands/scan.d.ts +25 -0
  261. package/dist/lib/commands/scan.d.ts.map +1 -0
  262. package/dist/lib/commands/scan.js +28 -0
  263. package/dist/lib/commands/scan.js.map +1 -0
  264. package/dist/lib/commands/search.d.ts +2 -0
  265. package/dist/lib/commands/search.d.ts.map +1 -0
  266. package/dist/lib/commands/search.js +212 -0
  267. package/dist/lib/commands/search.js.map +1 -0
  268. package/dist/lib/commands/select-candidate.d.ts +128 -0
  269. package/dist/lib/commands/select-candidate.d.ts.map +1 -0
  270. package/dist/lib/commands/select-candidate.js +518 -0
  271. package/dist/lib/commands/select-candidate.js.map +1 -0
  272. package/dist/lib/commands/singularity.d.ts +2 -0
  273. package/dist/lib/commands/singularity.d.ts.map +1 -0
  274. package/dist/lib/commands/singularity.js +185 -0
  275. package/dist/lib/commands/singularity.js.map +1 -0
  276. package/dist/lib/commands/slug-timestamp.d.ts +2 -0
  277. package/dist/lib/commands/slug-timestamp.d.ts.map +1 -0
  278. package/dist/lib/commands/slug-timestamp.js +54 -0
  279. package/dist/lib/commands/slug-timestamp.js.map +1 -0
  280. package/dist/lib/commands/tail.d.ts +2 -0
  281. package/dist/lib/commands/tail.d.ts.map +1 -0
  282. package/dist/lib/commands/tail.js +100 -0
  283. package/dist/lib/commands/tail.js.map +1 -0
  284. package/dist/lib/commands/todo.d.ts +2 -0
  285. package/dist/lib/commands/todo.d.ts.map +1 -0
  286. package/dist/lib/commands/todo.js +200 -0
  287. package/dist/lib/commands/todo.js.map +1 -0
  288. package/dist/lib/commands/watch.d.ts +2 -0
  289. package/dist/lib/commands/watch.d.ts.map +1 -0
  290. package/dist/lib/commands/watch.js +72 -0
  291. package/dist/lib/commands/watch.js.map +1 -0
  292. package/dist/lib/complexity.d.ts +55 -0
  293. package/dist/lib/complexity.d.ts.map +1 -0
  294. package/dist/lib/complexity.js +80 -0
  295. package/dist/lib/complexity.js.map +1 -0
  296. package/dist/lib/context/agents.d.ts +2 -0
  297. package/dist/lib/context/agents.d.ts.map +1 -0
  298. package/dist/lib/context/agents.js +344 -0
  299. package/dist/lib/context/agents.js.map +1 -0
  300. package/dist/lib/context/base.d.ts +2 -0
  301. package/dist/lib/context/base.d.ts.map +1 -0
  302. package/dist/lib/context/base.js +81 -0
  303. package/dist/lib/context/base.js.map +1 -0
  304. package/dist/lib/context/execute.d.ts +2 -0
  305. package/dist/lib/context/execute.d.ts.map +1 -0
  306. package/dist/lib/context/execute.js +753 -0
  307. package/dist/lib/context/execute.js.map +1 -0
  308. package/dist/lib/context/index.d.ts +2 -0
  309. package/dist/lib/context/index.d.ts.map +1 -0
  310. package/dist/lib/context/index.js +88 -0
  311. package/dist/lib/context/index.js.map +1 -0
  312. package/dist/lib/context/progress.d.ts +2 -0
  313. package/dist/lib/context/progress.d.ts.map +1 -0
  314. package/dist/lib/context/progress.js +178 -0
  315. package/dist/lib/context/progress.js.map +1 -0
  316. package/dist/lib/context/project.d.ts +2 -0
  317. package/dist/lib/context/project.d.ts.map +1 -0
  318. package/dist/lib/context/project.js +413 -0
  319. package/dist/lib/context/project.js.map +1 -0
  320. package/dist/lib/context/research.d.ts +2 -0
  321. package/dist/lib/context/research.d.ts.map +1 -0
  322. package/dist/lib/context/research.js +466 -0
  323. package/dist/lib/context/research.js.map +1 -0
  324. package/dist/lib/dead-ends.d.ts +28 -0
  325. package/dist/lib/dead-ends.d.ts.map +1 -0
  326. package/dist/lib/dead-ends.js +451 -0
  327. package/dist/lib/dead-ends.js.map +1 -0
  328. package/dist/lib/deps.d.ts +2 -0
  329. package/dist/lib/deps.d.ts.map +1 -0
  330. package/dist/lib/deps.js +630 -0
  331. package/dist/lib/deps.js.map +1 -0
  332. package/dist/lib/discussion.d.ts +2 -0
  333. package/dist/lib/discussion.d.ts.map +1 -0
  334. package/dist/lib/discussion.js +1041 -0
  335. package/dist/lib/discussion.js.map +1 -0
  336. package/dist/lib/drift.d.ts +36 -0
  337. package/dist/lib/drift.d.ts.map +1 -0
  338. package/dist/lib/drift.js +481 -0
  339. package/dist/lib/drift.js.map +1 -0
  340. package/dist/lib/evolve/_dimensions-features.d.ts +2 -0
  341. package/dist/lib/evolve/_dimensions-features.d.ts.map +1 -0
  342. package/dist/lib/evolve/_dimensions-features.js +369 -0
  343. package/dist/lib/evolve/_dimensions-features.js.map +1 -0
  344. package/dist/lib/evolve/_dimensions.d.ts +2 -0
  345. package/dist/lib/evolve/_dimensions.d.ts.map +1 -0
  346. package/dist/lib/evolve/_dimensions.js +358 -0
  347. package/dist/lib/evolve/_dimensions.js.map +1 -0
  348. package/dist/lib/evolve/_product-ideation.d.ts +2 -0
  349. package/dist/lib/evolve/_product-ideation.d.ts.map +1 -0
  350. package/dist/lib/evolve/_product-ideation.js +281 -0
  351. package/dist/lib/evolve/_product-ideation.js.map +1 -0
  352. package/dist/lib/evolve/_prompts.d.ts +2 -0
  353. package/dist/lib/evolve/_prompts.d.ts.map +1 -0
  354. package/dist/lib/evolve/_prompts.js +153 -0
  355. package/dist/lib/evolve/_prompts.js.map +1 -0
  356. package/dist/lib/evolve/cli.d.ts +2 -0
  357. package/dist/lib/evolve/cli.d.ts.map +1 -0
  358. package/dist/lib/evolve/cli.js +224 -0
  359. package/dist/lib/evolve/cli.js.map +1 -0
  360. package/dist/lib/evolve/discovery.d.ts +2 -0
  361. package/dist/lib/evolve/discovery.d.ts.map +1 -0
  362. package/dist/lib/evolve/discovery.js +391 -0
  363. package/dist/lib/evolve/discovery.js.map +1 -0
  364. package/dist/lib/evolve/index.d.ts +2 -0
  365. package/dist/lib/evolve/index.d.ts.map +1 -0
  366. package/dist/lib/evolve/index.js +88 -0
  367. package/dist/lib/evolve/index.js.map +1 -0
  368. package/dist/lib/evolve/orchestrator.d.ts +2 -0
  369. package/dist/lib/evolve/orchestrator.d.ts.map +1 -0
  370. package/dist/lib/evolve/orchestrator.js +851 -0
  371. package/dist/lib/evolve/orchestrator.js.map +1 -0
  372. package/dist/lib/evolve/scoring.d.ts +2 -0
  373. package/dist/lib/evolve/scoring.d.ts.map +1 -0
  374. package/dist/lib/evolve/scoring.js +118 -0
  375. package/dist/lib/evolve/scoring.js.map +1 -0
  376. package/dist/lib/evolve/state.d.ts +2 -0
  377. package/dist/lib/evolve/state.d.ts.map +1 -0
  378. package/dist/lib/evolve/state.js +264 -0
  379. package/dist/lib/evolve/state.js.map +1 -0
  380. package/dist/lib/evolve/types.d.ts +249 -0
  381. package/dist/lib/evolve/types.d.ts.map +1 -0
  382. package/dist/lib/evolve/types.js +3 -0
  383. package/dist/lib/evolve/types.js.map +1 -0
  384. package/dist/lib/frontmatter.d.ts +2 -0
  385. package/dist/lib/frontmatter.d.ts.map +1 -0
  386. package/dist/lib/frontmatter.js +513 -0
  387. package/dist/lib/frontmatter.js.map +1 -0
  388. package/dist/lib/gates.d.ts +2 -0
  389. package/dist/lib/gates.d.ts.map +1 -0
  390. package/dist/lib/gates.js +578 -0
  391. package/dist/lib/gates.js.map +1 -0
  392. package/dist/lib/genome.d.ts +10 -0
  393. package/dist/lib/genome.d.ts.map +1 -0
  394. package/dist/lib/genome.js +368 -0
  395. package/dist/lib/genome.js.map +1 -0
  396. package/dist/lib/got.d.ts +2 -0
  397. package/dist/lib/got.d.ts.map +1 -0
  398. package/dist/lib/got.js +280 -0
  399. package/dist/lib/got.js.map +1 -0
  400. package/dist/lib/invariants.d.ts +2 -0
  401. package/dist/lib/invariants.d.ts.map +1 -0
  402. package/dist/lib/invariants.js +298 -0
  403. package/dist/lib/invariants.js.map +1 -0
  404. package/dist/lib/knowledge.d.ts +2 -0
  405. package/dist/lib/knowledge.d.ts.map +1 -0
  406. package/dist/lib/knowledge.js +658 -0
  407. package/dist/lib/knowledge.js.map +1 -0
  408. package/dist/lib/long-term-roadmap.d.ts +2 -0
  409. package/dist/lib/long-term-roadmap.d.ts.map +1 -0
  410. package/dist/lib/long-term-roadmap.js +602 -0
  411. package/dist/lib/long-term-roadmap.js.map +1 -0
  412. package/dist/lib/markdown-split.d.ts +2 -0
  413. package/dist/lib/markdown-split.d.ts.map +1 -0
  414. package/dist/lib/markdown-split.js +199 -0
  415. package/dist/lib/markdown-split.js.map +1 -0
  416. package/dist/lib/mcp-server.d.ts +2 -0
  417. package/dist/lib/mcp-server.d.ts.map +1 -0
  418. package/dist/lib/mcp-server.js +2424 -0
  419. package/dist/lib/mcp-server.js.map +1 -0
  420. package/dist/lib/metrics.d.ts +16 -0
  421. package/dist/lib/metrics.d.ts.map +1 -0
  422. package/dist/lib/metrics.js +48 -0
  423. package/dist/lib/metrics.js.map +1 -0
  424. package/dist/lib/overstory.d.ts +2 -0
  425. package/dist/lib/overstory.d.ts.map +1 -0
  426. package/dist/lib/overstory.js +211 -0
  427. package/dist/lib/overstory.js.map +1 -0
  428. package/dist/lib/parallel.d.ts +2 -0
  429. package/dist/lib/parallel.d.ts.map +1 -0
  430. package/dist/lib/parallel.js +349 -0
  431. package/dist/lib/parallel.js.map +1 -0
  432. package/dist/lib/paths.d.ts +2 -0
  433. package/dist/lib/paths.d.ts.map +1 -0
  434. package/dist/lib/paths.js +254 -0
  435. package/dist/lib/paths.js.map +1 -0
  436. package/dist/lib/phase-complete-llm.d.ts +22 -0
  437. package/dist/lib/phase-complete-llm.d.ts.map +1 -0
  438. package/dist/lib/phase-complete-llm.js +331 -0
  439. package/dist/lib/phase-complete-llm.js.map +1 -0
  440. package/dist/lib/phase-complete.d.ts +46 -0
  441. package/dist/lib/phase-complete.d.ts.map +1 -0
  442. package/dist/lib/phase-complete.js +278 -0
  443. package/dist/lib/phase-complete.js.map +1 -0
  444. package/dist/lib/phase-io.d.ts +2 -0
  445. package/dist/lib/phase-io.d.ts.map +1 -0
  446. package/dist/lib/phase-io.js +126 -0
  447. package/dist/lib/phase-io.js.map +1 -0
  448. package/dist/lib/phase.d.ts +2 -0
  449. package/dist/lib/phase.d.ts.map +1 -0
  450. package/dist/lib/phase.js +1344 -0
  451. package/dist/lib/phase.js.map +1 -0
  452. package/dist/lib/plan-tournament.d.ts +63 -0
  453. package/dist/lib/plan-tournament.d.ts.map +1 -0
  454. package/dist/lib/plan-tournament.js +353 -0
  455. package/dist/lib/plan-tournament.js.map +1 -0
  456. package/dist/lib/refinement.d.ts +74 -0
  457. package/dist/lib/refinement.d.ts.map +1 -0
  458. package/dist/lib/refinement.js +283 -0
  459. package/dist/lib/refinement.js.map +1 -0
  460. package/dist/lib/requirements.d.ts +2 -0
  461. package/dist/lib/requirements.d.ts.map +1 -0
  462. package/dist/lib/requirements.js +355 -0
  463. package/dist/lib/requirements.js.map +1 -0
  464. package/dist/lib/research-bundle.d.ts +2 -0
  465. package/dist/lib/research-bundle.d.ts.map +1 -0
  466. package/dist/lib/research-bundle.js +246 -0
  467. package/dist/lib/research-bundle.js.map +1 -0
  468. package/dist/lib/roadmap.d.ts +2 -0
  469. package/dist/lib/roadmap.d.ts.map +1 -0
  470. package/dist/lib/roadmap.js +541 -0
  471. package/dist/lib/roadmap.js.map +1 -0
  472. package/dist/lib/sample.d.ts +16 -0
  473. package/dist/lib/sample.d.ts.map +1 -0
  474. package/dist/lib/sample.js +20 -0
  475. package/dist/lib/sample.js.map +1 -0
  476. package/dist/lib/scaffold.d.ts +2 -0
  477. package/dist/lib/scaffold.d.ts.map +1 -0
  478. package/dist/lib/scaffold.js +355 -0
  479. package/dist/lib/scaffold.js.map +1 -0
  480. package/dist/lib/scan/_utils.d.ts +11 -0
  481. package/dist/lib/scan/_utils.d.ts.map +1 -0
  482. package/dist/lib/scan/_utils.js +36 -0
  483. package/dist/lib/scan/_utils.js.map +1 -0
  484. package/dist/lib/scan/base64.d.ts +15 -0
  485. package/dist/lib/scan/base64.d.ts.map +1 -0
  486. package/dist/lib/scan/base64.js +66 -0
  487. package/dist/lib/scan/base64.js.map +1 -0
  488. package/dist/lib/scan/ignorefile.d.ts +30 -0
  489. package/dist/lib/scan/ignorefile.d.ts.map +1 -0
  490. package/dist/lib/scan/ignorefile.js +101 -0
  491. package/dist/lib/scan/ignorefile.js.map +1 -0
  492. package/dist/lib/scan/injection.d.ts +14 -0
  493. package/dist/lib/scan/injection.d.ts.map +1 -0
  494. package/dist/lib/scan/injection.js +39 -0
  495. package/dist/lib/scan/injection.js.map +1 -0
  496. package/dist/lib/scan/patterns.d.ts +17 -0
  497. package/dist/lib/scan/patterns.d.ts.map +1 -0
  498. package/dist/lib/scan/patterns.js +123 -0
  499. package/dist/lib/scan/patterns.js.map +1 -0
  500. package/dist/lib/scan/strip-markdown.d.ts +7 -0
  501. package/dist/lib/scan/strip-markdown.d.ts.map +1 -0
  502. package/dist/lib/scan/strip-markdown.js +38 -0
  503. package/dist/lib/scan/strip-markdown.js.map +1 -0
  504. package/dist/lib/scan/types.d.ts +23 -0
  505. package/dist/lib/scan/types.d.ts.map +1 -0
  506. package/dist/lib/scan/types.js +3 -0
  507. package/dist/lib/scan/types.js.map +1 -0
  508. package/dist/lib/scheduler-wait.d.ts +2 -0
  509. package/dist/lib/scheduler-wait.d.ts.map +1 -0
  510. package/dist/lib/scheduler-wait.js +59 -0
  511. package/dist/lib/scheduler-wait.js.map +1 -0
  512. package/dist/lib/scheduler.d.ts +254 -0
  513. package/dist/lib/scheduler.d.ts.map +1 -0
  514. package/dist/lib/scheduler.js +1147 -0
  515. package/dist/lib/scheduler.js.map +1 -0
  516. package/dist/lib/state.d.ts +2 -0
  517. package/dist/lib/state.d.ts.map +1 -0
  518. package/dist/lib/state.js +744 -0
  519. package/dist/lib/state.js.map +1 -0
  520. package/dist/lib/think.d.ts +18 -0
  521. package/dist/lib/think.d.ts.map +1 -0
  522. package/dist/lib/think.js +317 -0
  523. package/dist/lib/think.js.map +1 -0
  524. package/dist/lib/tracker.d.ts +2 -0
  525. package/dist/lib/tracker.d.ts.map +1 -0
  526. package/dist/lib/tracker.js +1121 -0
  527. package/dist/lib/tracker.js.map +1 -0
  528. package/dist/lib/types.d.ts +1514 -0
  529. package/dist/lib/types.d.ts.map +1 -0
  530. package/dist/lib/types.js +4 -0
  531. package/dist/lib/types.js.map +1 -0
  532. package/dist/lib/utils.d.ts +2 -0
  533. package/dist/lib/utils.d.ts.map +1 -0
  534. package/dist/lib/utils.js +1363 -0
  535. package/dist/lib/utils.js.map +1 -0
  536. package/dist/lib/verify.d.ts +2 -0
  537. package/dist/lib/verify.d.ts.map +1 -0
  538. package/dist/lib/verify.js +1153 -0
  539. package/dist/lib/verify.js.map +1 -0
  540. package/dist/lib/wireup/autofix.d.ts +2 -0
  541. package/dist/lib/wireup/autofix.d.ts.map +1 -0
  542. package/dist/lib/wireup/autofix.js +188 -0
  543. package/dist/lib/wireup/autofix.js.map +1 -0
  544. package/dist/lib/wireup/cli.d.ts +2 -0
  545. package/dist/lib/wireup/cli.d.ts.map +1 -0
  546. package/dist/lib/wireup/cli.js +194 -0
  547. package/dist/lib/wireup/cli.js.map +1 -0
  548. package/dist/lib/wireup/detection.d.ts +47 -0
  549. package/dist/lib/wireup/detection.d.ts.map +1 -0
  550. package/dist/lib/wireup/detection.js +410 -0
  551. package/dist/lib/wireup/detection.js.map +1 -0
  552. package/dist/lib/wireup/discovery.d.ts +2 -0
  553. package/dist/lib/wireup/discovery.d.ts.map +1 -0
  554. package/dist/lib/wireup/discovery.js +934 -0
  555. package/dist/lib/wireup/discovery.js.map +1 -0
  556. package/dist/lib/wireup/execution.d.ts +2 -0
  557. package/dist/lib/wireup/execution.d.ts.map +1 -0
  558. package/dist/lib/wireup/execution.js +573 -0
  559. package/dist/lib/wireup/execution.js.map +1 -0
  560. package/dist/lib/wireup/index.d.ts +2 -0
  561. package/dist/lib/wireup/index.d.ts.map +1 -0
  562. package/dist/lib/wireup/index.js +85 -0
  563. package/dist/lib/wireup/index.js.map +1 -0
  564. package/dist/lib/wireup/orchestrator.d.ts +2 -0
  565. package/dist/lib/wireup/orchestrator.d.ts.map +1 -0
  566. package/dist/lib/wireup/orchestrator.js +366 -0
  567. package/dist/lib/wireup/orchestrator.js.map +1 -0
  568. package/dist/lib/wireup/report.d.ts +47 -0
  569. package/dist/lib/wireup/report.d.ts.map +1 -0
  570. package/dist/lib/wireup/report.js +201 -0
  571. package/dist/lib/wireup/report.js.map +1 -0
  572. package/dist/lib/wireup/scenarios.d.ts +2 -0
  573. package/dist/lib/wireup/scenarios.d.ts.map +1 -0
  574. package/dist/lib/wireup/scenarios.js +516 -0
  575. package/dist/lib/wireup/scenarios.js.map +1 -0
  576. package/dist/lib/wireup/state.d.ts +2 -0
  577. package/dist/lib/wireup/state.d.ts.map +1 -0
  578. package/dist/lib/wireup/state.js +102 -0
  579. package/dist/lib/wireup/state.js.map +1 -0
  580. package/dist/lib/wireup/types.d.ts +376 -0
  581. package/dist/lib/wireup/types.d.ts.map +1 -0
  582. package/dist/lib/wireup/types.js +3 -0
  583. package/dist/lib/wireup/types.js.map +1 -0
  584. package/dist/lib/worktree.d.ts +2 -0
  585. package/dist/lib/worktree.d.ts.map +1 -0
  586. package/dist/lib/worktree.js +999 -0
  587. package/dist/lib/worktree.js.map +1 -0
  588. package/lib/autopilot-milestone.ts +136 -0
  589. package/lib/autopilot-pipeline.ts +1179 -0
  590. package/lib/autopilot-waves.ts +361 -0
  591. package/lib/autopilot.ts +1874 -0
  592. package/lib/autoplan.ts +280 -0
  593. package/lib/autoresearch.js +4 -0
  594. package/lib/autoresearch.ts +886 -0
  595. package/lib/backend.ts +1252 -0
  596. package/lib/benchmark.ts +341 -0
  597. package/lib/citations.ts +760 -0
  598. package/lib/cleanup.ts +1588 -0
  599. package/lib/cli/adapters.ts +41 -0
  600. package/lib/cli/agent.ts +83 -0
  601. package/lib/cli/index.ts +273 -0
  602. package/lib/cli/output.ts +33 -0
  603. package/lib/cli/scan-dispatch.ts +130 -0
  604. package/lib/cli/tools.ts +198 -0
  605. package/lib/commands/_dashboard-parsers.ts +275 -0
  606. package/lib/commands/analysis.ts +1851 -0
  607. package/lib/commands/assumptions.ts +232 -0
  608. package/lib/commands/blame.ts +174 -0
  609. package/lib/commands/budget.ts +148 -0
  610. package/lib/commands/check-plans.ts +233 -0
  611. package/lib/commands/config.ts +287 -0
  612. package/lib/commands/dashboard.ts +680 -0
  613. package/lib/commands/estimate.ts +204 -0
  614. package/lib/commands/eval-diff.ts +252 -0
  615. package/lib/commands/freshness.ts +213 -0
  616. package/lib/commands/health.ts +607 -0
  617. package/lib/commands/index.ts +266 -0
  618. package/lib/commands/install.ts +307 -0
  619. package/lib/commands/knowhow-aggregator.ts +345 -0
  620. package/lib/commands/knowledge-search.ts +153 -0
  621. package/lib/commands/long-term-roadmap.ts +390 -0
  622. package/lib/commands/patterns.ts +465 -0
  623. package/lib/commands/phase-info.ts +698 -0
  624. package/lib/commands/plan-lint.ts +546 -0
  625. package/lib/commands/plan-phase.ts +375 -0
  626. package/lib/commands/progress.ts +319 -0
  627. package/lib/commands/quality.ts +138 -0
  628. package/lib/commands/rollback.ts +195 -0
  629. package/lib/commands/scan.ts +72 -0
  630. package/lib/commands/search.ts +300 -0
  631. package/lib/commands/select-candidate.ts +687 -0
  632. package/lib/commands/singularity.ts +222 -0
  633. package/lib/commands/slug-timestamp.ts +74 -0
  634. package/lib/commands/tail.ts +129 -0
  635. package/lib/commands/todo.ts +273 -0
  636. package/lib/commands/watch.ts +80 -0
  637. package/lib/complexity.ts +117 -0
  638. package/lib/context/agents.ts +505 -0
  639. package/lib/context/base.ts +123 -0
  640. package/lib/context/execute.ts +977 -0
  641. package/lib/context/index.ts +110 -0
  642. package/lib/context/progress.ts +278 -0
  643. package/lib/context/project.ts +531 -0
  644. package/lib/context/research.ts +646 -0
  645. package/lib/dead-ends.ts +506 -0
  646. package/lib/deps.ts +773 -0
  647. package/lib/discussion.ts +1275 -0
  648. package/lib/drift.ts +519 -0
  649. package/lib/evolve/_dimensions-features.ts +525 -0
  650. package/lib/evolve/_dimensions.ts +511 -0
  651. package/lib/evolve/_product-ideation.ts +405 -0
  652. package/lib/evolve/_prompts.ts +178 -0
  653. package/lib/evolve/cli.ts +330 -0
  654. package/lib/evolve/discovery.ts +571 -0
  655. package/lib/evolve/index.ts +105 -0
  656. package/lib/evolve/orchestrator.ts +1139 -0
  657. package/lib/evolve/scoring.ts +167 -0
  658. package/lib/evolve/state.ts +330 -0
  659. package/lib/evolve/types.ts +290 -0
  660. package/lib/frontmatter.ts +615 -0
  661. package/lib/gates.ts +695 -0
  662. package/lib/genome.ts +402 -0
  663. package/lib/got.js +4 -0
  664. package/lib/got.ts +361 -0
  665. package/lib/invariants.ts +378 -0
  666. package/lib/knowledge.ts +768 -0
  667. package/lib/long-term-roadmap.ts +806 -0
  668. package/lib/markdown-split.ts +273 -0
  669. package/lib/mcp-server.ts +3292 -0
  670. package/lib/metrics.ts +49 -0
  671. package/lib/overstory.ts +270 -0
  672. package/lib/parallel.ts +570 -0
  673. package/lib/paths.ts +293 -0
  674. package/lib/phase-complete-llm.ts +376 -0
  675. package/lib/phase-complete.ts +366 -0
  676. package/lib/phase-io.ts +101 -0
  677. package/lib/phase.ts +1981 -0
  678. package/lib/plan-tournament.ts +426 -0
  679. package/lib/refinement.ts +349 -0
  680. package/lib/requirements.ts +469 -0
  681. package/lib/research-bundle.ts +300 -0
  682. package/lib/roadmap.ts +775 -0
  683. package/lib/scaffold.ts +480 -0
  684. package/lib/scan/_utils.ts +37 -0
  685. package/lib/scan/base64.ts +90 -0
  686. package/lib/scan/ignorefile.ts +109 -0
  687. package/lib/scan/injection.ts +67 -0
  688. package/lib/scan/patterns.ts +139 -0
  689. package/lib/scan/strip-markdown.ts +39 -0
  690. package/lib/scan/types.ts +28 -0
  691. package/lib/scheduler-wait.ts +58 -0
  692. package/lib/scheduler.ts +1370 -0
  693. package/lib/state.ts +1000 -0
  694. package/lib/think.ts +365 -0
  695. package/lib/tracker.ts +1591 -0
  696. package/lib/types.ts +1663 -0
  697. package/lib/utils.ts +1479 -0
  698. package/lib/verify.ts +1434 -0
  699. package/lib/wireup/autofix.ts +241 -0
  700. package/lib/wireup/cli.ts +278 -0
  701. package/lib/wireup/detection.ts +542 -0
  702. package/lib/wireup/discovery.ts +1063 -0
  703. package/lib/wireup/execution.ts +686 -0
  704. package/lib/wireup/index.ts +117 -0
  705. package/lib/wireup/orchestrator.ts +519 -0
  706. package/lib/wireup/report.ts +286 -0
  707. package/lib/wireup/scenarios.ts +616 -0
  708. package/lib/wireup/state.ts +139 -0
  709. package/lib/wireup/types.ts +436 -0
  710. package/lib/worktree.ts +1309 -0
  711. package/package.json +67 -0
package/lib/phase.ts ADDED
@@ -0,0 +1,1981 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Phase Lifecycle Operations -- add, insert, remove, complete, list
5
+ * plus milestone complete and validate consistency.
6
+ *
7
+ * Extracted from bin/grd-tools.js (Phase 03, Plan 05).
8
+ *
9
+ * Dependencies: utils.ts, frontmatter.ts (one-directional, no circular deps)
10
+ */
11
+
12
+ import type {
13
+ GrdConfig,
14
+ MilestoneInfo,
15
+ FrontmatterObject,
16
+ GateViolation,
17
+ PreflightResult,
18
+ PhaseCompleteOptions,
19
+ PhaseCompleteResult,
20
+ SchedulerConfig,
21
+ SuperpowersConfig,
22
+ } from './types';
23
+ import type { Scheduler } from './scheduler';
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const {
29
+ normalizePhaseName,
30
+ generateSlugInternal,
31
+ stripShippedSections,
32
+ execGit,
33
+ loadConfig,
34
+ getMilestoneInfo: getMilestoneInfoUtil,
35
+ output,
36
+ error,
37
+ }: {
38
+ normalizePhaseName: (phase: string) => string;
39
+ generateSlugInternal: (text: string) => string | null;
40
+ stripShippedSections: (content: string) => string;
41
+ execGit: (
42
+ cwd: string,
43
+ args: string[],
44
+ opts?: { allowBlocked?: boolean }
45
+ ) => { exitCode: number; stdout: string; stderr: string };
46
+ loadConfig: (cwd: string) => GrdConfig;
47
+ getMilestoneInfo: (cwd: string) => MilestoneInfo;
48
+ output: (result: unknown, raw: boolean, rawValue?: unknown) => never;
49
+ error: (message: string) => never;
50
+ } = require('./utils');
51
+
52
+ const {
53
+ extractFrontmatter,
54
+ }: {
55
+ extractFrontmatter: (content: string) => FrontmatterObject;
56
+ } = require('./frontmatter');
57
+
58
+ const {
59
+ runPreflightGates,
60
+ checkOrphanedPhases,
61
+ }: {
62
+ runPreflightGates: (cwd: string, command: string, options?: GateOptions) => PreflightResult;
63
+ checkOrphanedPhases: (cwd: string) => GateViolation[];
64
+ } = require('./gates');
65
+
66
+ const {
67
+ phasesDir: getPhasesDirPath,
68
+ phaseDir: getPhaseDirPath,
69
+ milestonesDir: getMilestonesDirPath,
70
+ archivedPhasesDir: getArchivedPhasesDir,
71
+ }: {
72
+ phasesDir: (cwd: string, milestone?: string | null) => string;
73
+ phaseDir: (cwd: string, milestone: string | undefined | null, phaseDirName: string) => string;
74
+ milestonesDir: (cwd: string) => string;
75
+ archivedPhasesDir: (cwd: string, version: string) => string;
76
+ } = require('./paths');
77
+
78
+ const { _phaseCompleteCore } = require('./phase-complete') as {
79
+ _phaseCompleteCore: (
80
+ cwd: string,
81
+ phaseNum: string,
82
+ options?: PhaseCompleteOptions
83
+ ) => PhaseCompleteResult;
84
+ };
85
+
86
+ const { readRoadmapFile, writeRoadmapFile, readStateFile, writeStateFile } =
87
+ require('./phase-io') as {
88
+ readRoadmapFile: (p: string) => string;
89
+ writeRoadmapFile: (p: string, content: string) => void;
90
+ readStateFile: (p: string) => string;
91
+ writeStateFile: (p: string, content: string) => void;
92
+ };
93
+
94
+ // ─── Domain Types ─────────────────────────────────────────────────────────────
95
+
96
+ /** Options for gate checks passed to runPreflightGates. */
97
+ interface GateOptions {
98
+ phase?: string;
99
+ skipGates?: boolean;
100
+ [key: string]: unknown;
101
+ }
102
+
103
+ /** Options for cmdPhaseRemove. */
104
+ interface PhaseRemoveOptions {
105
+ force?: boolean;
106
+ dryRun?: boolean;
107
+ remove_dir?: boolean;
108
+ raw?: boolean;
109
+ }
110
+
111
+ /** Options for cmdMilestoneComplete. */
112
+ interface MilestoneCompleteOptions {
113
+ name?: string;
114
+ dryRun?: boolean;
115
+ raw?: boolean;
116
+ }
117
+
118
+ /** Options for cmdPhaseBatchComplete. */
119
+ interface PhaseBatchCompleteOptions {
120
+ dryRun?: boolean;
121
+ force?: boolean;
122
+ raw?: boolean;
123
+ }
124
+
125
+ /** Options for cmdValidateConsistency. */
126
+ interface ValidateConsistencyOptions {
127
+ fix?: boolean;
128
+ }
129
+
130
+ /** Result from cmdPhaseAdd output. */
131
+ interface PhaseAddResult {
132
+ phase_number: number;
133
+ padded: string;
134
+ name: string;
135
+ slug: string | null;
136
+ directory: string;
137
+ schedule_affected: boolean;
138
+ warnings?: string[];
139
+ }
140
+
141
+ /** Result from cmdPhaseInsert output. */
142
+ interface PhaseInsertResult {
143
+ phase_number: string;
144
+ after_phase: string;
145
+ name: string;
146
+ slug: string | null;
147
+ directory: string;
148
+ schedule_affected: boolean;
149
+ }
150
+
151
+ /** A renamed directory or file entry. */
152
+ interface RenameEntry {
153
+ from: string;
154
+ to: string;
155
+ }
156
+
157
+ /** Result from _reorderDirectories. */
158
+ interface ReorderResult {
159
+ renamedDirs: RenameEntry[];
160
+ renamedFiles: RenameEntry[];
161
+ }
162
+
163
+ /** A renumber item for integer phases. */
164
+ interface IntegerRenumberItem {
165
+ dir: string;
166
+ oldInt: number;
167
+ decimal: number | null;
168
+ slug: string;
169
+ }
170
+
171
+ /** A renumber item for decimal phases. */
172
+ interface DecimalRenumberItem {
173
+ dir: string;
174
+ oldDecimal: number;
175
+ slug: string;
176
+ }
177
+
178
+ /** Archive context passed to _archiveMilestone. */
179
+ interface ArchiveContext {
180
+ roadmapPath: string;
181
+ reqPath: string;
182
+ milestoneName: string;
183
+ today: string;
184
+ phasesDir: string;
185
+ phaseCount: number;
186
+ totalPlans: number;
187
+ totalTasks: number;
188
+ accomplishments: string[];
189
+ phasesAlreadyInPlace: boolean;
190
+ }
191
+
192
+ /** Archive result from _archiveMilestone. */
193
+ interface ArchiveResult {
194
+ archivedPhaseCount: number;
195
+ }
196
+
197
+ /** Milestone complete result. */
198
+ interface MilestoneCompleteResult {
199
+ version: string;
200
+ name: string;
201
+ date: string;
202
+ phases: number;
203
+ plans: number;
204
+ tasks: number;
205
+ accomplishments: string[];
206
+ phases_already_in_place: boolean;
207
+ archived: {
208
+ roadmap: boolean;
209
+ requirements: boolean;
210
+ audit: boolean;
211
+ phases: boolean;
212
+ phase_count: number;
213
+ marker: boolean;
214
+ };
215
+ milestones_updated: boolean;
216
+ state_updated: boolean;
217
+ git_merge?: GitMergeResult;
218
+ }
219
+
220
+ /** Git merge result from milestone complete. */
221
+ interface GitMergeResult {
222
+ skipped?: boolean;
223
+ reason?: string;
224
+ error?: string;
225
+ merged?: boolean;
226
+ milestone_branch?: string;
227
+ base_branch?: string;
228
+ branch_deleted?: boolean;
229
+ }
230
+
231
+ /** Phase remove result. */
232
+ interface PhaseRemoveResult {
233
+ removed: string;
234
+ directory_deleted: string | null;
235
+ renamed_directories: RenameEntry[];
236
+ renamed_files: RenameEntry[];
237
+ roadmap_updated: boolean;
238
+ state_updated: boolean;
239
+ cleaned_worktrees?: string[];
240
+ dry_run?: boolean;
241
+ would_remove?: string;
242
+ would_renumber?: string[];
243
+ }
244
+
245
+ /** Batch complete result. */
246
+ interface BatchCompleteResult {
247
+ results: Array<{ phase: string; result?: PhaseCompleteResult; error?: string }>;
248
+ total_phases: number;
249
+ completed_count: number;
250
+ }
251
+
252
+ /** Phases list file result. */
253
+ interface PhasesListFileResult {
254
+ files: string[];
255
+ count: number;
256
+ phase_dir?: string | null;
257
+ error?: string;
258
+ }
259
+
260
+ /** Phases list directory result. */
261
+ interface PhasesListDirResult {
262
+ directories: string[];
263
+ count: number;
264
+ }
265
+
266
+ /** Consistency validation result. */
267
+ interface ConsistencyResult {
268
+ passed: boolean;
269
+ errors: string[];
270
+ warnings: string[];
271
+ warning_count: number;
272
+ fixed?: string[];
273
+ }
274
+
275
+ // ─── Phases List ──────────────────────────────────────────────────────────────
276
+
277
+ /**
278
+ * CLI command: List phase directories with optional filtering by type or phase number.
279
+ * @param cwd - Project working directory
280
+ * @param options - List options
281
+ * @param raw - Output raw text (newline-separated) instead of JSON
282
+ * @returns void — writes JSON or raw text to stdout and exits on error
283
+ */
284
+ function cmdPhasesList(cwd: string, options: Record<string, string>, raw: boolean): void {
285
+ const phasesDir: string = getPhasesDirPath(cwd);
286
+ const { type, phase } = options;
287
+
288
+ // If no phases directory, return empty
289
+ if (!fs.existsSync(phasesDir)) {
290
+ if (type) {
291
+ output({ files: [], count: 0 }, raw, '');
292
+ } else {
293
+ output({ directories: [], count: 0 }, raw, '');
294
+ }
295
+ return;
296
+ }
297
+
298
+ try {
299
+ // Get all phase directories
300
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
301
+ withFileTypes: true,
302
+ });
303
+ let dirs: string[] = entries
304
+ .filter((e: import('fs').Dirent) => e.isDirectory())
305
+ .map((e: import('fs').Dirent) => e.name);
306
+
307
+ // Sort numerically (handles decimals: 01, 02, 02.1, 02.2, 03)
308
+ dirs.sort((a: string, b: string) => {
309
+ const aNum: number = parseFloat(a.match(/^(\d+(?:\.\d+)?)/)?.[1] || '0');
310
+ const bNum: number = parseFloat(b.match(/^(\d+(?:\.\d+)?)/)?.[1] || '0');
311
+ return aNum - bNum;
312
+ });
313
+
314
+ // If filtering by phase number
315
+ if (phase) {
316
+ const normalized: string = normalizePhaseName(phase);
317
+ const match: string | undefined = dirs.find(
318
+ (d: string) => d.startsWith(normalized + '-') || d === normalized
319
+ );
320
+ if (!match) {
321
+ output({ files: [], count: 0, phase_dir: null, error: 'Phase not found' }, raw, '');
322
+ return;
323
+ }
324
+ dirs = [match];
325
+ }
326
+
327
+ // If listing files of a specific type
328
+ if (type) {
329
+ const files: string[] = [];
330
+ for (const dir of dirs) {
331
+ const dirPath: string = path.join(phasesDir, dir);
332
+ const dirFiles: string[] = fs.readdirSync(dirPath) as string[];
333
+
334
+ let filtered: string[];
335
+ if (type === 'plans') {
336
+ filtered = dirFiles.filter((f: string) => f.endsWith('-PLAN.md') || f === 'PLAN.md');
337
+ } else if (type === 'summaries') {
338
+ filtered = dirFiles.filter(
339
+ (f: string) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md'
340
+ );
341
+ } else {
342
+ filtered = dirFiles;
343
+ }
344
+
345
+ files.push(...filtered.sort());
346
+ }
347
+
348
+ const result: PhasesListFileResult = {
349
+ files,
350
+ count: files.length,
351
+ phase_dir: phase ? dirs[0].replace(/^\d+(?:\.\d+)?-?/, '') : null,
352
+ };
353
+ output(result, raw, files.join('\n'));
354
+ return;
355
+ }
356
+
357
+ // Default: list directories
358
+ output({ directories: dirs, count: dirs.length } as PhasesListDirResult, raw, dirs.join('\n'));
359
+ } catch (e) {
360
+ error('Failed to list phases: ' + (e as Error).message);
361
+ }
362
+ }
363
+
364
+ // ─── Phase Add ────────────────────────────────────────────────────────────────
365
+
366
+ /**
367
+ * CLI command: Add a new phase to the end of the roadmap and create its directory.
368
+ * @param cwd - Project working directory
369
+ * @param description - Human-readable phase description for the roadmap heading
370
+ * @param raw - Output raw padded number instead of JSON
371
+ * @param context - Optional context text for CONTEXT.md
372
+ * @returns void — writes JSON or raw text to stdout and exits on error
373
+ */
374
+ function cmdPhaseAdd(cwd: string, description: string, raw: boolean, context?: string): void {
375
+ if (!description) {
376
+ error('description required for phase add');
377
+ }
378
+
379
+ if (description.length > 60) {
380
+ error(
381
+ `description too long (${description.length} chars): must not exceed 60 characters. Shorten your description to fewer than the maximum characters, e.g.: phase add 'Short name'`
382
+ );
383
+ }
384
+
385
+ // Pre-flight gate checks
386
+ const gates: PreflightResult = runPreflightGates(cwd, 'phase-add');
387
+ if (!gates.passed) {
388
+ output(
389
+ {
390
+ gate_failed: true,
391
+ gate_errors: gates.errors,
392
+ gate_warnings: gates.warnings,
393
+ },
394
+ raw
395
+ );
396
+ return;
397
+ }
398
+
399
+ const roadmapPath: string = path.join(cwd, '.planning', 'ROADMAP.md');
400
+ let content: string;
401
+ try {
402
+ content = readRoadmapFile(roadmapPath);
403
+ } catch {
404
+ error('ROADMAP.md not found');
405
+ return;
406
+ }
407
+ const slug: string | null = generateSlugInternal(description);
408
+
409
+ // Find highest integer phase number across full content (including shipped sections)
410
+ const phasePattern: RegExp = /#{2,3}\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
411
+ let maxPhase = 0;
412
+ const existingPhaseNums: number[] = [];
413
+ let m: RegExpExecArray | null;
414
+ while ((m = phasePattern.exec(content)) !== null) {
415
+ const num: number = parseInt(m[1], 10);
416
+ if (!existingPhaseNums.includes(num)) existingPhaseNums.push(num);
417
+ if (num > maxPhase) maxPhase = num;
418
+ }
419
+
420
+ // Detect numbering gaps in existing phases
421
+ const addWarnings: string[] = [];
422
+ existingPhaseNums.sort((a: number, b: number) => a - b);
423
+ for (let i = 1; i < existingPhaseNums.length; i++) {
424
+ if (existingPhaseNums[i] !== existingPhaseNums[i - 1] + 1) {
425
+ addWarnings.push(
426
+ `Gap in phase sequence: ${existingPhaseNums[i - 1]} to ${existingPhaseNums[i]} (missing ${existingPhaseNums[i - 1] + 1})`
427
+ );
428
+ }
429
+ }
430
+
431
+ const newPhaseNum: number = maxPhase + 1;
432
+ const paddedNum: string = String(newPhaseNum).padStart(2, '0');
433
+ const dirName: string = `${paddedNum}-${slug}`;
434
+ const dirPath: string = getPhaseDirPath(cwd, null, dirName);
435
+
436
+ // Create directory
437
+ fs.mkdirSync(dirPath, { recursive: true });
438
+
439
+ // Write CONTEXT.md if context provided
440
+ if (context) {
441
+ const today: string = new Date().toISOString().slice(0, 10);
442
+ const contextContent: string = `---\nphase: "${paddedNum}"\nname: "${description}"\ncreated: ${today}\n---\n\n# Phase ${newPhaseNum}: ${description} -- Context\n\n${context}\n`;
443
+ fs.writeFileSync(path.join(dirPath, `${paddedNum}-CONTEXT.md`), contextContent, 'utf-8');
444
+ }
445
+
446
+ // Detect heading level used in existing ROADMAP (## or ###)
447
+ const headingLevel: string = /^## Phase \d+:/m.test(content) ? '##' : '###';
448
+
449
+ // Build phase entry (includes Duration for schedule computation)
450
+ const phaseEntry: string = `\n${headingLevel} Phase ${newPhaseNum}: ${description}\n\n**Goal:** ${description}\n**Depends on:** Phase ${maxPhase}\n**Duration:** 7d\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /grd:plan-phase ${newPhaseNum} to break down)\n`;
451
+
452
+ // Find insertion point: before last "---" or at end
453
+ let updatedContent: string;
454
+ const lastSeparator: number = content.lastIndexOf('\n---');
455
+ if (lastSeparator > 0) {
456
+ updatedContent = content.slice(0, lastSeparator) + phaseEntry + content.slice(lastSeparator);
457
+ } else {
458
+ updatedContent = content + phaseEntry;
459
+ }
460
+
461
+ writeRoadmapFile(roadmapPath, updatedContent);
462
+
463
+ const result: PhaseAddResult = {
464
+ phase_number: newPhaseNum,
465
+ padded: paddedNum,
466
+ name: description,
467
+ slug,
468
+ directory: path.relative(cwd, dirPath) as string,
469
+ schedule_affected: true,
470
+ ...(addWarnings.length > 0 ? { warnings: addWarnings } : {}),
471
+ };
472
+
473
+ output(result, raw, paddedNum);
474
+ }
475
+
476
+ // ─── Phase Insert (Decimal) ──────────────────────────────────────────────────
477
+
478
+ /**
479
+ * CLI command: Insert a decimal phase after a specified phase in the roadmap.
480
+ * @param cwd - Project working directory
481
+ * @param afterPhase - Phase number to insert after (e.g., '06')
482
+ * @param description - Human-readable phase description
483
+ * @param raw - Output raw decimal phase number instead of JSON
484
+ * @returns void — writes JSON or raw text to stdout and exits on error
485
+ */
486
+ function cmdPhaseInsert(cwd: string, afterPhase: string, description: string, raw: boolean): void {
487
+ if (!afterPhase || !description) {
488
+ error(
489
+ 'after-phase and description required for phase insert. Usage: phase insert <after-phase-number> <description>. Provide both arguments, e.g.: phase insert 2 "New phase description"'
490
+ );
491
+ }
492
+
493
+ // Pre-flight gate checks
494
+ const gates: PreflightResult = runPreflightGates(cwd, 'phase-insert');
495
+ if (!gates.passed) {
496
+ output(
497
+ {
498
+ gate_failed: true,
499
+ gate_errors: gates.errors,
500
+ gate_warnings: gates.warnings,
501
+ },
502
+ raw
503
+ );
504
+ return;
505
+ }
506
+
507
+ const roadmapPath: string = path.join(cwd, '.planning', 'ROADMAP.md');
508
+ let content: string;
509
+ try {
510
+ content = readRoadmapFile(roadmapPath);
511
+ } catch {
512
+ error('ROADMAP.md not found');
513
+ return;
514
+ }
515
+ const activeContent: string = stripShippedSections(content);
516
+ const slug: string | null = generateSlugInternal(description);
517
+
518
+ // Verify target phase exists (in active section only)
519
+ const afterPhaseEscaped: string = afterPhase.replace(/\./g, '\\.');
520
+ const targetPattern: RegExp = new RegExp(`#{2,}\\s*Phase\\s+${afterPhaseEscaped}:`, 'i');
521
+ if (!targetPattern.test(activeContent)) {
522
+ error(
523
+ `Phase ${afterPhase} not found in ROADMAP.md. Run "roadmap get-phase ${afterPhase}" to verify the phase exists, or check .planning/ROADMAP.md`
524
+ );
525
+ }
526
+
527
+ // Calculate next decimal using existing logic
528
+ const phasesDir: string = getPhasesDirPath(cwd);
529
+ const normalizedBase: string = normalizePhaseName(afterPhase);
530
+ const existingDecimals: number[] = [];
531
+
532
+ try {
533
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
534
+ withFileTypes: true,
535
+ });
536
+ const dirs: string[] = entries
537
+ .filter((e: import('fs').Dirent) => e.isDirectory())
538
+ .map((e: import('fs').Dirent) => e.name);
539
+ const decimalPattern: RegExp = new RegExp(`^${normalizedBase}\\.(\\d+)`);
540
+ for (const dir of dirs) {
541
+ const dm: RegExpMatchArray | null = dir.match(decimalPattern);
542
+ if (dm) existingDecimals.push(parseInt(dm[1], 10));
543
+ }
544
+ } catch {
545
+ // Phases directory may not exist yet; start decimal numbering from 1
546
+ }
547
+
548
+ const nextDecimal: number = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
549
+ const decimalPhase: string = `${normalizedBase}.${nextDecimal}`;
550
+ const dirName: string = `${decimalPhase}-${slug}`;
551
+ const dirPath: string = path.join(phasesDir, dirName);
552
+
553
+ // Create directory
554
+ fs.mkdirSync(dirPath, { recursive: true });
555
+
556
+ // Detect heading level used in existing ROADMAP (## or ###)
557
+ const headingLevel: string = /^## Phase \d+:/m.test(content) ? '##' : '###';
558
+
559
+ // Build phase entry (includes Duration for schedule computation)
560
+ const phaseEntry: string = `\n${headingLevel} Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Depends on:** Phase ${afterPhase}\n**Duration:** 3d\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /grd:plan-phase ${decimalPhase} to break down)\n`;
561
+
562
+ // Insert after the target phase section
563
+ const headerPattern: RegExp = new RegExp(
564
+ `(#{2,}\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`,
565
+ 'i'
566
+ );
567
+ const headerMatch: RegExpMatchArray | null = content.match(headerPattern);
568
+ if (!headerMatch) {
569
+ error(
570
+ `Could not find Phase ${afterPhase} header in ROADMAP.md. Ensure the phase heading matches the format "## Phase ${afterPhase}: <description>"`
571
+ );
572
+ }
573
+
574
+ const headerIdx: number = content.indexOf(headerMatch![0]);
575
+ const afterHeader: string = content.slice(headerIdx + headerMatch![0].length);
576
+ const nextPhaseMatch: RegExpMatchArray | null = afterHeader.match(/\n#{2,}\s+Phase\s+\d/i);
577
+
578
+ let insertIdx: number;
579
+ if (nextPhaseMatch && nextPhaseMatch.index !== undefined) {
580
+ insertIdx = headerIdx + headerMatch![0].length + nextPhaseMatch.index;
581
+ } else {
582
+ insertIdx = content.length;
583
+ }
584
+
585
+ const updatedContent: string =
586
+ content.slice(0, insertIdx) + phaseEntry + content.slice(insertIdx);
587
+ writeRoadmapFile(roadmapPath, updatedContent);
588
+
589
+ const result: PhaseInsertResult = {
590
+ phase_number: decimalPhase,
591
+ after_phase: afterPhase,
592
+ name: description,
593
+ slug,
594
+ directory: path.relative(cwd, dirPath) as string,
595
+ schedule_affected: true,
596
+ };
597
+
598
+ output(result, raw, decimalPhase);
599
+ }
600
+
601
+ // ─── Phase Remove ─────────────────────────────────────────────────────────────
602
+
603
+ /**
604
+ * Validate the targetPhase argument for phase remove.
605
+ * Calls error() and returns false if invalid; returns true if valid.
606
+ * @param targetPhase - Phase number to validate
607
+ */
608
+ function _validateRemoveArgs(targetPhase: string): boolean {
609
+ if (!targetPhase) {
610
+ error(
611
+ "phase number required for phase remove. Usage: phase remove <N>. Provide the phase number to remove, e.g.: phase remove 3. Run 'phase list' to see available phases."
612
+ );
613
+ return false;
614
+ }
615
+ return true;
616
+ }
617
+
618
+ /**
619
+ * Renumber integer phase directories after removing an integer phase.
620
+ * All directories with an integer part greater than removedInt are shifted down by 1.
621
+ * Mutates renamedDirs and renamedFiles arrays in place.
622
+ * @param phasesDir - Absolute path to the phases directory
623
+ * @param removedInt - The integer phase number that was removed
624
+ * @param renamedDirs - Accumulator for renamed directories
625
+ * @param renamedFiles - Accumulator for renamed files
626
+ */
627
+ function _renumberIntegerPhases(
628
+ phasesDir: string,
629
+ removedInt: number,
630
+ renamedDirs: RenameEntry[],
631
+ renamedFiles: RenameEntry[]
632
+ ): void {
633
+ try {
634
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
635
+ withFileTypes: true,
636
+ });
637
+ const dirs: string[] = entries
638
+ .filter((e: import('fs').Dirent) => e.isDirectory())
639
+ .map((e: import('fs').Dirent) => e.name)
640
+ .sort();
641
+
642
+ // Collect directories that need renumbering (integer phases > removed, and their decimals)
643
+ const toRename: IntegerRenumberItem[] = [];
644
+ for (const dir of dirs) {
645
+ const dm: RegExpMatchArray | null = dir.match(/^(\d+)(?:\.(\d+))?-(.+)$/);
646
+ if (!dm) continue;
647
+ const dirInt: number = parseInt(dm[1], 10);
648
+ if (dirInt > removedInt) {
649
+ toRename.push({
650
+ dir,
651
+ oldInt: dirInt,
652
+ decimal: dm[2] ? parseInt(dm[2], 10) : null,
653
+ slug: dm[3],
654
+ });
655
+ }
656
+ }
657
+
658
+ // Sort descending to avoid conflicts
659
+ toRename.sort((a: IntegerRenumberItem, b: IntegerRenumberItem) => {
660
+ if (a.oldInt !== b.oldInt) return b.oldInt - a.oldInt;
661
+ return (b.decimal || 0) - (a.decimal || 0);
662
+ });
663
+
664
+ for (const item of toRename) {
665
+ const newInt: number = item.oldInt - 1;
666
+ const newPadded: string = String(newInt).padStart(2, '0');
667
+ const oldPadded: string = String(item.oldInt).padStart(2, '0');
668
+ const decimalSuffix: string = item.decimal !== null ? `.${item.decimal}` : '';
669
+ const oldPrefix: string = `${oldPadded}${decimalSuffix}`;
670
+ const newPrefix: string = `${newPadded}${decimalSuffix}`;
671
+ const newDirName: string = `${newPrefix}-${item.slug}`;
672
+
673
+ // Rename directory
674
+ fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
675
+ renamedDirs.push({ from: item.dir, to: newDirName });
676
+
677
+ // Rename files inside
678
+ let dirFiles: string[];
679
+ try {
680
+ dirFiles = fs.readdirSync(path.join(phasesDir, newDirName)) as string[];
681
+ } catch (readDirErr) {
682
+ const typedErr = readDirErr as { code?: string; message: string };
683
+ if (typedErr.code && typedErr.code !== 'ENOENT') {
684
+ process.stderr.write(
685
+ `[phase] renumber read error (${typedErr.code}): ${typedErr.message}\n`
686
+ );
687
+ }
688
+ continue;
689
+ }
690
+ for (const f of dirFiles) {
691
+ if (f.startsWith(oldPrefix)) {
692
+ const newFileName: string = newPrefix + f.slice(oldPrefix.length);
693
+ fs.renameSync(
694
+ path.join(phasesDir, newDirName, f),
695
+ path.join(phasesDir, newDirName, newFileName)
696
+ );
697
+ renamedFiles.push({ from: f, to: newFileName });
698
+ }
699
+ }
700
+ }
701
+ } catch (renumberErr) {
702
+ const typedErr = renumberErr as { code?: string; message: string };
703
+ if (typedErr.code && typedErr.code !== 'ENOENT') {
704
+ process.stderr.write(`[phase] renumber error (${typedErr.code}): ${typedErr.message}\n`);
705
+ }
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Renumber decimal phase directories after removing a decimal phase.
711
+ * Sibling decimals with a higher decimal part than removedDecimal are shifted down by 1.
712
+ * Mutates renamedDirs and renamedFiles arrays in place.
713
+ * @param phasesDir - Absolute path to the phases directory
714
+ * @param baseInt - The integer part of the removed decimal phase (e.g. "06")
715
+ * @param removedDecimal - The decimal part that was removed (e.g. 2 for "06.2")
716
+ * @param renamedDirs - Accumulator for renamed directories
717
+ * @param renamedFiles - Accumulator for renamed files
718
+ */
719
+ function _renumberDecimalPhases(
720
+ phasesDir: string,
721
+ baseInt: string,
722
+ removedDecimal: number,
723
+ renamedDirs: RenameEntry[],
724
+ renamedFiles: RenameEntry[]
725
+ ): void {
726
+ try {
727
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
728
+ withFileTypes: true,
729
+ });
730
+ const dirs: string[] = entries
731
+ .filter((e: import('fs').Dirent) => e.isDirectory())
732
+ .map((e: import('fs').Dirent) => e.name)
733
+ .sort();
734
+
735
+ // Find sibling decimals with higher numbers
736
+ const decPattern: RegExp = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
737
+ const toRename: DecimalRenumberItem[] = [];
738
+ for (const dir of dirs) {
739
+ const dm: RegExpMatchArray | null = dir.match(decPattern);
740
+ if (dm && parseInt(dm[1], 10) > removedDecimal) {
741
+ toRename.push({
742
+ dir,
743
+ oldDecimal: parseInt(dm[1], 10),
744
+ slug: dm[2],
745
+ });
746
+ }
747
+ }
748
+
749
+ // Sort descending to avoid conflicts
750
+ toRename.sort((a: DecimalRenumberItem, b: DecimalRenumberItem) => b.oldDecimal - a.oldDecimal);
751
+
752
+ for (const item of toRename) {
753
+ const newDecimal: number = item.oldDecimal - 1;
754
+ const oldPhaseId: string = `${baseInt}.${item.oldDecimal}`;
755
+ const newPhaseId: string = `${baseInt}.${newDecimal}`;
756
+ const newDirName: string = `${baseInt}.${newDecimal}-${item.slug}`;
757
+
758
+ // Rename directory
759
+ fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
760
+ renamedDirs.push({ from: item.dir, to: newDirName });
761
+
762
+ // Rename files inside
763
+ const dirFiles: string[] = fs.readdirSync(path.join(phasesDir, newDirName)) as string[];
764
+ for (const f of dirFiles) {
765
+ // Files may have phase prefix like "06.2-01-PLAN.md"
766
+ if (f.includes(oldPhaseId)) {
767
+ const newFileName: string = f.replace(oldPhaseId, newPhaseId);
768
+ fs.renameSync(
769
+ path.join(phasesDir, newDirName, f),
770
+ path.join(phasesDir, newDirName, newFileName)
771
+ );
772
+ renamedFiles.push({ from: f, to: newFileName });
773
+ }
774
+ }
775
+ }
776
+ } catch {
777
+ // Phases directory may not exist; no decimal phases to rename
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Rename phase directories and files after a phase removal.
783
+ * Dispatches to _renumberIntegerPhases or _renumberDecimalPhases based on isDecimal.
784
+ * @param phasesDir - Absolute path to the phases directory
785
+ * @param normalized - Normalized phase number string (e.g. "06" or "06.2")
786
+ * @param isDecimal - Whether the removed phase was a decimal phase
787
+ */
788
+ function _reorderDirectories(
789
+ phasesDir: string,
790
+ normalized: string,
791
+ isDecimal: boolean
792
+ ): ReorderResult {
793
+ const renamedDirs: RenameEntry[] = [];
794
+ const renamedFiles: RenameEntry[] = [];
795
+
796
+ if (isDecimal) {
797
+ const baseParts: string[] = normalized.split('.');
798
+ const baseInt: string = baseParts[0];
799
+ const removedDecimal: number = parseInt(baseParts[1], 10);
800
+ _renumberDecimalPhases(phasesDir, baseInt, removedDecimal, renamedDirs, renamedFiles);
801
+ } else {
802
+ const removedInt: number = parseInt(normalized, 10);
803
+ _renumberIntegerPhases(phasesDir, removedInt, renamedDirs, renamedFiles);
804
+ }
805
+
806
+ return { renamedDirs, renamedFiles };
807
+ }
808
+
809
+ /**
810
+ * Update ROADMAP.md text after a phase removal: remove the target section and renumber
811
+ * all subsequent phase references (headings, checkboxes, plan refs, table rows, depends-on).
812
+ * @param roadmapContent - Current ROADMAP.md content
813
+ * @param targetPhase - The phase number that was removed (e.g. "06" or "06.2")
814
+ * @param normalized - Normalized phase number string
815
+ * @param isDecimal - Whether the removed phase was a decimal phase
816
+ */
817
+ function _reorderRoadmapEntries(
818
+ roadmapContent: string,
819
+ targetPhase: string,
820
+ normalized: string,
821
+ isDecimal: boolean
822
+ ): string {
823
+ const targetEscaped: string = targetPhase.replace(/\./g, '\\.');
824
+
825
+ // Remove the target phase section
826
+ const sectionPattern: RegExp = new RegExp(
827
+ `\\n?#{2,}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,}\\s+Phase\\s+\\d|$)`,
828
+ 'i'
829
+ );
830
+ roadmapContent = roadmapContent.replace(sectionPattern, '');
831
+
832
+ // Remove from phase list (checkbox)
833
+ const checkboxPattern: RegExp = new RegExp(
834
+ `\\n?-\\s*\\[[ x]\\]\\s*.*Phase\\s+${targetEscaped}[:\\s][^\\n]*`,
835
+ 'gi'
836
+ );
837
+ roadmapContent = roadmapContent.replace(checkboxPattern, '');
838
+
839
+ // Remove from progress table
840
+ const tableRowPattern: RegExp = new RegExp(
841
+ `\\n?\\|\\s*${targetEscaped}\\.?\\s[^|]*\\|[^\\n]*`,
842
+ 'gi'
843
+ );
844
+ roadmapContent = roadmapContent.replace(tableRowPattern, '');
845
+
846
+ // Renumber references in ROADMAP for subsequent integer phases
847
+ if (!isDecimal) {
848
+ const removedInt: number = parseInt(normalized, 10);
849
+
850
+ // Collect all integer phases > removedInt
851
+ const maxPhase = 99; // reasonable upper bound
852
+ for (let oldNum: number = maxPhase; oldNum > removedInt; oldNum--) {
853
+ const newNum: number = oldNum - 1;
854
+ const oldStr: string = String(oldNum);
855
+ const newStr: string = String(newNum);
856
+ const oldPad: string = oldStr.padStart(2, '0');
857
+ const newPad: string = newStr.padStart(2, '0');
858
+
859
+ // Phase headings: ### Phase 18: -> ### Phase 17: (or ## Phase)
860
+ roadmapContent = roadmapContent.replace(
861
+ new RegExp(`(#{2,}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
862
+ `$1${newStr}$2`
863
+ );
864
+
865
+ // Checkbox items: - [ ] **Phase 18:** -> - [ ] **Phase 17:**
866
+ roadmapContent = roadmapContent.replace(
867
+ new RegExp(`(Phase\\s+)${oldStr}([:\\s])`, 'g'),
868
+ `$1${newStr}$2`
869
+ );
870
+
871
+ // Plan references: 18-01 -> 17-01
872
+ roadmapContent = roadmapContent.replace(
873
+ new RegExp(`${oldPad}-(\\d{2})`, 'g'),
874
+ `${newPad}-$1`
875
+ );
876
+
877
+ // Table rows: | 18. -> | 17.
878
+ roadmapContent = roadmapContent.replace(
879
+ new RegExp(`(\\|\\s*)${oldStr}\\.\\s`, 'g'),
880
+ `$1${newStr}. `
881
+ );
882
+
883
+ // Depends on references
884
+ roadmapContent = roadmapContent.replace(
885
+ new RegExp(`(Depends on:\\*\\*\\s*Phase\\s+)${oldStr}\\b`, 'gi'),
886
+ `$1${newStr}`
887
+ );
888
+ }
889
+ }
890
+
891
+ return roadmapContent;
892
+ }
893
+
894
+ /**
895
+ * Patch frontmatter phase references inside plan/summary files when phases are renumbered.
896
+ * Currently a no-op placeholder -- frontmatter phase refs are not yet tracked in this workflow.
897
+ * @param _phasesDir - Absolute path to the phases directory
898
+ * @param _oldNum - Old phase number string
899
+ * @param _newNum - New phase number string
900
+ */
901
+ function _patchFrontmatterRefs(_phasesDir: string, _oldNum: string, _newNum: string): void {
902
+ // No frontmatter phase references are currently maintained in plan/summary files.
903
+ // This is a reserved hook for future frontmatter ref tracking.
904
+ }
905
+
906
+ /**
907
+ * CLI command: Remove a phase from the roadmap, delete its directory, and renumber subsequent phases.
908
+ * @param cwd - Project working directory
909
+ * @param targetPhase - Phase number to remove (integer or decimal)
910
+ * @param options - Remove options
911
+ * @param raw - Output raw text instead of JSON
912
+ * @returns void — writes JSON or raw text to stdout and exits on error
913
+ */
914
+ function cmdPhaseRemove(
915
+ cwd: string,
916
+ targetPhase: string,
917
+ options: PhaseRemoveOptions,
918
+ raw: boolean
919
+ ): void {
920
+ if (!_validateRemoveArgs(targetPhase)) return;
921
+
922
+ const roadmapPath: string = path.join(cwd, '.planning', 'ROADMAP.md');
923
+ const phasesDir: string = getPhasesDirPath(cwd);
924
+ const force: boolean = options.force || false;
925
+ const dryRun: boolean = options.dryRun || false;
926
+
927
+ // Read ROADMAP.md FIRST (before any mutations) to detect unreadable state early
928
+ let roadmapContent: string;
929
+ try {
930
+ roadmapContent = readRoadmapFile(roadmapPath);
931
+ } catch (readErr) {
932
+ error(
933
+ `Cannot read ROADMAP.md: ${(readErr as Error).message}. Ensure .planning/ROADMAP.md exists and is readable, then retry.`
934
+ );
935
+ return;
936
+ }
937
+
938
+ // Normalize the target
939
+ const normalized: string = normalizePhaseName(targetPhase);
940
+ const isDecimal: boolean = targetPhase.includes('.');
941
+
942
+ // Find and validate target directory
943
+ let targetDir: string | null = null;
944
+ try {
945
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
946
+ withFileTypes: true,
947
+ });
948
+ const dirs: string[] = entries
949
+ .filter((e: import('fs').Dirent) => e.isDirectory())
950
+ .map((e: import('fs').Dirent) => e.name)
951
+ .sort();
952
+ targetDir =
953
+ dirs.find((d: string) => d.startsWith(normalized + '-') || d === normalized) || null;
954
+ } catch {
955
+ // Phases directory may not exist; targetDir stays null
956
+ }
957
+
958
+ // Check for executed work (SUMMARY.md files) -- skip when dry-run
959
+ if (targetDir && !force && !dryRun) {
960
+ const targetPath: string = path.join(phasesDir, targetDir);
961
+ const files: string[] = fs.readdirSync(targetPath) as string[];
962
+ const summaries: string[] = files.filter(
963
+ (f: string) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md'
964
+ );
965
+ if (summaries.length > 0) {
966
+ error(
967
+ `Phase ${targetPhase} has ${summaries.length} executed plan(s). Use --force to remove anyway.`
968
+ );
969
+ }
970
+ }
971
+
972
+ // Dry-run: collect what would happen and return early
973
+ if (dryRun) {
974
+ // Predict which phases would be renumbered
975
+ const wouldRenumber: string[] = [];
976
+ try {
977
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
978
+ withFileTypes: true,
979
+ });
980
+ const dirs: string[] = entries
981
+ .filter((e: import('fs').Dirent) => e.isDirectory())
982
+ .map((e: import('fs').Dirent) => e.name)
983
+ .sort();
984
+ const removedInt: number = parseInt(normalized, 10);
985
+ for (const dir of dirs) {
986
+ const dm: RegExpMatchArray | null = dir.match(/^(\d+)(?:\.(\d+))?-(.+)$/);
987
+ if (!dm) continue;
988
+ const dirInt: number = parseInt(dm[1], 10);
989
+ if (dirInt > removedInt) {
990
+ wouldRenumber.push(dir);
991
+ }
992
+ }
993
+ } catch {
994
+ // Phases directory may not exist; dry-run preview will show empty renumber list
995
+ }
996
+
997
+ const result: PhaseRemoveResult = {
998
+ dry_run: true,
999
+ would_remove: targetDir || normalized,
1000
+ would_renumber: wouldRenumber,
1001
+ removed: targetPhase,
1002
+ directory_deleted: null,
1003
+ renamed_directories: [],
1004
+ renamed_files: [],
1005
+ roadmap_updated: false,
1006
+ state_updated: false,
1007
+ };
1008
+ output(result, raw, `dry-run: would remove phase ${targetPhase}`);
1009
+ return;
1010
+ }
1011
+
1012
+ // Delete target directory
1013
+ if (targetDir) {
1014
+ fs.rmSync(path.join(phasesDir, targetDir), {
1015
+ recursive: true,
1016
+ force: true,
1017
+ });
1018
+ }
1019
+
1020
+ // Clean up matching .worktrees/ directories
1021
+ const cleanedWorktrees: string[] = [];
1022
+ const worktreesDir: string = path.join(cwd, '.worktrees');
1023
+ if (fs.existsSync(worktreesDir)) {
1024
+ try {
1025
+ const wtEntries: import('fs').Dirent[] = fs.readdirSync(worktreesDir, {
1026
+ withFileTypes: true,
1027
+ });
1028
+ const wtPattern: RegExp = new RegExp(`-${normalized}(?:-|$)`);
1029
+ for (const entry of wtEntries) {
1030
+ if (entry.isDirectory() && wtPattern.test(entry.name)) {
1031
+ const wtPath: string = path.join(worktreesDir, entry.name);
1032
+ fs.rmSync(wtPath, { recursive: true, force: true });
1033
+ cleanedWorktrees.push(entry.name);
1034
+ }
1035
+ }
1036
+ } catch {
1037
+ // Non-fatal
1038
+ }
1039
+ }
1040
+
1041
+ // Renumber subsequent phases (directories + files)
1042
+ const { renamedDirs, renamedFiles }: ReorderResult = _reorderDirectories(
1043
+ phasesDir,
1044
+ normalized,
1045
+ isDecimal
1046
+ );
1047
+
1048
+ // Patch frontmatter refs for all renumbered phases
1049
+ _patchFrontmatterRefs(phasesDir, normalized, isDecimal ? normalized : normalized);
1050
+
1051
+ // Update ROADMAP.md (already read above)
1052
+ roadmapContent = _reorderRoadmapEntries(roadmapContent, targetPhase, normalized, isDecimal);
1053
+ writeRoadmapFile(roadmapPath, roadmapContent);
1054
+
1055
+ // Update STATE.md phase count
1056
+ const statePath: string = path.join(cwd, '.planning', 'STATE.md');
1057
+ if (fs.existsSync(statePath)) {
1058
+ let stateContent: string = readStateFile(statePath);
1059
+ // Update "Total Phases" field
1060
+ const totalPattern: RegExp = /(\*\*Total Phases:\*\*\s*)(\d+)/;
1061
+ const totalMatch: RegExpMatchArray | null = stateContent.match(totalPattern);
1062
+ if (totalMatch) {
1063
+ const oldTotal: number = parseInt(totalMatch[2], 10);
1064
+ stateContent = stateContent.replace(totalPattern, `$1${oldTotal - 1}`);
1065
+ }
1066
+ // Update "Phase: X of Y" pattern
1067
+ const ofPattern: RegExp = /(\bof\s+)(\d+)(\s*(?:\(|phases?))/i;
1068
+ const ofMatch: RegExpMatchArray | null = stateContent.match(ofPattern);
1069
+ if (ofMatch) {
1070
+ const oldTotal: number = parseInt(ofMatch[2], 10);
1071
+ stateContent = stateContent.replace(ofPattern, `$1${oldTotal - 1}$3`);
1072
+ }
1073
+ writeStateFile(statePath, stateContent);
1074
+ }
1075
+
1076
+ const result: PhaseRemoveResult = {
1077
+ removed: targetPhase,
1078
+ directory_deleted: targetDir || null,
1079
+ renamed_directories: renamedDirs,
1080
+ renamed_files: renamedFiles,
1081
+ roadmap_updated: true,
1082
+ state_updated: fs.existsSync(statePath),
1083
+ ...(cleanedWorktrees.length > 0 ? { cleaned_worktrees: cleanedWorktrees } : {}),
1084
+ };
1085
+
1086
+ output(result, raw, `Removed phase ${result.removed}`);
1087
+ }
1088
+
1089
+ // ─── Phase Complete (Transition) ──────────────────────────────────────────────
1090
+ // _phaseCompleteCore moved to lib/phase-complete.ts in Spec 3.
1091
+ // cmdPhaseComplete and cmdPhaseBatchComplete below import it from there.
1092
+
1093
+ /**
1094
+ * CLI command: Mark a phase as complete, update STATE.md, ROADMAP.md, and run quality analysis.
1095
+ * @param cwd - Project working directory
1096
+ * @param phaseNum - Phase number to complete (e.g., '02' or '2')
1097
+ * @param raw - Output raw text instead of JSON
1098
+ * @param options - Options (e.g., dryRun, force)
1099
+ * @returns void — writes JSON or raw text to stdout and exits on error
1100
+ */
1101
+ async function cmdPhaseComplete(
1102
+ cwd: string,
1103
+ phaseNum: string,
1104
+ raw: boolean,
1105
+ options?: PhaseCompleteOptions
1106
+ ): Promise<void> {
1107
+ if (!phaseNum) {
1108
+ error(
1109
+ "phase number required for phase complete. Usage: phase complete <N>. Provide the phase number to mark complete, e.g.: phase complete 3. Run 'phase list' to see available phases."
1110
+ );
1111
+ }
1112
+
1113
+ let result: PhaseCompleteResult;
1114
+ try {
1115
+ result = _phaseCompleteCore(cwd, phaseNum, options);
1116
+ } catch (e) {
1117
+ const config = loadConfig(cwd);
1118
+ if (config.phase_complete_llm_fallback === true) {
1119
+ process.stderr.write(`[phase-complete-llm] mechanical path failed, attempting fallback\n`);
1120
+ const { createScheduler } = require('./scheduler') as {
1121
+ createScheduler: (
1122
+ config: SchedulerConfig | undefined,
1123
+ superpowersConfig?: SuperpowersConfig
1124
+ ) => Scheduler | null;
1125
+ };
1126
+ const { attemptLlmFallbackCompletion } = require('./phase-complete-llm') as {
1127
+ attemptLlmFallbackCompletion: (
1128
+ cwd: string,
1129
+ phaseNum: string,
1130
+ scheduler: Scheduler | null,
1131
+ failure: Error | { gate_errors?: GateViolation[] }
1132
+ ) => Promise<PhaseCompleteResult | null>;
1133
+ };
1134
+ const scheduler = createScheduler(config.scheduler, config.superpowers);
1135
+ const fallbackResult = await attemptLlmFallbackCompletion(
1136
+ cwd,
1137
+ phaseNum,
1138
+ scheduler,
1139
+ e as Error
1140
+ );
1141
+ if (fallbackResult) {
1142
+ result = fallbackResult;
1143
+ } else {
1144
+ error((e as Error).message);
1145
+ return; // unreachable after error() but helps TS narrowing
1146
+ }
1147
+ } else {
1148
+ error((e as Error).message);
1149
+ return; // unreachable after error() but helps TS narrowing
1150
+ }
1151
+ }
1152
+
1153
+ // If the mechanical path returned gate_failed (not thrown), also try the LLM fallback.
1154
+ if (result.gate_failed) {
1155
+ const config = loadConfig(cwd);
1156
+ if (config.phase_complete_llm_fallback === true) {
1157
+ process.stderr.write(`[phase-complete-llm] gates failed, attempting fallback\n`);
1158
+ const { createScheduler } = require('./scheduler') as {
1159
+ createScheduler: (
1160
+ config: SchedulerConfig | undefined,
1161
+ superpowersConfig?: SuperpowersConfig
1162
+ ) => Scheduler | null;
1163
+ };
1164
+ const { attemptLlmFallbackCompletion } = require('./phase-complete-llm') as {
1165
+ attemptLlmFallbackCompletion: (
1166
+ cwd: string,
1167
+ phaseNum: string,
1168
+ scheduler: Scheduler | null,
1169
+ failure: Error | { gate_errors?: GateViolation[] }
1170
+ ) => Promise<PhaseCompleteResult | null>;
1171
+ };
1172
+ const scheduler = createScheduler(config.scheduler, config.superpowers);
1173
+ const fallbackResult = await attemptLlmFallbackCompletion(
1174
+ cwd,
1175
+ phaseNum,
1176
+ scheduler,
1177
+ { gate_errors: result.gate_errors }
1178
+ );
1179
+ if (fallbackResult) {
1180
+ result = fallbackResult;
1181
+ }
1182
+ // else: leave result as gate_failed — existing output logic renders it
1183
+ }
1184
+ }
1185
+
1186
+ let rawOutput = '';
1187
+ if (raw) {
1188
+ if (result.dry_run) {
1189
+ rawOutput = `dry-run: would complete phase ${phaseNum}`;
1190
+ } else if (result.gate_failed) {
1191
+ rawOutput = '';
1192
+ } else {
1193
+ rawOutput = `Phase ${phaseNum} complete. ${result.plans_executed} plans executed.`;
1194
+ if (result.next_phase) {
1195
+ rawOutput += ` Next: Phase ${result.next_phase}`;
1196
+ }
1197
+ if (
1198
+ result.quality_report &&
1199
+ result.quality_report.summary &&
1200
+ result.quality_report.summary.total_issues > 0
1201
+ ) {
1202
+ rawOutput += ` | Quality: ${result.quality_report.summary.total_issues} issue(s) found`;
1203
+ }
1204
+ if (result.cleanup_plan_generated) {
1205
+ rawOutput += ` | Cleanup plan generated: ${result.cleanup_plan_generated.path}`;
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ output(result, raw, rawOutput);
1211
+ }
1212
+
1213
+ // ─── Milestone Complete ───────────────────────────────────────────────────────
1214
+
1215
+ /**
1216
+ * Archive phase directories and supporting files (.planning/ROADMAP.md,
1217
+ * REQUIREMENTS.md, audit) for a completed milestone.
1218
+ * @param cwd - Project working directory
1219
+ * @param _version - Milestone version string (unused but kept for API consistency)
1220
+ * @param _sourceDir - Directory containing the phase subdirectories (unused but kept for API consistency)
1221
+ * @param archiveDir - Destination directory for archived files
1222
+ * @param ctx - Additional context for archival
1223
+ */
1224
+ function _archiveMilestone(
1225
+ cwd: string,
1226
+ version: string,
1227
+ _sourceDir: string,
1228
+ archiveDir: string,
1229
+ ctx: ArchiveContext
1230
+ ): ArchiveResult {
1231
+ const {
1232
+ roadmapPath,
1233
+ reqPath,
1234
+ milestoneName,
1235
+ today,
1236
+ phasesDir,
1237
+ phaseCount,
1238
+ totalPlans,
1239
+ totalTasks,
1240
+ accomplishments,
1241
+ phasesAlreadyInPlace,
1242
+ } = ctx;
1243
+
1244
+ // Archive phase directories to .planning/milestones/{version}-phases/
1245
+ const phasesArchiveDir: string = getArchivedPhasesDir(cwd, version);
1246
+ let archivedPhaseCount = 0;
1247
+
1248
+ if (phasesAlreadyInPlace) {
1249
+ // Phases already live under milestones/{version}/phases/ -- skip redundant copy
1250
+ archivedPhaseCount = phaseCount;
1251
+ } else {
1252
+ // Old-style layout -- copy phases to archive, then delete originals
1253
+ try {
1254
+ const phaseEntries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
1255
+ withFileTypes: true,
1256
+ });
1257
+ const phaseDirs: string[] = phaseEntries
1258
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1259
+ .map((e: import('fs').Dirent) => e.name);
1260
+ if (phaseDirs.length > 0) {
1261
+ fs.mkdirSync(phasesArchiveDir, { recursive: true });
1262
+ for (let _pi = 0; _pi < phaseDirs.length; _pi++) {
1263
+ const dir: string = phaseDirs[_pi];
1264
+ process.stderr.write(` Archiving phase ${_pi + 1}/${phaseDirs.length}: ${dir}\n`);
1265
+ fs.cpSync(path.join(phasesDir, dir), path.join(phasesArchiveDir, dir), {
1266
+ recursive: true,
1267
+ });
1268
+ fs.rmSync(path.join(phasesDir, dir), {
1269
+ recursive: true,
1270
+ force: true,
1271
+ });
1272
+ archivedPhaseCount++;
1273
+ }
1274
+ }
1275
+ } catch {
1276
+ // Phase archival is non-blocking
1277
+ }
1278
+ }
1279
+
1280
+ // Archive ROADMAP.md
1281
+ if (fs.existsSync(roadmapPath)) {
1282
+ const roadmapContent: string = readRoadmapFile(roadmapPath);
1283
+ fs.writeFileSync(path.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, 'utf-8');
1284
+ }
1285
+
1286
+ // Archive REQUIREMENTS.md
1287
+ if (fs.existsSync(reqPath)) {
1288
+ const reqContent: string = fs.readFileSync(reqPath, 'utf-8') as string;
1289
+ const archiveHeader: string = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
1290
+ fs.writeFileSync(
1291
+ path.join(archiveDir, `${version}-REQUIREMENTS.md`),
1292
+ archiveHeader + reqContent,
1293
+ 'utf-8'
1294
+ );
1295
+ }
1296
+
1297
+ // Archive audit file if exists
1298
+ const auditFile: string = path.join(cwd, '.planning', `${version}-MILESTONE-AUDIT.md`);
1299
+ if (fs.existsSync(auditFile)) {
1300
+ fs.renameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
1301
+ }
1302
+
1303
+ // Write archived.json metadata marker (REQ-60)
1304
+ const milestoneVersionDir: string = path.join(cwd, '.planning', 'milestones', version);
1305
+ fs.mkdirSync(milestoneVersionDir, { recursive: true });
1306
+ const markerPath: string = path.join(milestoneVersionDir, 'archived.json');
1307
+ const marker: {
1308
+ version: string;
1309
+ name: string;
1310
+ archived_date: string;
1311
+ phases: number;
1312
+ plans: number;
1313
+ tasks: number;
1314
+ accomplishments: string[];
1315
+ } = {
1316
+ version,
1317
+ name: milestoneName,
1318
+ archived_date: today,
1319
+ phases: phaseCount,
1320
+ plans: totalPlans,
1321
+ tasks: totalTasks,
1322
+ accomplishments,
1323
+ };
1324
+ fs.writeFileSync(markerPath, JSON.stringify(marker, null, 2) + '\n', 'utf-8');
1325
+
1326
+ return { archivedPhaseCount };
1327
+ }
1328
+
1329
+ /**
1330
+ * Patch STATE.md after a milestone is marked complete.
1331
+ * Updates Status, Last Activity, and Last Activity Description fields.
1332
+ * @param cwd - Project working directory
1333
+ * @param version - Milestone version string (e.g. 'v1.0')
1334
+ * @param today - ISO date string for today (YYYY-MM-DD)
1335
+ */
1336
+ function _updateStateAfterComplete(cwd: string, version: string, today: string): boolean {
1337
+ const statePath: string = path.join(cwd, '.planning', 'STATE.md');
1338
+ if (!fs.existsSync(statePath)) return false;
1339
+
1340
+ let stateContent: string = readStateFile(statePath);
1341
+ stateContent = stateContent.replace(/(\*\*Status:\*\*\s*).*/, `$1${version} milestone complete`);
1342
+ stateContent = stateContent.replace(/(\*\*Last Activity:\*\*\s*).*/, `$1${today}`);
1343
+ stateContent = stateContent.replace(
1344
+ /(\*\*Last Activity Description:\*\*\s*).*/,
1345
+ `$1${version} milestone completed and archived`
1346
+ );
1347
+ writeStateFile(statePath, stateContent);
1348
+ return true;
1349
+ }
1350
+
1351
+ /**
1352
+ * Rewrite MILESTONES.md after a milestone is complete, appending the new entry.
1353
+ * @param milestonesPath - Absolute path to MILESTONES.md
1354
+ * @param version - Milestone version string (e.g. 'v1.0')
1355
+ * @param milestoneName - Display name for the milestone
1356
+ * @param today - ISO date string for today (YYYY-MM-DD)
1357
+ * @param phaseCount - Number of phases completed
1358
+ * @param totalPlans - Total plans executed
1359
+ * @param totalTasks - Total tasks executed
1360
+ * @param accomplishments - List of one-liner accomplishment strings
1361
+ */
1362
+ function _rewriteRoadmapAfterComplete(
1363
+ milestonesPath: string,
1364
+ version: string,
1365
+ milestoneName: string,
1366
+ today: string,
1367
+ phaseCount: number,
1368
+ totalPlans: number,
1369
+ totalTasks: number,
1370
+ accomplishments: string[]
1371
+ ): void {
1372
+ const accomplishmentsList: string = accomplishments.map((a: string) => `- ${a}`).join('\n');
1373
+ const milestoneEntry: string = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
1374
+
1375
+ if (fs.existsSync(milestonesPath)) {
1376
+ const existing: string = fs.readFileSync(milestonesPath, 'utf-8') as string;
1377
+ fs.writeFileSync(milestonesPath, existing + '\n' + milestoneEntry, 'utf-8');
1378
+ } else {
1379
+ fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
1380
+ }
1381
+ }
1382
+
1383
+ /**
1384
+ * CLI command: Archive a completed milestone, gather stats, and update MILESTONES.md and STATE.md.
1385
+ * @param cwd - Project working directory
1386
+ * @param version - Milestone version to complete (e.g., 'v1.0')
1387
+ * @param options - Milestone options
1388
+ * @param raw - Output raw text instead of JSON
1389
+ * @returns void — writes JSON or raw text to stdout and exits on error
1390
+ */
1391
+ function cmdMilestoneComplete(
1392
+ cwd: string,
1393
+ version: string,
1394
+ options: MilestoneCompleteOptions,
1395
+ raw: boolean
1396
+ ): void {
1397
+ if (!version) {
1398
+ error(
1399
+ 'version required for milestone complete (e.g., v1.0). Usage: milestone complete <version>. Provide the milestone version, e.g.: milestone complete v1.2.0. Check .planning/ROADMAP.md for the current milestone version.'
1400
+ );
1401
+ }
1402
+
1403
+ const dryRun: boolean = (options && options.dryRun) || false;
1404
+
1405
+ // Dry-run: return preview without modifying anything
1406
+ if (dryRun) {
1407
+ const result: { dry_run: boolean; would_archive_version: string } = {
1408
+ dry_run: true,
1409
+ would_archive_version: version,
1410
+ };
1411
+ output(result, raw, `dry-run: would archive milestone ${version}`);
1412
+ return;
1413
+ }
1414
+
1415
+ // Pre-flight gate checks
1416
+ const gates: PreflightResult = runPreflightGates(cwd, 'milestone-complete');
1417
+ if (!gates.passed) {
1418
+ output(
1419
+ {
1420
+ gate_failed: true,
1421
+ gate_errors: gates.errors,
1422
+ gate_warnings: gates.warnings,
1423
+ },
1424
+ raw
1425
+ );
1426
+ return;
1427
+ }
1428
+
1429
+ const roadmapPath: string = path.join(cwd, '.planning', 'ROADMAP.md');
1430
+ const reqPath: string = path.join(cwd, '.planning', 'REQUIREMENTS.md');
1431
+ const statePath: string = path.join(cwd, '.planning', 'STATE.md');
1432
+ const milestonesPath: string = path.join(cwd, '.planning', 'MILESTONES.md');
1433
+ const archiveDir: string = getMilestonesDirPath(cwd);
1434
+ const phasesDir: string = getPhasesDirPath(cwd);
1435
+ const today: string = new Date().toISOString().split('T')[0];
1436
+ const milestoneName: string = options.name || version;
1437
+
1438
+ // Ensure archive directory exists
1439
+ fs.mkdirSync(archiveDir, { recursive: true });
1440
+
1441
+ // Check if phases are already under the milestone directory (new-style layout)
1442
+ const milestonePhaseDir: string = path.join(cwd, '.planning', 'milestones', version, 'phases');
1443
+ let phasesAlreadyInPlace = false;
1444
+ try {
1445
+ phasesAlreadyInPlace =
1446
+ fs.existsSync(milestonePhaseDir) &&
1447
+ (
1448
+ fs.readdirSync(milestonePhaseDir, {
1449
+ withFileTypes: true,
1450
+ }) as import('fs').Dirent[]
1451
+ ).some((e: import('fs').Dirent) => e.isDirectory());
1452
+ } catch {
1453
+ // Milestone phases directory may not exist; assume old-style layout
1454
+ }
1455
+
1456
+ // Determine the source directory for stat gathering
1457
+ const statsSourceDir: string = phasesAlreadyInPlace ? milestonePhaseDir : phasesDir;
1458
+
1459
+ // Gather stats from phases
1460
+ let phaseCount = 0;
1461
+ let totalPlans = 0;
1462
+ let totalTasks = 0;
1463
+ const accomplishments: string[] = [];
1464
+
1465
+ try {
1466
+ const entries: import('fs').Dirent[] = fs.readdirSync(statsSourceDir, {
1467
+ withFileTypes: true,
1468
+ });
1469
+ const dirs: string[] = entries
1470
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1471
+ .map((e: import('fs').Dirent) => e.name)
1472
+ .sort();
1473
+
1474
+ for (const dir of dirs) {
1475
+ phaseCount++;
1476
+ const phaseFiles: string[] = fs.readdirSync(path.join(statsSourceDir, dir)) as string[];
1477
+ const plans: string[] = phaseFiles.filter(
1478
+ (f: string) => f.endsWith('-PLAN.md') || f === 'PLAN.md'
1479
+ );
1480
+ const summaries: string[] = phaseFiles.filter(
1481
+ (f: string) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md'
1482
+ );
1483
+ totalPlans += plans.length;
1484
+
1485
+ // Extract one-liners from summaries
1486
+ for (let _si = 0; _si < summaries.length; _si++) {
1487
+ const s: string = summaries[_si];
1488
+ process.stderr.write(` Reading summary ${_si + 1}/${summaries.length}: ${s}\n`);
1489
+ try {
1490
+ const content: string = fs.readFileSync(
1491
+ path.join(statsSourceDir, dir, s),
1492
+ 'utf-8'
1493
+ ) as string;
1494
+ const fm: FrontmatterObject = extractFrontmatter(content);
1495
+ if (fm['one-liner']) {
1496
+ accomplishments.push(fm['one-liner'] as string);
1497
+ }
1498
+ // Count tasks
1499
+ const taskMatches: RegExpMatchArray | null = content.match(/##\s*Task\s*\d+/gi);
1500
+ totalTasks += taskMatches ? taskMatches.length : 0;
1501
+ } catch {
1502
+ // Summary file unreadable; skip one-liner and task count for this file
1503
+ }
1504
+ }
1505
+ }
1506
+ } catch {
1507
+ // Stats source directory may not exist; use zero stats
1508
+ }
1509
+
1510
+ // Archive phases, documents, and write metadata marker
1511
+ const archiveCtx: ArchiveContext = {
1512
+ roadmapPath,
1513
+ reqPath,
1514
+ milestoneName,
1515
+ today,
1516
+ phasesDir,
1517
+ phaseCount,
1518
+ totalPlans,
1519
+ totalTasks,
1520
+ accomplishments,
1521
+ phasesAlreadyInPlace,
1522
+ };
1523
+ const { archivedPhaseCount }: ArchiveResult = _archiveMilestone(
1524
+ cwd,
1525
+ version,
1526
+ statsSourceDir,
1527
+ archiveDir,
1528
+ archiveCtx
1529
+ );
1530
+
1531
+ // Append entry to MILESTONES.md
1532
+ _rewriteRoadmapAfterComplete(
1533
+ milestonesPath,
1534
+ version,
1535
+ milestoneName,
1536
+ today,
1537
+ phaseCount,
1538
+ totalPlans,
1539
+ totalTasks,
1540
+ accomplishments
1541
+ );
1542
+
1543
+ // Update STATE.md
1544
+ _updateStateAfterComplete(cwd, version, today);
1545
+
1546
+ // Merge milestone branch into base branch (if branching strategy is active)
1547
+ let gitMerge: GitMergeResult | null = null;
1548
+ try {
1549
+ const config: GrdConfig = loadConfig(cwd);
1550
+ if (config.branching_strategy && config.branching_strategy !== 'none') {
1551
+ const template: string = config.milestone_branch_template || 'grd/{milestone}-{slug}';
1552
+ let msName: string = milestoneName;
1553
+ try {
1554
+ const msInfo: MilestoneInfo = getMilestoneInfoUtil(cwd);
1555
+ msName = msInfo.name || milestoneName;
1556
+ } catch {
1557
+ // Use milestoneName from options
1558
+ }
1559
+ const msSlug: string = generateSlugInternal(msName) || 'milestone';
1560
+ const msBranch: string = template.replace('{milestone}', version).replace('{slug}', msSlug);
1561
+ const baseBranch: string = config.base_branch || 'main';
1562
+
1563
+ // Check if milestone branch exists
1564
+ const msCheck = execGit(cwd, ['rev-parse', '--verify', msBranch]);
1565
+ if (msCheck.exitCode !== 0) {
1566
+ gitMerge = {
1567
+ skipped: true,
1568
+ reason: `Milestone branch '${msBranch}' not found`,
1569
+ };
1570
+ } else {
1571
+ // Record current branch
1572
+ const headResult = execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
1573
+ const originalBranch: string =
1574
+ headResult.exitCode === 0 ? headResult.stdout.trim() : baseBranch;
1575
+
1576
+ // Checkout base branch
1577
+ const coResult = execGit(cwd, ['checkout', baseBranch]);
1578
+ if (coResult.exitCode !== 0) {
1579
+ gitMerge = {
1580
+ skipped: true,
1581
+ reason: `Failed to checkout '${baseBranch}'`,
1582
+ };
1583
+ } else {
1584
+ // Merge milestone branch
1585
+ const mergeResult = execGit(cwd, [
1586
+ 'merge',
1587
+ '--no-ff',
1588
+ msBranch,
1589
+ '-m',
1590
+ `Merge milestone ${version}: ${milestoneName}`,
1591
+ ]);
1592
+
1593
+ if (mergeResult.exitCode !== 0) {
1594
+ // Conflict -- abort and restore
1595
+ execGit(cwd, ['merge', '--abort']);
1596
+ execGit(cwd, ['checkout', originalBranch]);
1597
+ gitMerge = {
1598
+ error: 'Merge conflict',
1599
+ milestone_branch: msBranch,
1600
+ base_branch: baseBranch,
1601
+ };
1602
+ } else {
1603
+ // Delete milestone branch after successful merge
1604
+ execGit(cwd, ['branch', '-d', msBranch]);
1605
+ // Restore original branch (stay on base after milestone merge)
1606
+ if (originalBranch !== baseBranch) {
1607
+ execGit(cwd, ['checkout', originalBranch]);
1608
+ }
1609
+ gitMerge = {
1610
+ merged: true,
1611
+ milestone_branch: msBranch,
1612
+ base_branch: baseBranch,
1613
+ branch_deleted: true,
1614
+ };
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ } catch {
1620
+ // Git merge is non-blocking
1621
+ }
1622
+
1623
+ const result: MilestoneCompleteResult = {
1624
+ version,
1625
+ name: milestoneName,
1626
+ date: today,
1627
+ phases: phaseCount,
1628
+ plans: totalPlans,
1629
+ tasks: totalTasks,
1630
+ accomplishments,
1631
+ phases_already_in_place: phasesAlreadyInPlace,
1632
+ archived: {
1633
+ roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
1634
+ requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
1635
+ audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
1636
+ phases: archivedPhaseCount > 0,
1637
+ phase_count: archivedPhaseCount,
1638
+ marker: true,
1639
+ },
1640
+ milestones_updated: true,
1641
+ state_updated: fs.existsSync(statePath),
1642
+ ...(gitMerge ? { git_merge: gitMerge } : {}),
1643
+ };
1644
+
1645
+ output(
1646
+ result,
1647
+ raw,
1648
+ `Milestone ${result.version} complete: ${result.phases} phases, ${result.plans} plans`
1649
+ );
1650
+ }
1651
+
1652
+ // ─── Validate Consistency ─────────────────────────────────────────────────────
1653
+
1654
+ /**
1655
+ * CLI command: Validate phase numbering consistency between ROADMAP.md and disk directories.
1656
+ * @param cwd - Project working directory
1657
+ * @param raw - Output raw 'passed'/'failed' instead of JSON
1658
+ * @param options - Validation options (e.g., fix)
1659
+ * @returns void — writes JSON or raw text to stdout and exits on error
1660
+ */
1661
+ function cmdValidateConsistency(
1662
+ cwd: string,
1663
+ raw: boolean,
1664
+ options?: ValidateConsistencyOptions
1665
+ ): void {
1666
+ const fix: boolean = (options && options.fix) || false;
1667
+ const roadmapPath: string = path.join(cwd, '.planning', 'ROADMAP.md');
1668
+ const phasesDir: string = getPhasesDirPath(cwd);
1669
+ const errors_list: string[] = [];
1670
+ const warnings: string[] = [];
1671
+ const fixed: string[] = [];
1672
+
1673
+ // Check for ROADMAP
1674
+ let roadmapContent: string;
1675
+ try {
1676
+ roadmapContent = readRoadmapFile(roadmapPath);
1677
+ } catch {
1678
+ errors_list.push('ROADMAP.md not found');
1679
+ output({ passed: false, errors: errors_list, warnings } as ConsistencyResult, raw, 'failed');
1680
+ return;
1681
+ }
1682
+ const activeContent: string = stripShippedSections(roadmapContent);
1683
+
1684
+ // Extract phases from ROADMAP (active section only)
1685
+ const roadmapPhases: Set<string> = new Set();
1686
+ const phasePattern: RegExp = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
1687
+ let m: RegExpExecArray | null;
1688
+ while ((m = phasePattern.exec(activeContent)) !== null) {
1689
+ roadmapPhases.add(m[1]);
1690
+ }
1691
+
1692
+ // Get phases on disk
1693
+ const diskPhases: Set<string> = new Set();
1694
+ try {
1695
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
1696
+ withFileTypes: true,
1697
+ });
1698
+ const dirs: string[] = entries
1699
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1700
+ .map((e: import('fs').Dirent) => e.name);
1701
+ for (const dir of dirs) {
1702
+ const dm: RegExpMatchArray | null = dir.match(/^(\d+(?:\.\d+)?)/);
1703
+ if (dm) diskPhases.add(dm[1]);
1704
+ }
1705
+ } catch {
1706
+ // Phases directory may not exist; diskPhases stays empty
1707
+ }
1708
+
1709
+ // Check: phases in ROADMAP but not on disk
1710
+ for (const p of roadmapPhases) {
1711
+ if (!diskPhases.has(p) && !diskPhases.has(normalizePhaseName(p))) {
1712
+ warnings.push(`Phase ${p} in ROADMAP.md but no directory on disk`);
1713
+ }
1714
+ }
1715
+
1716
+ // Check: orphaned phases on disk but not in ROADMAP (errors, not warnings)
1717
+ const orphanViolations: GateViolation[] = checkOrphanedPhases(cwd);
1718
+ for (const v of orphanViolations) {
1719
+ errors_list.push(v.message);
1720
+ }
1721
+
1722
+ // Check: sequential phase numbers (integers only)
1723
+ const integerPhases: number[] = [...diskPhases]
1724
+ .filter((p: string) => !p.includes('.'))
1725
+ .map((p: string) => parseInt(p, 10))
1726
+ .sort((a: number, b: number) => a - b);
1727
+
1728
+ for (let i = 1; i < integerPhases.length; i++) {
1729
+ if (integerPhases[i] !== integerPhases[i - 1] + 1) {
1730
+ warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} \u2192 ${integerPhases[i]}`);
1731
+ }
1732
+ }
1733
+
1734
+ // Check: plan numbering within phases
1735
+ try {
1736
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
1737
+ withFileTypes: true,
1738
+ });
1739
+ const dirs: string[] = entries
1740
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1741
+ .map((e: import('fs').Dirent) => e.name)
1742
+ .sort();
1743
+
1744
+ for (const dir of dirs) {
1745
+ const phaseFiles: string[] = fs.readdirSync(path.join(phasesDir, dir)) as string[];
1746
+ const plans: string[] = phaseFiles.filter((f: string) => f.endsWith('-PLAN.md')).sort();
1747
+
1748
+ // Extract plan numbers
1749
+ const planNums: number[] = plans
1750
+ .map((p: string) => {
1751
+ const pm: RegExpMatchArray | null = p.match(/-(\d{2})-PLAN\.md$/);
1752
+ return pm ? parseInt(pm[1], 10) : null;
1753
+ })
1754
+ .filter((n: number | null): n is number => n !== null);
1755
+
1756
+ for (let i = 1; i < planNums.length; i++) {
1757
+ if (planNums[i] !== planNums[i - 1] + 1) {
1758
+ warnings.push(
1759
+ `Gap in plan numbering in ${dir}: plan ${planNums[i - 1]} \u2192 ${planNums[i]}`
1760
+ );
1761
+ }
1762
+ }
1763
+
1764
+ // Check: plans without summaries (completed plans)
1765
+ const summaries: string[] = phaseFiles.filter((f: string) => f.endsWith('-SUMMARY.md'));
1766
+ const planIds: Set<string> = new Set(plans.map((p: string) => p.replace('-PLAN.md', '')));
1767
+ const summaryIds: Set<string> = new Set(
1768
+ summaries.map((s: string) => s.replace('-SUMMARY.md', ''))
1769
+ );
1770
+
1771
+ // Summary without matching plan is suspicious (orphaned)
1772
+ for (const sid of summaryIds) {
1773
+ if (!planIds.has(sid)) {
1774
+ const orphanFile: string = `${sid}-SUMMARY.md`;
1775
+ const orphanPath: string = path.join(phasesDir, dir, orphanFile);
1776
+ if (fix) {
1777
+ try {
1778
+ fs.unlinkSync(orphanPath);
1779
+ fixed.push(orphanPath);
1780
+ } catch {
1781
+ warnings.push(`Failed to remove orphaned summary: ${orphanFile} in ${dir}`);
1782
+ }
1783
+ } else {
1784
+ warnings.push(`Orphaned summary ${orphanFile} in ${dir} has no matching PLAN.md`);
1785
+ }
1786
+ }
1787
+ }
1788
+ }
1789
+ } catch {
1790
+ // Phases directory may not exist; skip plan-numbering and orphan checks
1791
+ }
1792
+
1793
+ // Check: frontmatter in plans has required fields
1794
+ try {
1795
+ const entries: import('fs').Dirent[] = fs.readdirSync(phasesDir, {
1796
+ withFileTypes: true,
1797
+ });
1798
+ const dirs: string[] = entries
1799
+ .filter((e: import('fs').Dirent) => e.isDirectory())
1800
+ .map((e: import('fs').Dirent) => e.name);
1801
+
1802
+ for (const dir of dirs) {
1803
+ const phaseFiles: string[] = fs.readdirSync(path.join(phasesDir, dir)) as string[];
1804
+ const plans: string[] = phaseFiles.filter((f: string) => f.endsWith('-PLAN.md'));
1805
+
1806
+ for (const plan of plans) {
1807
+ const content: string = fs.readFileSync(path.join(phasesDir, dir, plan), 'utf-8') as string;
1808
+ const fm: FrontmatterObject = extractFrontmatter(content);
1809
+
1810
+ if (!fm.wave) {
1811
+ warnings.push(`${dir}/${plan}: missing 'wave' in frontmatter`);
1812
+ }
1813
+ }
1814
+ }
1815
+ } catch {
1816
+ // Phases directory may not exist; skip frontmatter validation
1817
+ }
1818
+
1819
+ const passed: boolean = errors_list.length === 0;
1820
+ output(
1821
+ {
1822
+ passed,
1823
+ errors: errors_list,
1824
+ warnings,
1825
+ warning_count: warnings.length,
1826
+ ...(fix ? { fixed } : {}),
1827
+ } as ConsistencyResult,
1828
+ raw,
1829
+ passed ? 'passed' : 'failed'
1830
+ );
1831
+ }
1832
+
1833
+ // ─── Version Bump ─────────────────────────────────────────────────────────────
1834
+
1835
+ /**
1836
+ * CLI command: Bump version in plugin.json, VERSION, and package.json.
1837
+ * @param cwd - Project working directory
1838
+ * @param version - Version string (with or without 'v' prefix)
1839
+ * @param raw - Output raw text instead of JSON
1840
+ * @returns void — writes JSON or raw text to stdout and exits on error
1841
+ */
1842
+ function cmdVersionBump(cwd: string, version: string, raw: boolean): void {
1843
+ if (!version) {
1844
+ error(
1845
+ 'version required for version bump (e.g., v1.0.0). Usage: milestone version-bump <version>. Provide the new version, e.g.: milestone version-bump v1.3.0. Current version can be found in .planning/config.json. To check current version: cat .planning/config.json | grep version'
1846
+ );
1847
+ }
1848
+
1849
+ // Strip leading 'v' prefix
1850
+ const semver: string = version.replace(/^v/, '');
1851
+
1852
+ const files: Record<string, string> = {
1853
+ VERSION: path.join(cwd, 'VERSION'),
1854
+ 'package.json': path.join(cwd, 'package.json'),
1855
+ '.claude-plugin/plugin.json': path.join(cwd, '.claude-plugin', 'plugin.json'),
1856
+ };
1857
+
1858
+ const updated: string[] = [];
1859
+
1860
+ // Update VERSION file
1861
+ if (fs.existsSync(files.VERSION)) {
1862
+ fs.writeFileSync(files.VERSION, semver + '\n', 'utf-8');
1863
+ updated.push('VERSION');
1864
+ }
1865
+
1866
+ // Update package.json
1867
+ if (fs.existsSync(files['package.json'])) {
1868
+ const pkg: { version: string } = JSON.parse(
1869
+ fs.readFileSync(files['package.json'], 'utf-8') as string
1870
+ );
1871
+ pkg.version = semver;
1872
+ fs.writeFileSync(files['package.json'], JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
1873
+ updated.push('package.json');
1874
+ }
1875
+
1876
+ // Update .claude-plugin/plugin.json
1877
+ if (fs.existsSync(files['.claude-plugin/plugin.json'])) {
1878
+ const plugin: { version: string } = JSON.parse(
1879
+ fs.readFileSync(files['.claude-plugin/plugin.json'], 'utf-8') as string
1880
+ );
1881
+ plugin.version = semver;
1882
+ fs.writeFileSync(
1883
+ files['.claude-plugin/plugin.json'],
1884
+ JSON.stringify(plugin, null, 2) + '\n',
1885
+ 'utf-8'
1886
+ );
1887
+ updated.push('.claude-plugin/plugin.json');
1888
+ }
1889
+
1890
+ const result: {
1891
+ version: string;
1892
+ files_updated: string[];
1893
+ count: number;
1894
+ } = {
1895
+ version: semver,
1896
+ files_updated: updated,
1897
+ count: updated.length,
1898
+ };
1899
+
1900
+ output(result, raw, `Bumped ${updated.length} files to ${semver}`);
1901
+ }
1902
+
1903
+ // ─── Batch Phase Complete ─────────────────────────────────────────────────────
1904
+
1905
+ /**
1906
+ * CLI command: Complete multiple phases in a single call.
1907
+ * @param cwd - Project working directory
1908
+ * @param phases - Array of phase numbers to complete
1909
+ * @param options - Options passed to each cmdPhaseComplete call
1910
+ * @param raw - Output raw text instead of JSON
1911
+ * @returns void — writes JSON or raw text to stdout and exits on error
1912
+ */
1913
+ function cmdPhaseBatchComplete(
1914
+ cwd: string,
1915
+ phases: string[],
1916
+ options: PhaseBatchCompleteOptions,
1917
+ raw: boolean
1918
+ ): void {
1919
+ if (!phases || phases.length === 0) {
1920
+ output({ error: 'phases list is required and must not be empty' }, raw, 'error');
1921
+ return;
1922
+ }
1923
+
1924
+ const results: Array<{
1925
+ phase: string;
1926
+ result?: PhaseCompleteResult;
1927
+ error?: string;
1928
+ }> = [];
1929
+ let completedCount = 0;
1930
+
1931
+ for (const phase of phases) {
1932
+ try {
1933
+ const phaseResult: PhaseCompleteResult = _phaseCompleteCore(cwd, phase, options);
1934
+ completedCount++;
1935
+ results.push({ phase, result: phaseResult });
1936
+ } catch (e) {
1937
+ results.push({ phase, error: (e as Error).message });
1938
+ }
1939
+ }
1940
+
1941
+ output(
1942
+ {
1943
+ results,
1944
+ total_phases: phases.length,
1945
+ completed_count: completedCount,
1946
+ } as BatchCompleteResult,
1947
+ raw,
1948
+ `Completed ${completedCount}/${phases.length} phases`
1949
+ );
1950
+ }
1951
+
1952
+ // ─── Atomic Write ─────────────────────────────────────────────────────────────
1953
+
1954
+ /**
1955
+ * Write content to a file atomically using a .tmp intermediate file.
1956
+ * On success, the .tmp file is renamed to the target path.
1957
+ * On failure, the original file is left untouched.
1958
+ * @param filePath - Absolute path to the target file
1959
+ * @param content - Content to write
1960
+ * @returns void — throws on write or rename failure
1961
+ */
1962
+ function atomicWriteFile(filePath: string, content: string): void {
1963
+ const tmpPath: string = filePath + '.tmp';
1964
+ fs.writeFileSync(tmpPath, content, 'utf-8');
1965
+ fs.renameSync(tmpPath, filePath);
1966
+ }
1967
+
1968
+ // ─── Exports ──────────────────────────────────────────────────────────────────
1969
+
1970
+ module.exports = {
1971
+ cmdPhasesList,
1972
+ cmdPhaseAdd,
1973
+ cmdPhaseInsert,
1974
+ cmdPhaseRemove,
1975
+ cmdPhaseComplete,
1976
+ cmdMilestoneComplete,
1977
+ cmdValidateConsistency,
1978
+ cmdVersionBump,
1979
+ cmdPhaseBatchComplete,
1980
+ atomicWriteFile,
1981
+ };