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
@@ -0,0 +1,312 @@
1
+ /**
2
+ * GSD Configuration Overlay
3
+ *
4
+ * Read-only TUI overlay showing the effective GSD configuration:
5
+ * token profile, model assignments, dynamic routing, git settings,
6
+ * budget, workflow toggles, and preference file sources.
7
+ * Opened via `/gsd show-config` or `/gsd config`.
8
+ */
9
+ import { matchesKey, Key, truncateToWidth } from "@gsd/pi-tui";
10
+ import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, loadProjectGSDPreferences, getGlobalGSDPreferencesPath, getProjectGSDPreferencesPath, resolveDynamicRoutingConfig, resolveEffectiveProfile, resolveModelWithFallbacksForUnit, resolveAutoSupervisorConfig, } from "./preferences.js";
11
+ function collectConfigSections() {
12
+ const sections = [];
13
+ const globalPrefs = loadGlobalGSDPreferences();
14
+ const projectPrefs = loadProjectGSDPreferences();
15
+ const effective = loadEffectiveGSDPreferences();
16
+ const prefs = effective?.preferences;
17
+ // ─── Sources ─────────────────────────────────────────────────────────
18
+ sections.push({
19
+ title: "Sources",
20
+ rows: [
21
+ { label: "Global", value: globalPrefs ? globalPrefs.path : `(none) ${getGlobalGSDPreferencesPath()}` },
22
+ { label: "Project", value: projectPrefs ? projectPrefs.path : `(none) ${getProjectGSDPreferencesPath()}` },
23
+ ],
24
+ });
25
+ // ─── Profile ─────────────────────────────────────────────────────────
26
+ const profile = resolveEffectiveProfile();
27
+ const profileRows = [
28
+ { label: "Token profile", value: `${profile}${!prefs?.token_profile ? " (default)" : ""}`, accent: true },
29
+ ];
30
+ if (prefs?.mode)
31
+ profileRows.push({ label: "Workflow mode", value: prefs.mode });
32
+ sections.push({ title: "Profile", rows: profileRows });
33
+ // ─── Models ──────────────────────────────────────────────────────────
34
+ const unitTypes = [
35
+ ["research", "research-milestone"],
36
+ ["planning", "plan-milestone"],
37
+ ["discuss", "discuss-milestone"],
38
+ ["execution", "execute-task"],
39
+ ["completion", "complete-slice"],
40
+ ["validation", "run-uat"],
41
+ ];
42
+ const modelRows = [];
43
+ for (const [label, unitType] of unitTypes) {
44
+ const resolved = resolveModelWithFallbacksForUnit(unitType);
45
+ if (resolved) {
46
+ let val = resolved.primary;
47
+ if (resolved.fallbacks.length > 0) {
48
+ val += ` \u2192 ${resolved.fallbacks.join(" \u2192 ")}`;
49
+ }
50
+ modelRows.push({ label, value: val });
51
+ }
52
+ else {
53
+ modelRows.push({ label, value: "(inherit)" });
54
+ }
55
+ }
56
+ // subagent is a direct config key
57
+ const models = prefs?.models;
58
+ const subVal = models?.subagent;
59
+ if (subVal) {
60
+ const model = typeof subVal === "string" ? subVal : subVal?.model ?? "?";
61
+ modelRows.push({ label: "subagent", value: model });
62
+ }
63
+ else {
64
+ modelRows.push({ label: "subagent", value: "(inherit)" });
65
+ }
66
+ sections.push({ title: "Models", rows: modelRows });
67
+ // ─── Dynamic Routing ─────────────────────────────────────────────────
68
+ const routing = resolveDynamicRoutingConfig();
69
+ const routingRows = [
70
+ { label: "Enabled", value: routing.enabled ? "yes" : "no", accent: routing.enabled },
71
+ ];
72
+ if (routing.enabled) {
73
+ routingRows.push({ label: "Escalate on fail", value: routing.escalate_on_failure !== false ? "yes" : "no" });
74
+ routingRows.push({ label: "Budget pressure", value: routing.budget_pressure !== false ? "yes" : "no" });
75
+ routingRows.push({ label: "Cross-provider", value: routing.cross_provider !== false ? "yes" : "no" });
76
+ if (routing.tier_models) {
77
+ const tm = routing.tier_models;
78
+ if (tm.light)
79
+ routingRows.push({ label: "[L] light", value: tm.light });
80
+ if (tm.standard)
81
+ routingRows.push({ label: "[S] standard", value: tm.standard });
82
+ if (tm.heavy)
83
+ routingRows.push({ label: "[H] heavy", value: tm.heavy });
84
+ }
85
+ }
86
+ sections.push({ title: "Dynamic Routing", rows: routingRows });
87
+ // ─── Git ─────────────────────────────────────────────────────────────
88
+ if (prefs?.git) {
89
+ const g = prefs.git;
90
+ const gitRows = [];
91
+ if (g.isolation !== undefined)
92
+ gitRows.push({ label: "Isolation", value: String(g.isolation) });
93
+ if (g.auto_push !== undefined)
94
+ gitRows.push({ label: "Auto push", value: String(g.auto_push) });
95
+ if (g.push_branches !== undefined)
96
+ gitRows.push({ label: "Push branches", value: String(g.push_branches) });
97
+ if (g.merge_strategy)
98
+ gitRows.push({ label: "Merge strategy", value: g.merge_strategy });
99
+ if (g.main_branch)
100
+ gitRows.push({ label: "Main branch", value: g.main_branch });
101
+ if (g.remote)
102
+ gitRows.push({ label: "Remote", value: g.remote });
103
+ if (gitRows.length > 0)
104
+ sections.push({ title: "Git", rows: gitRows });
105
+ }
106
+ // ─── Budget ──────────────────────────────────────────────────────────
107
+ if (prefs?.budget_ceiling !== undefined || prefs?.budget_enforcement) {
108
+ const budgetRows = [];
109
+ if (prefs.budget_ceiling !== undefined)
110
+ budgetRows.push({ label: "Ceiling", value: `$${prefs.budget_ceiling}` });
111
+ if (prefs.budget_enforcement)
112
+ budgetRows.push({ label: "Enforcement", value: String(prefs.budget_enforcement) });
113
+ sections.push({ title: "Budget", rows: budgetRows });
114
+ }
115
+ // ─── Auto Supervisor ─────────────────────────────────────────────────
116
+ if (prefs?.auto_supervisor) {
117
+ const sup = resolveAutoSupervisorConfig();
118
+ const supRows = [];
119
+ if (sup.model)
120
+ supRows.push({ label: "Model", value: sup.model });
121
+ supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
122
+ supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
123
+ supRows.push({ label: "Hard timeout", value: `${sup.hard_timeout_minutes}m` });
124
+ sections.push({ title: "Auto Supervisor", rows: supRows });
125
+ }
126
+ // ─── Toggles ─────────────────────────────────────────────────────────
127
+ const toggleRows = [];
128
+ if (prefs?.phases) {
129
+ const p = prefs.phases;
130
+ if (p.skip_research)
131
+ toggleRows.push({ label: "skip_research", value: "on" });
132
+ if (p.skip_reassess)
133
+ toggleRows.push({ label: "skip_reassess", value: "on" });
134
+ if (p.skip_slice_research)
135
+ toggleRows.push({ label: "skip_slice_research", value: "on" });
136
+ if (p.skip_milestone_validation)
137
+ toggleRows.push({ label: "skip_milestone_validation", value: "on" });
138
+ if (p.require_slice_discussion)
139
+ toggleRows.push({ label: "require_slice_discussion", value: "on" });
140
+ }
141
+ if (prefs?.uat_dispatch)
142
+ toggleRows.push({ label: "uat_dispatch", value: "on" });
143
+ if (prefs?.auto_visualize)
144
+ toggleRows.push({ label: "auto_visualize", value: "on" });
145
+ if (prefs?.auto_report === false)
146
+ toggleRows.push({ label: "auto_report", value: "off" });
147
+ if (prefs?.show_token_cost)
148
+ toggleRows.push({ label: "show_token_cost", value: "on" });
149
+ if (prefs?.forensics_dedup)
150
+ toggleRows.push({ label: "forensics_dedup", value: "on" });
151
+ if (prefs?.unique_milestone_ids)
152
+ toggleRows.push({ label: "unique_milestone_ids", value: "on" });
153
+ if (prefs?.service_tier)
154
+ toggleRows.push({ label: "service_tier", value: prefs.service_tier });
155
+ if (prefs?.search_provider && prefs.search_provider !== "auto")
156
+ toggleRows.push({ label: "search_provider", value: prefs.search_provider });
157
+ if (prefs?.context_selection)
158
+ toggleRows.push({ label: "context_selection", value: prefs.context_selection });
159
+ if (prefs?.widget_mode && prefs.widget_mode !== "full")
160
+ toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
161
+ if (prefs?.experimental?.rtk)
162
+ toggleRows.push({ label: "experimental.rtk", value: "on" });
163
+ if (toggleRows.length > 0)
164
+ sections.push({ title: "Toggles", rows: toggleRows });
165
+ // ─── Parallel ────────────────────────────────────────────────────────
166
+ if (prefs?.parallel) {
167
+ const pc = prefs.parallel;
168
+ const parallelRows = [];
169
+ if (pc.max_workers !== undefined)
170
+ parallelRows.push({ label: "Max workers", value: String(pc.max_workers) });
171
+ if (pc.merge_strategy)
172
+ parallelRows.push({ label: "Merge strategy", value: pc.merge_strategy });
173
+ if (pc.auto_merge)
174
+ parallelRows.push({ label: "Auto merge", value: pc.auto_merge });
175
+ if (parallelRows.length > 0)
176
+ sections.push({ title: "Parallel", rows: parallelRows });
177
+ }
178
+ // ─── Hooks ───────────────────────────────────────────────────────────
179
+ const postHooks = prefs?.post_unit_hooks?.filter(h => h.enabled !== false) ?? [];
180
+ const preHooks = prefs?.pre_dispatch_hooks?.filter(h => h.enabled !== false) ?? [];
181
+ if (postHooks.length > 0 || preHooks.length > 0) {
182
+ const hookRows = [];
183
+ if (preHooks.length > 0)
184
+ hookRows.push({ label: "Pre-dispatch", value: `${preHooks.length} active` });
185
+ if (postHooks.length > 0)
186
+ hookRows.push({ label: "Post-unit", value: `${postHooks.length} active` });
187
+ sections.push({ title: "Hooks", rows: hookRows });
188
+ }
189
+ // ─── Warnings ────────────────────────────────────────────────────────
190
+ const warnings = [
191
+ ...(globalPrefs?.warnings ?? []),
192
+ ...(projectPrefs?.warnings ?? []),
193
+ ];
194
+ if (warnings.length > 0) {
195
+ sections.push({
196
+ title: "Warnings",
197
+ rows: warnings.map(w => ({ label: "\u26a0", value: w })),
198
+ });
199
+ }
200
+ return sections;
201
+ }
202
+ // ─── Plain Text Formatter (headless/RPC fallback) ─────────────────────────
203
+ export function formatConfigText() {
204
+ const sections = collectConfigSections();
205
+ const lines = ["GSD Configuration\n"];
206
+ let maxLabel = 0;
207
+ for (const section of sections) {
208
+ for (const row of section.rows) {
209
+ if (row.label.length > maxLabel)
210
+ maxLabel = row.label.length;
211
+ }
212
+ }
213
+ const pad = Math.min(maxLabel + 2, 24);
214
+ for (const section of sections) {
215
+ lines.push("");
216
+ lines.push(section.title.toUpperCase());
217
+ for (const row of section.rows) {
218
+ lines.push(` ${row.label.padEnd(pad)}${row.value}`);
219
+ }
220
+ }
221
+ return lines.join("\n");
222
+ }
223
+ // ─── Overlay Class ────────────────────────────────────────────────────────
224
+ export class GSDConfigOverlay {
225
+ tui;
226
+ theme;
227
+ onClose;
228
+ sections;
229
+ cachedLines;
230
+ scrollOffset = 0;
231
+ disposed = false;
232
+ constructor(tui, theme, onClose) {
233
+ this.tui = tui;
234
+ this.theme = theme;
235
+ this.onClose = onClose;
236
+ this.sections = collectConfigSections();
237
+ }
238
+ invalidate() {
239
+ this.cachedLines = undefined;
240
+ }
241
+ dispose() {
242
+ this.disposed = true;
243
+ }
244
+ handleInput(data) {
245
+ if (matchesKey(data, Key.escape) || data === "q") {
246
+ this.dispose();
247
+ this.onClose();
248
+ return;
249
+ }
250
+ if (matchesKey(data, Key.down) || data === "j") {
251
+ this.scrollOffset++;
252
+ this.cachedLines = undefined;
253
+ this.tui.requestRender();
254
+ return;
255
+ }
256
+ if (matchesKey(data, Key.up) || data === "k") {
257
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
258
+ this.cachedLines = undefined;
259
+ this.tui.requestRender();
260
+ return;
261
+ }
262
+ if (matchesKey(data, Key.pageDown)) {
263
+ this.scrollOffset += 10;
264
+ this.cachedLines = undefined;
265
+ this.tui.requestRender();
266
+ return;
267
+ }
268
+ if (matchesKey(data, Key.pageUp)) {
269
+ this.scrollOffset = Math.max(0, this.scrollOffset - 10);
270
+ this.cachedLines = undefined;
271
+ this.tui.requestRender();
272
+ return;
273
+ }
274
+ }
275
+ render(width) {
276
+ if (this.cachedLines)
277
+ return this.cachedLines;
278
+ const t = this.theme;
279
+ const w = Math.max(width, 50);
280
+ const allLines = [];
281
+ // Header
282
+ allLines.push(t.bold(t.fg("accent", " GSD Configuration ")));
283
+ allLines.push(t.fg("muted", "\u2500".repeat(w)));
284
+ // Find max label width for alignment
285
+ let maxLabel = 0;
286
+ for (const section of this.sections) {
287
+ for (const row of section.rows) {
288
+ if (row.label.length > maxLabel)
289
+ maxLabel = row.label.length;
290
+ }
291
+ }
292
+ const labelPad = Math.min(maxLabel + 2, 24);
293
+ for (const section of this.sections) {
294
+ allLines.push("");
295
+ allLines.push(t.bold(t.fg("accent", ` ${section.title}`)));
296
+ for (const row of section.rows) {
297
+ const label = t.fg("muted", ` ${row.label.padEnd(labelPad)}`);
298
+ const value = row.accent ? t.bold(row.value) : row.value;
299
+ allLines.push(truncateToWidth(`${label}${value}`, w));
300
+ }
301
+ }
302
+ allLines.push("");
303
+ allLines.push(t.fg("muted", ` ${"\u2500".repeat(w - 4)}`));
304
+ allLines.push(t.fg("muted", " esc/q close \u2502 \u2191\u2193/jk scroll \u2502 /gsd prefs to edit"));
305
+ // Apply scroll
306
+ const maxScroll = Math.max(0, allLines.length - 20);
307
+ this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
308
+ const visible = allLines.slice(this.scrollOffset);
309
+ this.cachedLines = visible;
310
+ return visible;
311
+ }
312
+ }
@@ -300,8 +300,13 @@ export async function saveRequirementToDb(fields, basePath) {
300
300
  }
