gsd-remix 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (554) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +939 -0
  3. package/README.zh-CN.md +876 -0
  4. package/agents/gsd-advisor-researcher.md +127 -0
  5. package/agents/gsd-ai-researcher.md +133 -0
  6. package/agents/gsd-assumptions-analyzer.md +105 -0
  7. package/agents/gsd-code-fixer.md +517 -0
  8. package/agents/gsd-code-reviewer.md +371 -0
  9. package/agents/gsd-codebase-mapper.md +781 -0
  10. package/agents/gsd-debug-session-manager.md +314 -0
  11. package/agents/gsd-debugger.md +1452 -0
  12. package/agents/gsd-doc-classifier.md +168 -0
  13. package/agents/gsd-doc-synthesizer.md +204 -0
  14. package/agents/gsd-doc-verifier.md +217 -0
  15. package/agents/gsd-doc-writer.md +615 -0
  16. package/agents/gsd-domain-researcher.md +153 -0
  17. package/agents/gsd-eval-auditor.md +191 -0
  18. package/agents/gsd-eval-planner.md +154 -0
  19. package/agents/gsd-executor.md +603 -0
  20. package/agents/gsd-framework-selector.md +160 -0
  21. package/agents/gsd-integration-checker.md +470 -0
  22. package/agents/gsd-intel-updater.md +334 -0
  23. package/agents/gsd-nyquist-auditor.md +203 -0
  24. package/agents/gsd-pattern-mapper.md +335 -0
  25. package/agents/gsd-phase-researcher.md +841 -0
  26. package/agents/gsd-plan-checker.md +978 -0
  27. package/agents/gsd-planner.md +1251 -0
  28. package/agents/gsd-project-researcher.md +677 -0
  29. package/agents/gsd-research-synthesizer.md +247 -0
  30. package/agents/gsd-roadmapper.md +688 -0
  31. package/agents/gsd-security-auditor.md +155 -0
  32. package/agents/gsd-ui-auditor.md +495 -0
  33. package/agents/gsd-ui-checker.md +309 -0
  34. package/agents/gsd-ui-researcher.md +380 -0
  35. package/agents/gsd-user-profiler.md +171 -0
  36. package/agents/gsd-verifier.md +830 -0
  37. package/bin/install.js +7062 -0
  38. package/commands/gsd/add-backlog.md +79 -0
  39. package/commands/gsd/add-phase.md +43 -0
  40. package/commands/gsd/add-tests.md +41 -0
  41. package/commands/gsd/add-todo.md +47 -0
  42. package/commands/gsd/ai-integration-phase.md +36 -0
  43. package/commands/gsd/analyze-dependencies.md +34 -0
  44. package/commands/gsd/audit-fix.md +33 -0
  45. package/commands/gsd/audit-milestone.md +36 -0
  46. package/commands/gsd/audit-uat.md +24 -0
  47. package/commands/gsd/autonomous.md +46 -0
  48. package/commands/gsd/check-todos.md +45 -0
  49. package/commands/gsd/cleanup.md +23 -0
  50. package/commands/gsd/code-review-fix.md +52 -0
  51. package/commands/gsd/code-review.md +55 -0
  52. package/commands/gsd/complete-milestone.md +136 -0
  53. package/commands/gsd/debug.md +263 -0
  54. package/commands/gsd/discuss-phase.md +69 -0
  55. package/commands/gsd/do.md +30 -0
  56. package/commands/gsd/docs-update.md +48 -0
  57. package/commands/gsd/eval-review.md +32 -0
  58. package/commands/gsd/execute-phase.md +63 -0
  59. package/commands/gsd/explore.md +27 -0
  60. package/commands/gsd/extract_learnings.md +22 -0
  61. package/commands/gsd/fast.md +30 -0
  62. package/commands/gsd/forensics.md +56 -0
  63. package/commands/gsd/from-gsd2.md +47 -0
  64. package/commands/gsd/graphify.md +201 -0
  65. package/commands/gsd/health.md +22 -0
  66. package/commands/gsd/help.md +24 -0
  67. package/commands/gsd/import.md +37 -0
  68. package/commands/gsd/inbox.md +38 -0
  69. package/commands/gsd/ingest-docs.md +42 -0
  70. package/commands/gsd/insert-phase.md +32 -0
  71. package/commands/gsd/intel.md +179 -0
  72. package/commands/gsd/join-discord.md +19 -0
  73. package/commands/gsd/list-phase-assumptions.md +46 -0
  74. package/commands/gsd/list-workspaces.md +19 -0
  75. package/commands/gsd/manager.md +40 -0
  76. package/commands/gsd/map-codebase.md +71 -0
  77. package/commands/gsd/milestone-summary.md +51 -0
  78. package/commands/gsd/new-milestone.md +44 -0
  79. package/commands/gsd/new-project.md +46 -0
  80. package/commands/gsd/new-workspace.md +44 -0
  81. package/commands/gsd/next.md +28 -0
  82. package/commands/gsd/note.md +34 -0
  83. package/commands/gsd/pause-work.md +38 -0
  84. package/commands/gsd/plan-milestone-gaps.md +34 -0
  85. package/commands/gsd/plan-phase.md +52 -0
  86. package/commands/gsd/plan-review-convergence.md +52 -0
  87. package/commands/gsd/plant-seed.md +28 -0
  88. package/commands/gsd/pr-branch.md +25 -0
  89. package/commands/gsd/profile-user.md +46 -0
  90. package/commands/gsd/progress.md +25 -0
  91. package/commands/gsd/quick.md +173 -0
  92. package/commands/gsd/reapply-patches.md +331 -0
  93. package/commands/gsd/remove-phase.md +31 -0
  94. package/commands/gsd/remove-workspace.md +26 -0
  95. package/commands/gsd/research-phase.md +195 -0
  96. package/commands/gsd/resume-work.md +40 -0
  97. package/commands/gsd/review-backlog.md +62 -0
  98. package/commands/gsd/review.md +40 -0
  99. package/commands/gsd/scan.md +26 -0
  100. package/commands/gsd/secure-phase.md +35 -0
  101. package/commands/gsd/session-report.md +19 -0
  102. package/commands/gsd/set-profile.md +12 -0
  103. package/commands/gsd/settings.md +36 -0
  104. package/commands/gsd/ship.md +23 -0
  105. package/commands/gsd/sketch-wrap-up.md +31 -0
  106. package/commands/gsd/sketch.md +49 -0
  107. package/commands/gsd/spec-phase.md +62 -0
  108. package/commands/gsd/spike-wrap-up.md +31 -0
  109. package/commands/gsd/spike.md +46 -0
  110. package/commands/gsd/stats.md +18 -0
  111. package/commands/gsd/sync-skills.md +19 -0
  112. package/commands/gsd/thread.md +227 -0
  113. package/commands/gsd/ui-phase.md +34 -0
  114. package/commands/gsd/ui-review.md +32 -0
  115. package/commands/gsd/ultraplan-phase.md +33 -0
  116. package/commands/gsd/undo.md +34 -0
  117. package/commands/gsd/update.md +37 -0
  118. package/commands/gsd/validate-phase.md +35 -0
  119. package/commands/gsd/verify-work.md +38 -0
  120. package/commands/gsd/workstreams.md +69 -0
  121. package/get-shit-done/bin/gsd-tools.cjs +1263 -0
  122. package/get-shit-done/bin/lib/artifacts.cjs +52 -0
  123. package/get-shit-done/bin/lib/audit.cjs +757 -0
  124. package/get-shit-done/bin/lib/commands.cjs +1023 -0
  125. package/get-shit-done/bin/lib/config-schema.cjs +79 -0
  126. package/get-shit-done/bin/lib/config.cjs +463 -0
  127. package/get-shit-done/bin/lib/core.cjs +1794 -0
  128. package/get-shit-done/bin/lib/docs.cjs +267 -0
  129. package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
  130. package/get-shit-done/bin/lib/graphify.cjs +494 -0
  131. package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
  132. package/get-shit-done/bin/lib/init.cjs +1878 -0
  133. package/get-shit-done/bin/lib/intel.cjs +639 -0
  134. package/get-shit-done/bin/lib/learnings.cjs +378 -0
  135. package/get-shit-done/bin/lib/milestone.cjs +283 -0
  136. package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
  137. package/get-shit-done/bin/lib/phase.cjs +1058 -0
  138. package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
  139. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  140. package/get-shit-done/bin/lib/roadmap.cjs +523 -0
  141. package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
  142. package/get-shit-done/bin/lib/security.cjs +504 -0
  143. package/get-shit-done/bin/lib/state.cjs +1649 -0
  144. package/get-shit-done/bin/lib/template.cjs +226 -0
  145. package/get-shit-done/bin/lib/uat.cjs +288 -0
  146. package/get-shit-done/bin/lib/verify.cjs +1184 -0
  147. package/get-shit-done/bin/lib/workstream.cjs +495 -0
  148. package/get-shit-done/bin/repair-sdk.cjs +177 -0
  149. package/get-shit-done/contexts/dev.md +21 -0
  150. package/get-shit-done/contexts/research.md +22 -0
  151. package/get-shit-done/contexts/review.md +22 -0
  152. package/get-shit-done/references/agent-contracts.md +79 -0
  153. package/get-shit-done/references/ai-evals.md +156 -0
  154. package/get-shit-done/references/ai-frameworks.md +186 -0
  155. package/get-shit-done/references/artifact-types.md +131 -0
  156. package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
  157. package/get-shit-done/references/checkpoints.md +808 -0
  158. package/get-shit-done/references/common-bug-patterns.md +114 -0
  159. package/get-shit-done/references/context-budget.md +49 -0
  160. package/get-shit-done/references/continuation-format.md +253 -0
  161. package/get-shit-done/references/debugger-philosophy.md +76 -0
  162. package/get-shit-done/references/decimal-phase-calculation.md +64 -0
  163. package/get-shit-done/references/doc-conflict-engine.md +91 -0
  164. package/get-shit-done/references/domain-probes.md +125 -0
  165. package/get-shit-done/references/executor-examples.md +110 -0
  166. package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
  167. package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
  168. package/get-shit-done/references/gate-prompts.md +100 -0
  169. package/get-shit-done/references/gates.md +70 -0
  170. package/get-shit-done/references/git-integration.md +295 -0
  171. package/get-shit-done/references/git-planning-commit.md +40 -0
  172. package/get-shit-done/references/ios-scaffold.md +123 -0
  173. package/get-shit-done/references/mandatory-initial-read.md +2 -0
  174. package/get-shit-done/references/model-profile-resolution.md +38 -0
  175. package/get-shit-done/references/model-profiles.md +145 -0
  176. package/get-shit-done/references/phase-argument-parsing.md +61 -0
  177. package/get-shit-done/references/planner-antipatterns.md +89 -0
  178. package/get-shit-done/references/planner-gap-closure.md +62 -0
  179. package/get-shit-done/references/planner-reviews.md +39 -0
  180. package/get-shit-done/references/planner-revision.md +87 -0
  181. package/get-shit-done/references/planner-source-audit.md +73 -0
  182. package/get-shit-done/references/planning-config.md +460 -0
  183. package/get-shit-done/references/project-skills-discovery.md +19 -0
  184. package/get-shit-done/references/questioning.md +162 -0
  185. package/get-shit-done/references/revision-loop.md +97 -0
  186. package/get-shit-done/references/sketch-interactivity.md +41 -0
  187. package/get-shit-done/references/sketch-theme-system.md +94 -0
  188. package/get-shit-done/references/sketch-tooling.md +45 -0
  189. package/get-shit-done/references/sketch-variant-patterns.md +81 -0
  190. package/get-shit-done/references/tdd.md +330 -0
  191. package/get-shit-done/references/thinking-models-debug.md +44 -0
  192. package/get-shit-done/references/thinking-models-execution.md +50 -0
  193. package/get-shit-done/references/thinking-models-planning.md +62 -0
  194. package/get-shit-done/references/thinking-models-research.md +50 -0
  195. package/get-shit-done/references/thinking-models-verification.md +55 -0
  196. package/get-shit-done/references/thinking-partner.md +96 -0
  197. package/get-shit-done/references/ui-brand.md +160 -0
  198. package/get-shit-done/references/universal-anti-patterns.md +63 -0
  199. package/get-shit-done/references/user-profiling.md +681 -0
  200. package/get-shit-done/references/verification-overrides.md +227 -0
  201. package/get-shit-done/references/verification-patterns.md +612 -0
  202. package/get-shit-done/references/workstream-flag.md +111 -0
  203. package/get-shit-done/templates/AI-SPEC.md +246 -0
  204. package/get-shit-done/templates/DEBUG.md +169 -0
  205. package/get-shit-done/templates/README.md +76 -0
  206. package/get-shit-done/templates/SECURITY.md +61 -0
  207. package/get-shit-done/templates/UAT.md +265 -0
  208. package/get-shit-done/templates/UI-SPEC.md +100 -0
  209. package/get-shit-done/templates/VALIDATION.md +76 -0
  210. package/get-shit-done/templates/claude-md.md +145 -0
  211. package/get-shit-done/templates/codebase/architecture.md +255 -0
  212. package/get-shit-done/templates/codebase/concerns.md +310 -0
  213. package/get-shit-done/templates/codebase/conventions.md +307 -0
  214. package/get-shit-done/templates/codebase/integrations.md +280 -0
  215. package/get-shit-done/templates/codebase/stack.md +186 -0
  216. package/get-shit-done/templates/codebase/structure.md +285 -0
  217. package/get-shit-done/templates/codebase/testing.md +480 -0
  218. package/get-shit-done/templates/config.json +56 -0
  219. package/get-shit-done/templates/context.md +352 -0
  220. package/get-shit-done/templates/continue-here.md +78 -0
  221. package/get-shit-done/templates/copilot-instructions.md +7 -0
  222. package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
  223. package/get-shit-done/templates/dev-preferences.md +21 -0
  224. package/get-shit-done/templates/discovery.md +146 -0
  225. package/get-shit-done/templates/discussion-log.md +63 -0
  226. package/get-shit-done/templates/milestone-archive.md +123 -0
  227. package/get-shit-done/templates/milestone.md +115 -0
  228. package/get-shit-done/templates/phase-prompt.md +610 -0
  229. package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
  230. package/get-shit-done/templates/project.md +186 -0
  231. package/get-shit-done/templates/requirements.md +231 -0
  232. package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
  233. package/get-shit-done/templates/research-project/FEATURES.md +147 -0
  234. package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
  235. package/get-shit-done/templates/research-project/STACK.md +120 -0
  236. package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
  237. package/get-shit-done/templates/research.md +592 -0
  238. package/get-shit-done/templates/retrospective.md +54 -0
  239. package/get-shit-done/templates/roadmap.md +202 -0
  240. package/get-shit-done/templates/spec.md +307 -0
  241. package/get-shit-done/templates/state.md +184 -0
  242. package/get-shit-done/templates/summary-complex.md +59 -0
  243. package/get-shit-done/templates/summary-minimal.md +41 -0
  244. package/get-shit-done/templates/summary-standard.md +48 -0
  245. package/get-shit-done/templates/summary.md +248 -0
  246. package/get-shit-done/templates/user-profile.md +146 -0
  247. package/get-shit-done/templates/user-setup.md +311 -0
  248. package/get-shit-done/templates/verification-report.md +322 -0
  249. package/get-shit-done/workflows/add-phase.md +112 -0
  250. package/get-shit-done/workflows/add-tests.md +354 -0
  251. package/get-shit-done/workflows/add-todo.md +160 -0
  252. package/get-shit-done/workflows/ai-integration-phase.md +284 -0
  253. package/get-shit-done/workflows/analyze-dependencies.md +96 -0
  254. package/get-shit-done/workflows/audit-fix.md +175 -0
  255. package/get-shit-done/workflows/audit-milestone.md +340 -0
  256. package/get-shit-done/workflows/audit-uat.md +109 -0
  257. package/get-shit-done/workflows/autonomous.md +789 -0
  258. package/get-shit-done/workflows/check-todos.md +179 -0
  259. package/get-shit-done/workflows/cleanup.md +154 -0
  260. package/get-shit-done/workflows/code-review-fix.md +497 -0
  261. package/get-shit-done/workflows/code-review.md +515 -0
  262. package/get-shit-done/workflows/complete-milestone.md +847 -0
  263. package/get-shit-done/workflows/diagnose-issues.md +238 -0
  264. package/get-shit-done/workflows/discovery-phase.md +291 -0
  265. package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
  266. package/get-shit-done/workflows/discuss-phase-power.md +308 -0
  267. package/get-shit-done/workflows/discuss-phase.md +1378 -0
  268. package/get-shit-done/workflows/do.md +110 -0
  269. package/get-shit-done/workflows/docs-update.md +1155 -0
  270. package/get-shit-done/workflows/eval-review.md +155 -0
  271. package/get-shit-done/workflows/execute-phase.md +1677 -0
  272. package/get-shit-done/workflows/execute-plan.md +533 -0
  273. package/get-shit-done/workflows/explore.md +141 -0
  274. package/get-shit-done/workflows/extract_learnings.md +242 -0
  275. package/get-shit-done/workflows/fast.md +105 -0
  276. package/get-shit-done/workflows/forensics.md +265 -0
  277. package/get-shit-done/workflows/graduation.md +195 -0
  278. package/get-shit-done/workflows/health.md +314 -0
  279. package/get-shit-done/workflows/help.md +667 -0
  280. package/get-shit-done/workflows/import.md +246 -0
  281. package/get-shit-done/workflows/inbox.md +387 -0
  282. package/get-shit-done/workflows/ingest-docs.md +328 -0
  283. package/get-shit-done/workflows/insert-phase.md +130 -0
  284. package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
  285. package/get-shit-done/workflows/list-workspaces.md +56 -0
  286. package/get-shit-done/workflows/manager.md +365 -0
  287. package/get-shit-done/workflows/map-codebase.md +393 -0
  288. package/get-shit-done/workflows/milestone-summary.md +223 -0
  289. package/get-shit-done/workflows/new-milestone.md +611 -0
  290. package/get-shit-done/workflows/new-project.md +1391 -0
  291. package/get-shit-done/workflows/new-workspace.md +239 -0
  292. package/get-shit-done/workflows/next.md +220 -0
  293. package/get-shit-done/workflows/node-repair.md +92 -0
  294. package/get-shit-done/workflows/note.md +158 -0
  295. package/get-shit-done/workflows/pause-work.md +243 -0
  296. package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
  297. package/get-shit-done/workflows/plan-phase.md +1349 -0
  298. package/get-shit-done/workflows/plan-review-convergence.md +254 -0
  299. package/get-shit-done/workflows/plant-seed.md +172 -0
  300. package/get-shit-done/workflows/pr-branch.md +157 -0
  301. package/get-shit-done/workflows/profile-user.md +452 -0
  302. package/get-shit-done/workflows/progress.md +619 -0
  303. package/get-shit-done/workflows/quick.md +970 -0
  304. package/get-shit-done/workflows/remove-phase.md +155 -0
  305. package/get-shit-done/workflows/remove-workspace.md +92 -0
  306. package/get-shit-done/workflows/research-phase.md +89 -0
  307. package/get-shit-done/workflows/resume-project.md +326 -0
  308. package/get-shit-done/workflows/review.md +344 -0
  309. package/get-shit-done/workflows/scan.md +102 -0
  310. package/get-shit-done/workflows/secure-phase.md +166 -0
  311. package/get-shit-done/workflows/session-report.md +146 -0
  312. package/get-shit-done/workflows/settings.md +319 -0
  313. package/get-shit-done/workflows/ship.md +302 -0
  314. package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
  315. package/get-shit-done/workflows/sketch.md +286 -0
  316. package/get-shit-done/workflows/spec-phase.md +262 -0
  317. package/get-shit-done/workflows/spike-wrap-up.md +281 -0
  318. package/get-shit-done/workflows/spike.md +362 -0
  319. package/get-shit-done/workflows/stats.md +60 -0
  320. package/get-shit-done/workflows/sync-skills.md +182 -0
  321. package/get-shit-done/workflows/transition.md +693 -0
  322. package/get-shit-done/workflows/ui-phase.md +323 -0
  323. package/get-shit-done/workflows/ui-review.md +190 -0
  324. package/get-shit-done/workflows/ultraplan-phase.md +189 -0
  325. package/get-shit-done/workflows/undo.md +314 -0
  326. package/get-shit-done/workflows/update.md +587 -0
  327. package/get-shit-done/workflows/validate-phase.md +176 -0
  328. package/get-shit-done/workflows/verify-phase.md +465 -0
  329. package/get-shit-done/workflows/verify-work.md +740 -0
  330. package/hooks/dist/gsd-check-update-worker.js +108 -0
  331. package/hooks/dist/gsd-check-update.js +64 -0
  332. package/hooks/dist/gsd-context-monitor.js +192 -0
  333. package/hooks/dist/gsd-phase-boundary.sh +28 -0
  334. package/hooks/dist/gsd-prompt-guard.js +97 -0
  335. package/hooks/dist/gsd-read-guard.js +82 -0
  336. package/hooks/dist/gsd-read-injection-scanner.js +152 -0
  337. package/hooks/dist/gsd-session-state.sh +34 -0
  338. package/hooks/dist/gsd-statusline.js +293 -0
  339. package/hooks/dist/gsd-validate-commit.sh +48 -0
  340. package/hooks/dist/gsd-workflow-guard.js +94 -0
  341. package/hooks/gsd-check-update-worker.js +108 -0
  342. package/hooks/gsd-check-update.js +64 -0
  343. package/hooks/gsd-context-monitor.js +192 -0
  344. package/hooks/gsd-phase-boundary.sh +28 -0
  345. package/hooks/gsd-prompt-guard.js +97 -0
  346. package/hooks/gsd-read-guard.js +82 -0
  347. package/hooks/gsd-read-injection-scanner.js +152 -0
  348. package/hooks/gsd-session-state.sh +34 -0
  349. package/hooks/gsd-statusline.js +293 -0
  350. package/hooks/gsd-validate-commit.sh +48 -0
  351. package/hooks/gsd-workflow-guard.js +94 -0
  352. package/package.json +59 -0
  353. package/scripts/base64-scan.sh +262 -0
  354. package/scripts/build-hooks.js +95 -0
  355. package/scripts/gen-inventory-manifest.cjs +109 -0
  356. package/scripts/prompt-injection-scan.sh +201 -0
  357. package/scripts/run-tests.cjs +33 -0
  358. package/scripts/secret-scan.sh +227 -0
  359. package/sdk/package-lock.json +1998 -0
  360. package/sdk/package.json +52 -0
  361. package/sdk/prompts/agents/gsd-executor.md +110 -0
  362. package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
  363. package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
  364. package/sdk/prompts/agents/gsd-planner.md +214 -0
  365. package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
  366. package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
  367. package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
  368. package/sdk/prompts/agents/gsd-verifier.md +159 -0
  369. package/sdk/prompts/templates/project.md +186 -0
  370. package/sdk/prompts/templates/requirements.md +231 -0
  371. package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
  372. package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
  373. package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
  374. package/sdk/prompts/templates/research-project/STACK.md +120 -0
  375. package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
  376. package/sdk/prompts/templates/roadmap.md +202 -0
  377. package/sdk/prompts/templates/state.md +175 -0
  378. package/sdk/prompts/workflows/discuss-phase.md +126 -0
  379. package/sdk/prompts/workflows/execute-plan.md +106 -0
  380. package/sdk/prompts/workflows/plan-phase.md +84 -0
  381. package/sdk/prompts/workflows/research-phase.md +45 -0
  382. package/sdk/prompts/workflows/verify-phase.md +142 -0
  383. package/sdk/src/assembled-prompts.test.ts +349 -0
  384. package/sdk/src/cli-transport.test.ts +388 -0
  385. package/sdk/src/cli-transport.ts +130 -0
  386. package/sdk/src/cli.test.ts +383 -0
  387. package/sdk/src/cli.ts +670 -0
  388. package/sdk/src/config.test.ts +168 -0
  389. package/sdk/src/config.ts +177 -0
  390. package/sdk/src/context-engine.test.ts +295 -0
  391. package/sdk/src/context-engine.ts +170 -0
  392. package/sdk/src/context-truncation.test.ts +163 -0
  393. package/sdk/src/context-truncation.ts +233 -0
  394. package/sdk/src/e2e.integration.test.ts +178 -0
  395. package/sdk/src/errors.ts +72 -0
  396. package/sdk/src/event-stream.test.ts +661 -0
  397. package/sdk/src/event-stream.ts +441 -0
  398. package/sdk/src/failure-memory.test.ts +457 -0
  399. package/sdk/src/failure-memory.ts +1324 -0
  400. package/sdk/src/golden/capture.ts +95 -0
  401. package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
  402. package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
  403. package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
  404. package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
  405. package/sdk/src/golden/golden-integration-covered.ts +30 -0
  406. package/sdk/src/golden/golden-mutation-covered.ts +7 -0
  407. package/sdk/src/golden/golden-policy.test.ts +8 -0
  408. package/sdk/src/golden/golden-policy.ts +112 -0
  409. package/sdk/src/golden/golden.integration.test.ts +373 -0
  410. package/sdk/src/golden/init-golden-normalize.ts +15 -0
  411. package/sdk/src/golden/read-only-golden-rows.ts +77 -0
  412. package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
  413. package/sdk/src/golden/registry-canonical-commands.ts +31 -0
  414. package/sdk/src/gsd-tools.test.ts +409 -0
  415. package/sdk/src/gsd-tools.ts +595 -0
  416. package/sdk/src/headless-prompts.test.ts +159 -0
  417. package/sdk/src/index.ts +333 -0
  418. package/sdk/src/init-e2e.integration.test.ts +136 -0
  419. package/sdk/src/init-runner.test.ts +783 -0
  420. package/sdk/src/init-runner.ts +735 -0
  421. package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
  422. package/sdk/src/logger.test.ts +149 -0
  423. package/sdk/src/logger.ts +113 -0
  424. package/sdk/src/milestone-runner.test.ts +421 -0
  425. package/sdk/src/phase-prompt.test.ts +538 -0
  426. package/sdk/src/phase-prompt.ts +264 -0
  427. package/sdk/src/phase-runner-types.test.ts +421 -0
  428. package/sdk/src/phase-runner.integration.test.ts +377 -0
  429. package/sdk/src/phase-runner.test.ts +2333 -0
  430. package/sdk/src/phase-runner.ts +1203 -0
  431. package/sdk/src/plan-parser.test.ts +528 -0
  432. package/sdk/src/plan-parser.ts +427 -0
  433. package/sdk/src/prompt-builder.test.ts +306 -0
  434. package/sdk/src/prompt-builder.ts +193 -0
  435. package/sdk/src/prompt-sanitizer.test.ts +260 -0
  436. package/sdk/src/prompt-sanitizer.ts +71 -0
  437. package/sdk/src/query/QUERY-HANDLERS.md +317 -0
  438. package/sdk/src/query/audit-open.ts +722 -0
  439. package/sdk/src/query/check-auto-mode.test.ts +77 -0
  440. package/sdk/src/query/check-auto-mode.ts +50 -0
  441. package/sdk/src/query/check-completion.test.ts +113 -0
  442. package/sdk/src/query/check-completion.ts +182 -0
  443. package/sdk/src/query/check-gates.test.ts +103 -0
  444. package/sdk/src/query/check-gates.ts +112 -0
  445. package/sdk/src/query/check-ship-ready.test.ts +77 -0
  446. package/sdk/src/query/check-ship-ready.ts +103 -0
  447. package/sdk/src/query/check-verification-status.test.ts +143 -0
  448. package/sdk/src/query/check-verification-status.ts +160 -0
  449. package/sdk/src/query/commit.test.ts +202 -0
  450. package/sdk/src/query/commit.ts +301 -0
  451. package/sdk/src/query/config-gates.test.ts +89 -0
  452. package/sdk/src/query/config-gates.ts +69 -0
  453. package/sdk/src/query/config-mutation.test.ts +365 -0
  454. package/sdk/src/query/config-mutation.ts +497 -0
  455. package/sdk/src/query/config-query.test.ts +161 -0
  456. package/sdk/src/query/config-query.ts +190 -0
  457. package/sdk/src/query/context-history.test.ts +165 -0
  458. package/sdk/src/query/context-history.ts +467 -0
  459. package/sdk/src/query/decomposed-handlers.test.ts +365 -0
  460. package/sdk/src/query/detect-custom-files.ts +97 -0
  461. package/sdk/src/query/detect-phase-type.test.ts +105 -0
  462. package/sdk/src/query/detect-phase-type.ts +141 -0
  463. package/sdk/src/query/docs-init.ts +257 -0
  464. package/sdk/src/query/failure-capture.ts +58 -0
  465. package/sdk/src/query/frontmatter-array.test.ts +14 -0
  466. package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
  467. package/sdk/src/query/frontmatter-mutation.ts +343 -0
  468. package/sdk/src/query/frontmatter.test.ts +281 -0
  469. package/sdk/src/query/frontmatter.ts +397 -0
  470. package/sdk/src/query/helpers.test.ts +426 -0
  471. package/sdk/src/query/helpers.ts +482 -0
  472. package/sdk/src/query/index.ts +586 -0
  473. package/sdk/src/query/init-complex.test.ts +232 -0
  474. package/sdk/src/query/init-complex.ts +578 -0
  475. package/sdk/src/query/init.test.ts +522 -0
  476. package/sdk/src/query/init.ts +1046 -0
  477. package/sdk/src/query/intel.test.ts +90 -0
  478. package/sdk/src/query/intel.ts +404 -0
  479. package/sdk/src/query/normalize-query-command.test.ts +50 -0
  480. package/sdk/src/query/normalize-query-command.ts +56 -0
  481. package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
  482. package/sdk/src/query/phase-lifecycle.ts +1799 -0
  483. package/sdk/src/query/phase-list-queries.test.ts +88 -0
  484. package/sdk/src/query/phase-list-queries.ts +152 -0
  485. package/sdk/src/query/phase-ready.test.ts +65 -0
  486. package/sdk/src/query/phase-ready.ts +158 -0
  487. package/sdk/src/query/phase.test.ts +307 -0
  488. package/sdk/src/query/phase.ts +340 -0
  489. package/sdk/src/query/pipeline.test.ts +169 -0
  490. package/sdk/src/query/pipeline.ts +243 -0
  491. package/sdk/src/query/plan-execution-route.test.ts +166 -0
  492. package/sdk/src/query/plan-execution-route.ts +209 -0
  493. package/sdk/src/query/plan-task-structure.test.ts +65 -0
  494. package/sdk/src/query/plan-task-structure.ts +63 -0
  495. package/sdk/src/query/profile-extract-messages.ts +247 -0
  496. package/sdk/src/query/profile-output.ts +908 -0
  497. package/sdk/src/query/profile-questionnaire-data.ts +181 -0
  498. package/sdk/src/query/profile-sample.ts +184 -0
  499. package/sdk/src/query/profile-scan-sessions.ts +174 -0
  500. package/sdk/src/query/profile.test.ts +74 -0
  501. package/sdk/src/query/profile.ts +337 -0
  502. package/sdk/src/query/progress.test.ts +156 -0
  503. package/sdk/src/query/progress.ts +566 -0
  504. package/sdk/src/query/registry.test.ts +216 -0
  505. package/sdk/src/query/registry.ts +174 -0
  506. package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
  507. package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
  508. package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
  509. package/sdk/src/query/roadmap.test.ts +359 -0
  510. package/sdk/src/query/roadmap.ts +591 -0
  511. package/sdk/src/query/route-next-action.test.ts +61 -0
  512. package/sdk/src/query/route-next-action.ts +345 -0
  513. package/sdk/src/query/runtime-health.ts +7 -0
  514. package/sdk/src/query/schema-detect.ts +189 -0
  515. package/sdk/src/query/skill-manifest.ts +214 -0
  516. package/sdk/src/query/skills.test.ts +80 -0
  517. package/sdk/src/query/skills.ts +62 -0
  518. package/sdk/src/query/state-mutation.test.ts +450 -0
  519. package/sdk/src/query/state-mutation.ts +1444 -0
  520. package/sdk/src/query/state-project-load.ts +109 -0
  521. package/sdk/src/query/state.test.ts +347 -0
  522. package/sdk/src/query/state.ts +397 -0
  523. package/sdk/src/query/summary.test.ts +95 -0
  524. package/sdk/src/query/summary.ts +296 -0
  525. package/sdk/src/query/template.test.ts +180 -0
  526. package/sdk/src/query/template.ts +242 -0
  527. package/sdk/src/query/uat.test.ts +77 -0
  528. package/sdk/src/query/uat.ts +314 -0
  529. package/sdk/src/query/utils.test.ts +82 -0
  530. package/sdk/src/query/utils.ts +92 -0
  531. package/sdk/src/query/validate.test.ts +656 -0
  532. package/sdk/src/query/validate.ts +807 -0
  533. package/sdk/src/query/verify.test.ts +414 -0
  534. package/sdk/src/query/verify.ts +645 -0
  535. package/sdk/src/query/websearch.test.ts +31 -0
  536. package/sdk/src/query/websearch.ts +82 -0
  537. package/sdk/src/query/workspace.test.ts +119 -0
  538. package/sdk/src/query/workspace.ts +131 -0
  539. package/sdk/src/query/workstream.test.ts +51 -0
  540. package/sdk/src/query/workstream.ts +434 -0
  541. package/sdk/src/research-gate.test.ts +190 -0
  542. package/sdk/src/research-gate.ts +94 -0
  543. package/sdk/src/runtime-health.test.ts +176 -0
  544. package/sdk/src/runtime-health.ts +387 -0
  545. package/sdk/src/session-runner.test.ts +98 -0
  546. package/sdk/src/session-runner.ts +299 -0
  547. package/sdk/src/tool-scoping.test.ts +160 -0
  548. package/sdk/src/tool-scoping.ts +61 -0
  549. package/sdk/src/types.ts +917 -0
  550. package/sdk/src/workstream-utils.ts +33 -0
  551. package/sdk/src/ws-flag.test.ts +285 -0
  552. package/sdk/src/ws-transport.test.ts +161 -0
  553. package/sdk/src/ws-transport.ts +93 -0
  554. package/sdk/tsconfig.json +20 -0
