gsd-pi 2.65.0 → 2.66.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 (351) hide show
  1. package/dist/mcp-server.js +6 -2
  2. package/dist/resources/extensions/browser-tools/capture.js +20 -1
  3. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  4. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +2 -2
  6. package/dist/resources/extensions/gsd/auto/phases.js +48 -5
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
  8. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  9. package/dist/resources/extensions/gsd/auto/types.js +2 -0
  10. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
  16. package/dist/resources/extensions/gsd/auto-start.js +175 -12
  17. package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
  18. package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
  19. package/dist/resources/extensions/gsd/auto.js +21 -15
  20. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  22. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
  23. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
  27. package/dist/resources/extensions/gsd/commands/context.js +8 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -2
  29. package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
  30. package/dist/resources/extensions/gsd/config-overlay.js +312 -0
  31. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  32. package/dist/resources/extensions/gsd/detection.js +1 -1
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
  34. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  35. package/dist/resources/extensions/gsd/doctor.js +2 -1
  36. package/dist/resources/extensions/gsd/files.js +17 -0
  37. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  38. package/dist/resources/extensions/gsd/gsd-db.js +47 -4
  39. package/dist/resources/extensions/gsd/guided-flow.js +220 -29
  40. package/dist/resources/extensions/gsd/index.js +1 -1
  41. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  42. package/dist/resources/extensions/gsd/md-importer.js +14 -7
  43. package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
  44. package/dist/resources/extensions/gsd/notification-widget.js +2 -1
  45. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
  46. package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
  47. package/dist/resources/extensions/gsd/pre-execution-checks.js +26 -5
  48. package/dist/resources/extensions/gsd/preferences-types.js +3 -0
  49. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  50. package/dist/resources/extensions/gsd/preferences.js +9 -2
  51. package/dist/resources/extensions/gsd/preparation.js +1092 -0
  52. package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
  53. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  54. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  59. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  60. package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
  61. package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
  62. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  63. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  64. package/dist/resources/extensions/gsd/quick.js +19 -15
  65. package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
  66. package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
  67. package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
  68. package/dist/resources/extensions/gsd/session-lock.js +23 -1
  69. package/dist/resources/extensions/gsd/state.js +115 -28
  70. package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  71. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
  74. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
  75. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
  76. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
  77. package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
  78. package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
  79. package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
  80. package/dist/resources/extensions/gsd/undo.js +3 -2
  81. package/dist/resources/extensions/gsd/workflow-events.js +1 -0
  82. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  83. package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
  84. package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
  85. package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
  86. package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
  87. package/dist/resources/extensions/gsd/worktree.js +9 -0
  88. package/dist/resources/extensions/shared/interview-ui.js +1 -1
  89. package/dist/resources/extensions/subagent/agents.js +19 -5
  90. package/dist/web/standalone/.next/BUILD_ID +1 -1
  91. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  92. package/dist/web/standalone/.next/build-manifest.json +3 -3
  93. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  94. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  96. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.html +1 -1
  112. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  119. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  122. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  123. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  124. package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
  125. package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
  126. package/package.json +1 -1
  127. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
  129. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
  131. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
  133. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  148. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  149. package/packages/pi-coding-agent/package.json +1 -1
  150. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
  151. package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
  152. package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
  153. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
  155. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  158. package/packages/pi-tui/dist/components/image.d.ts +2 -0
  159. package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
  160. package/packages/pi-tui/dist/components/image.js +4 -0
  161. package/packages/pi-tui/dist/components/image.js.map +1 -1
  162. package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
  163. package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
  164. package/packages/pi-tui/dist/components/image.test.js +32 -0
  165. package/packages/pi-tui/dist/components/image.test.js.map +1 -0
  166. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  167. package/packages/pi-tui/dist/tui.js +3 -1
  168. package/packages/pi-tui/dist/tui.js.map +1 -1
  169. package/packages/pi-tui/src/components/image.test.ts +36 -0
  170. package/packages/pi-tui/src/components/image.ts +5 -0
  171. package/packages/pi-tui/src/tui.ts +3 -1
  172. package/pkg/package.json +1 -1
  173. package/src/resources/extensions/browser-tools/capture.ts +19 -1
  174. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  175. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
  176. package/src/resources/extensions/gsd/auto/loop.ts +2 -2
  177. package/src/resources/extensions/gsd/auto/phases.ts +68 -3
  178. package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
  179. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  180. package/src/resources/extensions/gsd/auto/types.ts +5 -0
  181. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
  183. package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
  184. package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
  185. package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
  186. package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
  187. package/src/resources/extensions/gsd/auto-start.ts +188 -10
  188. package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
  189. package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
  190. package/src/resources/extensions/gsd/auto.ts +19 -8
  191. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
  192. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
  193. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
  194. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
  195. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
  196. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
  197. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
  198. package/src/resources/extensions/gsd/commands/context.ts +7 -1
  199. package/src/resources/extensions/gsd/commands/handlers/core.ts +26 -2
  200. package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
  201. package/src/resources/extensions/gsd/config-overlay.ts +331 -0
  202. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  203. package/src/resources/extensions/gsd/detection.ts +1 -1
  204. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  205. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  206. package/src/resources/extensions/gsd/doctor.ts +2 -1
  207. package/src/resources/extensions/gsd/files.ts +19 -0
  208. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  209. package/src/resources/extensions/gsd/gsd-db.ts +46 -4
  210. package/src/resources/extensions/gsd/guided-flow.ts +254 -30
  211. package/src/resources/extensions/gsd/index.ts +1 -0
  212. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  213. package/src/resources/extensions/gsd/md-importer.ts +13 -6
  214. package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
  215. package/src/resources/extensions/gsd/notification-widget.ts +2 -1
  216. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
  217. package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
  218. package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -7
  219. package/src/resources/extensions/gsd/preferences-types.ts +25 -0
  220. package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
  221. package/src/resources/extensions/gsd/preferences.ts +9 -2
  222. package/src/resources/extensions/gsd/preparation.ts +1419 -0
  223. package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
  224. package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  225. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  226. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  227. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  228. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  229. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  230. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  231. package/src/resources/extensions/gsd/prompts/queue.md +2 -0
  232. package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
  233. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  234. package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  235. package/src/resources/extensions/gsd/quick.ts +20 -15
  236. package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
  237. package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
  238. package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
  239. package/src/resources/extensions/gsd/session-lock.ts +17 -1
  240. package/src/resources/extensions/gsd/state.ts +115 -26
  241. package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  242. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
  243. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
  244. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
  245. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
  246. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
  247. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
  248. package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
  249. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
  250. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
  251. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
  252. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
  253. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
  254. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
  255. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
  256. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
  257. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
  258. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
  259. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
  260. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
  261. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
  262. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
  263. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  264. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
  265. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
  266. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
  267. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
  269. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
  270. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
  271. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
  272. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
  273. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
  274. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
  275. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
  276. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +284 -20
  277. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
  278. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
  279. package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
  280. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
  281. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
  282. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
  283. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
  284. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
  285. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
  286. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
  287. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
  288. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
  289. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
  290. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
  291. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
  292. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
  293. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
  294. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
  295. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
  296. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
  297. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
  298. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
  299. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
  300. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
  301. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
  302. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
  303. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
  304. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
  305. package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
  306. package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
  307. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
  308. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
  309. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
  310. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
  311. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
  312. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
  313. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  314. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  315. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  316. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  317. package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
  318. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
  319. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  320. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
  321. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
  322. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
  323. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
  324. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
  325. package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
  326. package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
  327. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
  328. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  329. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
  330. package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
  331. package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
  332. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
  333. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
  334. package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
  335. package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
  336. package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
  337. package/src/resources/extensions/gsd/types.ts +4 -0
  338. package/src/resources/extensions/gsd/undo.ts +3 -2
  339. package/src/resources/extensions/gsd/workflow-events.ts +5 -3
  340. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  341. package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
  342. package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
  343. package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
  344. package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
  345. package/src/resources/extensions/gsd/worktree.ts +10 -0
  346. package/src/resources/extensions/shared/interview-ui.ts +1 -1
  347. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
  348. package/src/resources/extensions/subagent/agents.ts +30 -6
  349. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
  350. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → Bdk1mnQugYZh7ZxuXUYvc}/_buildManifest.js +0 -0
  351. /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → Bdk1mnQugYZh7ZxuXUYvc}/_ssgManifest.js +0 -0