301
301
  catch (diskErr) {
302
302
  logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveRequirementToDb', error: String(diskErr.message) });
303
- const rollbackAdapter = db._getAdapter();
304
- rollbackAdapter?.prepare('DELETE FROM requirements WHERE id = :id').run({ ':id': id });
303
+ try {
304
+ const rollbackAdapter = db._getAdapter();
305
+ rollbackAdapter?.prepare('DELETE FROM requirements WHERE id = :id').run({ ':id': id });
306
+ }
307
+ catch (rollbackErr) {
308
+ logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveRequirementToDb', id, error: String(rollbackErr.message) });
309
+ }
305
310
  throw diskErr;
306
311
  }
307
312
  invalidateStateCache();
@@ -399,7 +404,12 @@ export async function saveDecisionToDb(fields, basePath) {
399
404
  }
400
405
  catch (diskErr) {
401
406
  logError('manifest', 'disk write failed, rolling back DB row', { fn: 'saveDecisionToDb', error: String(diskErr.message) });
402
- adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
407
+ try {
408
+ adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
409
+ }
410
+ catch (rollbackErr) {
411
+ logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveDecisionToDb', id, error: String(rollbackErr.message) });
412
+ }
403
413
  throw diskErr;
404
414
  }
405
415
  // #2661: When a decision defers a slice, update the slice status in the DB
