gsd-pi 2.51.0 → 2.52.0-dev.655ad8a

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 (419) hide show
  1. package/README.md +59 -36
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-query.js +1 -1
  5. package/dist/headless-types.d.ts +28 -0
  6. package/dist/headless-types.js +7 -0
  7. package/dist/headless.d.ts +8 -3
  8. package/dist/headless.js +47 -16
  9. package/dist/help-text.js +16 -5
  10. package/dist/onboarding.js +5 -4
  11. package/dist/remote-questions-config.js +1 -1
  12. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  13. package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  15. package/dist/resources/extensions/get-secrets-from-user.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/phases.js +34 -8
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
  18. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  19. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  21. package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
  22. package/dist/resources/extensions/gsd/auto.js +30 -4
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  26. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  27. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  28. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  29. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
  30. package/dist/resources/extensions/gsd/detection.js +6 -6
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  32. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  33. package/dist/resources/extensions/gsd/git-service.js +4 -3
  34. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  35. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  36. package/dist/resources/extensions/gsd/guided-flow.js +4 -3
  37. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  38. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
  40. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  41. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  42. package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
  43. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  44. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  45. package/dist/resources/extensions/gsd/preferences.js +13 -13
  46. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  47. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  48. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  49. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  50. package/dist/resources/extensions/gsd/state.js +38 -30
  51. package/dist/resources/extensions/gsd/status-guards.js +12 -0
  52. package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
  53. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
  54. package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
  55. package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
  56. package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
  57. package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
  58. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
  59. package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
  60. package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
  61. package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
  62. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  63. package/dist/resources/extensions/gsd/validation.js +21 -0
  64. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  65. package/dist/resources/extensions/remote-questions/config.js +1 -1
  66. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  67. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  68. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  69. package/dist/resources/extensions/shared/rtk.js +14 -4
  70. package/dist/rtk.js +3 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  73. package/dist/web/standalone/.next/build-manifest.json +4 -4
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  76. package/dist/web/standalone/.next/required-server-files.json +3 -3
  77. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  78. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  80. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  88. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  97. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  150. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  166. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  168. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  170. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/index.html +1 -1
  180. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  181. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  182. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  183. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  184. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  185. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  186. package/dist/web/standalone/.next/server/app/page.js +2 -2
  187. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  189. package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
  190. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  191. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/middleware.js +2 -2
  194. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  196. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  197. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  198. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  199. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
  200. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  202. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  204. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  205. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
  206. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  207. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  208. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  209. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  210. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  211. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  212. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  213. package/dist/web/standalone/server.js +1 -1
  214. package/dist/wizard.js +4 -1
  215. package/package.json +2 -2
  216. package/packages/mcp-server/README.md +202 -0
  217. package/packages/mcp-server/package.json +36 -0
  218. package/packages/mcp-server/src/cli.ts +68 -0
  219. package/packages/mcp-server/src/index.ts +14 -0
  220. package/packages/mcp-server/src/mcp-server.test.ts +628 -0
  221. package/packages/mcp-server/src/server.ts +278 -0
  222. package/packages/mcp-server/src/session-manager.ts +328 -0
  223. package/packages/mcp-server/src/types.ts +107 -0
  224. package/packages/mcp-server/tsconfig.json +24 -0
  225. package/packages/pi-ai/dist/models.d.ts +14 -3
  226. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  227. package/packages/pi-ai/dist/models.js +53 -10
  228. package/packages/pi-ai/dist/models.js.map +1 -1
  229. package/packages/pi-ai/dist/models.test.js +102 -1
  230. package/packages/pi-ai/dist/models.test.js.map +1 -1
  231. package/packages/pi-ai/dist/types.d.ts +30 -0
  232. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  233. package/packages/pi-ai/dist/types.js.map +1 -1
  234. package/packages/pi-ai/src/models.test.ts +114 -1
  235. package/packages/pi-ai/src/models.ts +70 -13
  236. package/packages/pi-ai/src/types.ts +31 -0
  237. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  238. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  239. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  240. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  241. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  242. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  243. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  244. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  245. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  246. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  247. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  248. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  249. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  250. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  251. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  253. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  255. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  256. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  257. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/main.js +5 -3
  259. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  261. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  265. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  268. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  269. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  270. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  271. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  272. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  273. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  275. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  276. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  277. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  278. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  279. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  282. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  283. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  284. package/packages/pi-coding-agent/package.json +1 -1
  285. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  286. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  287. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  288. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  289. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  290. package/packages/pi-coding-agent/src/index.ts +3 -0
  291. package/packages/pi-coding-agent/src/main.ts +5 -3
  292. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  293. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  294. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  295. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  296. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  297. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  298. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  299. package/packages/rpc-client/package.json +20 -0
  300. package/pkg/package.json +1 -1
  301. package/scripts/ensure-workspace-builds.cjs +36 -8
  302. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  303. package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
  304. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  305. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  306. package/src/resources/extensions/get-secrets-from-user.ts +8 -0
  307. package/src/resources/extensions/gsd/auto/phases.ts +44 -7
  308. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
  309. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  310. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  311. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  312. package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
  313. package/src/resources/extensions/gsd/auto.ts +31 -4
  314. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
  315. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  316. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  317. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  318. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  319. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  320. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
  321. package/src/resources/extensions/gsd/detection.ts +6 -6
  322. package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
  323. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  324. package/src/resources/extensions/gsd/git-service.ts +4 -3
  325. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  326. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  327. package/src/resources/extensions/gsd/guided-flow.ts +4 -3
  328. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  329. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  330. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
  331. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  332. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  333. package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
  334. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  335. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  336. package/src/resources/extensions/gsd/preferences.ts +12 -13
  337. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  338. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  339. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  340. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  341. package/src/resources/extensions/gsd/state.ts +39 -30
  342. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  343. package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
  344. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
  345. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  346. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
  347. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
  348. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
  349. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  350. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  351. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
  352. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  353. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  354. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  355. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  356. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  357. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  358. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  359. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  360. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  361. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  362. package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
  363. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  364. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  365. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  366. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  367. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  368. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  369. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  370. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
  371. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  372. package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
  373. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  374. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
  375. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  376. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
  377. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  378. package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
  379. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  380. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  381. package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
  382. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
  383. package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
  384. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  385. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  386. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  387. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  388. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
  389. package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
  390. package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
  391. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  392. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  393. package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
  394. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
  395. package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
  396. package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
  397. package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
  398. package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
  399. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
  400. package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
  401. package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
  402. package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
  403. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  404. package/src/resources/extensions/gsd/validation.ts +23 -0
  405. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  406. package/src/resources/extensions/remote-questions/config.ts +1 -1
  407. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  408. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  409. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  410. package/src/resources/extensions/shared/rtk.ts +22 -4
  411. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  412. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  413. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  414. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  415. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  416. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  417. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
  418. /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
  419. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Unified error classifier for provider/network/server errors.
