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,1203 @@
1
+ /**
2
+ * Phase Runner — core state machine driving the full phase lifecycle.
3
+ *
4
+ * Orchestrates: discuss → research → plan → execute → verify → advance
5
+ * with config-driven step skipping, human gate callbacks, event emission,
6
+ * and structured error handling per step.
7
+ */
8
+
9
+ import type {
10
+ PhaseOpInfo,
11
+ PhaseStepResult,
12
+ PhaseRunnerResult,
13
+ HumanGateCallbacks,
14
+ PhaseRunnerOptions,
15
+ PlanResult,
16
+ SessionOptions,
17
+ ParsedPlan,
18
+ PhasePlanIndex,
19
+ PlanInfo,
20
+ } from './types.js';
21
+ import { PhaseStepType, PhaseType, GSDEventType } from './types.js';
22
+ import type { GSDConfig } from './config.js';
23
+ import type { GSDTools } from './gsd-tools.js';
24
+ import type { GSDEventStream } from './event-stream.js';
25
+ import type { PromptFactory } from './phase-prompt.js';
26
+ import type { ContextEngine } from './context-engine.js';
27
+ import type { GSDLogger } from './logger.js';
28
+ import { runPhaseStepSession, runPlanSession } from './session-runner.js';
29
+ import { readFile } from 'node:fs/promises';
30
+ import { join } from 'node:path';
31
+ import { checkResearchGate } from './research-gate.js';
32
+ import { recordPhaseArtifactFailureEvents, recordPlanResultFailure } from './failure-memory.js';
33
+
34
+ // ─── Error type ──────────────────────────────────────────────────────────────
35
+
36
+ export class PhaseRunnerError extends Error {
37
+ constructor(
38
+ message: string,
39
+ public readonly phaseNumber: string,
40
+ public readonly step: PhaseStepType,
41
+ public readonly cause?: Error,
42
+ ) {
43
+ super(message);
44
+ this.name = 'PhaseRunnerError';
45
+ }
46
+ }
47
+
48
+ // ─── Verification result enum ────────────────────────────────────────────────
49
+
50
+ export type VerificationOutcome = 'passed' | 'human_needed' | 'gaps_found';
51
+
52
+ // ─── PhaseRunner deps interface ──────────────────────────────────────────────
53
+
54
+ export interface PhaseRunnerDeps {
55
+ projectDir: string;
56
+ tools: GSDTools;
57
+ promptFactory: PromptFactory;
58
+ contextEngine: ContextEngine;
59
+ eventStream: GSDEventStream;
60
+ config: GSDConfig;
61
+ logger?: GSDLogger;
62
+ }
63
+
64
+ // ─── PhaseRunner ─────────────────────────────────────────────────────────────
65
+
66
+ export class PhaseRunner {
67
+ private readonly projectDir: string;
68
+ private readonly tools: GSDTools;
69
+ private readonly promptFactory: PromptFactory;
70
+ private readonly contextEngine: ContextEngine;
71
+ private readonly eventStream: GSDEventStream;
72
+ private readonly config: GSDConfig;
73
+ private readonly logger?: GSDLogger;
74
+
75
+ constructor(deps: PhaseRunnerDeps) {
76
+ this.projectDir = deps.projectDir;
77
+ this.tools = deps.tools;
78
+ this.promptFactory = deps.promptFactory;
79
+ this.contextEngine = deps.contextEngine;
80
+ this.eventStream = deps.eventStream;
81
+ this.config = deps.config;
82
+ this.logger = deps.logger;
83
+ }
84
+
85
+ /**
86
+ * Run a full phase lifecycle: discuss → research → plan → plan-check → execute → verify → advance.
87
+ *
88
+ * Each step is gated by config flags and phase state. Human gate callbacks
89
+ * are invoked at decision points; when not provided, auto-approve is used.
90
+ */
91
+ async run(phaseNumber: string, options?: PhaseRunnerOptions): Promise<PhaseRunnerResult> {
92
+ const startTime = Date.now();
93
+ const steps: PhaseStepResult[] = [];
94
+ const callbacks = options?.callbacks ?? {};
95
+
96
+ // ── Init: query phase state ──
97
+ let phaseOp: PhaseOpInfo;
98
+ try {
99
+ phaseOp = await this.tools.initPhaseOp(phaseNumber);
100
+ } catch (err) {
101
+ throw new PhaseRunnerError(
102
+ `Failed to initialize phase ${phaseNumber}: ${err instanceof Error ? err.message : String(err)}`,
103
+ phaseNumber,
104
+ PhaseStepType.Discuss,
105
+ err instanceof Error ? err : undefined,
106
+ );
107
+ }
108
+
109
+ // Validate phase exists
110
+ if (!phaseOp.phase_found) {
111
+ throw new PhaseRunnerError(
112
+ `Phase ${phaseNumber} not found on disk`,
113
+ phaseNumber,
114
+ PhaseStepType.Discuss,
115
+ );
116
+ }
117
+
118
+ const phaseName = phaseOp.phase_name;
119
+
120
+ // Emit phase_start
121
+ this.eventStream.emitEvent({
122
+ type: GSDEventType.PhaseStart,
123
+ timestamp: new Date().toISOString(),
124
+ sessionId: '',
125
+ phaseNumber,
126
+ phaseName,
127
+ });
128
+
129
+ const sessionOpts: SessionOptions = {
130
+ maxTurns: options?.maxTurnsPerStep ?? 50,
131
+ maxBudgetUsd: options?.maxBudgetPerStep ?? 5.0,
132
+ model: options?.model,
133
+ cwd: this.projectDir,
134
+ };
135
+
136
+ let halted = false;
137
+
138
+ // ── Step 1: Discuss ──
139
+ if (!halted) {
140
+ const shouldSkip = phaseOp.has_context || this.config.workflow.skip_discuss;
141
+ if (shouldSkip && !(this.config.workflow.auto_advance && !phaseOp.has_context && !this.config.workflow.skip_discuss)) {
142
+ this.logger?.debug(`Skipping discuss: has_context=${phaseOp.has_context}, skip_discuss=${this.config.workflow.skip_discuss}`);
143
+ } else if (!phaseOp.has_context && !this.config.workflow.skip_discuss && this.config.workflow.auto_advance) {
144
+ // AI self-discuss: auto-mode with no context — run a self-discuss session
145
+ const result = await this.retryOnce('self-discuss', () => this.runSelfDiscussStep(phaseNumber, sessionOpts));
146
+ steps.push(result);
147
+
148
+ // Re-query phase state to check if context was created
149
+ try {
150
+ phaseOp = await this.tools.initPhaseOp(phaseNumber);
151
+ } catch {
152
+ // If re-query fails, proceed with original state
153
+ }
154
+
155
+ if (!phaseOp.has_context) {
156
+ const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Discuss, 'No context after self-discuss step');
157
+ if (decision === 'stop') {
158
+ halted = true;
159
+ }
160
+ }
161
+ } else if (!shouldSkip) {
162
+ const result = await this.retryOnce('discuss', () => this.runStep(PhaseStepType.Discuss, phaseNumber, sessionOpts));
163
+ steps.push(result);
164
+
165
+ // Re-query phase state to check if context was created
166
+ try {
167
+ phaseOp = await this.tools.initPhaseOp(phaseNumber);
168
+ } catch {
169
+ // If re-query fails, proceed with original state
170
+ }
171
+
172
+ if (!phaseOp.has_context) {
173
+ // No context after discuss — invoke blocker callback
174
+ const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Discuss, 'No context after discuss step');
175
+ if (decision === 'stop') {
176
+ halted = true;
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // ── Step 2: Research ──
183
+ if (!halted) {
184
+ if (!this.config.workflow.research) {
185
+ this.logger?.debug('Skipping research: config.workflow.research=false');
186
+ } else {
187
+ const result = await this.retryOnce('research', () => this.runStep(PhaseStepType.Research, phaseNumber, sessionOpts));
188
+ steps.push(result);
189
+ }
190
+ }
191
+
192
+ // ── Step 2.5: Research gate (#1602) ──
193
+ // Check RESEARCH.md for unresolved open questions before planning
194
+ if (!halted && phaseOp.has_research) {
195
+ const gateResult = await this.checkResearchGate(phaseOp);
196
+ if (!gateResult.pass) {
197
+ const questionList = gateResult.unresolvedQuestions.join(', ');
198
+ const error = `RESEARCH.md has unresolved open questions: ${questionList}`;
199
+ this.logger?.warn(error, { phase: phaseNumber });
200
+ const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Research, error);
201
+ if (decision === 'stop') {
202
+ halted = true;
203
+ }
204
+ }
205
+ }
206
+
207
+ // ── Step 3: Plan ──
208
+ if (!halted) {
209
+ const result = await this.retryOnce('plan', () => this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts));
210
+ steps.push(result);
211
+
212
+ // Re-query to check for plans
213
+ try {
214
+ phaseOp = await this.tools.initPhaseOp(phaseNumber);
215
+ } catch {
216
+ // Proceed with prior state
217
+ }
218
+
219
+ if (!phaseOp.has_plans || phaseOp.plan_count === 0) {
220
+ const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Plan, 'No plans created after plan step');
221
+ if (decision === 'stop') {
222
+ halted = true;
223
+ }
224
+ }
225
+ }
226
+
227
+ // ── Step 3.5: Plan Check ──
228
+ if (!halted && this.config.workflow.plan_check) {
229
+ const planCheckResult = await this.retryOnce('plan-check', () => this.runPlanCheckStep(phaseNumber, sessionOpts));
230
+ steps.push(planCheckResult);
231
+
232
+ // If plan-check failed, re-plan once then re-check once (D023)
233
+ if (!planCheckResult.success) {
234
+ this.logger?.info(`Plan check failed for phase ${phaseNumber}, re-planning once (D023)`);
235
+
236
+ // Re-run plan step with feedback
237
+ const replanResult = await this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts);
238
+ steps.push(replanResult);
239
+
240
+ // Re-check once
241
+ const recheckResult = await this.runPlanCheckStep(phaseNumber, sessionOpts);
242
+ steps.push(recheckResult);
243
+
244
+ if (!recheckResult.success) {
245
+ this.logger?.warn(`Plan check failed again after re-plan for phase ${phaseNumber}. Proceeding with warning (D023).`);
246
+ }
247
+ }
248
+ }
249
+
250
+ // ── Step 4: Execute ──
251
+ if (!halted) {
252
+ const executeResult = await this.retryOnce('execute', () => this.runExecuteStep(phaseNumber, sessionOpts));
253
+ steps.push(executeResult);
254
+ }
255
+
256
+ // ── Step 5: Verify ──
257
+ if (!halted) {
258
+ if (!this.config.workflow.verifier) {
259
+ this.logger?.debug('Skipping verify: config.workflow.verifier=false');
260
+ } else {
261
+ // Verify has its own internal retry logic (gap closure). retryOnce only
262
+ // retries on unexpected session throws, not on verification outcomes like gaps_found.
263
+ const verifyResult = await this.retryOnce('verify', () => this.runVerifyStep(phaseNumber, sessionOpts, callbacks, options));
264
+ steps.push(verifyResult);
265
+
266
+ // Check if verify resulted in a halt
267
+ if (!verifyResult.success && verifyResult.error === 'halted_by_callback') {
268
+ halted = true;
269
+ }
270
+ }
271
+ }
272
+
273
+ // ── Step 6: Advance ──
274
+ // Only advance if verify passed — never mark a phase complete when gaps were found.
275
+ const verifyPassed = steps.every(s => s.step !== PhaseStepType.Verify || s.success);
276
+ if (!halted && verifyPassed) {
277
+ const advanceResult = await this.runAdvanceStep(phaseNumber, sessionOpts, callbacks);
278
+ steps.push(advanceResult);
279
+ } else if (!halted && !verifyPassed) {
280
+ this.logger?.warn(`Skipping advance for phase ${phaseNumber}: verification found gaps`);
281
+ }
282
+
283
+ const totalDurationMs = Date.now() - startTime;
284
+ const totalCostUsd = steps.reduce((sum, s) => {
285
+ const stepCost = s.planResults?.reduce((c, pr) => c + pr.totalCostUsd, 0) ?? 0;
286
+ return sum + stepCost;
287
+ }, 0);
288
+ const success = !halted && steps.every(s => s.success);
289
+
290
+ // Emit phase_complete
291
+ this.eventStream.emitEvent({
292
+ type: GSDEventType.PhaseComplete,
293
+ timestamp: new Date().toISOString(),
294
+ sessionId: '',
295
+ phaseNumber,
296
+ phaseName,
297
+ success,
298
+ totalCostUsd,
299
+ totalDurationMs,
300
+ stepsCompleted: steps.length,
301
+ });
302
+
303
+ await this.recordFailureSignals(phaseNumber, phaseName, phaseOp.phase_dir, steps);
304
+
305
+ return {
306
+ phaseNumber,
307
+ phaseName,
308
+ steps,
309
+ success,
310
+ totalCostUsd,
311
+ totalDurationMs,
312
+ };
313
+ }
314
+
315
+ // ─── Step runners ──────────────────────────────────────────────────────
316
+
317
+ /**
318
+ * Retry a step function once on failure.
319
+ * On first error/failure, logs a warning and calls the function once more.
320
+ * Returns the result from the last attempt.
321
+ */
322
+ private async retryOnce<T extends PhaseStepResult>(label: string, fn: () => Promise<T>): Promise<T> {
323
+ const result = await fn();
324
+ if (result.success) return result;
325
+
326
+ // Don't retry verify outcomes (gaps_found, human_needed) — they have their own retry logic.
327
+ if (result.error?.startsWith('verification_')) return result;
328
+
329
+ this.logger?.warn(`Step "${label}" failed, retrying once...`);
330
+ return fn();
331
+ }
332
+
333
+ /**
334
+ * Run the plan-check step.
335
+ * Loads the gsd-plan-checker agent definition, runs a Verify-scoped session,
336
+ * and parses output for PASS/FAIL signals.
337
+ */
338
+ private async runPlanCheckStep(
339
+ phaseNumber: string,
340
+ sessionOpts: SessionOptions,
341
+ ): Promise<PhaseStepResult> {
342
+ const stepStart = Date.now();
343
+
344
+ this.eventStream.emitEvent({
345
+ type: GSDEventType.PhaseStepStart,
346
+ timestamp: new Date().toISOString(),
347
+ sessionId: '',
348
+ phaseNumber,
349
+ step: PhaseStepType.PlanCheck,
350
+ });
351
+
352
+ let planResult: PlanResult;
353
+ try {
354
+ // Load plan-checker agent definition (same pattern as PromptFactory.loadAgentDef)
355
+ const agentDef = await this.promptFactory.loadAgentDef(PhaseType.Verify);
356
+
357
+ // Build prompt using Verify phase type for context resolution
358
+ const contextFiles = await this.contextEngine.resolveContextFiles(PhaseType.Verify);
359
+ let prompt = await this.promptFactory.buildPrompt(PhaseType.Verify, null, contextFiles);
360
+
361
+ // Supplement with plan-checker instructions
362
+ prompt += '\n\n## Plan Checker Instructions\n\nYou are a plan checker. Review the plans for this phase and verify they are well-formed, complete, and achievable. If all plans pass, output "VERIFICATION PASSED". If any issues are found, output "ISSUES FOUND" followed by a description of each issue.';
363
+
364
+ planResult = await runPhaseStepSession(
365
+ prompt,
366
+ PhaseStepType.PlanCheck,
367
+ this.config,
368
+ sessionOpts,
369
+ this.eventStream,
370
+ { phase: PhaseType.Verify, planName: undefined },
371
+ );
372
+ } catch (err) {
373
+ const durationMs = Date.now() - stepStart;
374
+ const errorMsg = err instanceof Error ? err.message : String(err);
375
+
376
+ this.eventStream.emitEvent({
377
+ type: GSDEventType.PhaseStepComplete,
378
+ timestamp: new Date().toISOString(),
379
+ sessionId: '',
380
+ phaseNumber,
381
+ step: PhaseStepType.PlanCheck,
382
+ success: false,
383
+ durationMs,
384
+ error: errorMsg,
385
+ });
386
+
387
+ return {
388
+ step: PhaseStepType.PlanCheck,
389
+ success: false,
390
+ durationMs,
391
+ error: errorMsg,
392
+ };
393
+ }
394
+
395
+ const durationMs = Date.now() - stepStart;
396
+ // Parse plan-check outcome: success if the session succeeded (real output parsing would check for VERIFICATION PASSED / ISSUES FOUND)
397
+ const success = planResult.success;
398
+
399
+ this.eventStream.emitEvent({
400
+ type: GSDEventType.PhaseStepComplete,
401
+ timestamp: new Date().toISOString(),
402
+ sessionId: planResult.sessionId,
403
+ phaseNumber,
404
+ step: PhaseStepType.PlanCheck,
405
+ success,
406
+ durationMs,
407
+ error: planResult.error?.messages.join('; ') || undefined,
408
+ });
409
+
410
+ return {
411
+ step: PhaseStepType.PlanCheck,
412
+ success,
413
+ durationMs,
414
+ error: planResult.error?.messages.join('; ') || undefined,
415
+ planResults: [planResult],
416
+ };
417
+ }
418
+
419
+ /**
420
+ * Run the self-discuss step for auto-mode.
421
+ * When auto_advance is true and no context exists, run an AI self-discuss
422
+ * session that identifies gray areas and makes opinionated decisions.
423
+ */
424
+ private async runSelfDiscussStep(
425
+ phaseNumber: string,
426
+ sessionOpts: SessionOptions,
427
+ ): Promise<PhaseStepResult> {
428
+ const stepStart = Date.now();
429
+
430
+ this.eventStream.emitEvent({
431
+ type: GSDEventType.PhaseStepStart,
432
+ timestamp: new Date().toISOString(),
433
+ sessionId: '',
434
+ phaseNumber,
435
+ step: PhaseStepType.Discuss,
436
+ });
437
+
438
+ let planResult: PlanResult;
439
+ try {
440
+ const contextFiles = await this.contextEngine.resolveContextFiles(PhaseType.Discuss);
441
+ let prompt = await this.promptFactory.buildPrompt(PhaseType.Discuss, null, contextFiles);
442
+
443
+ // Supplement with self-discuss instructions with pass cap
444
+ const maxPasses = this.config.workflow.max_discuss_passes ?? 3;
445
+ prompt += `\n\n## Self-Discuss Mode\n\nYou are the AI discussing decisions with yourself. No human is present. Identify 3-5 gray areas in the project scope, reason through each one, make opinionated choices, and write CONTEXT.md with your decisions.\n\n**CRITICAL: Single-pass only.** You MUST complete all decisions in ONE pass and write CONTEXT.md once. Do NOT re-read your own CONTEXT.md to find "gaps" and do additional passes. The maximum allowed passes is ${maxPasses} — if you have already written CONTEXT.md, you are DONE. Proceed to the next workflow step. Self-referential gap-finding loops waste resources without adding value.`;
446
+
447
+ planResult = await runPhaseStepSession(
448
+ prompt,
449
+ PhaseStepType.Discuss,
450
+ this.config,
451
+ sessionOpts,
452
+ this.eventStream,
453
+ { phase: PhaseType.Discuss, planName: undefined },
454
+ );
455
+ } catch (err) {
456
+ const durationMs = Date.now() - stepStart;
457
+ const errorMsg = err instanceof Error ? err.message : String(err);
458
+
459
+ this.eventStream.emitEvent({
460
+ type: GSDEventType.PhaseStepComplete,
461
+ timestamp: new Date().toISOString(),
462
+ sessionId: '',
463
+ phaseNumber,
464
+ step: PhaseStepType.Discuss,
465
+ success: false,
466
+ durationMs,
467
+ error: errorMsg,
468
+ });
469
+
470
+ return {
471
+ step: PhaseStepType.Discuss,
472
+ success: false,
473
+ durationMs,
474
+ error: errorMsg,
475
+ };
476
+ }
477
+
478
+ const durationMs = Date.now() - stepStart;
479
+ const success = planResult.success;
480
+
481
+ this.eventStream.emitEvent({
482
+ type: GSDEventType.PhaseStepComplete,
483
+ timestamp: new Date().toISOString(),
484
+ sessionId: planResult.sessionId,
485
+ phaseNumber,
486
+ step: PhaseStepType.Discuss,
487
+ success,
488
+ durationMs,
489
+ error: planResult.error?.messages.join('; ') || undefined,
490
+ });
491
+
492
+ return {
493
+ step: PhaseStepType.Discuss,
494
+ success,
495
+ durationMs,
496
+ error: planResult.error?.messages.join('; ') || undefined,
497
+ planResults: [planResult],
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Run a single phase step session (discuss, research, plan).
503
+ * Emits step start/complete events and captures errors.
504
+ */
505
+ private async runStep(
506
+ step: PhaseStepType,
507
+ phaseNumber: string,
508
+ sessionOpts: SessionOptions,
509
+ ): Promise<PhaseStepResult> {
510
+ const stepStart = Date.now();
511
+
512
+ this.eventStream.emitEvent({
513
+ type: GSDEventType.PhaseStepStart,
514
+ timestamp: new Date().toISOString(),
515
+ sessionId: '',
516
+ phaseNumber,
517
+ step,
518
+ });
519
+
520
+ let planResult: PlanResult;
521
+ try {
522
+ // Map step to PhaseType for prompt/context resolution
523
+ const phaseType = this.stepToPhaseType(step);
524
+ const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
525
+ const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
526
+
527
+ planResult = await runPhaseStepSession(
528
+ prompt,
529
+ step,
530
+ this.config,
531
+ sessionOpts,
532
+ this.eventStream,
533
+ { phase: phaseType, planName: undefined },
534
+ );
535
+ } catch (err) {
536
+ const durationMs = Date.now() - stepStart;
537
+ const errorMsg = err instanceof Error ? err.message : String(err);
538
+
539
+ this.eventStream.emitEvent({
540
+ type: GSDEventType.PhaseStepComplete,
541
+ timestamp: new Date().toISOString(),
542
+ sessionId: '',
543
+ phaseNumber,
544
+ step,
545
+ success: false,
546
+ durationMs,
547
+ error: errorMsg,
548
+ });
549
+
550
+ return {
551
+ step,
552
+ success: false,
553
+ durationMs,
554
+ error: errorMsg,
555
+ };
556
+ }
557
+
558
+ const durationMs = Date.now() - stepStart;
559
+ const success = planResult.success;
560
+
561
+ this.eventStream.emitEvent({
562
+ type: GSDEventType.PhaseStepComplete,
563
+ timestamp: new Date().toISOString(),
564
+ sessionId: planResult.sessionId,
565
+ phaseNumber,
566
+ step,
567
+ success,
568
+ durationMs,
569
+ error: planResult.error?.messages.join('; ') || undefined,
570
+ });
571
+
572
+ return {
573
+ step,
574
+ success,
575
+ durationMs,
576
+ error: planResult.error?.messages.join('; ') || undefined,
577
+ planResults: [planResult],
578
+ };
579
+ }
580
+
581
+ /**
582
+ * Run the execute step — uses phase-plan-index for wave-grouped parallel execution.
583
+ * Plans in the same wave run concurrently via Promise.allSettled().
584
+ * Waves execute sequentially (wave 1 completes before wave 2 starts).
585
+ * Respects config.parallelization: false to fall back to sequential execution.
586
+ * Filters out plans with has_summary: true (already completed).
587
+ */
588
+ private async runExecuteStep(
589
+ phaseNumber: string,
590
+ sessionOpts: SessionOptions,
591
+ ): Promise<PhaseStepResult> {
592
+ const stepStart = Date.now();
593
+
594
+ this.eventStream.emitEvent({
595
+ type: GSDEventType.PhaseStepStart,
596
+ timestamp: new Date().toISOString(),
597
+ sessionId: '',
598
+ phaseNumber,
599
+ step: PhaseStepType.Execute,
600
+ });
601
+
602
+ // Get the plan index from gsd-tools
603
+ let planIndex: PhasePlanIndex;
604
+ try {
605
+ planIndex = await this.tools.phasePlanIndex(phaseNumber);
606
+ } catch (err) {
607
+ const durationMs = Date.now() - stepStart;
608
+ const errorMsg = err instanceof Error ? err.message : String(err);
609
+ this.eventStream.emitEvent({
610
+ type: GSDEventType.PhaseStepComplete,
611
+ timestamp: new Date().toISOString(),
612
+ sessionId: '',
613
+ phaseNumber,
614
+ step: PhaseStepType.Execute,
615
+ success: false,
616
+ durationMs,
617
+ error: errorMsg,
618
+ });
619
+ return {
620
+ step: PhaseStepType.Execute,
621
+ success: false,
622
+ durationMs,
623
+ error: errorMsg,
624
+ };
625
+ }
626
+
627
+ // Filter to incomplete plans only (has_summary === false)
628
+ const incompletePlans = planIndex.plans.filter(p => !p.has_summary);
629
+
630
+ if (incompletePlans.length === 0) {
631
+ const durationMs = Date.now() - stepStart;
632
+ this.eventStream.emitEvent({
633
+ type: GSDEventType.PhaseStepComplete,
634
+ timestamp: new Date().toISOString(),
635
+ sessionId: '',
636
+ phaseNumber,
637
+ step: PhaseStepType.Execute,
638
+ success: true,
639
+ durationMs,
640
+ });
641
+ return {
642
+ step: PhaseStepType.Execute,
643
+ success: true,
644
+ durationMs,
645
+ planResults: [],
646
+ };
647
+ }
648
+
649
+ const planResults: PlanResult[] = [];
650
+
651
+ // Sequential fallback when parallelization is disabled
652
+ if (this.config.parallelization === false) {
653
+ for (const plan of incompletePlans) {
654
+ const result = await this.executeSinglePlan(phaseNumber, plan.id, sessionOpts);
655
+ planResults.push(result);
656
+ }
657
+ } else {
658
+ // Group incomplete plans by wave, sort waves numerically
659
+ const waveMap = new Map<number, PlanInfo[]>();
660
+ for (const plan of incompletePlans) {
661
+ const existing = waveMap.get(plan.wave) ?? [];
662
+ existing.push(plan);
663
+ waveMap.set(plan.wave, existing);
664
+ }
665
+ const sortedWaves = [...waveMap.keys()].sort((a, b) => a - b);
666
+
667
+ for (const waveNum of sortedWaves) {
668
+ const wavePlans = waveMap.get(waveNum)!;
669
+ const wavePlanIds = wavePlans.map(p => p.id);
670
+
671
+ // Emit wave_start
672
+ this.eventStream.emitEvent({
673
+ type: GSDEventType.WaveStart,
674
+ timestamp: new Date().toISOString(),
675
+ sessionId: '',
676
+ phaseNumber,
677
+ waveNumber: waveNum,
678
+ planCount: wavePlans.length,
679
+ planIds: wavePlanIds,
680
+ });
681
+
682
+ const waveStart = Date.now();
683
+
684
+ // Execute all plans in this wave concurrently
685
+ const settled = await Promise.allSettled(
686
+ wavePlans.map(plan => this.executeSinglePlan(phaseNumber, plan.id, sessionOpts)),
687
+ );
688
+
689
+ // Map settled results to PlanResult[]
690
+ let successCount = 0;
691
+ let failureCount = 0;
692
+ for (const outcome of settled) {
693
+ if (outcome.status === 'fulfilled') {
694
+ planResults.push(outcome.value);
695
+ if (outcome.value.success) successCount++;
696
+ else failureCount++;
697
+ } else {
698
+ failureCount++;
699
+ planResults.push({
700
+ success: false,
701
+ sessionId: '',
702
+ totalCostUsd: 0,
703
+ durationMs: 0,
704
+ usage: { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 },
705
+ numTurns: 0,
706
+ error: {
707
+ subtype: 'error_during_execution',
708
+ messages: [outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)],
709
+ },
710
+ });
711
+ }
712
+ }
713
+
714
+ // Emit wave_complete
715
+ this.eventStream.emitEvent({
716
+ type: GSDEventType.WaveComplete,
717
+ timestamp: new Date().toISOString(),
718
+ sessionId: '',
719
+ phaseNumber,
720
+ waveNumber: waveNum,
721
+ successCount,
722
+ failureCount,
723
+ durationMs: Date.now() - waveStart,
724
+ });
725
+ }
726
+ }
727
+
728
+ const durationMs = Date.now() - stepStart;
729
+ const allSucceeded = planResults.every(r => r.success);
730
+
731
+ this.eventStream.emitEvent({
732
+ type: GSDEventType.PhaseStepComplete,
733
+ timestamp: new Date().toISOString(),
734
+ sessionId: '',
735
+ phaseNumber,
736
+ step: PhaseStepType.Execute,
737
+ success: allSucceeded,
738
+ durationMs,
739
+ });
740
+
741
+ return {
742
+ step: PhaseStepType.Execute,
743
+ success: allSucceeded,
744
+ durationMs,
745
+ planResults,
746
+ };
747
+ }
748
+
749
+ /**
750
+ * Execute a single plan by ID within the execute step.
751
+ */
752
+ private async executeSinglePlan(
753
+ phaseNumber: string,
754
+ planId: string,
755
+ sessionOpts: SessionOptions,
756
+ ): Promise<PlanResult> {
757
+ try {
758
+ const phaseType = PhaseType.Execute;
759
+ const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
760
+ const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
761
+
762
+ return await runPhaseStepSession(
763
+ prompt,
764
+ PhaseStepType.Execute,
765
+ this.config,
766
+ sessionOpts,
767
+ this.eventStream,
768
+ { phase: phaseType, planName: planId },
769
+ );
770
+ } catch (err) {
771
+ return {
772
+ success: false,
773
+ sessionId: '',
774
+ totalCostUsd: 0,
775
+ durationMs: 0,
776
+ usage: { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 },
777
+ numTurns: 0,
778
+ error: {
779
+ subtype: 'error_during_execution',
780
+ messages: [err instanceof Error ? err.message : String(err)],
781
+ },
782
+ };
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Run the verify step with full gap closure cycle.
788
+ * Verification outcome routing:
789
+ * - passed → proceed to advance
790
+ * - human_needed → invoke onVerificationReview callback
791
+ * - gaps_found → plan (create gap plans) → execute (run gap plans) → re-verify
792
+ * Gap closure retries are capped at configurable maxGapRetries (default 1).
793
+ */
794
+ private async runVerifyStep(
795
+ phaseNumber: string,
796
+ sessionOpts: SessionOptions,
797
+ callbacks: HumanGateCallbacks,
798
+ options?: PhaseRunnerOptions,
799
+ ): Promise<PhaseStepResult> {
800
+ const stepStart = Date.now();
801
+
802
+ this.eventStream.emitEvent({
803
+ type: GSDEventType.PhaseStepStart,
804
+ timestamp: new Date().toISOString(),
805
+ sessionId: '',
806
+ phaseNumber,
807
+ step: PhaseStepType.Verify,
808
+ });
809
+
810
+ const maxGapRetries = options?.maxGapRetries ?? 1;
811
+ let gapRetryCount = 0;
812
+ let lastResult: PlanResult | undefined;
813
+ let outcome: VerificationOutcome = 'passed';
814
+ const allPlanResults: PlanResult[] = [];
815
+
816
+ while (true) {
817
+ try {
818
+ const phaseType = PhaseType.Verify;
819
+ const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
820
+ const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
821
+
822
+ lastResult = await runPhaseStepSession(
823
+ prompt,
824
+ PhaseStepType.Verify,
825
+ this.config,
826
+ sessionOpts,
827
+ this.eventStream,
828
+ { phase: phaseType },
829
+ );
830
+ allPlanResults.push(lastResult);
831
+ } catch (err) {
832
+ const durationMs = Date.now() - stepStart;
833
+ const errorMsg = err instanceof Error ? err.message : String(err);
834
+
835
+ this.eventStream.emitEvent({
836
+ type: GSDEventType.PhaseStepComplete,
837
+ timestamp: new Date().toISOString(),
838
+ sessionId: '',
839
+ phaseNumber,
840
+ step: PhaseStepType.Verify,
841
+ success: false,
842
+ durationMs,
843
+ error: errorMsg,
844
+ });
845
+
846
+ return {
847
+ step: PhaseStepType.Verify,
848
+ success: false,
849
+ durationMs,
850
+ error: errorMsg,
851
+ planResults: allPlanResults.length > 0 ? allPlanResults : undefined,
852
+ };
853
+ }
854
+
855
+ // Parse verification outcome from session result
856
+ outcome = this.parseVerificationOutcome(lastResult);
857
+
858
+ if (outcome === 'passed') {
859
+ break;
860
+ }
861
+
862
+ if (outcome === 'human_needed') {
863
+ // Invoke verification review callback
864
+ const decision = await this.invokeVerificationCallback(callbacks, phaseNumber, {
865
+ step: PhaseStepType.Verify,
866
+ success: lastResult.success,
867
+ durationMs: Date.now() - stepStart,
868
+ planResults: allPlanResults,
869
+ });
870
+
871
+ if (decision === 'accept') {
872
+ outcome = 'passed';
873
+ break; // Treat as passed
874
+ } else if (decision === 'retry' && gapRetryCount < maxGapRetries) {
875
+ gapRetryCount++;
876
+ continue;
877
+ } else {
878
+ // reject or exceeded retries
879
+ const durationMs = Date.now() - stepStart;
880
+ this.eventStream.emitEvent({
881
+ type: GSDEventType.PhaseStepComplete,
882
+ timestamp: new Date().toISOString(),
883
+ sessionId: lastResult.sessionId,
884
+ phaseNumber,
885
+ step: PhaseStepType.Verify,
886
+ success: false,
887
+ durationMs,
888
+ error: 'halted_by_callback',
889
+ });
890
+ return {
891
+ step: PhaseStepType.Verify,
892
+ success: false,
893
+ durationMs,
894
+ error: 'halted_by_callback',
895
+ planResults: allPlanResults,
896
+ };
897
+ }
898
+ }
899
+
900
+ if (outcome === 'gaps_found') {
901
+ if (gapRetryCount < maxGapRetries) {
902
+ gapRetryCount++;
903
+ this.logger?.info(`Gap closure attempt ${gapRetryCount}/${maxGapRetries} for phase ${phaseNumber}`);
904
+
905
+ // ── Gap closure cycle: plan → execute → re-verify ──
906
+
907
+ // 1. Run a plan step to create gap plans
908
+ try {
909
+ const planResult = await this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts);
910
+ if (planResult.planResults) {
911
+ allPlanResults.push(...planResult.planResults);
912
+ }
913
+ } catch (err) {
914
+ this.logger?.warn(`Gap closure plan step failed: ${err instanceof Error ? err.message : String(err)}`);
915
+ // Proceed to re-verify anyway
916
+ }
917
+
918
+ // 2. Re-query phase state to discover newly created gap plans
919
+ try {
920
+ await this.tools.initPhaseOp(phaseNumber);
921
+ } catch (err) {
922
+ this.logger?.warn(`Gap closure re-query failed, proceeding with stale state: ${err instanceof Error ? err.message : String(err)}`);
923
+ }
924
+
925
+ // 3. Execute gap plans via the wave-capable runExecuteStep
926
+ try {
927
+ const executeResult = await this.runExecuteStep(phaseNumber, sessionOpts);
928
+ if (executeResult.planResults) {
929
+ allPlanResults.push(...executeResult.planResults);
930
+ }
931
+ } catch (err) {
932
+ this.logger?.warn(`Gap closure execute step failed: ${err instanceof Error ? err.message : String(err)}`);
933
+ // Proceed to re-verify anyway
934
+ }
935
+
936
+ // 4. Continue the loop to re-verify
937
+ continue;
938
+ }
939
+ // Exceeded gap closure retries — proceed
940
+ break;
941
+ }
942
+
943
+ break; // Safety: unknown outcome → proceed
944
+ }
945
+
946
+ const durationMs = Date.now() - stepStart;
947
+ const verifySuccess = outcome === 'passed';
948
+
949
+ this.eventStream.emitEvent({
950
+ type: GSDEventType.PhaseStepComplete,
951
+ timestamp: new Date().toISOString(),
952
+ sessionId: lastResult?.sessionId ?? '',
953
+ phaseNumber,
954
+ step: PhaseStepType.Verify,
955
+ success: verifySuccess,
956
+ durationMs,
957
+ ...(!verifySuccess && { error: `verification_${outcome}` }),
958
+ });
959
+
960
+ return {
961
+ step: PhaseStepType.Verify,
962
+ success: verifySuccess,
963
+ durationMs,
964
+ planResults: allPlanResults,
965
+ ...(!verifySuccess && { error: `verification_${outcome}` }),
966
+ };
967
+ }
968
+
969
+ /**
970
+ * Run the advance step — mark phase complete.
971
+ * Gated by config.workflow.auto_advance or callback approval.
972
+ */
973
+ private async runAdvanceStep(
974
+ phaseNumber: string,
975
+ _sessionOpts: SessionOptions,
976
+ callbacks: HumanGateCallbacks,
977
+ ): Promise<PhaseStepResult> {
978
+ const stepStart = Date.now();
979
+
980
+ this.eventStream.emitEvent({
981
+ type: GSDEventType.PhaseStepStart,
982
+ timestamp: new Date().toISOString(),
983
+ sessionId: '',
984
+ phaseNumber,
985
+ step: PhaseStepType.Advance,
986
+ });
987
+
988
+ // Check if auto_advance or callback approves
989
+ let shouldAdvance = this.config.workflow.auto_advance;
990
+
991
+ if (!shouldAdvance && callbacks.onBlockerDecision) {
992
+ try {
993
+ const decision = await callbacks.onBlockerDecision({
994
+ phaseNumber,
995
+ step: PhaseStepType.Advance,
996
+ error: undefined,
997
+ });
998
+ shouldAdvance = decision !== 'stop';
999
+ } catch (err) {
1000
+ this.logger?.warn(`Advance callback threw, auto-approving: ${err instanceof Error ? err.message : String(err)}`);
1001
+ shouldAdvance = true; // Auto-approve on callback error
1002
+ }
1003
+ } else if (!shouldAdvance) {
1004
+ // No callback, auto-approve
1005
+ shouldAdvance = true;
1006
+ }
1007
+
1008
+ if (!shouldAdvance) {
1009
+ const durationMs = Date.now() - stepStart;
1010
+ this.eventStream.emitEvent({
1011
+ type: GSDEventType.PhaseStepComplete,
1012
+ timestamp: new Date().toISOString(),
1013
+ sessionId: '',
1014
+ phaseNumber,
1015
+ step: PhaseStepType.Advance,
1016
+ success: false,
1017
+ durationMs,
1018
+ error: 'advance_rejected',
1019
+ });
1020
+ return {
1021
+ step: PhaseStepType.Advance,
1022
+ success: false,
1023
+ durationMs,
1024
+ error: 'advance_rejected',
1025
+ };
1026
+ }
1027
+
1028
+ try {
1029
+ await this.tools.phaseComplete(phaseNumber);
1030
+ } catch (err) {
1031
+ const durationMs = Date.now() - stepStart;
1032
+ const errorMsg = err instanceof Error ? err.message : String(err);
1033
+
1034
+ this.eventStream.emitEvent({
1035
+ type: GSDEventType.PhaseStepComplete,
1036
+ timestamp: new Date().toISOString(),
1037
+ sessionId: '',
1038
+ phaseNumber,
1039
+ step: PhaseStepType.Advance,
1040
+ success: false,
1041
+ durationMs,
1042
+ error: errorMsg,
1043
+ });
1044
+
1045
+ return {
1046
+ step: PhaseStepType.Advance,
1047
+ success: false,
1048
+ durationMs,
1049
+ error: errorMsg,
1050
+ };
1051
+ }
1052
+
1053
+ const durationMs = Date.now() - stepStart;
1054
+
1055
+ this.eventStream.emitEvent({
1056
+ type: GSDEventType.PhaseStepComplete,
1057
+ timestamp: new Date().toISOString(),
1058
+ sessionId: '',
1059
+ phaseNumber,
1060
+ step: PhaseStepType.Advance,
1061
+ success: true,
1062
+ durationMs,
1063
+ });
1064
+
1065
+ return {
1066
+ step: PhaseStepType.Advance,
1067
+ success: true,
1068
+ durationMs,
1069
+ };
1070
+ }
1071
+
1072
+ // ─── Helpers ───────────────────────────────────────────────────────────
1073
+
1074
+ /**
1075
+ * Map PhaseStepType to PhaseType for prompt/context resolution.
1076
+ */
1077
+ private stepToPhaseType(step: PhaseStepType): PhaseType {
1078
+ const mapping: Record<string, PhaseType> = {
1079
+ [PhaseStepType.Discuss]: PhaseType.Discuss,
1080
+ [PhaseStepType.Research]: PhaseType.Research,
1081
+ [PhaseStepType.Plan]: PhaseType.Plan,
1082
+ [PhaseStepType.PlanCheck]: PhaseType.Verify,
1083
+ [PhaseStepType.Execute]: PhaseType.Execute,
1084
+ [PhaseStepType.Verify]: PhaseType.Verify,
1085
+ };
1086
+ return mapping[step] ?? PhaseType.Execute;
1087
+ }
1088
+
1089
+ /**
1090
+ * Parse the verification outcome from a PlanResult.
1091
+ * In a real implementation, this would parse the session output for
1092
+ * structured verification signals. For now, map from success/error.
1093
+ */
1094
+ private parseVerificationOutcome(result: PlanResult): VerificationOutcome {
1095
+ if (result.success) return 'passed';
1096
+ if (result.error?.subtype === 'human_review_needed') return 'human_needed';
1097
+ return 'gaps_found';
1098
+ }
1099
+
1100
+ /**
1101
+ * Check RESEARCH.md for unresolved open questions (#1602).
1102
+ * Returns the gate result — pass means safe to proceed to planning.
1103
+ */
1104
+ private async checkResearchGate(phaseOp: PhaseOpInfo): Promise<{ pass: boolean; unresolvedQuestions: string[] }> {
1105
+ try {
1106
+ const researchPath = phaseOp.research_path ||
1107
+ join(phaseOp.phase_dir, `${phaseOp.padded_phase}-RESEARCH.md`);
1108
+ const content = await readFile(researchPath, 'utf-8');
1109
+ return checkResearchGate(content);
1110
+ } catch {
1111
+ // File doesn't exist or can't be read — pass (nothing to gate on)
1112
+ return { pass: true, unresolvedQuestions: [] };
1113
+ }
1114
+ }
1115
+
1116
+ /**
1117
+ * Invoke the onBlockerDecision callback, falling back to auto-approve.
1118
+ */
1119
+ private async invokeBlockerCallback(
1120
+ callbacks: HumanGateCallbacks,
1121
+ phaseNumber: string,
1122
+ step: PhaseStepType,
1123
+ error?: string,
1124
+ ): Promise<'retry' | 'skip' | 'stop'> {
1125
+ if (!callbacks.onBlockerDecision) {
1126
+ return 'skip'; // Auto-approve: skip the blocker
1127
+ }
1128
+
1129
+ try {
1130
+ const decision = await callbacks.onBlockerDecision({ phaseNumber, step, error });
1131
+ // Validate return value
1132
+ if (decision === 'retry' || decision === 'skip' || decision === 'stop') {
1133
+ return decision;
1134
+ }
1135
+ this.logger?.warn(`Unexpected blocker callback return value: ${String(decision)}, falling back to skip`);
1136
+ return 'skip';
1137
+ } catch (err) {
1138
+ this.logger?.warn(`Blocker callback threw, auto-approving: ${err instanceof Error ? err.message : String(err)}`);
1139
+ return 'skip'; // Auto-approve on error
1140
+ }
1141
+ }
1142
+
1143
+ /**
1144
+ * Invoke the onVerificationReview callback, falling back to auto-accept.
1145
+ */
1146
+ private async invokeVerificationCallback(
1147
+ callbacks: HumanGateCallbacks,
1148
+ phaseNumber: string,
1149
+ stepResult: PhaseStepResult,
1150
+ ): Promise<'accept' | 'reject' | 'retry'> {
1151
+ if (!callbacks.onVerificationReview) {
1152
+ return 'accept'; // Auto-approve
1153
+ }
1154
+
1155
+ try {
1156
+ const decision = await callbacks.onVerificationReview({ phaseNumber, stepResult });
1157
+ if (decision === 'accept' || decision === 'reject' || decision === 'retry') {
1158
+ return decision;
1159
+ }
1160
+ this.logger?.warn(`Unexpected verification callback return value: ${String(decision)}, falling back to accept`);
1161
+ return 'accept';
1162
+ } catch (err) {
1163
+ this.logger?.warn(`Verification callback threw, auto-accepting: ${err instanceof Error ? err.message : String(err)}`);
1164
+ return 'accept'; // Auto-approve on error
1165
+ }
1166
+ }
1167
+
1168
+ private async recordFailureSignals(
1169
+ phaseNumber: string,
1170
+ phaseName: string,
1171
+ phaseDir: string,
1172
+ steps: PhaseStepResult[],
1173
+ ): Promise<void> {
1174
+ try {
1175
+ for (const step of steps) {
1176
+ for (const result of step.planResults ?? []) {
1177
+ if (!result.success) {
1178
+ await recordPlanResultFailure(
1179
+ this.projectDir,
1180
+ {
1181
+ phaseNumber,
1182
+ phaseName,
1183
+ phaseDir,
1184
+ step: step.step,
1185
+ },
1186
+ result,
1187
+ );
1188
+ }
1189
+ }
1190
+ }
1191
+
1192
+ await recordPhaseArtifactFailureEvents(this.projectDir, {
1193
+ phaseNumber,
1194
+ phaseName,
1195
+ phaseDir,
1196
+ });
1197
+ } catch (err) {
1198
+ this.logger?.warn(
1199
+ `Failure memory capture failed for phase ${phaseNumber}: ${err instanceof Error ? err.message : String(err)}`,
1200
+ );
1201
+ }
1202
+ }
1203
+ }