gsd-pi 2.65.0-dev.5c8557b → 2.65.0-dev.d0517ff

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 (316) 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/run-unit.js +13 -2
  5. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
  9. package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
  10. package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
  11. package/dist/resources/extensions/gsd/auto-start.js +42 -11
  12. package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
  13. package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
  14. package/dist/resources/extensions/gsd/auto.js +21 -15
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  17. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
  18. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
  20. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
  21. package/dist/resources/extensions/gsd/commands/context.js +8 -1
  22. package/dist/resources/extensions/gsd/commands/handlers/core.js +20 -0
  23. package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
  24. package/dist/resources/extensions/gsd/config-overlay.js +312 -0
  25. package/dist/resources/extensions/gsd/db-writer.js +13 -3
  26. package/dist/resources/extensions/gsd/detection.js +1 -1
  27. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
  28. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  29. package/dist/resources/extensions/gsd/doctor.js +2 -1
  30. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  31. package/dist/resources/extensions/gsd/gsd-db.js +47 -4
  32. package/dist/resources/extensions/gsd/guided-flow.js +220 -29
  33. package/dist/resources/extensions/gsd/index.js +1 -1
  34. package/dist/resources/extensions/gsd/json-persistence.js +5 -2
  35. package/dist/resources/extensions/gsd/md-importer.js +14 -7
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
  37. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -5
  38. package/dist/resources/extensions/gsd/preferences-types.js +3 -0
  39. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  40. package/dist/resources/extensions/gsd/preferences.js +9 -2
  41. package/dist/resources/extensions/gsd/preparation.js +1092 -0
  42. package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  44. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  46. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  48. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  49. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  50. package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
  51. package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
  52. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  53. package/dist/resources/extensions/gsd/quick.js +19 -15
  54. package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
  55. package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
  56. package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
  57. package/dist/resources/extensions/gsd/session-lock.js +23 -1
  58. package/dist/resources/extensions/gsd/state.js +115 -28
  59. package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  60. package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
  61. package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
  62. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
  63. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
  64. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
  65. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
  66. package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
  67. package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
  68. package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
  69. package/dist/resources/extensions/gsd/undo.js +3 -2
  70. package/dist/resources/extensions/gsd/workflow-events.js +1 -0
  71. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  72. package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
  73. package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
  74. package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
  75. package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
  76. package/dist/resources/extensions/gsd/worktree.js +9 -0
  77. package/dist/resources/extensions/shared/interview-ui.js +1 -1
  78. package/dist/web/standalone/.next/BUILD_ID +1 -1
  79. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  80. package/dist/web/standalone/.next/build-manifest.json +3 -3
  81. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  82. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  84. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.html +1 -1
  100. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  107. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  110. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  111. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  112. package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
  113. package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
  114. package/package.json +1 -1
  115. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
  117. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
  119. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
  121. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
  138. package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
  139. package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
  140. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
  142. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
  143. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
  145. package/packages/pi-tui/dist/components/image.d.ts +2 -0
  146. package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
  147. package/packages/pi-tui/dist/components/image.js +4 -0
  148. package/packages/pi-tui/dist/components/image.js.map +1 -1
  149. package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
  150. package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
  151. package/packages/pi-tui/dist/components/image.test.js +32 -0
  152. package/packages/pi-tui/dist/components/image.test.js.map +1 -0
  153. package/packages/pi-tui/src/components/image.test.ts +36 -0
  154. package/packages/pi-tui/src/components/image.ts +5 -0
  155. package/src/resources/extensions/browser-tools/capture.ts +19 -1
  156. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
  157. package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
  158. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  159. package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
  160. package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
  161. package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
  162. package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
  163. package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
  164. package/src/resources/extensions/gsd/auto-start.ts +45 -10
  165. package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
  166. package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
  167. package/src/resources/extensions/gsd/auto.ts +19 -8
  168. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
  169. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
  170. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
  171. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
  173. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
  174. package/src/resources/extensions/gsd/commands/context.ts +7 -1
  175. package/src/resources/extensions/gsd/commands/handlers/core.ts +23 -0
  176. package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
  177. package/src/resources/extensions/gsd/config-overlay.ts +331 -0
  178. package/src/resources/extensions/gsd/db-writer.ts +11 -3
  179. package/src/resources/extensions/gsd/detection.ts +1 -1
  180. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
  181. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  182. package/src/resources/extensions/gsd/doctor.ts +2 -1
  183. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  184. package/src/resources/extensions/gsd/gsd-db.ts +46 -4
  185. package/src/resources/extensions/gsd/guided-flow.ts +254 -30
  186. package/src/resources/extensions/gsd/index.ts +1 -0
  187. package/src/resources/extensions/gsd/json-persistence.ts +6 -3
  188. package/src/resources/extensions/gsd/md-importer.ts +13 -6
  189. package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
  190. package/src/resources/extensions/gsd/pre-execution-checks.ts +15 -7
  191. package/src/resources/extensions/gsd/preferences-types.ts +25 -0
  192. package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
  193. package/src/resources/extensions/gsd/preferences.ts +9 -2
  194. package/src/resources/extensions/gsd/preparation.ts +1419 -0
  195. package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
  196. package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
  197. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  198. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
  199. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  200. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
  201. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
  202. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
  203. package/src/resources/extensions/gsd/prompts/queue.md +2 -0
  204. package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
  205. package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
  206. package/src/resources/extensions/gsd/quick.ts +20 -15
  207. package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
  208. package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
  209. package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
  210. package/src/resources/extensions/gsd/session-lock.ts +17 -1
  211. package/src/resources/extensions/gsd/state.ts +115 -26
  212. package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
  213. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
  214. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
  215. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
  216. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
  217. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
  218. package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
  219. package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
  220. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
  221. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
  222. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
  223. package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
  224. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
  225. package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
  226. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
  227. package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
  228. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
  229. package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
  230. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
  231. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
  232. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  233. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
  234. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
  235. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
  236. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
  237. package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
  238. package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
  239. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
  240. package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
  241. package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
  242. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
  243. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +218 -20
  244. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
  245. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
  246. package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
  247. package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
  248. package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
  249. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
  250. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
  251. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
  252. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
  253. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
  254. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
  255. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
  256. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
  257. package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
  258. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
  259. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
  260. package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
  261. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
  262. package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
  263. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
  265. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
  266. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
  267. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
  268. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
  269. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
  270. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
  271. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
  272. package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
  273. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
  274. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
  275. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
  276. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
  277. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
  278. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
  279. package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
  280. package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
  281. package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
  282. package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
  283. package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
  284. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
  285. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
  286. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
  287. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
  288. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
  289. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
  290. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
  291. package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
  292. package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
  293. package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
  295. package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
  296. package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
  297. package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
  298. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
  299. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
  300. package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
  301. package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
  302. package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
  303. package/src/resources/extensions/gsd/types.ts +4 -0
  304. package/src/resources/extensions/gsd/undo.ts +3 -2
  305. package/src/resources/extensions/gsd/workflow-events.ts +5 -3
  306. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  307. package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
  308. package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
  309. package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
  310. package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
  311. package/src/resources/extensions/gsd/worktree.ts +10 -0
  312. package/src/resources/extensions/shared/interview-ui.ts +1 -1
  313. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
  314. package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
  315. /package/dist/web/standalone/.next/static/{qq3YfHPfyqvh3DIMVmsRH → JwdBI3y1H8vtBKiYvWfEK}/_buildManifest.js +0 -0
  316. /package/dist/web/standalone/.next/static/{qq3YfHPfyqvh3DIMVmsRH → JwdBI3y1H8vtBKiYvWfEK}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@