@@ -945,7 +945,7 @@ function resolveVersionCatalogAccessors(basePath, versionCatalogFiles, settingsF
945
945
  }
946
946
  return accessors;
947
947
  }
948
- function scanProjectFiles(basePath) {
948
+ export function scanProjectFiles(basePath) {
949
949
  const files = [];
950
950
  const queue = [{ path: basePath, depth: 0 }];
951
951
  while (queue.length > 0 && files.length < MAX_RECURSIVE_SCAN_FILES) {
@@ -4,6 +4,7 @@ import { findMilestoneIds } from "./guided-flow.js";
4
4
  import { parseUnitId } from "./unit-id.js";
5
5
  import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
6
6
  import { parseRoadmap } from "./parsers-legacy.js";
7
+ import { isClosedStatus } from "./status-guards.js";
7
8
  import { readFileSync } from "node:fs";
8
9
  const SLICE_DISPATCH_TYPES = new Set([
9
10
  "research-slice",
@@ -46,7 +47,7 @@ export function getPriorSliceCompletionBlocker(base, _mainBranch, unitType, unit
46
47
  if (rows.length > 0) {
47
48
  slices = rows.map((r) => ({
48
49
  id: r.id,
49
- done: r.status === "complete",
50
+ done: isClosedStatus(r.status),
50
51
  depends: r.depends ?? [],
51
52
  }));
52
53
  }
@@ -211,6 +211,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
211
211
  - `budget_ceiling`: number — optional per-parallel-run budget ceiling.
212
212
  - `merge_strategy`: `"per-slice"` or `"per-milestone"` — when to merge worktree results back. Default: `"per-milestone"`.
213
213
  - `auto_merge`: `"auto"`, `"confirm"`, or `"manual"` — merge behavior after completion. `"auto"` merges immediately; `"confirm"` asks first; `"manual"` leaves branches for you. Default: `"confirm"`.
214
+ - `worker_model`: string — optional model override for parallel milestone workers. When set, workers use this model (e.g. `"claude-haiku-4-5"`) instead of inheriting the coordinator's model. Useful for cost savings on execution-heavy milestones.
214
215
 
215
216
  - `verification_commands`: string[] — shell commands to run as verification after task execution (e.g., `["npm test", "npm run lint"]`). Commands run in order; if any fails, the task is marked as needing fixes.
216
217
 
@@ -75,7 +75,8 @@ function validatePreferenceShape(preferences) {
75
75
  }
76
76
  return issues;
77
77
  }
78
- function buildStateMarkdown(state) {
78
+ /** Build STATE.md content from derived state. Exported for guided-flow pre-dispatch rebuild (#3475). */
79
+ export function buildStateMarkdown(state) {
79
80
  const lines = [];
80
81
  lines.push("# GSD State", "");
81
82
  const activeMilestone = state.activeMilestone
@@ -39,6 +39,7 @@ const BASELINE_PATTERNS = [
39
39
  // ── GSD state directory (symlink to external storage) ──
40
40
  ".gsd",
41
41
  ".gsd-id",
42
+ ".bg-shell/",
42
43
  // ── OS junk ──
43
44
  ".DS_Store",
44
45
  "Thumbs.db",
@@ -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,