@@ -357,6 +357,7 @@ function initSchema(db, fileBacked) {
357
357
  db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
358
358
  db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
359
359
  db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
360
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
360
361
  // v14 index — slice dependency lookups
361
362
  db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
362
363
  db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
@@ -390,6 +391,28 @@ function migrateSchema(db) {
390
391
  const currentVersion = row ? row["v"] : 0;
391
392
  if (currentVersion >= SCHEMA_VERSION)
392
393
  return;
394
+ // Backup database before migration so a mid-migration crash doesn't
395
+ // leave a partially-migrated DB with no recovery path.
396
+ // WAL-safe: checkpoint first to flush WAL into the main DB file, then copy.
397
+ if (currentPath && currentPath !== ":memory:" && existsSync(currentPath)) {
398
+ try {
399
+ const backupPath = `${currentPath}.backup-v${currentVersion}`;
400
+ if (!existsSync(backupPath)) {
401
+ // Flush WAL to main DB file before copying — without this, the backup
402
+ // may be missing committed data that only exists in the -wal file.
403
+ try {
404
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
405
+ }
406
+ catch { /* checkpoint is best-effort */ }
407
+ copyFileSync(currentPath, backupPath);
408
+ }
409
+ }
410
+ catch (backupErr) {
411
+ // Log but proceed — blocking migration leaves the DB stuck at an old
412
+ // schema version permanently on read-only or full filesystems.
413
+ logWarning("db", `Pre-migration backup failed: ${backupErr instanceof Error ? backupErr.message : String(backupErr)}`);
414
+ }
415
+ }
393
416
  db.exec("BEGIN");
394
417
  try {
395
418
  if (currentVersion < 2) {
@@ -644,6 +667,7 @@ function migrateSchema(db) {
644
667
  db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
645
668
  db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
646
669
  db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
670
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
647
671
  db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
648
672
  ":version": 13,
649
673
  ":applied_at": new Date().toISOString(),
@@ -935,8 +959,20 @@ export function _resetProvider() {
935
959
  export function upsertDecision(d) {
936
960
  if (!currentDb)
937
961
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
938
- currentDb.prepare(`INSERT OR REPLACE INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by)
939
- VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :superseded_by)`).run({
962
+ // Use ON CONFLICT DO UPDATE instead of INSERT OR REPLACE to preserve the
963
+ // seq column. INSERT OR REPLACE deletes then reinserts, resetting seq and
964
+ // corrupting decision ordering in DECISIONS.md after reconcile replay.
965
+ currentDb.prepare(`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by)
966
+ VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :superseded_by)
967
+ ON CONFLICT(id) DO UPDATE SET
968
+ when_context = excluded.when_context,
969
+ scope = excluded.scope,
970
+ decision = excluded.decision,
971
+ choice = excluded.choice,
972
+ rationale = excluded.rationale,
973
+ revisable = excluded.revisable,
974
+ made_by = excluded.made_by,
975
+ superseded_by = excluded.superseded_by`).run({
940
976
  ":id": d.id,
941
977
  ":when_context": d.when_context,
942
978
  ":scope": d.scope,
@@ -1007,7 +1043,9 @@ export function insertMilestone(m) {
1007
1043
  )`).run({
1008
1044
  ":id": m.id,
1009
1045
  ":title": m.title ?? "",
1010
- ":status": m.status ?? "active",
1046
+ // Default to "queued" never auto-create milestones as "active" (#3380).
1047
+ // Callers that need "active" must pass it explicitly.
1048
+ ":status": m.status ?? "queued",
1011
1049
  ":depends_on": JSON.stringify(m.depends_on ?? []),
1012
1050
  ":created_at": new Date().toISOString(),
1013
1051
  ":vision": m.planning?.vision ?? "",
@@ -1198,6 +1236,11 @@ export function updateTaskStatus(milestoneId, sliceId, taskId, status, completed
1198
1236
  ":id": taskId,
1199
1237
  });
1200
1238
  }
1239
+ export function setTaskBlockerDiscovered(milestoneId, sliceId, taskId, discovered) {
1240
+ if (!currentDb)
1241
+ return;
1242
+ currentDb.prepare(`UPDATE tasks SET blocker_discovered = :discovered WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":discovered": discovered ? 1 : 0, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1243
+ }
1201
1244
  export function upsertTaskPlanning(milestoneId, sliceId, taskId, planning) {
1202
1245
  if (!currentDb)
1203
1246
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -1323,7 +1366,7 @@ export function getSliceTasks(milestoneId, sliceId) {
1323
1366
  export function insertVerificationEvidence(e) {
1324
1367
  if (!currentDb)
1325
1368
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1326
- currentDb.prepare(`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
1369
+ currentDb.prepare(`INSERT OR IGNORE INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
1327
1370
  VALUES (:task_id, :slice_id, :milestone_id, :command, :exit_code, :verdict, :duration_ms, :created_at)`).run({
1328
1371
  ":task_id": e.taskId,
1329
1372
  ":slice_id": e.sliceId,
@@ -6,7 +6,7 @@
6
6
  * No execution state, no hooks, no tools — the LLM does the rest.
7
7
  */
8
8
  import { showNextAction } from "../shared/tui.js";
9
- import { loadFile } from "./files.js";
9
+ import { loadFile, saveFile } from "./files.js";
10
10
  import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
11
11
  import { parseRoadmapSlices } from "./roadmap-slices.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
@@ -34,6 +34,19 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
34
34
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
35
35
  import { selectAndApplyModel } from "./auto-model-selection.js";
36
36
  import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
37
+ import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, formatEcosystemBrief, } from "./preparation.js";
38
+ // ─── Preparation result storage ─────────────────────────────────────────────
39
+ // Stores the most recent preparation result for injection into discuss prompts.
40
+ // S02 will consume this when building the prepared discussion prompt.
41
+ let lastPreparationResult = null;
42
+ /** Get the most recent preparation result (for S02 prompt building). */
43
+ export function getLastPreparationResult() {
44
+ return lastPreparationResult;
45
+ }
46
+ /** Clear the preparation result (called after discussion completes). */
47
+ export function clearPreparationResult() {
48
+ lastPreparationResult = null;
49
+ }
37
50
  // ─── Re-exports (preserve public API for existing importers) ────────────────
38
51
  export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
39
52
  export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
@@ -74,7 +87,7 @@ function _getPendingAutoStart(basePath) {
74
87
  * Exported for testing (#2985).
75
88
  */
76
89
  export function setPendingAutoStart(basePath, entry) {
77
- pendingAutoStartMap.set(basePath, entry);
90
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry });
78
91
  }
79
92
  /**
80
93
  * Clear pending auto-start state.
@@ -248,8 +261,10 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
248
261
  // "Grammar is too complex" when the combined tool schema is too large.
249
262
  // Discuss flows only need a small subset of GSD tools — strip the heavy
250
263
  // planning/execution/completion tools to keep the grammar within limits.
264
+ let savedTools = null;
251
265
  if (unitType?.startsWith("discuss-")) {
252
266
  const currentTools = pi.getActiveTools();
267
+ savedTools = currentTools;
253
268
  // Keep all non-GSD tools (builtins, other extensions) and only the
254
269
  // GSD tools on the discuss allowlist.
255
270
  const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
@@ -268,6 +283,12 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
268
283
  content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${note}`,
269
284
  display: false,
270
285
  }, { triggerTurn: true });
286
+ // Restore full tool set after the message is queued. The LLM turn has
287
+ // already captured the scoped set — restoring prevents the narrowed
288
+ // tools from leaking into subsequent dispatches (#3628).
289
+ if (savedTools) {
290
+ pi.setActiveTools(savedTools);
291
+ }
271
292
  }
272
293
  /**
273
294
  * Resolve a model ID string to a model object from available models.
@@ -340,6 +361,86 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
340
361
  multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
341
362
  });
342
363
  }
364
+ /**
365
+ * Build the prepared discuss prompt with brief injection.
366
+ * Uses the discuss-prepared template which encodes the 4-layer discussion protocol.
367
+ *
368
+ * @param nextId - The milestone ID being discussed
369
+ * @param preamble - Preamble text for the discuss prompt
370
+ * @param _basePath - Root directory of the project (unused, kept for signature consistency)
371
+ * @param prepResult - Preparation result containing briefs to inject
372
+ * @returns The prepared discuss prompt string
373
+ */
374
+ function buildPreparedPrompt(nextId, preamble, _basePath, prepResult) {
375
+ const milestoneRel = `.gsd/milestones/${nextId}`;
376
+ // Use context-enhanced instead of context for prepared discussions
377
+ const inlinedTemplates = [
378
+ inlineTemplate("project", "Project"),
379
+ inlineTemplate("requirements", "Requirements"),
380
+ inlineTemplate("context-enhanced", "Context Enhanced"),
381
+ inlineTemplate("roadmap", "Roadmap"),
382
+ inlineTemplate("decisions", "Decisions"),
383
+ ].join("\n\n---\n\n");
384
+ // Format the briefs from the preparation result
385
+ const codebaseBrief = prepResult.codebaseBrief || formatCodebaseBrief(prepResult.codebase);
386
+ const priorContextBrief = prepResult.priorContextBrief || formatPriorContextBrief(prepResult.priorContext);
387
+ const ecosystemBrief = prepResult.ecosystemBrief || formatEcosystemBrief(prepResult.ecosystem);
388
+ return loadPrompt("discuss-prepared", {
389
+ milestoneId: nextId,
390
+ preamble,
391
+ codebaseBrief,
392
+ priorContextBrief,
393
+ ecosystemBrief,
394
+ contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
395
+ roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
396
+ inlinedTemplates,
397
+ commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
398
+ multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
399
+ });
400
+ }
401
+ /**
402
+ * Run preparation phase if enabled, then build the discuss prompt.
403
+ * This is the main entry point for new milestone discussions with preparation.
404
+ * Stores the preparation result for S02 to inject into the discuss prompt.
405
+ *
406
+ * When preparation succeeds, uses the discuss-prepared template with brief injection.
407
+ * Falls back to the standard discuss template when preparation is disabled or fails.
408
+ *
409
+ * @param ctx - Extension command context with UI for progress notifications
410
+ * @param nextId - The milestone ID being discussed
411
+ * @param preamble - Preamble text for the discuss prompt
412
+ * @param basePath - Root directory of the project
413
+ * @returns The discuss prompt string
414
+ */
415
+ async function prepareAndBuildDiscussPrompt(ctx, nextId, preamble, basePath) {
416
+ // Clear stale preparation result immediately to prevent cross-session/project
417
+ // state leaks. This ensures data from a prior milestone/project never leaks
418
+ // into subsequent discussions (adversarial review fix #3602).
419
+ lastPreparationResult = null;
420
+ const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
421
+ // Run preparation if enabled (default: true)
422
+ if (prefs.discuss_preparation !== false) {
423
+ try {
424
+ const prepResult = await runPreparation(basePath, ctx.ui, {
425
+ discuss_preparation: prefs.discuss_preparation,
426
+ discuss_web_research: prefs.discuss_web_research,
427
+ discuss_depth: prefs.discuss_depth,
428
+ });
429
+ lastPreparationResult = prepResult;
430
+ // Use prepared prompt if preparation was enabled and produced results
431
+ if (prepResult.enabled) {
432
+ return buildPreparedPrompt(nextId, preamble, basePath, prepResult);
433
+ }
434
+ }
435
+ catch {
436
+ // If preparation throws, ensure stale data doesn't persist
437
+ lastPreparationResult = null;
438
+ }
439
+ }
440
+ // Fall back to standard discuss prompt for backward compatibility
441
+ // lastPreparationResult is already null (cleared at entry or on error)
442
+ return buildDiscussPrompt(nextId, preamble, basePath);
443
+ }
343
444
  /**
344
445
  * Bootstrap a .gsd/ project from scratch for headless use.
345
446
  * Ensures git repo, .gsd/ structure, gitignore, and preferences all exist.
@@ -376,7 +477,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
376
477
  // Build and dispatch the headless discuss prompt
377
478
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
378
479
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
379
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId });
480
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
380
481
  // Dispatch — headless milestone creation is a planning activity
381
482
  await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "plan-milestone");
382
483
  }
@@ -457,6 +558,7 @@ async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
457
558
  contextPath: sliceContextPath,
458
559
  projectRoot: base,
459
560
  inlinedTemplates,
561
+ structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
460
562
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
461
563
  });
462
564
  }
@@ -474,6 +576,16 @@ export async function showDiscuss(ctx, pi, basePath) {
474
576
  // Invalidate caches to pick up artifacts written by a just-completed discuss/plan
475
577
  invalidateAllCaches();
476
578
  const state = await deriveState(basePath);
579
+ // Rebuild STATE.md from derived state before any dispatch (#3475).
580
+ // Without this, guided prompts read a stale STATE.md cache and the
581
+ // agent bootstraps from the wrong milestone.
582
+ try {
583
+ const { buildStateMarkdown } = await import("./doctor.js");
584
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
585
+ }
586
+ catch (err) {
587
+ logWarning("guided", `STATE.md rebuild failed: ${err.message}`);
588
+ }
477
589
  // No active milestone (or corrupted milestone with undefined id) —
478
590
  // check for pending milestones to discuss instead
479
591
  if (!state.activeMilestone?.id) {
@@ -521,28 +633,30 @@ export async function showDiscuss(ctx, pi, basePath) {
521
633
  const basePrompt = loadPrompt("guided-discuss-milestone", {
522
634
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
523
635
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
636
+ fastPathInstruction: "",
524
637
  });
525
638
  const seed = draftContent
526
639
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
527
640
  : basePrompt;
528
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
641
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
529
642
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
530
643
  }
531
644
  else if (choice === "discuss_fresh") {
532
645
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
533
646
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
534
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
647
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
535
648
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
536
649
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
537
650
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
651
+ fastPathInstruction: "",
538
652
  }), "gsd-discuss", ctx, "discuss-milestone");
539
653
  }
540
654
  else if (choice === "skip_milestone") {
541
655
  const milestoneIds = findMilestoneIds(basePath);
542
656
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
543
657
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
544
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
545
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
658
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
659
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
546
660
  }
547
661
  return;
548
662
  }
@@ -672,7 +786,8 @@ export async function showDiscuss(ctx, pi, basePath) {
672
786
  if (confirm !== "rediscuss")
673
787
  continue;
674
788
  }
675
- const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss });
789
+ const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
790
+ const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
676
791
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
677
792
  // Wait for the discuss session to finish, then loop back to the picker
678
793
  await ctx.waitForIdle();
@@ -712,16 +827,55 @@ async function showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones)
712
827
  const chosen = pendingMilestones.find(m => m.id === choice);
713
828
  if (!chosen)
714
829
  return;
715
- await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title);
830
+ const hasDraft = !!resolveMilestoneFile(basePath, chosen.id, "CONTEXT-DRAFT");
831
+ let fastPath = hasDraft;
832
+ if (!hasDraft) {
833
+ const mode = await showNextAction(ctx, {
834
+ title: `Discuss ${chosen.id}`,
835
+ summary: [
836
+ "Choose how to start the discussion.",
837
+ "Fast path skips generic scouting — use it when you already know the scope.",
838
+ ],
839
+ actions: [
840
+ {
841
+ id: "full",
842
+ label: "Full discussion",
843
+ description: "Scout the codebase, ask open-ended questions, explore deeply",
844
+ recommended: true,
845
+ },
846
+ {
847
+ id: "fast",
848
+ label: "I have the scope — fast path",
849
+ description: "Treat your first message as authoritative seed context; skip scouting",
850
+ },
851
+ ],
852
+ notYetMessage: "Run /gsd discuss when ready.",
853
+ });
854
+ if (mode === "not_yet")
855
+ return;
856
+ fastPath = mode === "fast";
857
+ }
858
+ await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title, { fastPath });
716
859
  }
717
860
  /**
718
861
  * Dispatch the guided-discuss-milestone prompt for a milestone without
719
862
  * setting pendingAutoStart — so discussing a queued milestone does not
720
863
  * implicitly activate it when the session ends.
721
864
  */
722
- async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitle) {
865
+ async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitle, opts = {}) {
723
866
  const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
724
867
  const draftContent = draftFile ? await loadFile(draftFile) : null;
868
+ const hasSeed = !!(draftContent || opts.fastPath);
869
+ const fastPathInstruction = hasSeed
870
+ ? [
871
+ "> **Fast path active — scope provided.**",
872
+ "> Do NOT perform a generic codebase scouting pass.",
873
+ "> Do at most 2 targeted reads to check for obvious conflicts with existing work.",
874
+ "> Treat the seed context or the operator's first message as authoritative.",
875
+ "> Move directly to the depth summary and write step.",
876
+ "> Ask only questions where the answer would materially change scope.",
877
+ ].join("\n")
878
+ : "";
725
879
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
726
880
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
727
881
  const basePrompt = loadPrompt("guided-discuss-milestone", {
@@ -730,6 +884,7 @@ async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitl
730
884
  inlinedTemplates: discussMilestoneTemplates,
731
885
  structuredQuestionsAvailable,
732
886
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
887
+ fastPathInstruction,
733
888
  });
734
889
  const prompt = draftContent
735
890
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
@@ -856,8 +1011,8 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
856
1011
  const milestoneIds = findMilestoneIds(basePath);
857
1012
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
858
1013
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
859
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
860
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1014
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1015
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
861
1016
  return true;
862
1017
  }
863
1018
  // "back" or null
@@ -955,14 +1110,35 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
955
1110
  }
956
1111
  }
957
1112
  const state = await deriveState(basePath);
1113
+ // Rebuild STATE.md from derived state before any dispatch (#3475).
1114
+ try {
1115
+ const { buildStateMarkdown } = await import("./doctor.js");
1116
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
1117
+ }
1118
+ catch (err) {
1119
+ logWarning("guided", `STATE.md rebuild failed: ${err.message}`);
1120
+ }
958
1121
  if (!state.activeMilestone?.id) {
959
1122
  // Guard: if a discuss session is already in flight, don't re-inject the prompt.
960
1123
  // Both /gsd and /gsd auto reach this branch when no milestone exists yet.
961
1124
  // Without this guard, every subsequent /gsd call overwrites the pending auto-start
962
1125
  // and fires another dispatchWorkflow, resetting the conversation mid-interview.
963
1126
  if (pendingAutoStartMap.has(basePath)) {
964
- ctx.ui.notify("Discussion already in progress answer the question above to continue.", "info");
965
- return;
1127
+ // #3274: If /clear interrupted the discussion, the pending entry is stale.
1128
+ // Detect staleness: no manifest, no CONTEXT.md, AND entry is older than
1129
+ // 30s (avoids race between .set() and LLM writing first artifact).
1130
+ const entry = pendingAutoStartMap.get(basePath);
1131
+ const ageMs = Date.now() - (entry.createdAt || 0);
1132
+ const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
1133
+ const milestoneHasContext = existsSync(join(gsdRoot(basePath), "milestones", entry.milestoneId, `${entry.milestoneId}-CONTEXT.md`));
1134
+ if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
1135
+ // Stale entry from an interrupted discussion — clear and continue
1136
+ pendingAutoStartMap.delete(basePath);
1137
+ }
1138
+ else {
1139
+ ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
1140
+ return;
1141
+ }
966
1142
  }
967
1143
  const milestoneIds = findMilestoneIds(basePath);
968
1144
  // Sanity check (#456): if findMilestoneIds returns [] but the milestones
@@ -989,8 +1165,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
989
1165
  const isFirst = milestoneIds.length === 0;
990
1166
  if (isFirst) {
991
1167
  // First ever — skip wizard, just ask directly
992
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
993
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
1168
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1169
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
994
1170
  }
995
1171
  else {
996
1172
  const choice = await showNextAction(ctx, {
@@ -1007,8 +1183,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1007
1183
  notYetMessage: "Run /gsd when ready.",
1008
1184
  });
1009
1185
  if (choice === "new_milestone") {
1010
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1011
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1186
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1187
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1012
1188
  }
1013
1189
  }
1014
1190
  return;
@@ -1039,8 +1215,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1039
1215
  const milestoneIds = findMilestoneIds(basePath);
1040
1216
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1041
1217
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1042
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1043
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1218
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1219
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1044
1220
  }
1045
1221
  else if (choice === "status") {
1046
1222
  const { fireStatusViaCommand } = await import("./commands.js");
@@ -1081,28 +1257,30 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1081
1257
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1082
1258
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1083
1259
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1260
+ fastPathInstruction: "",
1084
1261
  });
1085
1262
  const seed = draftContent
1086
1263
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1087
1264
  : basePrompt;
1088
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1265
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1089
1266
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1090
1267
  }
1091
1268
  else if (choice === "discuss_fresh") {
1092
1269
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1093
1270
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1094
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1271
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1095
1272
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1096
1273
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1097
1274
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1275
+ fastPathInstruction: "",
1098
1276
  }), "gsd-discuss", ctx, "discuss-milestone");
1099
1277
  }
1100
1278
  else if (choice === "skip_milestone") {
1101
1279
  const milestoneIds = findMilestoneIds(basePath);
1102
1280
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1103
1281
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1104
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1105
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1282
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1283
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1106
1284
  }
1107
1285
  return;
1108
1286
  }
@@ -1110,7 +1288,18 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1110
1288
  if (!state.activeSlice) {
1111
1289
  const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
1112
1290
  const hasRoadmap = !!(roadmapFile && await loadFile(roadmapFile));
1113
- if (!hasRoadmap) {
1291
+ // A roadmap file with zero parseable slices (placeholder text) should be
1292
+ // treated the same as no roadmap — offer "Create roadmap" instead of "Go auto"
1293
+ // which would immediately get stuck in blocked state (#3441).
1294
+ let roadmapHasSlices = false;
1295
+ if (hasRoadmap) {
1296
+ const roadmapContent = await loadFile(roadmapFile);
1297
+ if (roadmapContent) {
1298
+ const parsed = parseRoadmapSlices(roadmapContent);
1299
+ roadmapHasSlices = parsed.length > 0;
1300
+ }
1301
+ }
1302
+ if (!hasRoadmap || !roadmapHasSlices) {
1114
1303
  // No roadmap → discuss or plan
1115
1304
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
1116
1305
  const hasContext = !!(contextFile && await loadFile(contextFile));
@@ -1146,7 +1335,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1146
1335
  notYetMessage: "Run /gsd when ready.",
1147
1336
  });
1148
1337
  if (choice === "plan") {
1149
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1338
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1150
1339
  const planMilestoneTemplates = [
1151
1340
  inlineTemplate("roadmap", "Roadmap"),
1152
1341
  inlineTemplate("plan", "Slice Plan"),
@@ -1173,14 +1362,15 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1173
1362
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1174
1363
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1175
1364
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1365
+ fastPathInstruction: "",
1176
1366
  }), "gsd-run", ctx, "discuss-milestone");
1177
1367
  }