8
8
 
9
9
  import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@gsd/pi-coding-agent";
10
10
  import { showNextAction } from "../shared/tui.js";
11
- import { loadFile } from "./files.js";
11
+ import { loadFile, saveFile } from "./files.js";
12
12
  import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
13
13
  import { parseRoadmapSlices } from "./roadmap-slices.js";
14
14
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
@@ -40,6 +40,28 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
40
40
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
41
41
  import { selectAndApplyModel } from "./auto-model-selection.js";
42
42
  import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
43
+ import {
44
+ runPreparation,
45
+ formatCodebaseBrief,
46
+ formatPriorContextBrief,
47
+ formatEcosystemBrief,
48
+ type PreparationResult,
49
+ } from "./preparation.js";
50
+
51
+ // ─── Preparation result storage ─────────────────────────────────────────────
52
+ // Stores the most recent preparation result for injection into discuss prompts.
53
+ // S02 will consume this when building the prepared discussion prompt.
54
+ let lastPreparationResult: PreparationResult | null = null;
55
+
56
+ /** Get the most recent preparation result (for S02 prompt building). */
57
+ export function getLastPreparationResult(): PreparationResult | null {
58
+ return lastPreparationResult;
59
+ }
60
+
61
+ /** Clear the preparation result (called after discussion completes). */
62
+ export function clearPreparationResult(): void {
63
+ lastPreparationResult = null;
64
+ }
43
65
 