3
+ *
4
+ * Consolidates patterns from:
5
+ * - isTransientNetworkError() in preferences-models.ts
6
+ * - classifyProviderError() in provider-error-pause.ts
7
+ *
8
+ * Single entry point: classifyError(errorMsg, retryAfterMs?)
9
+ *
10
+ * @see https://github.com/gsd-build/gsd/issues/2577
11
+ */
12
+ export function createRetryState() {
13
+ return { networkRetryCount: 0, consecutiveTransientCount: 0, currentRetryModelId: undefined };
14
+ }
15
+ export function resetRetryState(state) {
16
+ state.networkRetryCount = 0;
17
+ state.consecutiveTransientCount = 0;
18
+ state.currentRetryModelId = undefined;
19
+ }
20
+ // ── Classification ──────────────────────────────────────────────────────────
21
+ const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i;
22
+ const RATE_LIMIT_RE = /rate.?limit|too many requests|429/i;
23
+ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns/i;
24
+ const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
25
+ // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
26
+ const CONNECTION_RE = /terminated|connection.?refused|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
27
+ const STREAM_RE = /Unexpected end of JSON|Unexpected token.*JSON|Expected double-quoted property name|SyntaxError.*JSON/i;
28
+ const RESET_DELAY_RE = /reset in (\d+)s/i;
29
+ /**
30
+ * Classify an error message into one of the ErrorClass kinds.
31
+ *
32
+ * Classification order:
33
+ * 1. Permanent (auth/billing/quota) — unless also rate-limited
34
+ * 2. Rate limit (429, rate.?limit, too many requests)
35
+ * 3. Network (ECONNRESET, ETIMEDOUT, socket hang up, fetch failed, dns)
36
+ * 4. Server (500/502/503, overloaded, server_error)
37
+ * 5. Connection (terminated, ECONNREFUSED, EPIPE, other side closed)
38
+ * 6. Stream truncation (malformed JSON from mid-stream cut)
39
+ * 7. Unknown
40
+ */
41
+ export function classifyError(errorMsg, retryAfterMs) {
42
+ const isPermanent = PERMANENT_RE.test(errorMsg);
43
+ const isRateLimit = RATE_LIMIT_RE.test(errorMsg);
44
+ // 1. Permanent — but rate limit takes precedence
45
+ if (isPermanent && !isRateLimit) {
46
+ return { kind: "permanent" };
47
+ }
48
+ // 2. Rate limit
49
+ if (isRateLimit) {
50
+ if (retryAfterMs != null && retryAfterMs > 0) {
51
+ return { kind: "rate-limit", retryAfterMs };
52
+ }
53
+ const resetMatch = errorMsg.match(RESET_DELAY_RE);
54
+ const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000;
55
+ return { kind: "rate-limit", retryAfterMs: delayMs };
56
+ }
57
+ // 3. Network errors — same-model retry candidate
58
+ if (NETWORK_RE.test(errorMsg)) {
59
+ // Exclude if also matches permanent signals (already handled above for
60
+ // rate-limit, but double-check for non-rate-limit permanent overlap like
61
+ // "billing" appearing alongside "network").
62
+ return { kind: "network", retryAfterMs: retryAfterMs ?? 3_000 };
63
+ }
64
+ // 4. Server errors — try fallback model
65
+ if (SERVER_RE.test(errorMsg)) {
66
+ return { kind: "server", retryAfterMs: retryAfterMs ?? 30_000 };
67
+ }
68
+ // 5. Connection errors — try fallback model
69
+ if (CONNECTION_RE.test(errorMsg)) {
70
+ return { kind: "connection", retryAfterMs: retryAfterMs ?? 15_000 };
71
+ }
72
+ // 6. Stream truncation — downstream symptom of connection drop
73
+ if (STREAM_RE.test(errorMsg)) {
74
+ return { kind: "stream", retryAfterMs: retryAfterMs ?? 15_000 };
75
+ }
76
+ // 7. Unknown
77
+ return { kind: "unknown" };
78
+ }
79
+ // ── Helpers ─────────────────────────────────────────────────────────────────
80
+ /** Returns true for all transient (auto-resumable) error kinds. */
81
+ export function isTransient(cls) {
82
+ switch (cls.kind) {
83
+ case "network":
84
+ case "rate-limit":
85
+ case "server":
86
+ case "stream":
87
+ case "connection":
88
+ return true;
89
+ default:
90
+ return false;
91
+ }
92
+ }
93
+ /**
94
+ * Backward-compatible thin wrapper.
95
+ *
96
+ * Returns true when the error is a transient *network* error specifically
97
+ * (worth retrying the same model). Permanent signals (auth, billing, quota)
98
+ * cause this to return false even if a network keyword is present.
99
+ */
100
+ export function isTransientNetworkError(errorMsg) {
101
+ if (!errorMsg)
102
+ return false;
103
+ const cls = classifyError(errorMsg);
104
+ return cls.kind === "network";
105
+ }
@@ -446,11 +446,12 @@ export class GitServiceImpl {
446
446
  }
