gsd-pi 2.50.0-dev.d210a87 → 2.51.0-dev.7d435fe

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 (302) hide show
  1. package/README.md +4 -4
  2. package/dist/headless-events.d.ts +18 -0
  3. package/dist/headless-events.js +36 -0
  4. package/dist/headless-types.d.ts +28 -0
  5. package/dist/headless-types.js +7 -0
  6. package/dist/headless.d.ts +8 -3
  7. package/dist/headless.js +47 -16
  8. package/dist/help-text.js +16 -5
  9. package/dist/onboarding.js +5 -4
  10. package/dist/remote-questions-config.js +1 -1
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
  13. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
  14. package/dist/resources/extensions/gsd/auto-start.js +2 -0
  15. package/dist/resources/extensions/gsd/auto-timers.js +24 -2
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
  17. package/dist/resources/extensions/gsd/auto-worktree.js +21 -0
  18. package/dist/resources/extensions/gsd/auto.js +4 -2
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +95 -69
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  22. package/dist/resources/extensions/gsd/claude-import.js +60 -9
  23. package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
  24. package/dist/resources/extensions/gsd/commands-config.js +10 -5
  25. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  26. package/dist/resources/extensions/gsd/detection.js +6 -6
  27. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  28. package/dist/resources/extensions/gsd/error-classifier.js +105 -0
  29. package/dist/resources/extensions/gsd/gitignore.js +7 -7
  30. package/dist/resources/extensions/gsd/gsd-db.js +298 -45
  31. package/dist/resources/extensions/gsd/init-wizard.js +2 -2
  32. package/dist/resources/extensions/gsd/key-manager.js +7 -16
  33. package/dist/resources/extensions/gsd/memory-store.js +28 -13
  34. package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
  35. package/dist/resources/extensions/gsd/preferences-models.js +1 -13
  36. package/dist/resources/extensions/gsd/preferences.js +13 -13
  37. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  38. package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
  39. package/dist/resources/extensions/gsd/rule-registry.js +1 -1
  40. package/dist/resources/extensions/gsd/service-tier.js +13 -2
  41. package/dist/resources/extensions/gsd/state.js +21 -2
  42. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -10
  43. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -17
  44. package/dist/resources/extensions/gsd/tools/complete-task.js +7 -18
  45. package/dist/resources/extensions/gsd/tools/plan-milestone.js +26 -17
  46. package/dist/resources/extensions/gsd/tools/plan-slice.js +25 -14
  47. package/dist/resources/extensions/gsd/tools/plan-task.js +21 -11
  48. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +47 -37
  49. package/dist/resources/extensions/gsd/tools/replan-slice.js +49 -38
  50. package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
  51. package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
  52. package/dist/resources/extensions/remote-questions/config.js +1 -1
  53. package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
  54. package/dist/resources/extensions/search-the-web/native-search.js +1 -1
  55. package/dist/resources/extensions/search-the-web/provider.js +1 -1
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +21 -21
  58. package/dist/web/standalone/.next/build-manifest.json +3 -3
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  79. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/experimental/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/index.html +1 -1
  123. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  124. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  126. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  128. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  129. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app-paths-manifest.json +21 -21
  131. package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
  132. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  135. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  136. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  137. package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +9 -0
  138. package/dist/web/standalone/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-024d82be84800e52.js} +1 -1
  139. package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +1 -0
  140. package/dist/wizard.js +4 -1
  141. package/package.json +2 -2
  142. package/packages/pi-ai/dist/models.d.ts +14 -3
  143. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/models.js +53 -10
  145. package/packages/pi-ai/dist/models.js.map +1 -1
  146. package/packages/pi-ai/dist/models.test.js +102 -1
  147. package/packages/pi-ai/dist/models.test.js.map +1 -1
  148. package/packages/pi-ai/dist/types.d.ts +30 -0
  149. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  150. package/packages/pi-ai/dist/types.js.map +1 -1
  151. package/packages/pi-ai/src/models.test.ts +114 -1
  152. package/packages/pi-ai/src/models.ts +70 -13
  153. package/packages/pi-ai/src/types.ts +31 -0
  154. package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
  155. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/cli/args.js +3 -0
  157. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
  160. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
  163. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
  165. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
  167. package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
  170. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  172. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/main.js +5 -3
  176. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
  178. package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
  179. package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
  182. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
  184. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
  186. package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
  188. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
  190. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
  192. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
  193. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
  194. package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
  195. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
  196. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  199. package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
  200. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  201. package/packages/pi-coding-agent/package.json +1 -1
  202. package/packages/pi-coding-agent/src/cli/args.ts +4 -0
  203. package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
  204. package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
  205. package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
  206. package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
  207. package/packages/pi-coding-agent/src/index.ts +3 -0
  208. package/packages/pi-coding-agent/src/main.ts +5 -3
  209. package/packages/pi-coding-agent/src/modes/index.ts +8 -1
  210. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
  211. package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
  212. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
  213. package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
  214. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
  215. package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
  216. package/pkg/package.json +1 -1
  217. package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
  218. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
  219. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
  220. package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
  221. package/src/resources/extensions/gsd/auto-start.ts +2 -0
  222. package/src/resources/extensions/gsd/auto-timers.ts +25 -1
  223. package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
  224. package/src/resources/extensions/gsd/auto-worktree.ts +21 -0
  225. package/src/resources/extensions/gsd/auto.ts +5 -2
  226. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +115 -72
  227. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
  228. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  229. package/src/resources/extensions/gsd/claude-import.ts +58 -9
  230. package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
  231. package/src/resources/extensions/gsd/commands-config.ts +11 -5
  232. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  233. package/src/resources/extensions/gsd/detection.ts +6 -6
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  235. package/src/resources/extensions/gsd/error-classifier.ts +139 -0
  236. package/src/resources/extensions/gsd/gitignore.ts +7 -7
  237. package/src/resources/extensions/gsd/gsd-db.ts +355 -63
  238. package/src/resources/extensions/gsd/init-wizard.ts +2 -2
  239. package/src/resources/extensions/gsd/key-manager.ts +7 -16
  240. package/src/resources/extensions/gsd/memory-store.ts +29 -18
  241. package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
  242. package/src/resources/extensions/gsd/preferences-models.ts +1 -13
  243. package/src/resources/extensions/gsd/preferences.ts +12 -13
  244. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  245. package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
  246. package/src/resources/extensions/gsd/rule-registry.ts +1 -1
  247. package/src/resources/extensions/gsd/service-tier.ts +14 -2
  248. package/src/resources/extensions/gsd/state.ts +22 -2
  249. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
  250. package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
  251. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
  252. package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
  253. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  254. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
  255. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  256. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
  257. package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
  258. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
  259. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
  260. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
  261. package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
  262. package/src/resources/extensions/gsd/tests/git-service.test.ts +1 -1
  263. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
  265. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
  266. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
  267. package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
  268. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  269. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  270. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
  271. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
  272. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
  273. package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
  274. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
  275. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
  276. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
  277. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
  278. package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
  279. package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
  280. package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
  281. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
  282. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
  283. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -14
  284. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -21
  285. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -22
  286. package/src/resources/extensions/gsd/tools/plan-milestone.ts +28 -18
  287. package/src/resources/extensions/gsd/tools/plan-slice.ts +28 -16
  288. package/src/resources/extensions/gsd/tools/plan-task.ts +24 -12
  289. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +54 -42
  290. package/src/resources/extensions/gsd/tools/replan-slice.ts +53 -40
  291. package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
  292. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  293. package/src/resources/extensions/remote-questions/config.ts +1 -1
  294. package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
  295. package/src/resources/extensions/search-the-web/native-search.ts +1 -1
  296. package/src/resources/extensions/search-the-web/provider.ts +1 -1
  297. package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
  298. package/dist/web/standalone/.next/static/css/de141508b083f922.css +0 -1
  299. /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
  300. /package/dist/web/standalone/.next/static/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_buildManifest.js +0 -0
  301. /package/dist/web/standalone/.next/static/{yJIyd5cXPNpmXTv18ZlyC → RqOU-jOv9uZ1Q03P6L6nn}/_ssgManifest.js +0 -0
  302. /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