44
66
  // ─── Re-exports (preserve public API for existing importers) ────────────────
45
67
  export {
@@ -85,6 +107,7 @@ interface PendingAutoStartEntry {
85
107
  basePath: string;
86
108
  milestoneId: string; // the milestone being discussed
87
109
  step?: boolean; // preserve step mode through discuss → auto transition
110
+ createdAt: number; // timestamp for staleness detection (#3274)
88
111
  }
89
112
 
90
113
  const pendingAutoStartMap = new Map<string, PendingAutoStartEntry>();
@@ -104,8 +127,8 @@ function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
104
127
  * Store pending auto-start state for a project.
105
128
  * Exported for testing (#2985).
106
129
  */
107
- export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean }): void {
108
- pendingAutoStartMap.set(basePath, entry as PendingAutoStartEntry);
130
+ export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean; createdAt?: number }): void {
131
+ pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry } as PendingAutoStartEntry);
109
132
  }
110
133
 
111
134
  /**
@@ -295,8 +318,10 @@ async function dispatchWorkflow(
295
318
  // "Grammar is too complex" when the combined tool schema is too large.
296
319
  // Discuss flows only need a small subset of GSD tools — strip the heavy
297
320
  // planning/execution/completion tools to keep the grammar within limits.
321
+ let savedTools: string[] | null = null;
298
322
  if (unitType?.startsWith("discuss-")) {
299
323
  const currentTools = pi.getActiveTools();
324
+ savedTools = currentTools;
300
325
  // Keep all non-GSD tools (builtins, other extensions) and only the
301
326
  // GSD tools on the discuss allowlist.
302
327
  const scopedTools = currentTools.filter(
@@ -322,6 +347,13 @@ async function dispatchWorkflow(
322
347
  },
323
348
  { triggerTurn: true },
324
349
  );
350
+
351
+ // Restore full tool set after the message is queued. The LLM turn has
352
+ // already captured the scoped set — restoring prevents the narrowed
353
+ // tools from leaking into subsequent dispatches (#3628).
354
+ if (savedTools) {
355
+ pi.setActiveTools(savedTools);
356
+ }
325
357
  }
326
358
 
327
359
  /**
@@ -411,6 +443,104 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
411
443
  });
412
444
  }
413
445
 
446
+ /**
447
+ * Build the prepared discuss prompt with brief injection.
448
+ * Uses the discuss-prepared template which encodes the 4-layer discussion protocol.
449
+ *
450
+ * @param nextId - The milestone ID being discussed
451
+ * @param preamble - Preamble text for the discuss prompt
452
+ * @param _basePath - Root directory of the project (unused, kept for signature consistency)
453
+ * @param prepResult - Preparation result containing briefs to inject
454
+ * @returns The prepared discuss prompt string
455
+ */
456
+ function buildPreparedPrompt(
457
+ nextId: string,
458
+ preamble: string,
459
+ _basePath: string,
460
+ prepResult: PreparationResult,
461
+ ): string {
462
+ const milestoneRel = `.gsd/milestones/${nextId}`;
463
+
464
+ // Use context-enhanced instead of context for prepared discussions
465
+ const inlinedTemplates = [
466
+ inlineTemplate("project", "Project"),
467
+ inlineTemplate("requirements", "Requirements"),
468
+ inlineTemplate("context-enhanced", "Context Enhanced"),
469
+ inlineTemplate("roadmap", "Roadmap"),
470
+ inlineTemplate("decisions", "Decisions"),
471
+ ].join("\n\n---\n\n");
472
+
473
+ // Format the briefs from the preparation result
474
+ const codebaseBrief = prepResult.codebaseBrief || formatCodebaseBrief(prepResult.codebase);
475
+ const priorContextBrief = prepResult.priorContextBrief || formatPriorContextBrief(prepResult.priorContext);
476
+ const ecosystemBrief = prepResult.ecosystemBrief || formatEcosystemBrief(prepResult.ecosystem);
477
+
478
+ return loadPrompt("discuss-prepared", {
479
+ milestoneId: nextId,
480
+ preamble,
481
+ codebaseBrief,
482
+ priorContextBrief,
483
+ ecosystemBrief,
484
+ contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
485
+ roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
486
+ inlinedTemplates,
487
+ commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
488
+ multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
489
+ });
490
+ }
491
+
492
+ /**
493
+ * Run preparation phase if enabled, then build the discuss prompt.
494
+ * This is the main entry point for new milestone discussions with preparation.
495
+ * Stores the preparation result for S02 to inject into the discuss prompt.
496
+ *
497
+ * When preparation succeeds, uses the discuss-prepared template with brief injection.
498
+ * Falls back to the standard discuss template when preparation is disabled or fails.
499
+ *
500
+ * @param ctx - Extension command context with UI for progress notifications
501
+ * @param nextId - The milestone ID being discussed
502
+ * @param preamble - Preamble text for the discuss prompt
503
+ * @param basePath - Root directory of the project
504
+ * @returns The discuss prompt string
505
+ */
506
+ async function prepareAndBuildDiscussPrompt(
507
+ ctx: ExtensionCommandContext,
508
+ nextId: string,
509
+ preamble: string,
510
+ basePath: string,
511
+ ): Promise<string> {
512
+ // Clear stale preparation result immediately to prevent cross-session/project
513
+ // state leaks. This ensures data from a prior milestone/project never leaks
514
+ // into subsequent discussions (adversarial review fix #3602).
515
+ lastPreparationResult = null;
516
+
517
+ const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
518
+
519
+ // Run preparation if enabled (default: true)
520
+ if (prefs.discuss_preparation !== false) {
521
+ try {
522
+ const prepResult = await runPreparation(basePath, ctx.ui, {
523
+ discuss_preparation: prefs.discuss_preparation,
524
+ discuss_web_research: prefs.discuss_web_research,
525
+ discuss_depth: prefs.discuss_depth,
526
+ });
527
+ lastPreparationResult = prepResult;
528
+
529
+ // Use prepared prompt if preparation was enabled and produced results
530
+ if (prepResult.enabled) {
531
+ return buildPreparedPrompt(nextId, preamble, basePath, prepResult);
532
+ }
533
+ } catch {
534
+ // If preparation throws, ensure stale data doesn't persist
535
+ lastPreparationResult = null;
536
+ }
537
+ }
538
+
539
+ // Fall back to standard discuss prompt for backward compatibility
540
+ // lastPreparationResult is already null (cleared at entry or on error)
541
+ return buildDiscussPrompt(nextId, preamble, basePath);
542
+ }
543
+
414
544
  /**
415
545
  * Bootstrap a .gsd/ project from scratch for headless use.
416
546
  * Ensures git repo, .gsd/ structure, gitignore, and preferences all exist.
@@ -460,7 +590,7 @@ export async function showHeadlessMilestoneCreation(
460
590
  const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
461
591
 
462
592
  // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
463
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId });
593
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
464
594
 
465
595
  // Dispatch — headless milestone creation is a planning activity
466
596
  await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "plan-milestone");
@@ -480,7 +610,7 @@ async function buildDiscussSlicePrompt(
480
610
  sid: string,
481
611
  sTitle: string,
482
612
  base: string,
483
- options?: { rediscuss?: boolean },
613
+ options?: { rediscuss?: boolean; structuredQuestionsAvailable?: string },
484
614
  ): Promise<string> {
485
615
  const inlined: string[] = [];
486
616
 
@@ -560,6 +690,7 @@ async function buildDiscussSlicePrompt(
560
690
  contextPath: sliceContextPath,
561
691
  projectRoot: base,
562
692
  inlinedTemplates,
693
+ structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
563
694
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
564
695
  });
565
696
  }
@@ -585,6 +716,16 @@ export async function showDiscuss(
585
716
 
586
717
  const state = await deriveState(basePath);
587
718
 
719
+ // Rebuild STATE.md from derived state before any dispatch (#3475).
720
+ // Without this, guided prompts read a stale STATE.md cache and the
721
+ // agent bootstraps from the wrong milestone.
722
+ try {
723
+ const { buildStateMarkdown } = await import("./doctor.js");
724
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
725
+ } catch (err) {
726
+ logWarning("guided", `STATE.md rebuild failed: ${(err as Error).message}`);
727
+ }
728
+
588
729
  // No active milestone (or corrupted milestone with undefined id) —
589
730
  // check for pending milestones to discuss instead
590
731
  if (!state.activeMilestone?.id) {
@@ -636,26 +777,28 @@ export async function showDiscuss(
636
777
  const basePrompt = loadPrompt("guided-discuss-milestone", {
637
778
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
638
779
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
780
+ fastPathInstruction: "",
639
781
  });
640
782
  const seed = draftContent
641
783
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
642
784
  : basePrompt;
643
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
785
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
644
786
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
645
787
  } else if (choice === "discuss_fresh") {
646
788
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
647
789
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
648
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
790
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
649
791
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
650
792
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
651
793
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
794
+ fastPathInstruction: "",
652
795
  }), "gsd-discuss", ctx, "discuss-milestone");
653
796
  } else if (choice === "skip_milestone") {
654
797
  const milestoneIds = findMilestoneIds(basePath);
655
798
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
656
799
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
657
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
658
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
800
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
801
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
659
802
  }
660
803
  return;
661
804
  }
@@ -801,7 +944,8 @@ export async function showDiscuss(
801
944
  if (confirm !== "rediscuss") continue;
802
945
  }
803
946
 
804
- const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss });
947
+ const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
948
+ const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
805
949
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
806
950
 
807
951
  // Wait for the discuss session to finish, then loop back to the picker
@@ -851,7 +995,36 @@ async function showDiscussQueuedMilestone(
851
995
  const chosen = pendingMilestones.find(m => m.id === choice);
852
996
  if (!chosen) return;
853
997
 
854
- await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title);
998
+ const hasDraft = !!resolveMilestoneFile(basePath, chosen.id, "CONTEXT-DRAFT");
999
+ let fastPath = hasDraft;
1000
+
1001
+ if (!hasDraft) {
1002
+ const mode = await showNextAction(ctx, {
1003
+ title: `Discuss ${chosen.id}`,
1004
+ summary: [
1005
+ "Choose how to start the discussion.",
1006
+ "Fast path skips generic scouting — use it when you already know the scope.",
1007
+ ],
1008
+ actions: [
1009
+ {
1010
+ id: "full",
1011
+ label: "Full discussion",
1012
+ description: "Scout the codebase, ask open-ended questions, explore deeply",
1013
+ recommended: true,
1014
+ },
1015
+ {
1016
+ id: "fast",
1017
+ label: "I have the scope — fast path",
1018
+ description: "Treat your first message as authoritative seed context; skip scouting",
1019
+ },
1020
+ ],
1021
+ notYetMessage: "Run /gsd discuss when ready.",
1022
+ });
1023
+ if (mode === "not_yet") return;
1024
+ fastPath = mode === "fast";
1025
+ }
1026
+
1027
+ await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title, { fastPath });
855
1028
  }
856
1029
 
857
1030
  /**
@@ -865,9 +1038,21 @@ async function dispatchDiscussForMilestone(
865
1038
  basePath: string,
866
1039
  mid: string,
867
1040
  milestoneTitle: string,
1041
+ opts: { fastPath?: boolean } = {},
868
1042
  ): Promise<void> {
869
1043
  const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
870
1044
  const draftContent = draftFile ? await loadFile(draftFile) : null;
1045
+ const hasSeed = !!(draftContent || opts.fastPath);
1046
+ const fastPathInstruction = hasSeed
1047
+ ? [
1048
+ "> **Fast path active — scope provided.**",
1049
+ "> Do NOT perform a generic codebase scouting pass.",
1050
+ "> Do at most 2 targeted reads to check for obvious conflicts with existing work.",
1051
+ "> Treat the seed context or the operator's first message as authoritative.",
1052
+ "> Move directly to the depth summary and write step.",
1053
+ "> Ask only questions where the answer would materially change scope.",
1054
+ ].join("\n")
1055
+ : "";
871
1056
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
872
1057
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
873
1058
  const basePrompt = loadPrompt("guided-discuss-milestone", {
@@ -876,6 +1061,7 @@ async function dispatchDiscussForMilestone(
876
1061
  inlinedTemplates: discussMilestoneTemplates,
877
1062
  structuredQuestionsAvailable,
878
1063
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
1064
+ fastPathInstruction,
879
1065
  });
880
1066
  const prompt = draftContent
881
1067
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
@@ -1016,8 +1202,8 @@ async function handleMilestoneActions(
1016
1202
  const milestoneIds = findMilestoneIds(basePath);
1017
1203
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1018
1204
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1019
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1020
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1205
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1206
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1021
1207
  `New milestone ${nextId}.`,
1022
1208
  basePath
1023
1209
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1137,14 +1323,36 @@ export async function showSmartEntry(
1137
1323
 
1138
1324
  const state = await deriveState(basePath);
1139
1325
 
1326
+ // Rebuild STATE.md from derived state before any dispatch (#3475).
1327
+ try {
1328
+ const { buildStateMarkdown } = await import("./doctor.js");
1329
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
1330
+ } catch (err) {
1331
+ logWarning("guided", `STATE.md rebuild failed: ${(err as Error).message}`);
1332
+ }
1333
+
1140
1334
  if (!state.activeMilestone?.id) {
1141
1335
  // Guard: if a discuss session is already in flight, don't re-inject the prompt.
1142
1336
  // Both /gsd and /gsd auto reach this branch when no milestone exists yet.
1143
1337
  // Without this guard, every subsequent /gsd call overwrites the pending auto-start
1144
1338
  // and fires another dispatchWorkflow, resetting the conversation mid-interview.
1145
1339
  if (pendingAutoStartMap.has(basePath)) {
1146
- ctx.ui.notify("Discussion already in progress answer the question above to continue.", "info");
1147
- return;
1340
+ // #3274: If /clear interrupted the discussion, the pending entry is stale.
1341
+ // Detect staleness: no manifest, no CONTEXT.md, AND entry is older than
1342
+ // 30s (avoids race between .set() and LLM writing first artifact).
1343
+ const entry = pendingAutoStartMap.get(basePath)!;
1344
+ const ageMs = Date.now() - (entry.createdAt || 0);
1345
+ const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
1346
+ const milestoneHasContext = existsSync(
1347
+ join(gsdRoot(basePath), "milestones", entry.milestoneId, `${entry.milestoneId}-CONTEXT.md`),
1348
+ );
1349
+ if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
1350
+ // Stale entry from an interrupted discussion — clear and continue
1351
+ pendingAutoStartMap.delete(basePath);
1352
+ } else {
1353
+ ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
1354
+ return;
1355
+ }
1148
1356
  }
1149
1357
 
1150
1358
  const milestoneIds = findMilestoneIds(basePath);
@@ -1175,8 +1383,8 @@ export async function showSmartEntry(
1175
1383
 
1176
1384
  if (isFirst) {
1177
1385
  // First ever — skip wizard, just ask directly
1178
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1179
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1386
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1387
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1180
1388
  `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
1181
1389
  basePath
1182
1390
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1196,8 +1404,8 @@ export async function showSmartEntry(
1196
1404
  });
1197
1405
 
1198
1406
  if (choice === "new_milestone") {
1199
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1200
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1407
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1408
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1201
1409
  `New milestone ${nextId}.`,
1202
1410
  basePath
1203
1411
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1235,8 +1443,8 @@ export async function showSmartEntry(
1235
1443
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1236
1444
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1237
1445
 
1238
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1239
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1446
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1447
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1240
1448
  `New milestone ${nextId}.`,
1241
1449
  basePath
1242
1450
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1282,26 +1490,28 @@ export async function showSmartEntry(
1282
1490
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1283
1491
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1284
1492
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1493
+ fastPathInstruction: "",
1285
1494
  });
1286
1495
  const seed = draftContent
1287
1496
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
1288
1497
  : basePrompt;
1289
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1498
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1290
1499
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1291
1500
  } else if (choice === "discuss_fresh") {
1292
1501
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1293
1502
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1294
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1503
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1295
1504
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1296
1505
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1297
1506
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1507
+ fastPathInstruction: "",
1298
1508
  }), "gsd-discuss", ctx, "discuss-milestone");
1299
1509
  } else if (choice === "skip_milestone") {
1300
1510
  const milestoneIds = findMilestoneIds(basePath);
1301
1511
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1302
1512
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1303
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1304
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1513
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1514
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1305
1515
  `New milestone ${nextId}.`,
1306
1516
  basePath
1307
1517
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1314,7 +1524,19 @@ export async function showSmartEntry(
1314
1524
  const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
1315
1525
  const hasRoadmap = !!(roadmapFile && await loadFile(roadmapFile));
1316
1526
 
1317
- if (!hasRoadmap) {
1527
+ // A roadmap file with zero parseable slices (placeholder text) should be
1528
+ // treated the same as no roadmap — offer "Create roadmap" instead of "Go auto"
1529
+ // which would immediately get stuck in blocked state (#3441).
1530
+ let roadmapHasSlices = false;
1531
+ if (hasRoadmap) {
1532
+ const roadmapContent = await loadFile(roadmapFile!);
1533
+ if (roadmapContent) {
1534
+ const parsed = parseRoadmapSlices(roadmapContent);
1535
+ roadmapHasSlices = parsed.length > 0;
1536
+ }
1537
+ }
1538
+
1539
+ if (!hasRoadmap || !roadmapHasSlices) {
1318
1540
  // No roadmap → discuss or plan
1319
1541
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
1320
1542
  const hasContext = !!(contextFile && await loadFile(contextFile));
@@ -1353,7 +1575,7 @@ export async function showSmartEntry(
1353
1575
  });
1354
1576
 
1355
1577
  if (choice === "plan") {
1356
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1578
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1357
1579
  const planMilestoneTemplates = [
1358
1580
  inlineTemplate("roadmap", "Roadmap"),
1359
1581
  inlineTemplate("plan", "Slice Plan"),
@@ -1379,13 +1601,14 @@ export async function showSmartEntry(
1379
1601
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1380
1602
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1381
1603
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1604
+ fastPathInstruction: "",
1382
1605
  }), "gsd-run", ctx, "discuss-milestone");
1383
1606
  } else if (choice === "skip_milestone") {
1384
1607
  const milestoneIds = findMilestoneIds(basePath);
1385
1608
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1386
1609
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
1387
- pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1388
- await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
1610
+ pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
1611
+ await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId,
1389
1612
  `New milestone ${nextId}.`,
1390
1613
  basePath
1391
1614
  ), "gsd-run", ctx, "discuss-milestone");
@@ -1514,7 +1737,8 @@ export async function showSmartEntry(
1514
1737
  }),
1515
1738
  }), "gsd-run", ctx, "plan-slice");
1516
1739
  } else if (choice === "discuss") {
1517
- await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext }), "gsd-run", ctx, "discuss-slice");
1740
+ const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1741
+ await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
1518
1742
  } else if (choice === "research") {
1519
1743
  const researchTemplates = inlineTemplate("research", "Research");
1520
1744
  await dispatchWorkflow(pi, loadPrompt("guided-research-slice", {
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI } from "@gsd/pi-coding-agent";
2
2
 
3
3
  export {
4
+ isDepthConfirmationAnswer,
4
5
  isDepthVerified,
5
6
  isQueuePhaseActive,
6
7
  setQueuePhaseActive,
@@ -1,5 +1,6 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
+ import { randomBytes } from "node:crypto";
3
4
 
4
5
  /**
5
6
  * Load a JSON file with validation, returning a default on failure.
@@ -51,9 +52,11 @@ export function loadJsonFileOrNull<T>(
51
52
  export function saveJsonFile<T>(filePath: string, data: T): void {
52
53
  try {
53
54
  mkdirSync(dirname(filePath), { recursive: true });
54
- const tmp = filePath + ".tmp";
55
+ // Use randomized tmp suffix to prevent concurrent-write data loss
56
+ const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
55
57
  writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
56
58
  renameSync(tmp, filePath);
59
+ // No cleanup needed — renameSync atomically removes tmp on success
57
60
  } catch {
58
61
  // Non-fatal — don't let persistence failures break operation
59
62
  }
@@ -66,7 +69,7 @@ export function saveJsonFile<T>(filePath: string, data: T): void {
66
69
  export function writeJsonFileAtomic<T>(filePath: string, data: T): void {
67
70
  try {
68
71
  mkdirSync(dirname(filePath), { recursive: true });
69
- const tmp = filePath + ".tmp";
72
+ const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
70
73
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
71
74
  renameSync(tmp, filePath);
72
75
  } catch {
@@ -530,11 +530,6 @@ export function migrateHierarchyToDb(basePath: string): {
530
530
  // Ghost milestone: no CONTEXT, ROADMAP, or SUMMARY → skip
531
531
  if (!hasRoadmap && !hasContext && !hasSummary) continue;
532
532
 
533
- // Determine milestone status
534
- let milestoneStatus = 'active';
535
- if (hasSummary) milestoneStatus = 'complete';
536
- else if (hasParked) milestoneStatus = 'parked';
537
-
538
533
  // Determine milestone title from roadmap H1 or CONTEXT heading
539
534
  let milestoneTitle = '';
540
535
  let roadmapContent: string | null = null;
@@ -544,6 +539,16 @@ export function migrateHierarchyToDb(basePath: string): {
544
539
  roadmap = parseRoadmap(roadmapContent);
545
540
  milestoneTitle = roadmap.title;
546
541
  }
542
+
543
+ // Determine milestone status
544
+ let milestoneStatus = 'active';
545
+ if (hasSummary) milestoneStatus = 'complete';
546
+ else if (hasParked) milestoneStatus = 'parked';
547
+ // Import milestones with all-done roadmap slices as complete (#3390, #3379)
548
+ // even when SUMMARY.md is missing — the roadmap checkboxes are authoritative.
549
+ else if (roadmap && roadmap.slices.length > 0 && roadmap.slices.every(s => s.done)) {
550
+ milestoneStatus = 'complete';
551
+ }
547
552
  if (!milestoneTitle && hasContext) {
548
553
  const contextContent = readFileSync(contextPath!, 'utf-8');
549
554
  const h1Match = contextContent.match(/^#\s+(.+)/m);
@@ -586,7 +591,8 @@ export function migrateHierarchyToDb(basePath: string): {
586
591
  // Parse roadmap for slices
587
592
  if (!roadmap) continue;
588
593
 
589
- for (const sliceEntry of roadmap.slices) {
594
+ for (let si = 0; si < roadmap.slices.length; si++) {
595
+ const sliceEntry = roadmap.slices[si]!;
590
596
  // Per K002: use 'complete' not 'done'
591
597
  const sliceStatus = sliceEntry.done ? 'complete' : 'pending';
592
598
 
@@ -606,6 +612,7 @@ export function migrateHierarchyToDb(basePath: string): {
606
612
  risk: sliceEntry.risk,
607
613
  depends: sliceEntry.depends,
608
614
  demo: sliceEntry.demo,
615
+ sequence: si + 1, // Preserve roadmap parse order (#3356)
609
616
  planning: {
610
617
  goal: plan?.goal ?? '',
611
618
  },
@@ -550,19 +550,27 @@ export function spawnWorker(
550
550
 
551
551
  let child: ChildProcess;
552
552
  try {
553
+ const workerEnv: Record<string, string | undefined> = {
554
+ ...process.env,
555
+ GSD_MILESTONE_LOCK: milestoneId,
556
+ // Pass the real project root so workers don't need to re-derive it.
557
+ // Without this, process.cwd() resolves symlinks and the worktree
558
+ // path heuristic can match the user-level ~/.gsd instead of the
559
+ // project .gsd, causing writes to ~ and corrupting user config.
560
+ GSD_PROJECT_ROOT: basePath,
561
+ // Prevent workers from spawning their own parallel sessions
562
+ GSD_PARALLEL_WORKER: "1",
563
+ };
564
+
565
+ // Apply worker model override if configured, so workers use a cheaper
566
+ // model (e.g. Haiku) rather than inheriting the coordinator's model.
567
+ if (state.config.worker_model) {
568
+ workerEnv.GSD_WORKER_MODEL = state.config.worker_model;
569
+ }
570
+
553
571
  child = spawn(process.execPath, [binPath, "headless", "--json", "auto"], {
554
572
  cwd: worker.worktreePath,
555
- env: {
556
- ...process.env,
557
- GSD_MILESTONE_LOCK: milestoneId,
558
- // Pass the real project root so workers don't need to re-derive it.
559
- // Without this, process.cwd() resolves symlinks and the worktree
560
- // path heuristic can match the user-level ~/.gsd instead of the
561
- // project .gsd, causing writes to ~ and corrupting user config.
562
- GSD_PROJECT_ROOT: basePath,
563
- // Prevent workers from spawning their own parallel sessions
564
- GSD_PARALLEL_WORKER: "1",
565
- },
573
+ env: workerEnv,
566
574
  stdio: ["ignore", "pipe", "pipe"],
567
575
  detached: false,
568
576
  });