1178
1368
  else if (choice === "skip_milestone") {
1179
1369
  const milestoneIds = findMilestoneIds(basePath);
1180
1370
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1181
1371
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1182
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1183
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1372
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1373
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1184
1374
  }
1185
1375
  else if (choice === "discard_milestone") {
1186
1376
  const confirmed = await showConfirm(ctx, {
@@ -1306,7 +1496,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1306
1496
  }), "gsd-run", ctx, "plan-slice");
1307
1497
  }
1308
1498
  else if (choice === "discuss") {
1309
- await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext }), "gsd-run", ctx, "discuss-slice");
1499
+ const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1500
+ await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
1310
1501
  }
1311
1502
  else if (choice === "research") {
1312
1503
  const researchTemplates = inlineTemplate("research", "Research");
@@ -1,4 +1,4 @@
1
- export { isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
1
+ export { isDepthConfirmationAnswer, isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
2
2
  export default async function registerExtension(pi) {
3
3
  const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
4
4
  registerGsdExtension(pi);
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { randomBytes } from "node:crypto";
3
4
  /**
4
5
  * Load a JSON file with validation, returning a default on failure.
5
6
  * Handles missing files, corrupt JSON, and schema mismatches uniformly.
@@ -45,9 +46,11 @@ export function loadJsonFileOrNull(filePath, validate) {
45
46
  export function saveJsonFile(filePath, data) {
46
47
  try {
47
48
  mkdirSync(dirname(filePath), { recursive: true });
48
- const tmp = filePath + ".tmp";
49
+ // Use randomized tmp suffix to prevent concurrent-write data loss
50
+ const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
49
51
  writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
50
52
  renameSync(tmp, filePath);
53
+ // No cleanup needed — renameSync atomically removes tmp on success
51
54
  }
52
55
  catch {
53
56
  // Non-fatal — don't let persistence failures break operation
@@ -60,7 +63,7 @@ export function saveJsonFile(filePath, data) {
60
63
  export function writeJsonFileAtomic(filePath, data) {
61
64
  try {
62
65
  mkdirSync(dirname(filePath), { recursive: true });
63
- const tmp = filePath + ".tmp";
66
+ const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
64
67
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
65
68
  renameSync(tmp, filePath);
66
69
  }
@@ -438,12 +438,6 @@ export function migrateHierarchyToDb(basePath) {
438
438
  // Ghost milestone: no CONTEXT, ROADMAP, or SUMMARY → skip
439
439
  if (!hasRoadmap && !hasContext && !hasSummary)
440
440
  continue;
441
- // Determine milestone status
442
- let milestoneStatus = 'active';
443
- if (hasSummary)
444
- milestoneStatus = 'complete';
445
- else if (hasParked)
446
- milestoneStatus = 'parked';
447
441
  // Determine milestone title from roadmap H1 or CONTEXT heading
448
442
  let milestoneTitle = '';
449
443
  let roadmapContent = null;
@@ -453,6 +447,17 @@ export function migrateHierarchyToDb(basePath) {
453
447
  roadmap = parseRoadmap(roadmapContent);
454
448
  milestoneTitle = roadmap.title;
455
449
  }
450
+ // Determine milestone status
451
+ let milestoneStatus = 'active';
452
+ if (hasSummary)
453
+ milestoneStatus = 'complete';
454
+ else if (hasParked)
455
+ milestoneStatus = 'parked';
456
+ // Import milestones with all-done roadmap slices as complete (#3390, #3379)
457
+ // even when SUMMARY.md is missing — the roadmap checkboxes are authoritative.
458
+ else if (roadmap && roadmap.slices.length > 0 && roadmap.slices.every(s => s.done)) {
459
+ milestoneStatus = 'complete';
460
+ }
456
461
  if (!milestoneTitle && hasContext) {
457
462
  const contextContent = readFileSync(contextPath, 'utf-8');
458
463
  const h1Match = contextContent.match(/^#\s+(.+)/m);
@@ -492,7 +497,8 @@ export function migrateHierarchyToDb(basePath) {
492
497
  // Parse roadmap for slices
493
498
  if (!roadmap)
494
499
  continue;
495
- for (const sliceEntry of roadmap.slices) {
500
+ for (let si = 0; si < roadmap.slices.length; si++) {
501
+ const sliceEntry = roadmap.slices[si];
496
502
  // Per K002: use 'complete' not 'done'
497
503
  const sliceStatus = sliceEntry.done ? 'complete' : 'pending';
498
504
  // Parse slice plan early so goal is available for insertSlice planning column
@@ -510,6 +516,7 @@ export function migrateHierarchyToDb(basePath) {
510
516
  risk: sliceEntry.risk,
511
517
  depends: sliceEntry.depends,
512
518
  demo: sliceEntry.demo,
519
+ sequence: si + 1, // Preserve roadmap parse order (#3356)
513
520
  planning: {
514
521
  goal: plan?.goal ?? '',
515
522
  },