447
447
  /**
448
448
  * Create a snapshot ref for the given label (typically a slice branch name).
449
- * Gated on prefs.snapshots === true. Ref path: refs/gsd/snapshots/<label>/<timestamp>
449
+ * Enabled by default; opt out with prefs.snapshots === false.
450
+ * Ref path: refs/gsd/snapshots/<label>/<timestamp>
450
451
  * The ref points at HEAD, capturing the current commit before destructive operations.
451
452
  */
452
453
  createSnapshot(label) {
453
- if (this.prefs.snapshots !== true)
454
+ if (this.prefs.snapshots === false)
454
455
  return;
455
456
  const now = new Date();
456
457
  const ts = now.getFullYear().toString()
@@ -470,7 +471,7 @@ export class GitServiceImpl {
470
471
  * Stub: to be implemented in T03.
471
472
  */
472
473
  runPreMergeCheck() {
473
- if (this.prefs.pre_merge_check === false || this.prefs.pre_merge_check === undefined) {
474
+ if (this.prefs.pre_merge_check === false) {
474
475
  return { passed: true, skipped: true };
475
476
  }
476
477
  // Determine command: explicit string or auto-detect from package.json
@@ -1,8 +1,8 @@
1
1
  /**
2
- * GSD bootstrappers for .gitignore and preferences.md
2
+ * GSD bootstrappers for .gitignore and PREFERENCES.md
3
3
  *
4
4
  * Ensures baseline .gitignore exists with universally-correct patterns.
5
- * Creates an empty preferences.md template if it doesn't exist.
5
+ * Creates an empty PREFERENCES.md template if it doesn't exist.
6
6
  * Both idempotent — non-destructive if already present.
7
7
  */
8
8
  import { join } from "node:path";
@@ -190,16 +190,16 @@ export function untrackRuntimeFiles(basePath) {
190
190
  }
191
191
  }
192
192
  /**
193
- * Ensure basePath/.gsd/preferences.md exists as an empty template.
193
+ * Ensure basePath/.gsd/PREFERENCES.md exists as an empty template.
194
194
  * Creates the file with frontmatter only if it doesn't exist.
195
195
  * Returns true if created, false if already exists.
196
196
  *
197
- * Checks both lowercase (canonical) and uppercase (legacy) to avoid
198
- * creating a duplicate when an uppercase file already exists.
197
+ * Checks both uppercase (canonical) and lowercase (legacy) to avoid
198
+ * creating a duplicate when a lowercase file already exists.
199
199
  */
200
200
  export function ensurePreferences(basePath) {
201
- const preferencesPath = join(gsdRoot(basePath), "preferences.md");
202
- const legacyPath = join(gsdRoot(basePath), "PREFERENCES.md");
201
+ const preferencesPath = join(gsdRoot(basePath), "PREFERENCES.md");
202
+ const legacyPath = join(gsdRoot(basePath), "preferences.md");
203
203
  if (existsSync(preferencesPath) || existsSync(legacyPath)) {
204
204
  return false;
205
205
  }
@@ -75,25 +75,34 @@ function normalizeRows(rows) {
75
75
  }
76
76
  function createAdapter(rawDb) {
77
77
  const db = rawDb;
78
+ const stmtCache = new Map();
79
+ function wrapStmt(raw) {
80
+ return {
81
+ run(...params) {
82
+ return raw.run(...params);
83
+ },
84
+ get(...params) {
85
+ return normalizeRow(raw.get(...params));
86
+ },
87
+ all(...params) {
88
+ return normalizeRows(raw.all(...params));
89
+ },
90
+ };
91
+ }
78
92
  return {
79
93
  exec(sql) {
80
94
  db.exec(sql);
81
95
  },
82
96
  prepare(sql) {
83
- const stmt = db.prepare(sql);
84
- return {
85
- run(...params) {
86
- return stmt.run(...params);
87
- },
88
- get(...params) {
89
- return normalizeRow(stmt.get(...params));
90
- },
91
- all(...params) {
92
- return normalizeRows(stmt.all(...params));
93
- },
94
- };
97
+ let cached = stmtCache.get(sql);
98
+ if (cached)
99
+ return cached;
100
+ cached = wrapStmt(db.prepare(sql));
101
+ stmtCache.set(sql, cached);
102
+ return cached;
95
103
  },
96
104
  close() {
105
+ stmtCache.clear();
97
106
  db.close();
98
107
  },
99
108
  };
@@ -109,12 +118,21 @@ function openRawDb(path) {
109
118
  const Database = providerModule;
110
119
  return new Database(path);
111
120
  }
112
- const SCHEMA_VERSION = 12;
121
+ const SCHEMA_VERSION = 14;
113
122
  function initSchema(db, fileBacked) {
114
123
  if (fileBacked)
115
124
  db.exec("PRAGMA journal_mode=WAL");
116
125
  if (fileBacked)
117
126
  db.exec("PRAGMA busy_timeout = 5000");
127
+ if (fileBacked)
128
+ db.exec("PRAGMA synchronous = NORMAL");
129
+ if (fileBacked)
130
+ db.exec("PRAGMA auto_vacuum = INCREMENTAL");
131
+ if (fileBacked)
132
+ db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
133
+ if (fileBacked)
134
+ db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
135
+ db.exec("PRAGMA temp_store = MEMORY");
118
136
  db.exec("PRAGMA foreign_keys = ON");
119
137
  db.exec("BEGIN");
120
138
  try {
@@ -226,7 +244,7 @@ function initSchema(db, fileBacked) {
226
244
  proof_level TEXT NOT NULL DEFAULT '',
227
245
  integration_closure TEXT NOT NULL DEFAULT '',
228
246
  observability_impact TEXT NOT NULL DEFAULT '',
229
- sequence INTEGER DEFAULT 0, -- DEAD CODE: no tool exposes sequence always 0
247
+ sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
230
248
  replan_triggered_at TEXT DEFAULT NULL,
231
249
  PRIMARY KEY (milestone_id, id),
232
250
  FOREIGN KEY (milestone_id) REFERENCES milestones(id)
@@ -258,7 +276,7 @@ function initSchema(db, fileBacked) {
258
276
  expected_output TEXT NOT NULL DEFAULT '[]',
259
277
  observability_impact TEXT NOT NULL DEFAULT '',
260
278
  full_plan_md TEXT NOT NULL DEFAULT '',
261
- sequence INTEGER DEFAULT 0, -- DEAD CODE: no tool exposes sequence always 0
279
+ sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
262
280
  PRIMARY KEY (milestone_id, slice_id, id),
263
281
  FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
264
282
  )
@@ -318,9 +336,28 @@ function initSchema(db, fileBacked) {
318
336
  PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
319
337
  FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
320
338
  )
339
+ `);
340
+ // Slice dependency junction table (v14)
341
+ db.exec(`
342
+ CREATE TABLE IF NOT EXISTS slice_dependencies (
343
+ milestone_id TEXT NOT NULL,
344
+ slice_id TEXT NOT NULL,
345
+ depends_on_slice_id TEXT NOT NULL,
346
+ PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
347
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
348
+ FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
349
+ )
321
350
  `);
322
351
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
323
352
  db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
353
+ // v13 indexes — hot-path dispatch queries
354
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
355
+ db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
356
+ db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
357
+ db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
358
+ db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
359
+ // v14 index — slice dependency lookups
360
+ db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
324
361
  db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
325
362
  db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
326
363
  db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
@@ -599,6 +636,35 @@ function migrateSchema(db) {
599
636
  ":applied_at": new Date().toISOString(),
600
637
  });
601
638
  }
639
+ if (currentVersion < 13) {
640
+ // Hot-path indexes for auto-loop dispatch queries
641
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
642
+ db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
643
+ db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
644
+ db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
645
+ db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
646
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
647
+ ":version": 13,
648
+ ":applied_at": new Date().toISOString(),
649
+ });
650
+ }
651
+ if (currentVersion < 14) {
652
+ db.exec(`
653
+ CREATE TABLE IF NOT EXISTS slice_dependencies (
654
+ milestone_id TEXT NOT NULL,
655
+ slice_id TEXT NOT NULL,
656
+ depends_on_slice_id TEXT NOT NULL,
657
+ PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
658
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
659
+ FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
660
+ )
661
+ `);
662
+ db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
663
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
664
+ ":version": 14,
665
+ ":applied_at": new Date().toISOString(),
666
+ });
667
+ }
602
668
  db.exec("COMMIT");
603
669
  }
604
670
  catch (err) {
@@ -655,6 +721,11 @@ export function closeDatabase() {
655
721
  currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
656
722
  }
657
723
  catch { /* non-fatal — best effort before close */ }
724
+ try {
725
+ // Incremental vacuum to reclaim space without blocking
726
+ currentDb.exec('PRAGMA incremental_vacuum(64)');
727
+ }
728
+ catch { /* non-fatal */ }
658
729
  try {
659
730
  currentDb.close();
660
731
  }
@@ -666,9 +737,31 @@ export function closeDatabase() {
666
737
  currentPid = 0;
667
738
  }
668
739
  }
740
+ /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
741
+ export function vacuumDatabase() {
742
+ if (!currentDb)
743
+ return;
744
+ try {
745
+ currentDb.exec('VACUUM');
746
+ }
747
+ catch { /* non-fatal */ }
748
+ }
749
+ let _txDepth = 0;
669
750
  export function transaction(fn) {
670
751
  if (!currentDb)
671
752
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
753
+ // Re-entrant: if already inside a transaction, just run fn() without
754
+ // starting a new one. SQLite does not support nested BEGIN/COMMIT.
755
+ if (_txDepth > 0) {
756
+ _txDepth++;
757
+ try {
758
+ return fn();
759
+ }
760
+ finally {
761
+ _txDepth--;
762
+ }
763
+ }
764
+ _txDepth++;
672
765
  currentDb.exec("BEGIN");
673
766
  try {
674
767
  const result = fn();
@@ -679,6 +772,9 @@ export function transaction(fn) {
679
772
  currentDb.exec("ROLLBACK");
680
773
  throw err;
681
774
  }
775
+ finally {
776
+ _txDepth--;
777
+ }
682
778
  }
683
779
  export function insertDecision(d) {
684
780
  if (!currentDb)
@@ -1112,6 +1208,16 @@ export function updateSliceStatus(milestoneId, sliceId, status, completedAt) {
1112
1208
  ":id": sliceId,
1113
1209
  });
1114
1210
  }
1211
+ export function setTaskSummaryMd(milestoneId, sliceId, taskId, md) {
1212
+ if (!currentDb)
1213
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1214
+ currentDb.prepare(`UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId, ":md": md });
1215
+ }
1216
+ export function setSliceSummaryMd(milestoneId, sliceId, summaryMd, uatMd) {
1217
+ if (!currentDb)
1218
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1219
+ currentDb.prepare(`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
1220
+ }
1115
1221
  function rowToTask(row) {
1116
1222
  return {
1117
1223
  milestone_id: row["milestone_id"],
@@ -1216,6 +1322,16 @@ export function getMilestone(id) {
1216
1322
  return null;
1217
1323
  return rowToMilestone(row);
1218
1324
  }
1325
+ /**
1326
+ * Update a milestone's status in the database.
1327
+ * Used by park/unpark to keep the DB in sync with the filesystem marker.
1328
+ * See: https://github.com/gsd-build/gsd-2/issues/2694
1329
+ */
1330
+ export function updateMilestoneStatus(milestoneId, status, completedAt) {
1331
+ if (!currentDb)
1332
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1333
+ currentDb.prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
1334
+ }
1219
1335
  export function getActiveMilestoneFromDb() {
1220
1336
  if (!currentDb)
1221
1337
  return null;
@@ -1227,18 +1343,22 @@ export function getActiveMilestoneFromDb() {
1227
1343
  export function getActiveSliceFromDb(milestoneId) {
1228
1344
  if (!currentDb)
1229
1345
  return null;
1230
- const rows = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid AND status NOT IN ('complete', 'done') ORDER BY sequence, id").all({ ":mid": milestoneId });
1231
- if (rows.length === 0)
1346
+ // Single query: find the first non-complete slice whose dependencies are all satisfied.
1347
+ // Uses json_each() to expand the JSON depends array and checks each dep is complete.
1348
+ const row = currentDb.prepare(`SELECT s.* FROM slices s
1349
+ WHERE s.milestone_id = :mid
1350
+ AND s.status NOT IN ('complete', 'done')
1351
+ AND NOT EXISTS (
1352
+ SELECT 1 FROM json_each(s.depends) AS dep
1353
+ WHERE dep.value NOT IN (
1354
+ SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done')
1355
+ )
1356
+ )
1357
+ ORDER BY s.sequence, s.id
1358
+ LIMIT 1`).get({ ":mid": milestoneId });
1359
+ if (!row)
1232
1360
  return null;
1233
- const completedRows = currentDb.prepare("SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done')").all({ ":mid": milestoneId });
1234
- const completedIds = new Set(completedRows.map((r) => r["id"]));
1235
- for (const row of rows) {
1236
- const slice = rowToSlice(row);
1237
- if (slice.depends.length === 0 || slice.depends.every((d) => completedIds.has(d))) {
1238
- return slice;
1239
- }
1240
- }
1241
- return null;
1361
+ return rowToSlice(row);
1242
1362
  }
1243
1363
  export function getActiveTaskFromDb(milestoneId, sliceId) {
1244
1364
  if (!currentDb)
@@ -1262,6 +1382,60 @@ export function getArtifact(path) {
1262
1382
  return null;
1263
1383
  return rowToArtifact(row);
1264
1384
  }
1385
+ // ─── Lightweight Query Variants (hot-path optimized) ─────────────────────
1386
+ /** Fast milestone status check — avoids deserializing JSON planning fields. */
1387
+ export function getActiveMilestoneIdFromDb() {
1388
+ if (!currentDb)
1389
+ return null;
1390
+ const row = currentDb.prepare("SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'parked') ORDER BY id LIMIT 1").get();
1391
+ if (!row)
1392
+ return null;
1393
+ return { id: row["id"], status: row["status"] };
1394
+ }
1395
+ /** Fast slice status check — avoids deserializing JSON depends/planning fields. */
1396
+ export function getSliceStatusSummary(milestoneId) {
1397
+ if (!currentDb)
1398
+ return [];
1399
+ return currentDb.prepare("SELECT id, status FROM slices WHERE milestone_id = :mid ORDER BY sequence, id").all({ ":mid": milestoneId }).map((r) => ({ id: r["id"], status: r["status"] }));
1400
+ }
1401
+ /** Fast task status check — avoids deserializing JSON arrays and large text fields. */
1402
+ export function getActiveTaskIdFromDb(milestoneId, sliceId) {
1403
+ if (!currentDb)
1404
+ return null;
1405
+ const row = currentDb.prepare("SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1").get({ ":mid": milestoneId, ":sid": sliceId });
1406
+ if (!row)
1407
+ return null;
1408
+ return { id: row["id"], status: row["status"], title: row["title"] };
1409
+ }
1410
+ /** Count tasks by status for a slice — useful for progress reporting without full row load. */
1411
+ export function getSliceTaskCounts(milestoneId, sliceId) {
1412
+ if (!currentDb)
1413
+ return { total: 0, done: 0, pending: 0 };
1414
+ const row = currentDb.prepare(`SELECT
1415
+ COUNT(*) as total,
1416
+ SUM(CASE WHEN status IN ('complete', 'done') THEN 1 ELSE 0 END) as done,
1417
+ SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
1418
+ FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": milestoneId, ":sid": sliceId });
1419
+ if (!row)
1420
+ return { total: 0, done: 0, pending: 0 };
1421
+ return { total: row["total"] ?? 0, done: row["done"] ?? 0, pending: row["pending"] ?? 0 };
1422
+ }
1423
+ // ─── Slice Dependencies (junction table) ─────────────────────────────────
1424
+ /** Sync the slice_dependencies junction table from a slice's JSON depends array. */
1425
+ export function syncSliceDependencies(milestoneId, sliceId, depends) {
1426
+ if (!currentDb)
1427
+ return;
1428
+ currentDb.prepare("DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid").run({ ":mid": milestoneId, ":sid": sliceId });
1429
+ for (const dep of depends) {
1430
+ currentDb.prepare("INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (:mid, :sid, :dep)").run({ ":mid": milestoneId, ":sid": sliceId, ":dep": dep });
1431
+ }
1432
+ }
1433
+ /** Get all slices that depend on a given slice. */
1434
+ export function getDependentSlices(milestoneId, sliceId) {
1435
+ if (!currentDb)
1436
+ return [];
1437
+ return currentDb.prepare("SELECT slice_id FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid").all({ ":mid": milestoneId, ":sid": sliceId }).map((r) => r["slice_id"]);
1438
+ }
1265
1439
  // ─── Worktree DB Helpers ──────────────────────────────────────────────────
1266
1440
  export function copyWorktreeDb(srcDbPath, destDbPath) {
1267
1441
  try {
@@ -1278,10 +1452,13 @@ export function copyWorktreeDb(srcDbPath, destDbPath) {
1278
1452
  }
1279
1453
  }
1280
1454
  export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1281
- const zero = { decisions: 0, requirements: 0, artifacts: 0, conflicts: [] };
1455
+ const zero = { decisions: 0, requirements: 0, artifacts: 0, milestones: 0, slices: 0, tasks: 0, memories: 0, verification_evidence: 0, conflicts: [] };
1282
1456
  if (!existsSync(worktreeDbPath))
1283
1457
  return zero;
1284
- if (worktreeDbPath.includes("'")) {
1458
+ // Sanitize path: reject any characters that could break ATTACH syntax.
1459
+ // ATTACH DATABASE doesn't support parameterized paths in all providers,
1460
+ // so we use strict allowlist validation instead.
1461
+ if (/['";\x00]/.test(worktreeDbPath)) {
1285
1462
  process.stderr.write("gsd-db: worktree DB reconciliation failed: path contains unsafe characters\n");
1286
1463
  return zero;
1287
1464
  }
@@ -1305,17 +1482,19 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1305
1482
  const reqConf = adapter.prepare(`SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`).all();
1306
1483
  for (const row of reqConf)
1307
1484
  conflicts.push(`requirement ${row["id"]}: modified in both`);
1308
- const merged = { decisions: 0, requirements: 0, artifacts: 0 };
1485
+ const merged = { decisions: 0, requirements: 0, artifacts: 0, milestones: 0, slices: 0, tasks: 0, memories: 0, verification_evidence: 0 };
1486
+ function countChanges(result) {
1487
+ return typeof result === "object" && result !== null ? (result.changes ?? 0) : 0;
1488
+ }
1309
1489
  adapter.exec("BEGIN");
1310
1490
  try {
1311
- const dR = adapter.prepare(`
1491
+ merged.decisions = countChanges(adapter.prepare(`
1312
1492
  INSERT OR REPLACE INTO decisions (
1313
1493
  id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by
1314
1494
  )
1315
1495
  SELECT id, when_context, scope, decision, choice, rationale, revisable, ${hasMadeBy ? "made_by" : "'agent'"}, superseded_by FROM wt.decisions
1316
- `).run();
1317
- merged.decisions = typeof dR === "object" && dR !== null ? (dR.changes ?? 0) : 0;
1318
- const rR = adapter.prepare(`
1496
+ `).run());
1497
+ merged.requirements = countChanges(adapter.prepare(`
1319
1498
  INSERT OR REPLACE INTO requirements (
1320
1499
  id, class, status, description, why, source, primary_owner,
1321
1500
  supporting_slices, validation, notes, full_content, superseded_by
@@ -1323,16 +1502,74 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1323
1502
  SELECT id, class, status, description, why, source, primary_owner,
1324
1503
  supporting_slices, validation, notes, full_content, superseded_by
1325
1504
  FROM wt.requirements
1326
- `).run();
1327
- merged.requirements = typeof rR === "object" && rR !== null ? (rR.changes ?? 0) : 0;
1328
- const aR = adapter.prepare(`
1505
+ `).run());
1506
+ merged.artifacts = countChanges(adapter.prepare(`
1329
1507
  INSERT OR REPLACE INTO artifacts (
1330
1508
  path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
1331
1509
  )
1332
1510
  SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
1333
1511
  FROM wt.artifacts
1334
- `).run();
1335
- merged.artifacts = typeof aR === "object" && aR !== null ? (aR.changes ?? 0) : 0;
1512
+ `).run());
1513
+ // Merge milestones worktree may have updated status/planning fields
1514
+ merged.milestones = countChanges(adapter.prepare(`
1515
+ INSERT OR REPLACE INTO milestones (
1516
+ id, title, status, depends_on, created_at, completed_at,
1517
+ vision, success_criteria, key_risks, proof_strategy,
1518
+ verification_contract, verification_integration, verification_operational, verification_uat,
1519
+ definition_of_done, requirement_coverage, boundary_map_markdown
1520
+ )
1521
+ SELECT id, title, status, depends_on, created_at, completed_at,
1522
+ vision, success_criteria, key_risks, proof_strategy,
1523
+ verification_contract, verification_integration, verification_operational, verification_uat,
1524
+ definition_of_done, requirement_coverage, boundary_map_markdown
1525
+ FROM wt.milestones
1526
+ `).run());
1527
+ // Merge slices — preserve worktree progress (status, summaries, planning)
1528
+ merged.slices = countChanges(adapter.prepare(`
1529
+ INSERT OR REPLACE INTO slices (
1530
+ milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
1531
+ full_summary_md, full_uat_md, goal, success_criteria, proof_level,
1532
+ integration_closure, observability_impact, sequence, replan_triggered_at
1533
+ )
1534
+ SELECT milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
1535
+ full_summary_md, full_uat_md, goal, success_criteria, proof_level,
1536
+ integration_closure, observability_impact, sequence, replan_triggered_at
1537
+ FROM wt.slices
1538
+ `).run());
1539
+ // Merge tasks — preserve execution results, status, summaries
1540
+ merged.tasks = countChanges(adapter.prepare(`
1541
+ INSERT OR REPLACE INTO tasks (
1542
+ milestone_id, slice_id, id, title, status, one_liner, narrative,
1543
+ verification_result, duration, completed_at, blocker_discovered,
1544
+ deviations, known_issues, key_files, key_decisions, full_summary_md,
1545
+ description, estimate, files, verify, inputs, expected_output,
1546
+ observability_impact, full_plan_md, sequence
1547
+ )
1548
+ SELECT milestone_id, slice_id, id, title, status, one_liner, narrative,
1549
+ verification_result, duration, completed_at, blocker_discovered,
1550
+ deviations, known_issues, key_files, key_decisions, full_summary_md,
1551
+ description, estimate, files, verify, inputs, expected_output,
1552
+ observability_impact, full_plan_md, sequence
1553
+ FROM wt.tasks
1554
+ `).run());
1555
+ // Merge memories — keep worktree-learned insights
1556
+ merged.memories = countChanges(adapter.prepare(`
1557
+ INSERT OR REPLACE INTO memories (
1558
+ seq, id, category, content, confidence, source_unit_type, source_unit_id,
1559
+ created_at, updated_at, superseded_by, hit_count
1560
+ )
1561
+ SELECT seq, id, category, content, confidence, source_unit_type, source_unit_id,
1562
+ created_at, updated_at, superseded_by, hit_count
1563
+ FROM wt.memories
1564
+ `).run());
1565
+ // Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
1566
+ merged.verification_evidence = countChanges(adapter.prepare(`
1567
+ INSERT OR IGNORE INTO verification_evidence (
1568
+ task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
1569
+ )
1570
+ SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
1571
+ FROM wt.verification_evidence
1572
+ `).run());
1336
1573
  adapter.exec("COMMIT");
1337
1574
  }
1338
1575
  catch (txErr) {
@@ -1388,20 +1625,36 @@ export function insertAssessment(entry) {
1388
1625
  ":created_at": new Date().toISOString(),
1389
1626
  });
1390
1627
  }
1391
- export function deleteTask(milestoneId, sliceId, taskId) {
1628
+ export function deleteAssessmentByScope(milestoneId, scope) {
1629
+ if (!currentDb)
1630
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1631
+ currentDb.prepare(`DELETE FROM assessments WHERE milestone_id = :mid AND scope = :scope`).run({ ":mid": milestoneId, ":scope": scope });
1632
+ }
1633
+ export function deleteVerificationEvidence(milestoneId, sliceId, taskId) {
1392
1634
  if (!currentDb)
1393
1635
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1394
- // Must delete verification_evidence first (FK constraint)
1395
1636
  currentDb.prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1396
- currentDb.prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1637
+ }
1638
+ export function deleteTask(milestoneId, sliceId, taskId) {
1639
+ if (!currentDb)
1640
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1641
+ transaction(() => {
1642
+ // Must delete verification_evidence first (FK constraint)
1643
+ currentDb.prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1644
+ currentDb.prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
1645
+ });
1397
1646
  }
1398
1647
  export function deleteSlice(milestoneId, sliceId) {
1399
1648
  if (!currentDb)
1400
1649
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1401
- // Cascade-style manual deletion: evidence → tasks → slice
1402
- currentDb.prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1403
- currentDb.prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1404
- currentDb.prepare(`DELETE FROM slices WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1650
+ transaction(() => {
1651
+ // Cascade-style manual deletion: evidence tasks dependencies slice
1652
+ currentDb.prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1653
+ currentDb.prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1654
+ currentDb.prepare(`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1655
+ currentDb.prepare(`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1656
+ currentDb.prepare(`DELETE FROM slices WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
1657
+ });
1405
1658
  }
1406
1659
  export function updateSliceFields(milestoneId, sliceId, fields) {
1407
1660
  if (!currentDb)