@@ -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)
@@ -336,9 +336,9 @@ function bootstrapGsdDirectory(basePath, prefs, signals) {
336
336
  assertSafeDirectory(basePath);
337
337
  const gsd = gsdRoot(basePath);
338
338
  mkdirSync(join(gsd, "milestones"), { recursive: true });
339
- // Write preferences.md from wizard answers
339
+ // Write PREFERENCES.md from wizard answers
340
340
  const preferencesContent = buildPreferencesFile(prefs);
341
- writeFileSync(join(gsd, "preferences.md"), preferencesContent, "utf-8");
341
+ writeFileSync(join(gsd, "PREFERENCES.md"), preferencesContent, "utf-8");
342
342
  // Seed CONTEXT.md with detected project signals
343
343
  const contextContent = buildContextSeed(signals);
344
344
  if (contextContent) {
@@ -110,21 +110,12 @@ export function findProvider(idOrLabel) {
110
110
  */
111
111
  export function getAllKeyStatuses(auth) {
112
112
  return PROVIDER_REGISTRY.map((provider) => {
113
- const creds = auth.getCredentialsForProvider(provider.id);
113
+ const rawCreds = auth.getCredentialsForProvider(provider.id);
114
+ // Filter out empty-key entries (left by legacy removeProviderToken or skipped onboarding)
115
+ const creds = rawCreds.filter((c) => !(c.type === "api_key" && !c.key));
114
116
  const envKey = provider.envVar ? process.env[provider.envVar] : undefined;
115
117
  if (creds.length > 0) {
116
118
  const firstCred = creds[0];
117
- // Skip empty keys (from skipped onboarding)
118
- if (firstCred.type === "api_key" && !firstCred.key) {
119
- return {
120
- provider,
121
- configured: false,
122
- source: "none",
123
- credentialCount: 0,
124
- description: "empty key (skipped setup)",
125
- backedOff: false,
126
- };
127
- }
128
119
  const desc = creds.length > 1
129
120
  ? `${creds.length} keys (round-robin)`
130
121
  : describeCredential(firstCred);
@@ -222,7 +213,7 @@ export async function handleAddKey(providerArg, ctx, auth) {
222
213
  else {
223
214
  // Interactive provider picker
224
215
  const options = PROVIDER_REGISTRY.map((p) => {
225
- const creds = auth.getCredentialsForProvider(p.id);
216
+ const creds = auth.getCredentialsForProvider(p.id).filter((c) => !(c.type === "api_key" && !c.key));
226
217
  const existing = creds.length > 0 ? " (configured)" : "";
227
218
  return `[${p.category}] ${p.label}${existing}`;
228
219
  });
@@ -285,7 +276,7 @@ export async function handleRemoveKey(providerArg, ctx, auth) {
285
276
  else {
286
277
  // Show only configured providers
287
278
  const configured = PROVIDER_REGISTRY.filter((p) => {
288
- const creds = auth.getCredentialsForProvider(p.id);
279
+ const creds = auth.getCredentialsForProvider(p.id).filter((c) => !(c.type === "api_key" && !c.key));
289
280
  return creds.length > 0;
290
281
  });
291
282
  if (configured.length === 0) {
@@ -508,7 +499,7 @@ export async function handleRotateKey(providerArg, ctx, auth) {
508
499
  // Show only configured API key providers
509
500
  const configured = PROVIDER_REGISTRY.filter((p) => {
510
501
  const creds = auth.getCredentialsForProvider(p.id);
511
- return creds.some((c) => c.type === "api_key");
502
+ return creds.some((c) => c.type === "api_key" && c.key);
512
503
  });
513
504
  if (configured.length === 0) {
514
505
  ctx.ui.notify("No API keys configured to rotate.", "info");
@@ -646,7 +637,7 @@ export function runKeyDoctor(auth) {
646
637
  if (!envValue)
647
638
  continue;
648
639
  const creds = auth.getCredentialsForProvider(provider.id);
649
- const apiKey = creds.find((c) => c.type === "api_key");
640
+ const apiKey = creds.find((c) => c.type === "api_key" && c.key);
650
641
  if (apiKey?.key && apiKey.key !== envValue) {
651
642
  findings.push({
652
643
  severity: "warning",
@@ -71,6 +71,9 @@ export function getActiveMemoriesRanked(limit = 30) {
71
71
  /**
72
72
  * Generate the next memory ID: MEM + zero-padded 3-digit from MAX(seq).
73
73
  * Returns MEM001 if no memories exist.
74
+ *
75
+ * NOTE: For race-safe creation, prefer createMemory() which inserts with a
76
+ * placeholder ID then updates to the seq-derived ID atomically.
74
77
  */
75
78
  export function nextMemoryId() {
76
79
  if (!isDbAvailable())
@@ -94,7 +97,9 @@ export function nextMemoryId() {
94
97
  }
95
98
  // ─── Mutation Functions ─────────────────────────────────────────────────────
96
99
  /**
97
- * Insert a new memory with auto-assigned ID.
100
+ * Insert a new memory with a race-safe auto-assigned ID.
101
+ * Uses AUTOINCREMENT seq to derive the ID after insert, avoiding
102
+ * the read-then-write race in concurrent scenarios (e.g. worktrees).
98
103
  * Returns the assigned ID, or null on failure.
99
104
  */
100
105
  export function createMemory(fields) {
@@ -104,11 +109,12 @@ export function createMemory(fields) {
104
109
  if (!adapter)
105
110
  return null;
106
111
  try {
107
- const id = nextMemoryId();
108
112
  const now = new Date().toISOString();
113
+ // Insert with a temporary placeholder ID — seq is auto-assigned
114
+ const placeholder = `_TMP_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
109
115
  adapter.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
110
116
  VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`).run({
111
- ':id': id,
117
+ ':id': placeholder,
112
118
  ':category': fields.category,
113
119
  ':content': fields.content,
114
120
  ':confidence': fields.confidence ?? 0.8,
@@ -117,7 +123,17 @@ export function createMemory(fields) {
117
123
  ':created_at': now,
118
124
  ':updated_at': now,
119
125
  });
120
- return id;
126
+ // Derive the real ID from the assigned seq
127
+ const row = adapter.prepare('SELECT seq FROM memories WHERE id = :id').get({ ':id': placeholder });
128
+ if (!row)
129
+ return placeholder; // fallback — should not happen
130
+ const seq = row['seq'];
131
+ const realId = `MEM${String(seq).padStart(3, '0')}`;
132
+ adapter.prepare('UPDATE memories SET id = :real_id WHERE id = :placeholder').run({
133
+ ':real_id': realId,
134
+ ':placeholder': placeholder,
135
+ });
136
+ return realId;
121
137
  }
122
138
  catch {
123
139
  return null;
@@ -258,15 +274,14 @@ export function enforceMemoryCap(max = 50) {
258
274
  if (count <= max)
259
275
  return;
260
276
  const excess = count - max;
261
- // Find the IDs of the lowest-ranked active memories
262
- const rows = adapter.prepare(`SELECT id FROM memories
263
- WHERE superseded_by IS NULL
264
- ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
265
- LIMIT :limit`).all({ ':limit': excess });
266
- const now = new Date().toISOString();
267
- for (const row of rows) {
268
- adapter.prepare('UPDATE memories SET superseded_by = :reason, updated_at = :now WHERE id = :id').run({ ':reason': 'CAP_EXCEEDED', ':now': now, ':id': row['id'] });
269
- }
277
+ // Batch update: supersede lowest-ranked active memories in a single statement
278
+ adapter.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
279
+ WHERE id IN (
280
+ SELECT id FROM memories
281
+ WHERE superseded_by IS NULL
282
+ ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
283
+ LIMIT :limit
284
+ )`).run({ ':now': new Date().toISOString(), ':limit': excess });
270
285
  }
271
286
  catch {
272
287
  // non-fatal