@@ -0,0 +1,1126 @@
1
+ /**
2
+ * Unit tests for phase lifecycle handlers.
3
+ *
4
+ * Tests phaseAdd, phaseAddBatch, phaseInsert, phaseScaffold, replaceInCurrentMilestone,
5
+ * and readModifyWriteRoadmapMd.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { mkdtemp, writeFile, readFile, rm, mkdir, readdir } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+ import { existsSync } from 'node:fs';
13
+
14
+ // ─── Fixtures ─────────────────────────────────────────────────────────────
15
+
16
+ const MINIMAL_ROADMAP = `# Roadmap
17
+
18
+ ## Current Milestone: v3.0 SDK-First Migration
19
+
20
+ ### Phase 9: Foundation
21
+
22
+ **Goal:** Build foundation
23
+ **Requirements**: TBD
24
+ **Depends on:** Phase 8
25
+ **Plans:** 3 plans
26
+
27
+ Plans:
28
+ - [x] 09-01 (Foundation setup)
29
+
30
+ ### Phase 10: Read-Only Queries
31
+
32
+ **Goal:** Port queries.
33
+ **Requirements**: TBD
34
+ **Depends on:** Phase 9
35
+ **Plans:** 3 plans
36
+
37
+ Plans:
38
+ - [x] 10-01 (Query setup)
39
+
40
+ ---
41
+ *Last updated: 2026-04-08*
42
+ `;
43
+
44
+ const ROADMAP_WITH_DETAILS = `# Roadmap
45
+
46
+ <details>
47
+ <summary>v1.0 (shipped)</summary>
48
+
49
+ ### Phase 1: Old Phase
50
+
51
+ **Goal:** Shipped already
52
+ **Plans:** 2 plans
53
+
54
+ </details>
55
+
56
+ ## Current Milestone: v3.0 SDK-First Migration
57
+
58
+ ### Phase 9: Foundation
59
+
60
+ **Goal:** Build foundation
61
+ **Requirements**: TBD
62
+ **Plans:** 3 plans
63
+
64
+ ### Phase 10: Read-Only Queries
65
+
66
+ **Goal:** Port queries.
67
+ **Requirements**: TBD
68
+ **Plans:** 3 plans
69
+
70
+ ---
71
+ *Last updated: 2026-04-08*
72
+ `;
73
+
74
+ const MINIMAL_STATE = `---
75
+ gsd_state_version: 1.0
76
+ milestone: v3.0
77
+ milestone_name: SDK-First Migration
78
+ status: executing
79
+ ---
80
+
81
+ # Project State
82
+
83
+ ## Current Position
84
+
85
+ Phase: 10 (Read-Only Queries) — EXECUTING
86
+ Plan: 2 of 3
87
+ Status: Executing Phase 10
88
+
89
+ ## Session Continuity
90
+
91
+ Last session: 2026-04-07T10:00:00.000Z
92
+ Stopped at: Completed 10-02-PLAN.md
93
+ `;
94
+
95
+ /** Create a test project with .planning structure. */
96
+ async function setupTestProject(
97
+ tmpDir: string,
98
+ opts?: { roadmap?: string; state?: string; config?: Record<string, unknown>; phases?: string[] }
99
+ ): Promise<string> {
100
+ const planningDir = join(tmpDir, '.planning');
101
+ await mkdir(planningDir, { recursive: true });
102
+ const phasesDir = join(planningDir, 'phases');
103
+ await mkdir(phasesDir, { recursive: true });
104
+ await writeFile(join(planningDir, 'ROADMAP.md'), opts?.roadmap || MINIMAL_ROADMAP, 'utf-8');
105
+ await writeFile(join(planningDir, 'STATE.md'), opts?.state || MINIMAL_STATE, 'utf-8');
106
+ await writeFile(
107
+ join(planningDir, 'config.json'),
108
+ JSON.stringify(opts?.config || { model_profile: 'balanced', phase_naming: 'sequential' }),
109
+ 'utf-8'
110
+ );
111
+ // Create phase directories if requested
112
+ if (opts?.phases) {
113
+ for (const phase of opts.phases) {
114
+ await mkdir(join(phasesDir, phase), { recursive: true });
115
+ await writeFile(join(phasesDir, phase, '.gitkeep'), '', 'utf-8');
116
+ }
117
+ }
118
+ return tmpDir;
119
+ }
120
+
121
+ // ─── Tests ────────────────────────────────────────────────────────────────
122
+
123
+ let tmpDir: string;
124
+
125
+ beforeEach(async () => {
126
+ tmpDir = await mkdtemp(join(tmpdir(), 'gsd-lifecycle-'));
127
+ });
128
+
129
+ afterEach(async () => {
130
+ await rm(tmpDir, { recursive: true, force: true });
131
+ });
132
+
133
+ // ─── replaceInCurrentMilestone ──────────────────────────────────────────
134
+
135
+ describe('replaceInCurrentMilestone', () => {
136
+ it('replaces in full content when no details blocks', async () => {
137
+ const { replaceInCurrentMilestone } = await import('./phase-lifecycle.js');
138
+ const content = '### Phase 9: Foundation\n**Plans:** 3 plans\n';
139
+ const result = replaceInCurrentMilestone(content, /3 plans/, '4 plans');
140
+ expect(result).toContain('4 plans');
141
+ });
142
+
143
+ it('only replaces after last </details> block', async () => {
144
+ const { replaceInCurrentMilestone } = await import('./phase-lifecycle.js');
145
+ const content = '<details>\n### Phase 1: Old\n**Plans:** 3 plans\n</details>\n\n### Phase 9: Current\n**Plans:** 3 plans\n';
146
+ const result = replaceInCurrentMilestone(content, /3 plans/, '4 plans');
147
+ // Should only replace in the current milestone section (after </details>)
148
+ const before = result.slice(0, result.indexOf('</details>') + '</details>'.length);
149
+ const after = result.slice(result.indexOf('</details>') + '</details>'.length);
150
+ expect(before).toContain('3 plans'); // old milestone untouched
151
+ expect(after).toContain('4 plans'); // current milestone updated
152
+ });
153
+ });
154
+
155
+ // ─── readModifyWriteRoadmapMd ───────────────────────────────────────────
156
+
157
+ describe('readModifyWriteRoadmapMd', () => {
158
+ it('reads, modifies, and writes ROADMAP.md atomically', async () => {
159
+ const { readModifyWriteRoadmapMd } = await import('./phase-lifecycle.js');
160
+ await setupTestProject(tmpDir);
161
+ const result = await readModifyWriteRoadmapMd(tmpDir, (content) => {
162
+ return content.replace('Port queries.', 'Port all queries.');
163
+ });
164
+ expect(result).toContain('Port all queries.');
165
+ const ondisk = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
166
+ expect(ondisk).toContain('Port all queries.');
167
+ });
168
+
169
+ it('creates and releases lockfile', async () => {
170
+ const { readModifyWriteRoadmapMd } = await import('./phase-lifecycle.js');
171
+ await setupTestProject(tmpDir);
172
+ await readModifyWriteRoadmapMd(tmpDir, (c) => c);
173
+ // Lock should be released after operation
174
+ const lockPath = join(tmpDir, '.planning', 'ROADMAP.md.lock');
175
+ expect(existsSync(lockPath)).toBe(false);
176
+ });
177
+ });
178
+
179
+ // ─── phaseAdd ──────────────────────────────────────────────────────────
180
+
181
+ describe('phaseAdd', () => {
182
+ it('creates directory and updates ROADMAP.md for sequential phase', async () => {
183
+ const { phaseAdd } = await import('./phase-lifecycle.js');
184
+ await setupTestProject(tmpDir, {
185
+ phases: ['09-foundation', '10-read-only-queries'],
186
+ });
187
+
188
+ const result = await phaseAdd(['New Feature'], tmpDir);
189
+ const data = result.data as Record<string, unknown>;
190
+
191
+ expect(data.phase_number).toBe(11);
192
+ expect(data.padded).toBe('11');
193
+ expect(data.name).toBe('New Feature');
194
+ expect(data.slug).toBe('new-feature');
195
+ expect(data.naming_mode).toBe('sequential');
196
+
197
+ // Verify directory was created
198
+ const dir = data.directory as string;
199
+ expect(dir).toContain('11-new-feature');
200
+ const phasesDir = join(tmpDir, '.planning', 'phases');
201
+ const entries = await readdir(phasesDir, { withFileTypes: true });
202
+ const newDir = entries.find(e => e.isDirectory() && e.name.includes('11-new-feature'));
203
+ expect(newDir).toBeTruthy();
204
+
205
+ // Verify .gitkeep
206
+ expect(existsSync(join(phasesDir, newDir!.name, '.gitkeep'))).toBe(true);
207
+
208
+ // Verify ROADMAP.md updated
209
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
210
+ expect(roadmap).toContain('### Phase 11: New Feature');
211
+ expect(roadmap).toContain('**Goal:** [To be planned]');
212
+ });
213
+
214
+ it('skips phases >= 999 when calculating next number (backlog exclusion)', async () => {
215
+ const { phaseAdd } = await import('./phase-lifecycle.js');
216
+ const roadmapWith999 = MINIMAL_ROADMAP.replace(
217
+ '---\n*Last updated',
218
+ '### Phase 999: Backlog\n\n**Goal:** Backlog items\n**Plans:** 0 plans\n\n---\n*Last updated'
219
+ );
220
+ await setupTestProject(tmpDir, { roadmap: roadmapWith999 });
221
+
222
+ const result = await phaseAdd(['After Ten'], tmpDir);
223
+ const data = result.data as Record<string, unknown>;
224
+ // Should be 11, not 1000
225
+ expect(data.phase_number).toBe(11);
226
+ });
227
+
228
+ it('throws GSDError with Validation for empty description', async () => {
229
+ const { phaseAdd } = await import('./phase-lifecycle.js');
230
+ await setupTestProject(tmpDir);
231
+
232
+ await expect(phaseAdd([], tmpDir)).rejects.toThrow('description required');
233
+ });
234
+
235
+ it('inserts phase entry before last --- separator', async () => {
236
+ const { phaseAdd } = await import('./phase-lifecycle.js');
237
+ await setupTestProject(tmpDir);
238
+
239
+ await phaseAdd(['Inserted Phase'], tmpDir);
240
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
241
+
242
+ // The new phase should appear before the trailing ---
243
+ const phaseIdx = roadmap.indexOf('### Phase 11: Inserted Phase');
244
+ const sepIdx = roadmap.lastIndexOf('\n---');
245
+ expect(phaseIdx).toBeLessThan(sepIdx);
246
+ expect(phaseIdx).toBeGreaterThan(0);
247
+ });
248
+ });
249
+
250
+ // ─── phaseAddBatch ─────────────────────────────────────────────────────
251
+
252
+ describe('phaseAddBatch', () => {
253
+ it('adds multiple sequential phases in one pass', async () => {
254
+ const { phaseAddBatch } = await import('./phase-lifecycle.js');
255
+ await setupTestProject(tmpDir, {
256
+ phases: ['09-foundation', '10-read-only-queries'],
257
+ });
258
+
259
+ const result = await phaseAddBatch(['Alpha', 'Beta'], tmpDir);
260
+ const data = result.data as { phases: Array<Record<string, unknown>>; count: number };
261
+
262
+ expect(data.count).toBe(2);
263
+ expect(data.phases[0].phase_number).toBe(11);
264
+ expect(data.phases[0].name).toBe('Alpha');
265
+ expect(data.phases[1].phase_number).toBe(12);
266
+ expect(data.phases[1].name).toBe('Beta');
267
+
268
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
269
+ expect(roadmap).toContain('### Phase 11: Alpha');
270
+ expect(roadmap).toContain('### Phase 12: Beta');
271
+
272
+ const phasesDir = join(tmpDir, '.planning', 'phases');
273
+ expect(existsSync(join(phasesDir, '11-alpha', '.gitkeep'))).toBe(true);
274
+ expect(existsSync(join(phasesDir, '12-beta', '.gitkeep'))).toBe(true);
275
+ });
276
+
277
+ it('accepts --descriptions JSON array', async () => {
278
+ const { phaseAddBatch } = await import('./phase-lifecycle.js');
279
+ await setupTestProject(tmpDir, { phases: ['09-foundation', '10-read-only-queries'] });
280
+
281
+ const result = await phaseAddBatch(
282
+ ['--descriptions', JSON.stringify(['One', 'Two'])],
283
+ tmpDir,
284
+ );
285
+ const data = result.data as { count: number };
286
+ expect(data.count).toBe(2);
287
+ });
288
+
289
+ it('throws when no descriptions', async () => {
290
+ const { phaseAddBatch } = await import('./phase-lifecycle.js');
291
+ await setupTestProject(tmpDir);
292
+
293
+ await expect(phaseAddBatch([], tmpDir)).rejects.toThrow('descriptions array required');
294
+ });
295
+ });
296
+
297
+ // ─── phaseInsert ────────────────────────────────────────────────────────
298
+
299
+ describe('phaseInsert', () => {
300
+ it('creates decimal phase directory after target phase', async () => {
301
+ const { phaseInsert } = await import('./phase-lifecycle.js');
302
+ await setupTestProject(tmpDir, {
303
+ phases: ['09-foundation', '10-read-only-queries'],
304
+ });
305
+
306
+ const result = await phaseInsert(['10', 'Urgent Fix'], tmpDir);
307
+ const data = result.data as Record<string, unknown>;
308
+
309
+ expect(data.phase_number).toBe('10.1');
310
+ expect(data.after_phase).toBe('10');
311
+ expect(data.name).toBe('Urgent Fix');
312
+ expect(data.slug).toBe('urgent-fix');
313
+
314
+ // Verify directory created
315
+ const dir = data.directory as string;
316
+ expect(dir).toContain('10.1-urgent-fix');
317
+ const phasesDir = join(tmpDir, '.planning', 'phases');
318
+ const entries = await readdir(phasesDir, { withFileTypes: true });
319
+ const newDir = entries.find(e => e.isDirectory() && e.name.includes('10.1-urgent-fix'));
320
+ expect(newDir).toBeTruthy();
321
+ });
322
+
323
+ it('scans both directories and ROADMAP.md for existing decimals to avoid collisions', async () => {
324
+ const { phaseInsert } = await import('./phase-lifecycle.js');
325
+ await setupTestProject(tmpDir, {
326
+ phases: ['09-foundation', '10-read-only-queries', '10.1-hotfix'],
327
+ });
328
+
329
+ const result = await phaseInsert(['10', 'Another Fix'], tmpDir);
330
+ const data = result.data as Record<string, unknown>;
331
+ // Should be 10.2 since 10.1 already exists on disk
332
+ expect(data.phase_number).toBe('10.2');
333
+ });
334
+
335
+ it('inserts section in ROADMAP.md after target phase', async () => {
336
+ const { phaseInsert } = await import('./phase-lifecycle.js');
337
+ await setupTestProject(tmpDir);
338
+
339
+ await phaseInsert(['10', 'Urgent Fix'], tmpDir);
340
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
341
+
342
+ expect(roadmap).toContain('### Phase 10.1: Urgent Fix (INSERTED)');
343
+ // Should appear after Phase 10 section
344
+ const phase10Idx = roadmap.indexOf('### Phase 10:');
345
+ const insertedIdx = roadmap.indexOf('### Phase 10.1:');
346
+ expect(insertedIdx).toBeGreaterThan(phase10Idx);
347
+ });
348
+
349
+ it('throws GSDError for missing target phase', async () => {
350
+ const { phaseInsert } = await import('./phase-lifecycle.js');
351
+ await setupTestProject(tmpDir);
352
+
353
+ await expect(phaseInsert(['99', 'Missing'], tmpDir)).rejects.toThrow('Phase 99 not found');
354
+ });
355
+
356
+ it('throws GSDError with Validation for missing args', async () => {
357
+ const { phaseInsert } = await import('./phase-lifecycle.js');
358
+ await setupTestProject(tmpDir);
359
+
360
+ await expect(phaseInsert([], tmpDir)).rejects.toThrow('after-phase and description required');
361
+ });
362
+ });
363
+
364
+ // ─── phaseScaffold ──────────────────────────────────────────────────────
365
+
366
+ describe('phaseScaffold', () => {
367
+ it('creates context template for a phase', async () => {
368
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
369
+ await setupTestProject(tmpDir, {
370
+ phases: ['09-foundation'],
371
+ });
372
+
373
+ const result = await phaseScaffold(['context', '9'], tmpDir);
374
+ const data = result.data as Record<string, unknown>;
375
+
376
+ expect(data.created).toBe(true);
377
+ const filePath = data.path as string;
378
+ expect(filePath).toContain('09-CONTEXT.md');
379
+
380
+ // Check content
381
+ const fullPath = join(tmpDir, '.planning', 'phases', '09-foundation', '09-CONTEXT.md');
382
+ expect(existsSync(fullPath)).toBe(true);
383
+ const content = await readFile(fullPath, 'utf-8');
384
+ expect(content).toContain('phase: "09"');
385
+ expect(content).toContain('Context');
386
+ });
387
+
388
+ it('creates uat template', async () => {
389
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
390
+ await setupTestProject(tmpDir, {
391
+ phases: ['09-foundation'],
392
+ });
393
+
394
+ const result = await phaseScaffold(['uat', '9'], tmpDir);
395
+ const data = result.data as Record<string, unknown>;
396
+
397
+ expect(data.created).toBe(true);
398
+ const fullPath = join(tmpDir, '.planning', 'phases', '09-foundation', '09-UAT.md');
399
+ expect(existsSync(fullPath)).toBe(true);
400
+ const content = await readFile(fullPath, 'utf-8');
401
+ expect(content).toContain('User Acceptance Testing');
402
+ });
403
+
404
+ it('creates verification template', async () => {
405
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
406
+ await setupTestProject(tmpDir, {
407
+ phases: ['09-foundation'],
408
+ });
409
+
410
+ const result = await phaseScaffold(['verification', '9'], tmpDir);
411
+ const data = result.data as Record<string, unknown>;
412
+
413
+ expect(data.created).toBe(true);
414
+ const fullPath = join(tmpDir, '.planning', 'phases', '09-foundation', '09-VERIFICATION.md');
415
+ expect(existsSync(fullPath)).toBe(true);
416
+ const content = await readFile(fullPath, 'utf-8');
417
+ expect(content).toContain('Verification');
418
+ });
419
+
420
+ it('creates phase-dir under phases/', async () => {
421
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
422
+ await setupTestProject(tmpDir);
423
+
424
+ const result = await phaseScaffold(['phase-dir', '15', 'New Module'], tmpDir);
425
+ const data = result.data as Record<string, unknown>;
426
+
427
+ expect(data.created).toBe(true);
428
+ const dir = data.directory as string;
429
+ expect(dir).toContain('15-new-module');
430
+ });
431
+
432
+ it('returns already_exists for existing file', async () => {
433
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
434
+ await setupTestProject(tmpDir, {
435
+ phases: ['09-foundation'],
436
+ });
437
+
438
+ // Create first
439
+ await phaseScaffold(['context', '9'], tmpDir);
440
+ // Second call should return already_exists
441
+ const result = await phaseScaffold(['context', '9'], tmpDir);
442
+ const data = result.data as Record<string, unknown>;
443
+ expect(data.created).toBe(false);
444
+ expect(data.reason).toBe('already_exists');
445
+ });
446
+
447
+ it('throws GSDError for unknown type', async () => {
448
+ const { phaseScaffold } = await import('./phase-lifecycle.js');
449
+ await setupTestProject(tmpDir, {
450
+ phases: ['09-foundation'],
451
+ });
452
+
453
+ await expect(phaseScaffold(['badtype', '9'], tmpDir)).rejects.toThrow('Unknown scaffold type');
454
+ });
455
+ });
456
+
457
+ // ─── phaseRemove ─────────────────────────────────────────────────────────
458
+
459
+ const ROADMAP_FOR_REMOVE = `# Roadmap
460
+
461
+ ## Current Milestone: v3.0 SDK-First Migration
462
+
463
+ ### Phase 5: Auth
464
+
465
+ **Goal:** Build authentication
466
+ **Requirements**: TBD
467
+ **Depends on:** Phase 4
468
+ **Plans:** 2 plans
469
+
470
+ Plans:
471
+ - [x] 05-01 (Auth setup)
472
+ - [x] 05-02 (Auth complete)
473
+
474
+ ### Phase 6: Dashboard
475
+
476
+ **Goal:** Build dashboard
477
+ **Requirements**: TBD
478
+ **Depends on:** Phase 5
479
+ **Plans:** 3 plans
480
+
481
+ Plans:
482
+ - [ ] 06-01 (Dashboard setup)
483
+
484
+ ### Phase 7: API
485
+
486
+ **Goal:** Build API layer
487
+ **Requirements**: TBD
488
+ **Depends on:** Phase 6
489
+ **Plans:** 2 plans
490
+
491
+ Plans:
492
+ - [ ] 07-01 (API setup)
493
+
494
+ ---
495
+ *Last updated: 2026-04-08*
496
+ `;
497
+
498
+ const STATE_FOR_REMOVE = `---
499
+ gsd_state_version: 1.0
500
+ milestone: v3.0
501
+ milestone_name: SDK-First Migration
502
+ status: executing
503
+ progress:
504
+ total_phases: 7
505
+ completed_phases: 4
506
+ total_plans: 15
507
+ completed_plans: 12
508
+ percent: 80
509
+ ---
510
+
511
+ # Project State
512
+
513
+ ## Current Position
514
+
515
+ Phase: 6 (Dashboard) — EXECUTING
516
+ Plan: 1 of 3
517
+ Status: Executing Phase 6
518
+
519
+ ## Session Continuity
520
+
521
+ Last session: 2026-04-08T10:00:00.000Z
522
+ Stopped at: Started
523
+ `;
524
+
525
+ describe('phaseRemove', () => {
526
+ it('removes integer phase directory and renumbers subsequent phases', async () => {
527
+ const { phaseRemove } = await import('./phase-lifecycle.js');
528
+ const phasesDir = join(tmpDir, '.planning', 'phases');
529
+ await setupTestProject(tmpDir, {
530
+ roadmap: ROADMAP_FOR_REMOVE,
531
+ state: STATE_FOR_REMOVE,
532
+ phases: ['05-auth', '06-dashboard', '07-api'],
533
+ });
534
+ // Create files inside directories to verify file renaming
535
+ await writeFile(join(phasesDir, '06-dashboard', '06-01-PLAN.md'), 'plan', 'utf-8');
536
+ await writeFile(join(phasesDir, '07-api', '07-01-PLAN.md'), 'plan', 'utf-8');
537
+
538
+ const result = await phaseRemove(['6'], tmpDir);
539
+ const data = result.data as Record<string, unknown>;
540
+
541
+ expect(data.removed).toBe('6');
542
+ expect(data.directory_deleted).toBeTruthy();
543
+ expect(data.roadmap_updated).toBe(true);
544
+ expect(data.state_updated).toBe(true);
545
+
546
+ // Phase 6 dir should be gone
547
+ const entries = await readdir(phasesDir, { withFileTypes: true });
548
+ const dirNames = entries.filter(e => e.isDirectory()).map(e => e.name);
549
+ expect(dirNames.find(d => d.includes('06-dashboard'))).toBeUndefined();
550
+
551
+ // Phase 7 should have been renamed to 06
552
+ const renamedDir = dirNames.find(d => d.includes('06-api'));
553
+ expect(renamedDir).toBeTruthy();
554
+
555
+ // Files inside renamed dir should also be renamed
556
+ const files = await readdir(join(phasesDir, renamedDir!));
557
+ expect(files.some(f => f.includes('06-01'))).toBe(true);
558
+ expect(files.some(f => f.includes('07-01'))).toBe(false);
559
+ });
560
+
561
+ it('removes decimal phase and renumbers sibling decimals', async () => {
562
+ const { phaseRemove } = await import('./phase-lifecycle.js');
563
+ const decimalRoadmap = ROADMAP_FOR_REMOVE.replace(
564
+ '### Phase 7: API',
565
+ '### Phase 6.1: Hotfix A\n\n**Goal:** Fix A\n**Plans:** 1 plans\n\n### Phase 6.2: Hotfix B\n\n**Goal:** Fix B\n**Plans:** 1 plans\n\n### Phase 6.3: Hotfix C\n\n**Goal:** Fix C\n**Plans:** 1 plans\n\n### Phase 7: API'
566
+ );
567
+ const phasesDir = join(tmpDir, '.planning', 'phases');
568
+ await setupTestProject(tmpDir, {
569
+ roadmap: decimalRoadmap,
570
+ state: STATE_FOR_REMOVE,
571
+ phases: ['05-auth', '06-dashboard', '06.1-hotfix-a', '06.2-hotfix-b', '06.3-hotfix-c', '07-api'],
572
+ });
573
+ // Create files with phase ID in name
574
+ await writeFile(join(phasesDir, '06.2-hotfix-b', '06.2-01-PLAN.md'), 'plan', 'utf-8');
575
+ await writeFile(join(phasesDir, '06.3-hotfix-c', '06.3-01-PLAN.md'), 'plan', 'utf-8');
576
+
577
+ const result = await phaseRemove(['6.1'], tmpDir);
578
+ const data = result.data as Record<string, unknown>;
579
+
580
+ expect(data.removed).toBe('6.1');
581
+
582
+ // 06.1 should be gone
583
+ const entries = await readdir(phasesDir, { withFileTypes: true });
584
+ const dirNames = entries.filter(e => e.isDirectory()).map(e => e.name);
585
+ expect(dirNames.find(d => d.includes('06.1-hotfix-a'))).toBeUndefined();
586
+
587
+ // 06.2 should become 06.1, 06.3 should become 06.2
588
+ expect(dirNames.find(d => d.includes('06.1-hotfix-b'))).toBeTruthy();
589
+ expect(dirNames.find(d => d.includes('06.2-hotfix-c'))).toBeTruthy();
590
+ expect(dirNames.find(d => d.includes('06.3'))).toBeUndefined();
591
+
592
+ // Files inside renamed dirs should be renamed
593
+ const dir1Files = await readdir(join(phasesDir, '06.1-hotfix-b'));
594
+ expect(dir1Files.some(f => f.includes('06.1-01'))).toBe(true);
595
+ const dir2Files = await readdir(join(phasesDir, '06.2-hotfix-c'));
596
+ expect(dir2Files.some(f => f.includes('06.2-01'))).toBe(true);
597
+ });
598
+
599
+ it('requires --force to remove phase with SUMMARY files', async () => {
600
+ const { phaseRemove } = await import('./phase-lifecycle.js');
601
+ const phasesDir = join(tmpDir, '.planning', 'phases');
602
+ await setupTestProject(tmpDir, {
603
+ roadmap: ROADMAP_FOR_REMOVE,
604
+ state: STATE_FOR_REMOVE,
605
+ phases: ['05-auth', '06-dashboard', '07-api'],
606
+ });
607
+ // Create a SUMMARY file to simulate executed work
608
+ await writeFile(join(phasesDir, '06-dashboard', '06-01-SUMMARY.md'), 'summary', 'utf-8');
609
+
610
+ await expect(phaseRemove(['6'], tmpDir)).rejects.toThrow('--force');
611
+ });
612
+
613
+ it('allows removal with --force even when SUMMARY files exist', async () => {
614
+ const { phaseRemove } = await import('./phase-lifecycle.js');
615
+ const phasesDir = join(tmpDir, '.planning', 'phases');
616
+ await setupTestProject(tmpDir, {
617
+ roadmap: ROADMAP_FOR_REMOVE,
618
+ state: STATE_FOR_REMOVE,
619
+ phases: ['05-auth', '06-dashboard', '07-api'],
620
+ });
621
+ await writeFile(join(phasesDir, '06-dashboard', '06-01-SUMMARY.md'), 'summary', 'utf-8');
622
+
623
+ const result = await phaseRemove(['6', '--force'], tmpDir);
624
+ const data = result.data as Record<string, unknown>;
625
+ expect(data.removed).toBe('6');
626
+ expect(data.directory_deleted).toBeTruthy();
627
+ });
628
+
629
+ it('throws GSDError when ROADMAP.md is missing', async () => {
630
+ const { phaseRemove } = await import('./phase-lifecycle.js');
631
+ // Set up without ROADMAP.md
632
+ const planningDir = join(tmpDir, '.planning');
633
+ await mkdir(planningDir, { recursive: true });
634
+ const phasesDir = join(planningDir, 'phases');
635
+ await mkdir(phasesDir, { recursive: true });
636
+ await writeFile(join(planningDir, 'STATE.md'), STATE_FOR_REMOVE, 'utf-8');
637
+
638
+ await expect(phaseRemove(['6'], tmpDir)).rejects.toThrow('ROADMAP.md not found');
639
+ });
640
+
641
+ it('throws GSDError when phase number is missing', async () => {
642
+ const { phaseRemove } = await import('./phase-lifecycle.js');
643
+ await setupTestProject(tmpDir, {
644
+ roadmap: ROADMAP_FOR_REMOVE,
645
+ state: STATE_FOR_REMOVE,
646
+ });
647
+
648
+ await expect(phaseRemove([], tmpDir)).rejects.toThrow('phase number required');
649
+ });
650
+
651
+ it('updates ROADMAP.md by removing phase section and renumbering', async () => {
652
+ const { phaseRemove } = await import('./phase-lifecycle.js');
653
+ await setupTestProject(tmpDir, {
654
+ roadmap: ROADMAP_FOR_REMOVE,
655
+ state: STATE_FOR_REMOVE,
656
+ phases: ['05-auth', '06-dashboard', '07-api'],
657
+ });
658
+
659
+ await phaseRemove(['6'], tmpDir);
660
+
661
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
662
+ // Phase 6 section should be removed
663
+ expect(roadmap).not.toContain('### Phase 6: Dashboard');
664
+ // Phase 7 should be renumbered to 6
665
+ expect(roadmap).toContain('### Phase 6: API');
666
+ // Plan references should be renumbered
667
+ expect(roadmap).toContain('06-01');
668
+ expect(roadmap).not.toContain('07-01');
669
+ });
670
+
671
+ it('decrements total_phases in STATE.md frontmatter', async () => {
672
+ const { phaseRemove } = await import('./phase-lifecycle.js');
673
+ await setupTestProject(tmpDir, {
674
+ roadmap: ROADMAP_FOR_REMOVE,
675
+ state: STATE_FOR_REMOVE,
676
+ phases: ['05-auth', '06-dashboard', '07-api'],
677
+ });
678
+
679
+ await phaseRemove(['6'], tmpDir);
680
+
681
+ const stateContent = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
682
+ // total_phases should be decremented from 7 to 6
683
+ expect(stateContent).toMatch(/total_phases:\s*6/);
684
+ });
685
+ });
686
+
687
+ // ─── phaseComplete ─────────────────────────────────────────────────────────
688
+
689
+ const ROADMAP_FOR_COMPLETE = `# Roadmap
690
+
691
+ <details>
692
+ <summary>v1.0 (shipped)</summary>
693
+
694
+ ### Phase 1: Old Phase
695
+
696
+ **Goal:** Shipped already
697
+ **Plans:** 2 plans
698
+
699
+ </details>
700
+
701
+ ## Current Milestone: v3.0 SDK-First Migration
702
+
703
+ | Phase | Plans | Status | Completed |
704
+ |-------|-------|--------|-----------|
705
+ | 9. | 3/3 | Complete | 2026-04-01 |
706
+ | 10. | 0/3 | In Progress | |
707
+ | 11. | 0/2 | Not Started | |
708
+
709
+ - [x] Phase 9: Foundation (completed 2026-04-01)
710
+ - [ ] Phase 10: Read-Only Queries
711
+ - [ ] Phase 11: Final Phase
712
+
713
+ ### Phase 9: Foundation
714
+
715
+ **Goal:** Build foundation
716
+ **Requirements**: FOUND-01, FOUND-02
717
+ **Depends on:** Phase 8
718
+ **Plans:** 3/3 plans complete
719
+
720
+ Plans:
721
+ - [x] 09-01 (Foundation setup)
722
+ - [x] 09-02 (Foundation core)
723
+ - [x] 09-03 (Foundation tests)
724
+
725
+ ### Phase 10: Read-Only Queries
726
+
727
+ **Goal:** Port queries
728
+ **Requirements**: QUERY-01
729
+ **Depends on:** Phase 9
730
+ **Plans:** 3 plans
731
+
732
+ Plans:
733
+ - [x] 10-01 (Query setup)
734
+ - [x] 10-02 (Query core)
735
+ - [ ] 10-03 (Query tests)
736
+
737
+ ### Phase 11: Final Phase
738
+
739
+ **Goal:** Final work
740
+ **Requirements**: FINAL-01
741
+ **Depends on:** Phase 10
742
+ **Plans:** 2 plans
743
+
744
+ Plans:
745
+ - [ ] 11-01 (Final setup)
746
+ - [ ] 11-02 (Final complete)
747
+
748
+ ---
749
+ *Last updated: 2026-04-08*
750
+ `;
751
+
752
+ const STATE_FOR_COMPLETE = `---
753
+ gsd_state_version: 1.0
754
+ milestone: v3.0
755
+ milestone_name: SDK-First Migration
756
+ status: executing
757
+ progress:
758
+ total_phases: 3
759
+ completed_phases: 1
760
+ total_plans: 8
761
+ completed_plans: 5
762
+ percent: 33
763
+ ---
764
+
765
+ # Project State
766
+
767
+ ## Current Position
768
+
769
+ Phase: 10 of 3 (Read-Only Queries) — EXECUTING
770
+ Plan: 3 of 3
771
+ Status: Executing Phase 10
772
+ Last activity: 2026-04-08
773
+
774
+ ## Performance Metrics
775
+
776
+ **Velocity:**
777
+
778
+ - Total plans completed: 3
779
+ - Average duration: --
780
+ - Total execution time: 0 hours
781
+
782
+ **By Phase:**
783
+
784
+ | Phase | Plans | Total | Avg/Plan |
785
+ |-------|-------|-------|----------|
786
+ | 9 | 3 | - | - |
787
+
788
+ ## Session Continuity
789
+
790
+ Last session: 2026-04-08T10:00:00.000Z
791
+ Stopped at: Completed 10-03-PLAN.md
792
+ `;
793
+
794
+ const REQUIREMENTS_FOR_COMPLETE = `# Requirements
795
+
796
+ ## Checklist
797
+
798
+ - [x] **FOUND-01** Foundation setup
799
+ - [x] **FOUND-02** Foundation core
800
+ - [ ] **QUERY-01** Query implementation
801
+ - [ ] **FINAL-01** Final work
802
+
803
+ ## Traceability
804
+
805
+ | Requirement | Phase | Status |
806
+ |-------------|-------|--------|
807
+ | FOUND-01 | Phase 9 | Complete |
808
+ | FOUND-02 | Phase 9 | Complete |
809
+ | QUERY-01 | Phase 10 | In Progress |
810
+ | FINAL-01 | Phase 11 | Pending |
811
+ `;
812
+
813
+ describe('phaseComplete', () => {
814
+ it('marks phase checkbox, updates progress table, and plan count in ROADMAP.md', async () => {
815
+ const { phaseComplete } = await import('./phase-lifecycle.js');
816
+ await setupTestProject(tmpDir, {
817
+ roadmap: ROADMAP_FOR_COMPLETE,
818
+ state: STATE_FOR_COMPLETE,
819
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
820
+ });
821
+ // Create PLAN and SUMMARY files for phase 10
822
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
823
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan1', 'utf-8');
824
+ await writeFile(join(p10Dir, '10-02-PLAN.md'), 'plan2', 'utf-8');
825
+ await writeFile(join(p10Dir, '10-03-PLAN.md'), 'plan3', 'utf-8');
826
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary1', 'utf-8');
827
+ await writeFile(join(p10Dir, '10-02-SUMMARY.md'), 'summary2', 'utf-8');
828
+ await writeFile(join(p10Dir, '10-03-SUMMARY.md'), 'summary3', 'utf-8');
829
+ // Create REQUIREMENTS.md
830
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
831
+
832
+ const result = await phaseComplete(['10'], tmpDir);
833
+ const data = result.data as Record<string, unknown>;
834
+
835
+ expect(data.completed_phase).toBe('10');
836
+ expect(data.plans_executed).toBe('3/3');
837
+ expect(data.is_last_phase).toBe(false);
838
+ expect(data.next_phase).toBeTruthy();
839
+ expect(data.roadmap_updated).toBe(true);
840
+
841
+ // Check ROADMAP.md updates
842
+ const roadmap = await readFile(join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
843
+ // Checkbox should be marked
844
+ expect(roadmap).toMatch(/\[x\].*Phase 10/);
845
+ // Progress table should show Complete
846
+ expect(roadmap).toMatch(/10\.?\s*\|.*3\/3.*\|.*Complete/i);
847
+ // Plan count in section should be updated
848
+ expect(roadmap).toContain('3/3 plans complete');
849
+ // Plan checkboxes should be [x]
850
+ expect(roadmap).toMatch(/\[x\] 10-01/);
851
+ expect(roadmap).toMatch(/\[x\] 10-02/);
852
+ expect(roadmap).toMatch(/\[x\] 10-03/);
853
+ });
854
+
855
+ it('updates REQUIREMENTS.md checkboxes and traceability table', async () => {
856
+ const { phaseComplete } = await import('./phase-lifecycle.js');
857
+ await setupTestProject(tmpDir, {
858
+ roadmap: ROADMAP_FOR_COMPLETE,
859
+ state: STATE_FOR_COMPLETE,
860
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
861
+ });
862
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
863
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan1', 'utf-8');
864
+ await writeFile(join(p10Dir, '10-02-PLAN.md'), 'plan2', 'utf-8');
865
+ await writeFile(join(p10Dir, '10-03-PLAN.md'), 'plan3', 'utf-8');
866
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary1', 'utf-8');
867
+ await writeFile(join(p10Dir, '10-02-SUMMARY.md'), 'summary2', 'utf-8');
868
+ await writeFile(join(p10Dir, '10-03-SUMMARY.md'), 'summary3', 'utf-8');
869
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
870
+
871
+ await phaseComplete(['10'], tmpDir);
872
+
873
+ const req = await readFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
874
+ // QUERY-01 checkbox should be marked
875
+ expect(req).toMatch(/\[x\].*\*\*QUERY-01\*\*/);
876
+ // Traceability should show Complete for QUERY-01
877
+ expect(req).toMatch(/QUERY-01\s*\|.*\|\s*Complete\s*\|/);
878
+ // FINAL-01 should remain Pending
879
+ expect(req).toMatch(/FINAL-01\s*\|.*\|\s*Pending\s*\|/);
880
+ });
881
+
882
+ it('updates STATE.md fields: current phase, status, completed phases, percent', async () => {
883
+ const { phaseComplete } = await import('./phase-lifecycle.js');
884
+ await setupTestProject(tmpDir, {
885
+ roadmap: ROADMAP_FOR_COMPLETE,
886
+ state: STATE_FOR_COMPLETE,
887
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
888
+ });
889
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
890
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan', 'utf-8');
891
+ await writeFile(join(p10Dir, '10-02-PLAN.md'), 'plan', 'utf-8');
892
+ await writeFile(join(p10Dir, '10-03-PLAN.md'), 'plan', 'utf-8');
893
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary', 'utf-8');
894
+ await writeFile(join(p10Dir, '10-02-SUMMARY.md'), 'summary', 'utf-8');
895
+ await writeFile(join(p10Dir, '10-03-SUMMARY.md'), 'summary', 'utf-8');
896
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
897
+
898
+ await phaseComplete(['10'], tmpDir);
899
+
900
+ const state = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
901
+ // Phase should advance to 11
902
+ expect(state).toMatch(/Phase:\s*11/);
903
+ // Status should indicate ready to plan
904
+ expect(state).toMatch(/Status:\s*Ready to plan/);
905
+ // Completed phases should be incremented from 1 to 2
906
+ expect(state).toMatch(/completed_phases:\s*2/);
907
+ // Percent should be recalculated (2/3 = 67%)
908
+ expect(state).toMatch(/percent:\s*67/);
909
+ });
910
+
911
+ it('detects next phase from filesystem, falls back to ROADMAP.md', async () => {
912
+ const { phaseComplete } = await import('./phase-lifecycle.js');
913
+ await setupTestProject(tmpDir, {
914
+ roadmap: ROADMAP_FOR_COMPLETE,
915
+ state: STATE_FOR_COMPLETE,
916
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
917
+ });
918
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
919
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan', 'utf-8');
920
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary', 'utf-8');
921
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
922
+
923
+ const result = await phaseComplete(['10'], tmpDir);
924
+ const data = result.data as Record<string, unknown>;
925
+
926
+ // Next phase should be 11 (from filesystem)
927
+ expect(data.next_phase).toBe('11');
928
+ expect(data.is_last_phase).toBe(false);
929
+ });
930
+
931
+ it('sets is_last_phase when completing the final phase', async () => {
932
+ const { phaseComplete } = await import('./phase-lifecycle.js');
933
+ await setupTestProject(tmpDir, {
934
+ roadmap: ROADMAP_FOR_COMPLETE,
935
+ state: STATE_FOR_COMPLETE,
936
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
937
+ });
938
+ const p11Dir = join(tmpDir, '.planning', 'phases', '11-final-phase');
939
+ await writeFile(join(p11Dir, '11-01-PLAN.md'), 'plan', 'utf-8');
940
+ await writeFile(join(p11Dir, '11-01-SUMMARY.md'), 'summary', 'utf-8');
941
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
942
+
943
+ const result = await phaseComplete(['11'], tmpDir);
944
+ const data = result.data as Record<string, unknown>;
945
+
946
+ expect(data.is_last_phase).toBe(true);
947
+ expect(data.next_phase).toBeNull();
948
+
949
+ // State should show milestone complete
950
+ const state = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
951
+ expect(state).toMatch(/Status:\s*Milestone complete/);
952
+ });
953
+
954
+ it('collects UAT/VERIFICATION warnings without blocking', async () => {
955
+ const { phaseComplete } = await import('./phase-lifecycle.js');
956
+ await setupTestProject(tmpDir, {
957
+ roadmap: ROADMAP_FOR_COMPLETE,
958
+ state: STATE_FOR_COMPLETE,
959
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
960
+ });
961
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
962
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan', 'utf-8');
963
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary', 'utf-8');
964
+ // Create UAT file with pending status
965
+ await writeFile(join(p10Dir, '10-UAT.md'), '---\nresult: pending\n---\nPending tests', 'utf-8');
966
+ // Create VERIFICATION file with gaps
967
+ await writeFile(join(p10Dir, '10-VERIFICATION.md'), '---\nstatus: gaps_found\n---\nGaps', 'utf-8');
968
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
969
+
970
+ const result = await phaseComplete(['10'], tmpDir);
971
+ const data = result.data as Record<string, unknown>;
972
+
973
+ // Should complete despite warnings
974
+ expect(data.completed_phase).toBe('10');
975
+ expect(data.has_warnings).toBe(true);
976
+ const warnings = data.warnings as string[];
977
+ expect(warnings.length).toBeGreaterThan(0);
978
+ expect(warnings.some(w => w.includes('pending'))).toBe(true);
979
+ expect(warnings.some(w => w.includes('gaps'))).toBe(true);
980
+ });
981
+
982
+ it('throws GSDError for missing phase', async () => {
983
+ const { phaseComplete } = await import('./phase-lifecycle.js');
984
+ await setupTestProject(tmpDir, {
985
+ roadmap: ROADMAP_FOR_COMPLETE,
986
+ state: STATE_FOR_COMPLETE,
987
+ phases: ['09-foundation'],
988
+ });
989
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
990
+
991
+ await expect(phaseComplete(['99'], tmpDir)).rejects.toThrow('Phase 99 not found');
992
+ });
993
+
994
+ it('updates performance metrics table in STATE.md', async () => {
995
+ const { phaseComplete } = await import('./phase-lifecycle.js');
996
+ await setupTestProject(tmpDir, {
997
+ roadmap: ROADMAP_FOR_COMPLETE,
998
+ state: STATE_FOR_COMPLETE,
999
+ phases: ['09-foundation', '10-read-only-queries', '11-final-phase'],
1000
+ });
1001
+ const p10Dir = join(tmpDir, '.planning', 'phases', '10-read-only-queries');
1002
+ await writeFile(join(p10Dir, '10-01-PLAN.md'), 'plan', 'utf-8');
1003
+ await writeFile(join(p10Dir, '10-02-PLAN.md'), 'plan', 'utf-8');
1004
+ await writeFile(join(p10Dir, '10-03-PLAN.md'), 'plan', 'utf-8');
1005
+ await writeFile(join(p10Dir, '10-01-SUMMARY.md'), 'summary', 'utf-8');
1006
+ await writeFile(join(p10Dir, '10-02-SUMMARY.md'), 'summary', 'utf-8');
1007
+ await writeFile(join(p10Dir, '10-03-SUMMARY.md'), 'summary', 'utf-8');
1008
+ await writeFile(join(tmpDir, '.planning', 'REQUIREMENTS.md'), REQUIREMENTS_FOR_COMPLETE, 'utf-8');
1009
+
1010
+ await phaseComplete(['10'], tmpDir);
1011
+
1012
+ const state = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
1013
+ // Total plans completed should be incremented: 3 + 3 = 6
1014
+ expect(state).toContain('Total plans completed: 6');
1015
+ // By Phase table should have a row for phase 10
1016
+ expect(state).toMatch(/\|\s*10\s*\|\s*3\s*\|/);
1017
+ });
1018
+ });
1019
+
1020
+ // ─── phasesClear ────────────────────────────────────────────────────────────
1021
+
1022
+ describe('phasesClear', () => {
1023
+ it('throws GSDError without --confirm flag, showing count', async () => {
1024
+ const { phasesClear } = await import('./phase-lifecycle.js');
1025
+ await setupTestProject(tmpDir, {
1026
+ phases: ['09-foundation', '10-read-only-queries', '999.1-backlog'],
1027
+ });
1028
+
1029
+ // Should throw with count of dirs to delete (2, not 3 since 999.1 is excluded)
1030
+ await expect(phasesClear([], tmpDir)).rejects.toThrow(/2 phase director/);
1031
+ });
1032
+
1033
+ it('deletes all dirs except 999.x with --confirm', async () => {
1034
+ const { phasesClear } = await import('./phase-lifecycle.js');
1035
+ await setupTestProject(tmpDir, {
1036
+ phases: ['09-foundation', '10-read-only-queries', '999.1-backlog'],
1037
+ });
1038
+
1039
+ const result = await phasesClear(['--confirm'], tmpDir);
1040
+ const data = result.data as Record<string, unknown>;
1041
+
1042
+ expect(data.cleared).toBe(2);
1043
+
1044
+ // Verify filesystem
1045
+ const phasesDir = join(tmpDir, '.planning', 'phases');
1046
+ const entries = await readdir(phasesDir, { withFileTypes: true });
1047
+ const dirNames = entries.filter(e => e.isDirectory()).map(e => e.name);
1048
+ expect(dirNames.length).toBe(1);
1049
+ expect(dirNames[0]).toContain('999');
1050
+ });
1051
+
1052
+ it('returns 0 cleared when phases dir is empty', async () => {
1053
+ const { phasesClear } = await import('./phase-lifecycle.js');
1054
+ await setupTestProject(tmpDir, { phases: [] });
1055
+
1056
+ const result = await phasesClear(['--confirm'], tmpDir);
1057
+ const data = result.data as Record<string, unknown>;
1058
+ expect(data.cleared).toBe(0);
1059
+ });
1060
+ });
1061
+
1062
+ // ─── phasesArchive ──────────────────────────────────────────────────────────
1063
+
1064
+ describe('phasesArchive', () => {
1065
+ it('moves milestone phase dirs to milestones/{version}-phases/', async () => {
1066
+ const { phasesArchive } = await import('./phase-lifecycle.js');
1067
+ await setupTestProject(tmpDir, {
1068
+ phases: ['09-foundation', '10-read-only-queries'],
1069
+ });
1070
+
1071
+ const result = await phasesArchive(['v3.0'], tmpDir);
1072
+ const data = result.data as Record<string, unknown>;
1073
+
1074
+ expect(data.version).toBe('v3.0');
1075
+ expect((data.archived as number)).toBeGreaterThan(0);
1076
+
1077
+ // Verify archive directory exists
1078
+ const archiveDir = join(tmpDir, '.planning', 'milestones', 'v3.0-phases');
1079
+ expect(existsSync(archiveDir)).toBe(true);
1080
+
1081
+ // Verify dirs were moved
1082
+ const archivedEntries = await readdir(archiveDir, { withFileTypes: true });
1083
+ const archivedDirs = archivedEntries.filter(e => e.isDirectory()).map(e => e.name);
1084
+ expect(archivedDirs.length).toBeGreaterThan(0);
1085
+
1086
+ // Original dirs should be gone
1087
+ const phasesDir = join(tmpDir, '.planning', 'phases');
1088
+ const remaining = await readdir(phasesDir, { withFileTypes: true });
1089
+ const remainingDirs = remaining.filter(e => e.isDirectory()).map(e => e.name);
1090
+ expect(remainingDirs.length).toBe(0);
1091
+ });
1092
+ });
1093
+
1094
+ // ─── Registry integration ──────────────────────────────────────────────────
1095
+
1096
+ describe('lifecycle handlers in registry', () => {
1097
+ it('registers all 7 lifecycle handlers with dot notation', async () => {
1098
+ const { createRegistry } = await import('./index.js');
1099
+ const registry = createRegistry();
1100
+
1101
+ const commands = [
1102
+ 'phase.add', 'phase.insert', 'phase.remove', 'phase.complete',
1103
+ 'phase.scaffold', 'phases.clear', 'phases.archive',
1104
+ ];
1105
+
1106
+ for (const cmd of commands) {
1107
+ const handler = registry.getHandler(cmd);
1108
+ expect(handler, `${cmd} should be registered`).toBeDefined();
1109
+ }
1110
+ });
1111
+
1112
+ it('registers space-delimited aliases', async () => {
1113
+ const { createRegistry } = await import('./index.js');
1114
+ const registry = createRegistry();
1115
+
1116
+ const commands = [
1117
+ 'phase add', 'phase insert', 'phase remove', 'phase complete',
1118
+ 'phase scaffold', 'phases clear', 'phases archive',
1119
+ ];
1120
+
1121
+ for (const cmd of commands) {
1122
+ const handler = registry.getHandler(cmd);
1123
+ expect(handler, `${cmd} should be registered`).toBeDefined();
1124
+ }
1125
+ });
1126
+ });