@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.5457a158

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 (338) hide show
  1. package/dist/cli-style.d.ts +17 -0
  2. package/dist/cli-style.js +28 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/headless-events.d.ts +4 -2
  5. package/dist/headless-events.js +7 -29
  6. package/dist/models-resolver.d.ts +3 -13
  7. package/dist/models-resolver.js +3 -22
  8. package/dist/resource-loader.js +2 -14
  9. package/dist/resources/.managed-resources-content-hash +1 -1
  10. package/dist/resources/extensions/bg-shell/utilities.js +5 -2
  11. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
  13. package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -1
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
  16. package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +12 -14
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  19. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
  20. package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
  21. package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
  22. package/dist/resources/extensions/gsd/auto.js +8 -20
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
  27. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  28. package/dist/resources/extensions/gsd/captures.js +5 -15
  29. package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
  30. package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
  31. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  32. package/dist/resources/extensions/gsd/db/engine.js +755 -0
  33. package/dist/resources/extensions/gsd/db/queries.js +372 -0
  34. package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
  35. package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
  36. package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
  37. package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
  38. package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
  39. package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
  40. package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
  41. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  42. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
  44. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  45. package/dist/resources/extensions/gsd/git-service.js +1 -0
  46. package/dist/resources/extensions/gsd/gitignore.js +3 -0
  47. package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
  48. package/dist/resources/extensions/gsd/guidance.js +98 -0
  49. package/dist/resources/extensions/gsd/guided-flow.js +51 -5
  50. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  51. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  52. package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
  53. package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
  54. package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
  55. package/dist/resources/extensions/gsd/model-router.js +3 -0
  56. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  57. package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
  58. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
  59. package/dist/resources/extensions/gsd/paths.js +37 -24
  60. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  61. package/dist/resources/extensions/gsd/preferences-models.js +12 -46
  62. package/dist/resources/extensions/gsd/preferences.js +14 -0
  63. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  67. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  68. package/dist/resources/extensions/gsd/publication.js +87 -0
  69. package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
  70. package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
  71. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
  72. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
  73. package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -20
  75. package/dist/resources/extensions/gsd/status-guards.js +56 -8
  76. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  77. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  78. package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
  79. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
  80. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  81. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
  82. package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
  83. package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
  84. package/dist/resources/extensions/gsd/undo.js +8 -7
  85. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  86. package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
  87. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  88. package/dist/resources/extensions/gsd/unit-registry.js +350 -0
  89. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  90. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  91. package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
  92. package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
  93. package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
  94. package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
  95. package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
  96. package/dist/resources/extensions/gsd/worktree-root.js +28 -6
  97. package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
  98. package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
  99. package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
  100. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  101. package/dist/web/standalone/.next/BUILD_ID +1 -1
  102. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  103. package/dist/web/standalone/.next/build-manifest.json +2 -2
  104. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  126. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  132. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  134. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/index.html +1 -1
  156. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  157. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  158. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  159. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  160. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  161. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  162. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  163. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  164. package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
  165. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  166. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/worktree-cli.js +3 -6
  171. package/dist/worktree-status-banner.js +7 -11
  172. package/package.json +1 -1
  173. package/packages/cloud-mcp-gateway/package.json +2 -2
  174. package/packages/contracts/dist/workflow.d.ts +4 -0
  175. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  176. package/packages/contracts/dist/workflow.js.map +1 -1
  177. package/packages/contracts/package.json +1 -1
  178. package/packages/daemon/package.json +4 -4
  179. package/packages/gsd-agent-core/package.json +5 -5
  180. package/packages/gsd-agent-modes/package.json +7 -7
  181. package/packages/mcp-server/dist/cli.js +6 -3
  182. package/packages/mcp-server/dist/cli.js.map +1 -1
  183. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  184. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  185. package/packages/mcp-server/dist/workflow-tools.js +46 -21
  186. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  187. package/packages/mcp-server/package.json +3 -3
  188. package/packages/native/package.json +1 -1
  189. package/packages/pi-agent-core/package.json +1 -1
  190. package/packages/pi-ai/dist/models.generated.d.ts +294 -239
  191. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  192. package/packages/pi-ai/dist/models.generated.js +260 -256
  193. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  194. package/packages/pi-ai/package.json +1 -1
  195. package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
  197. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  198. package/packages/pi-coding-agent/package.json +7 -7
  199. package/packages/pi-tui/package.json +2 -2
  200. package/packages/rpc-client/package.json +2 -2
  201. package/pkg/package.json +1 -1
  202. package/src/resources/extensions/bg-shell/utilities.ts +5 -2
  203. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  204. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
  205. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
  206. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  207. package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
  208. package/src/resources/extensions/gsd/auto/phases.ts +10 -1
  209. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
  210. package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
  211. package/src/resources/extensions/gsd/auto-start.ts +12 -15
  212. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  213. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
  214. package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
  215. package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
  216. package/src/resources/extensions/gsd/auto.ts +20 -24
  217. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  218. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
  219. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
  220. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
  221. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  222. package/src/resources/extensions/gsd/captures.ts +5 -16
  223. package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
  224. package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
  225. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  226. package/src/resources/extensions/gsd/db/engine.ts +809 -0
  227. package/src/resources/extensions/gsd/db/queries.ts +453 -0
  228. package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
  229. package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
  230. package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
  231. package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
  232. package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
  233. package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
  234. package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
  235. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  236. package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
  237. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
  238. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  239. package/src/resources/extensions/gsd/git-service.ts +1 -0
  240. package/src/resources/extensions/gsd/gitignore.ts +3 -0
  241. package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
  242. package/src/resources/extensions/gsd/guidance.ts +139 -0
  243. package/src/resources/extensions/gsd/guided-flow.ts +50 -5
  244. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  245. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  246. package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
  247. package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
  248. package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
  249. package/src/resources/extensions/gsd/model-router.ts +3 -0
  250. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  251. package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
  252. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
  253. package/src/resources/extensions/gsd/paths.ts +42 -22
  254. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  255. package/src/resources/extensions/gsd/preferences-models.ts +10 -46
  256. package/src/resources/extensions/gsd/preferences.ts +18 -0
  257. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  258. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  259. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  260. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  261. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  262. package/src/resources/extensions/gsd/publication.ts +122 -0
  263. package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
  264. package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
  265. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
  266. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
  267. package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
  268. package/src/resources/extensions/gsd/state.ts +4 -21
  269. package/src/resources/extensions/gsd/status-guards.ts +59 -8
  270. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  271. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
  272. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
  273. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
  274. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
  275. package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
  276. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  277. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
  278. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  279. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
  280. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
  281. package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
  282. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
  283. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
  284. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  285. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
  286. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  287. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  288. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  289. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  290. package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
  291. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
  292. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  293. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
  294. package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
  295. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
  296. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
  297. package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
  298. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  299. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  300. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  301. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  302. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
  303. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  304. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  305. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  306. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
  307. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
  308. package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
  309. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
  310. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
  311. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
  312. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
  313. package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
  314. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  315. package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
  316. package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
  317. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  318. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
  319. package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
  320. package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
  321. package/src/resources/extensions/gsd/undo.ts +9 -8
  322. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  323. package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
  324. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  325. package/src/resources/extensions/gsd/unit-registry.ts +425 -0
  326. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  327. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  328. package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
  330. package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
  331. package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
  332. package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
  333. package/src/resources/extensions/gsd/worktree-root.ts +29 -6
  334. package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
  335. package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
  336. package/src/resources/skills/gsd-browser/SKILL.md +1 -1
  337. /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
  338. /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
@@ -0,0 +1,755 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: GSD engine — connection ownership, lifecycle, schema/migrations,
3
+ // and transaction primitives for the single-writer layer. The shared handle
4
+ // (currentDb) lives here; writers (db/writers/*) and the Query Module
5
+ // (db/queries.ts) read it through getDb()/getDbOrNull().
6
+ //
7
+ // This file legitimately holds DDL and BEGIN/COMMIT control, so it is
8
+ // allowlisted in tests/single-writer-invariant.test.ts alongside db/writers/.
9
+ import { createRequire } from "node:module";
10
+ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { GSDError, GSD_STALE_STATE } from "../errors.js";
13
+ import { logError, logWarning } from "../workflow-logger.js";
14
+ import { createDbAdapter } from "../db-adapter.js";
15
+ import { createBaseSchemaObjects } from "../db-base-schema.js";
16
+ import { createCoordinationTablesV24 } from "../db-coordination-schema.js";
17
+ import { createDbConnectionCache } from "../db-connection-cache.js";
18
+ import { backupDatabaseBeforeMigration } from "../db-migration-backup.js";
19
+ import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, } from "../db-migration-steps.js";
20
+ import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "../db-memory-fts-schema.js";
21
+ import { createDbOpenState } from "../db-open-state.js";
22
+ import { createRuntimeKvTableV25 } from "../db-runtime-kv-schema.js";
23
+ import { getCurrentSchemaVersion, recordSchemaVersion } from "../db-schema-metadata.js";
24
+ import { createDbTransactionRunner } from "../db-transaction.js";
25
+ import { ensureVerificationEvidenceDedupIndex } from "../db-verification-evidence-schema.js";
26
+ import { BETTER_SQLITE3_PACKAGE, createSqliteProviderLoader, suppressSqliteWarning, } from "../db-provider.js";
27
+ let _gsdRequire;
28
+ function getGsdRequire() {
29
+ if (_gsdRequire !== undefined)
30
+ return _gsdRequire;
31
+ try {
32
+ _gsdRequire = createRequire(import.meta.url);
33
+ }
34
+ catch {
35
+ _gsdRequire = null;
36
+ }
37
+ return _gsdRequire;
38
+ }
39
+ const providerLoader = createSqliteProviderLoader({
40
+ tryRequireNodeSqlite: () => {
41
+ const req = getGsdRequire();
42
+ if (!req)
43
+ throw new Error("unavailable");
44
+ return req("node:sqlite");
45
+ },
46
+ tryRequireBetterSqlite3: () => {
47
+ const req = getGsdRequire();
48
+ if (!req)
49
+ throw new Error("unavailable");
50
+ return req(BETTER_SQLITE3_PACKAGE);
51
+ },
52
+ suppressSqliteWarning,
53
+ nodeVersion: process.versions.node,
54
+ writeStderr: (message) => process.stderr.write(message),
55
+ });
56
+ export const SCHEMA_VERSION = 29;
57
+ function initSchema(db, fileBacked, dbPath) {
58
+ const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
59
+ if (fileBacked)
60
+ db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
61
+ if (fileBacked)
62
+ db.exec("PRAGMA busy_timeout = 5000");
63
+ if (fileBacked)
64
+ db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
65
+ if (fileBacked)
66
+ db.exec("PRAGMA auto_vacuum = INCREMENTAL");
67
+ if (fileBacked)
68
+ db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
69
+ if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin")
70
+ db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
71
+ db.exec("PRAGMA temp_store = MEMORY");
72
+ db.exec("PRAGMA foreign_keys = ON");
73
+ db.exec("BEGIN");
74
+ try {
75
+ createBaseSchemaObjects(db, {
76
+ tryCreateMemoriesFts,
77
+ ensureVerificationEvidenceDedupIndex,
78
+ });
79
+ const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
80
+ if (existing && existing["cnt"] === 0) {
81
+ createCoordinationTablesV24(db);
82
+ createRuntimeKvTableV25(db);
83
+ // Fresh install — all tables are created above with the full current schema,
84
+ // so it is safe to create all migration-specific indexes here. For existing
85
+ // databases these indexes are created inside the individual migration guards
86
+ // in migrateSchema() after the corresponding columns have been added.
87
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
88
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
89
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
90
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
91
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
92
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
93
+ recordSchemaVersion(db, SCHEMA_VERSION);
94
+ }
95
+ db.exec("COMMIT");
96
+ }
97
+ catch (err) {
98
+ db.exec("ROLLBACK");
99
+ throw err;
100
+ }
101
+ migrateSchema(db, dbPath);
102
+ }
103
+ export function _isLikelyWslDrvFsPathForTest(dbPath) {
104
+ if (!dbPath || process.platform !== "linux")
105
+ return false;
106
+ const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
107
+ if (drvFsPathPattern.test(dbPath))
108
+ return true;
109
+ try {
110
+ return drvFsPathPattern.test(realpathSync(dbPath));
111
+ }
112
+ catch {
113
+ return false;
114
+ }
115
+ }
116
+ /**
117
+ * Create the FTS5 virtual table for memories plus the triggers that keep it
118
+ * in sync with the base table. FTS5 may be unavailable on stripped-down
119
+ * SQLite builds — callers should treat failure as non-fatal and fall back
120
+ * to LIKE-based scans in `memory-store.queryMemoriesRanked`.
121
+ */
122
+ export function tryCreateMemoriesFts(db) {
123
+ return tryCreateMemoriesFtsSchema(db, {
124
+ onUnavailable: (message) => logWarning("db", message),
125
+ });
126
+ }
127
+ export function isMemoriesFtsAvailable(db) {
128
+ return isMemoriesFtsAvailableSchema(db);
129
+ }
130
+ function backfillMemoriesFts(db) {
131
+ db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
132
+ }
133
+ function copyQualityGateRowsToRepairedTable(db) {
134
+ db.exec(`
135
+ INSERT OR IGNORE INTO quality_gates_new
136
+ (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
137
+ SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
138
+ FROM quality_gates
139
+ `);
140
+ }
141
+ function migrateSchema(db, dbPath) {
142
+ const currentVersion = getCurrentSchemaVersion(db);
143
+ if (currentVersion >= SCHEMA_VERSION)
144
+ return;
145
+ backupDatabaseBeforeMigration(db, dbPath, currentVersion, {
146
+ existsSync,
147
+ copyFileSync,
148
+ logWarning,
149
+ });
150
+ db.exec("BEGIN");
151
+ try {
152
+ if (currentVersion < 2) {
153
+ applyMigrationV2Artifacts(db);
154
+ recordSchemaVersion(db, 2);
155
+ }
156
+ if (currentVersion < 3) {
157
+ applyMigrationV3Memories(db);
158
+ recordSchemaVersion(db, 3);
159
+ }
160
+ if (currentVersion < 4) {
161
+ applyMigrationV4DecisionMadeBy(db);
162
+ recordSchemaVersion(db, 4);
163
+ }
164
+ if (currentVersion < 5) {
165
+ applyMigrationV5HierarchyTables(db);
166
+ recordSchemaVersion(db, 5);
167
+ }
168
+ if (currentVersion < 6) {
169
+ applyMigrationV6SliceSummaries(db);
170
+ recordSchemaVersion(db, 6);
171
+ }
172
+ if (currentVersion < 7) {
173
+ applyMigrationV7Dependencies(db);
174
+ recordSchemaVersion(db, 7);
175
+ }
176
+ if (currentVersion < 8) {
177
+ applyMigrationV8PlanningFields(db);
178
+ recordSchemaVersion(db, 8);
179
+ }
180
+ if (currentVersion < 9) {
181
+ applyMigrationV9Ordering(db);
182
+ recordSchemaVersion(db, 9);
183
+ }
184
+ if (currentVersion < 10) {
185
+ applyMigrationV10ReplanTrigger(db);
186
+ recordSchemaVersion(db, 10);
187
+ }
188
+ if (currentVersion < 11) {
189
+ applyMigrationV11TaskPlanning(db);
190
+ recordSchemaVersion(db, 11);
191
+ }
192
+ if (currentVersion < 12) {
193
+ // NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
194
+ // expression, which is invalid SQLite syntax and causes startup errors on
195
+ // DBs that migrate through v12. The corrected DDL uses
196
+ // task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
197
+ // were created with the broken DDL are repaired by the v22 migration below.
198
+ applyMigrationV12QualityGates(db);
199
+ recordSchemaVersion(db, 12);
200
+ }
201
+ if (currentVersion < 13) {
202
+ applyMigrationV13HotPathIndexes(db, ensureVerificationEvidenceDedupIndex);
203
+ recordSchemaVersion(db, 13);
204
+ }
205
+ if (currentVersion < 14) {
206
+ applyMigrationV14SliceDependencies(db);
207
+ recordSchemaVersion(db, 14);
208
+ }
209
+ if (currentVersion < 15) {
210
+ applyMigrationV15AuditTables(db);
211
+ recordSchemaVersion(db, 15);
212
+ }
213
+ if (currentVersion < 16) {
214
+ applyMigrationV16EscalationSource(db);
215
+ recordSchemaVersion(db, 16);
216
+ }
217
+ if (currentVersion < 17) {
218
+ applyMigrationV17TaskEscalation(db);
219
+ recordSchemaVersion(db, 17);
220
+ }
221
+ if (currentVersion < 18) {
222
+ applyMigrationV18MemorySources(db);
223
+ recordSchemaVersion(db, 18);
224
+ }
225
+ if (currentVersion < 19) {
226
+ applyMigrationV19MemoryFts(db, {
227
+ tryCreateMemoriesFts,
228
+ isMemoriesFtsAvailable,
229
+ backfillMemoriesFts,
230
+ logWarning,
231
+ });
232
+ recordSchemaVersion(db, 19);
233
+ }
234
+ if (currentVersion < 20) {
235
+ applyMigrationV20MemoryRelations(db);
236
+ recordSchemaVersion(db, 20);
237
+ }
238
+ if (currentVersion < 21) {
239
+ applyMigrationV21StructuredMemories(db);
240
+ recordSchemaVersion(db, 21);
241
+ }
242
+ if (currentVersion < 22) {
243
+ applyMigrationV22QualityGateRepair(db, { copyQualityGateRowsToRepairedTable });
244
+ recordSchemaVersion(db, 22);
245
+ }
246
+ if (currentVersion < 23) {
247
+ applyMigrationV23MilestoneQueue(db);
248
+ recordSchemaVersion(db, 23);
249
+ }
250
+ if (currentVersion < 24) {
251
+ // v24: auto-mode coordination tables. See createCoordinationTablesV24
252
+ // for full schema + invariants. No-op for fresh installs (the same
253
+ // helper runs in the fresh-install path); for upgraded DBs this is
254
+ // the only place these tables get created.
255
+ createCoordinationTablesV24(db);
256
+ recordSchemaVersion(db, 24);
257
+ }
258
+ if (currentVersion < 25) {
259
+ // v25: runtime_kv non-correctness-critical key-value storage. See
260
+ // createRuntimeKvTableV25 for the full schema + invariants.
261
+ createRuntimeKvTableV25(db);
262
+ recordSchemaVersion(db, 25);
263
+ }
264
+ if (currentVersion < 26) {
265
+ applyMigrationV26MilestoneCommitAttributions(db);
266
+ recordSchemaVersion(db, 26);
267
+ }
268
+ if (currentVersion < 27) {
269
+ applyMigrationV27ArtifactHash(db);
270
+ recordSchemaVersion(db, 27);
271
+ }
272
+ if (currentVersion < 28) {
273
+ applyMigrationV28MemoryLastHitAt(db);
274
+ recordSchemaVersion(db, 28);
275
+ }
276
+ if (currentVersion < 29) {
277
+ applyMigrationV29RepositoryTargets(db);
278
+ recordSchemaVersion(db, 29);
279
+ }
280
+ db.exec("COMMIT");
281
+ }
282
+ catch (err) {
283
+ db.exec("ROLLBACK");
284
+ throw err;
285
+ }
286
+ }
287
+ let currentDb = null;
288
+ let currentPath = null;
289
+ let currentPid = 0;
290
+ let _exitHandlerRegistered = false;
291
+ const _dbOpenState = createDbOpenState();
292
+ /**
293
+ * Identity key of the workspace whose connection is currently active
294
+ * (currentDb). Set by openDatabaseByWorkspace(); null when the active
295
+ * connection was opened via the legacy openDatabase(path) path.
296
+ */
297
+ let _currentIdentityKey = null;
298
+ /**
299
+ * Workspace-scoped connection cache.
300
+ * Key: GsdWorkspace.identityKey (realpath-normalized project root).
301
+ * Value: the DB path and open adapter for that workspace.
302
+ *
303
+ * Sibling worktrees of the same project share the same identityKey (set by
304
+ * createWorkspace) and therefore reuse the same cached connection, preserving
305
+ * shared-WAL semantics. Different projects get distinct cache entries.
306
+ *
307
+ * NOTE: Only one connection is "active" at a time (currentDb/currentPath).
308
+ * The cache allows fast re-activation of a previously opened connection when
309
+ * callers switch between known workspaces via openDatabaseByWorkspace().
310
+ */
311
+ const _dbCache = createDbConnectionCache();
312
+ /** Test helper: expose the internal cache for inspection. Not for production use. */
313
+ export function _getDbCache() {
314
+ return _dbCache.asReadonlyMap();
315
+ }
316
+ function closeCachedConnection(entry, source) {
317
+ try {
318
+ entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
319
+ }
320
+ catch (e) {
321
+ if (source === "workspace")
322
+ logWarning("db", `WAL checkpoint (byWorkspace) failed: ${e.message}`);
323
+ }
324
+ try {
325
+ entry.db.exec("PRAGMA incremental_vacuum(64)");
326
+ }
327
+ catch (e) {
328
+ if (source === "workspace")
329
+ logWarning("db", `incremental vacuum (byWorkspace) failed: ${e.message}`);
330
+ }
331
+ try {
332
+ entry.db.close();
333
+ }
334
+ catch (e) {
335
+ if (source === "workspace")
336
+ logWarning("db", `database close (byWorkspace) failed: ${e.message}`);
337
+ }
338
+ }
339
+ /**
340
+ * Close and evict every entry in the workspace connection cache, then call
341
+ * closeDatabase() to close the active connection.
342
+ *
343
+ * Use this for test teardown or process-shutdown paths where every open
344
+ * connection must be flushed. Normal callers should use closeDatabase() or
345
+ * closeDatabaseByWorkspace() instead.
346
+ */
347
+ export function closeAllDatabases() {
348
+ // Close all non-active cached connections first.
349
+ _dbCache.closeNonActive(currentDb, (entry) => closeCachedConnection(entry, "all"));
350
+ closeDatabase();
351
+ }
352
+ /**
353
+ * Open (or reuse) the database connection scoped to the given workspace.
354
+ *
355
+ * Uses workspace.identityKey as the cache key, so sibling worktrees of the
356
+ * same project resolve to the same connection. On a cache hit the existing
357
+ * adapter is reactivated as the current connection without re-opening the
358
+ * file. On a cache miss, delegates to openDatabase() for the full
359
+ * open + schema-init + migration flow, then caches the result.
360
+ *
361
+ * When switching to a different workspace, the previously active connection
362
+ * is preserved in the cache (not closed), so callers can switch back to it
363
+ * cheaply via a subsequent openDatabaseByWorkspace() call.
364
+ *
365
+ * @param workspace A GsdWorkspace created by createWorkspace().
366
+ * @returns true if the connection is open and ready, false otherwise.
367
+ */
368
+ export function openDatabaseByWorkspace(workspace) {
369
+ const key = workspace.identityKey;
370
+ const dbPath = workspace.contract.projectDb;
371
+ const cached = _dbCache.get(key);
372
+ if (cached) {
373
+ // Reactivate the cached connection as the current singleton.
374
+ currentDb = cached.db;
375
+ currentPath = cached.dbPath;
376
+ currentPid = process.pid;
377
+ _dbOpenState.markAttempted();
378
+ _currentIdentityKey = key;
379
+ return true;
380
+ }
381
+ // Cache miss — need to open a new connection.
382
+ //
383
+ // If there is a currently active workspace connection, stash it in the
384
+ // cache under its identity key before calling openDatabase(), because
385
+ // openDatabase() will call closeDatabase() when the path changes (which
386
+ // would destroy the existing adapter). By nulling out currentDb first,
387
+ // we prevent openDatabase() from closing the live adapter.
388
+ let oldDb = null;
389
+ let oldPath = null;
390
+ let oldPid = 0;
391
+ let oldKey = null;
392
+ if (currentDb !== null && _currentIdentityKey !== null) {
393
+ // Snapshot the old globals so we can restore them on failure.
394
+ oldDb = currentDb;
395
+ oldPath = currentPath;
396
+ oldPid = currentPid;
397
+ oldKey = _currentIdentityKey;
398
+ // Save the current connection so it stays alive in the cache.
399
+ _dbCache.set(_currentIdentityKey, {
400
+ dbPath: currentPath,
401
+ db: currentDb,
402
+ });
403
+ // Detach from globals so openDatabase() opens fresh without closing it.
404
+ currentDb = null;
405
+ currentPath = null;
406
+ currentPid = 0;
407
+ _currentIdentityKey = null;
408
+ }
409
+ // Run the full open/schema/migration flow for the new workspace.
410
+ // openDatabase() can throw on corrupt DB or permission error — catch so we
411
+ // can restore the previous connection rather than leaving globals null.
412
+ let opened;
413
+ try {
414
+ opened = openDatabase(dbPath);
415
+ }
416
+ catch (err) {
417
+ // Failed to open the new DB. Restore the previous workspace connection so
418
+ // the caller's workspace remains active (it is still safe in _dbCache).
419
+ if (oldDb !== null) {
420
+ currentDb = oldDb;
421
+ currentPath = oldPath;
422
+ currentPid = oldPid;
423
+ _currentIdentityKey = oldKey;
424
+ }
425
+ throw err;
426
+ }
427
+ if (opened && currentDb) {
428
+ _dbCache.set(key, { dbPath, db: currentDb });
429
+ _currentIdentityKey = key;
430
+ }
431
+ else if (!opened && oldDb !== null) {
432
+ // Restore the previous connection so the caller's workspace remains active.
433
+ // The failed attempt left no live adapter, so the globals stayed null.
434
+ currentDb = oldDb;
435
+ currentPath = oldPath;
436
+ currentPid = oldPid;
437
+ _currentIdentityKey = oldKey;
438
+ }
439
+ return opened;
440
+ }
441
+ /**
442
+ * Open (or reuse) the database connection scoped to the workspace in a
443
+ * MilestoneScope. Thin delegation to openDatabaseByWorkspace().
444
+ */
445
+ export function openDatabaseByScope(scope) {
446
+ return openDatabaseByWorkspace(scope.workspace);
447
+ }
448
+ /**
449
+ * Close the database connection for the given workspace and remove it from
450
+ * the cache. If the workspace's connection is currently active (currentDb),
451
+ * performs a full closeDatabase() including WAL checkpoint. Otherwise only
452
+ * removes the cache entry (the adapter was already replaced by a later open).
453
+ */
454
+ export function closeDatabaseByWorkspace(workspace) {
455
+ const key = workspace.identityKey;
456
+ const cached = _dbCache.get(key);
457
+ if (!cached)
458
+ return;
459
+ _dbCache.delete(key);
460
+ if (currentDb === cached.db) {
461
+ // This workspace's connection is the active one — full close.
462
+ closeDatabase();
463
+ }
464
+ else {
465
+ // Connection was displaced by a later open; close the adapter directly.
466
+ closeCachedConnection(cached, "workspace");
467
+ }
468
+ }
469
+ export function getDbProvider() {
470
+ providerLoader.load();
471
+ return providerLoader.getProviderName();
472
+ }
473
+ export function isDbAvailable() {
474
+ return currentDb !== null;
475
+ }
476
+ /**
477
+ * Returns true if openDatabase() has been called at least once this session.
478
+ * Used to distinguish "DB not yet initialized" from "DB genuinely unavailable"
479
+ * so that early callers (e.g. before_agent_start context injection) don't
480
+ * trigger a false degraded-mode warning.
481
+ */
482
+ export function wasDbOpenAttempted() {
483
+ return _dbOpenState.snapshot().attempted;
484
+ }
485
+ export function getDbStatus() {
486
+ providerLoader.load();
487
+ const openState = _dbOpenState.snapshot();
488
+ return {
489
+ available: currentDb !== null,
490
+ provider: providerLoader.getProviderName(),
491
+ attempted: openState.attempted,
492
+ lastError: openState.lastError,
493
+ lastPhase: openState.lastPhase,
494
+ };
495
+ }
496
+ export function openDatabase(path) {
497
+ _dbOpenState.markAttempted();
498
+ if (currentDb && currentPath !== path)
499
+ closeDatabase();
500
+ if (currentDb && currentPath === path)
501
+ return true;
502
+ // Reset error state only when a new open attempt is actually going to run.
503
+ _dbOpenState.clearError();
504
+ let rawDb;
505
+ let fallbackOpen = null;
506
+ try {
507
+ rawDb = providerLoader.openRaw(path);
508
+ }
509
+ catch (primaryErr) {
510
+ _dbOpenState.recordError("open", primaryErr);
511
+ // node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
512
+ fallbackOpen = providerLoader.tryOpenBetterSqliteFallback(path);
513
+ if (fallbackOpen) {
514
+ rawDb = fallbackOpen.rawDb;
515
+ _dbOpenState.clearError();
516
+ }
517
+ if (!rawDb)
518
+ throw primaryErr;
519
+ }
520
+ if (!rawDb)
521
+ return false;
522
+ const adapter = createDbAdapter(rawDb);
523
+ const fileBacked = path !== ":memory:";
524
+ try {
525
+ initSchema(adapter, fileBacked, path);
526
+ }
527
+ catch (err) {
528
+ // Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
529
+ // Attempt VACUUM recovery before giving up (see #2519).
530
+ if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
531
+ try {
532
+ adapter.exec("VACUUM");
533
+ initSchema(adapter, fileBacked, path);
534
+ process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
535
+ }
536
+ catch (retryErr) {
537
+ _dbOpenState.recordError("vacuum-recovery", retryErr);
538
+ try {
539
+ adapter.close();
540
+ }
541
+ catch (e) {
542
+ logWarning("db", `close after VACUUM failed: ${e.message}`);
543
+ }
544
+ throw retryErr;
545
+ }
546
+ }
547
+ else {
548
+ _dbOpenState.recordError("initSchema", err);
549
+ try {
550
+ adapter.close();
551
+ }
552
+ catch (e) {
553
+ logWarning("db", `close after initSchema failed: ${e.message}`);
554
+ }
555
+ throw err;
556
+ }
557
+ }
558
+ // Commit fallback provider switch only after open + schema both succeeded.
559
+ if (fallbackOpen)
560
+ providerLoader.commitFallback(fallbackOpen);
561
+ currentDb = adapter;
562
+ currentPath = path;
563
+ currentPid = process.pid;
564
+ if (!_exitHandlerRegistered) {
565
+ _exitHandlerRegistered = true;
566
+ process.on("exit", () => { try {
567
+ closeDatabase();
568
+ }
569
+ catch (e) {
570
+ logWarning("db", `exit handler close failed: ${e.message}`);
571
+ } });
572
+ }
573
+ return true;
574
+ }
575
+ export function closeDatabase() {
576
+ if (currentDb) {
577
+ try {
578
+ currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
579
+ }
580
+ catch (e) {
581
+ logWarning("db", `WAL checkpoint failed: ${e.message}`);
582
+ }
583
+ try {
584
+ // Incremental vacuum to reclaim space without blocking
585
+ currentDb.exec('PRAGMA incremental_vacuum(64)');
586
+ }
587
+ catch (e) {
588
+ logWarning("db", `incremental vacuum failed: ${e.message}`);
589
+ }
590
+ try {
591
+ currentDb.close();
592
+ }
593
+ catch (e) {
594
+ logWarning("db", `database close failed: ${e.message}`);
595
+ }
596
+ // If this connection was workspace-tracked, evict it from the cache so
597
+ // subsequent openDatabaseByWorkspace() calls re-open rather than reactivate
598
+ // a closed adapter.
599
+ if (_currentIdentityKey !== null) {
600
+ _dbCache.delete(_currentIdentityKey);
601
+ _currentIdentityKey = null;
602
+ }
603
+ currentDb = null;
604
+ currentPath = null;
605
+ currentPid = 0;
606
+ }
607
+ // Reset session-scoped state unconditionally so stale error info from a
608
+ // failed open doesn't persist into the next open attempt or status check.
609
+ _dbOpenState.reset();
610
+ }
611
+ /**
612
+ * Re-open the active database connection from disk.
613
+ *
614
+ * Auto-mode can observe artifacts written by a workflow server running in a
615
+ * different process before its long-lived singleton has re-synchronized. The
616
+ * recovery path uses this to force the next state derivation to read from the
617
+ * current on-disk database instead of continuing with a possibly stale handle.
618
+ */
619
+ export function refreshOpenDatabaseFromDisk() {
620
+ if (!currentDb || !currentPath)
621
+ return false;
622
+ if (currentPath === ":memory:")
623
+ return false;
624
+ const dbPath = currentPath;
625
+ const identityKey = _currentIdentityKey;
626
+ try {
627
+ closeDatabase();
628
+ const opened = openDatabase(dbPath);
629
+ if (opened && identityKey && currentDb) {
630
+ _dbCache.set(identityKey, { dbPath, db: currentDb });
631
+ _currentIdentityKey = identityKey;
632
+ }
633
+ return opened;
634
+ }
635
+ catch (e) {
636
+ logWarning("db", `database refresh failed: ${e.message}`);
637
+ return false;
638
+ }
639
+ }
640
+ /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
641
+ export function vacuumDatabase() {
642
+ if (!currentDb)
643
+ return;
644
+ try {
645
+ currentDb.exec('VACUUM');
646
+ }
647
+ catch (e) {
648
+ logWarning("db", `VACUUM failed: ${e.message}`);
649
+ }
650
+ }
651
+ /** Flush WAL into gsd.db so `git add .gsd/gsd.db` stages current state — safe while DB is open. */
652
+ export function checkpointDatabase() {
653
+ if (!currentDb)
654
+ return;
655
+ try {
656
+ currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
657
+ }
658
+ catch (e) {
659
+ logWarning("db", `WAL checkpoint failed: ${e.message}`);
660
+ }
661
+ }
662
+ /**
663
+ * Copy the live database file to `.gsd/backups/<label>-<timestamp>.db` so a
664
+ * destructive operation (e.g. recover, which clears the hierarchy tables) is
665
+ * reversible. Checkpoints the WAL first so the snapshot is complete. Returns
666
+ * the backup path, or null if no DB is open or the copy failed.
667
+ */
668
+ export function backupDatabaseSnapshot(label) {
669
+ if (!currentPath)
670
+ return null;
671
+ try {
672
+ checkpointDatabase();
673
+ const backupsDir = join(dirname(currentPath), "backups");
674
+ mkdirSync(backupsDir, { recursive: true });
675
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
676
+ const dest = join(backupsDir, `${label}-${stamp}.db`);
677
+ copyFileSync(currentPath, dest);
678
+ return dest;
679
+ }
680
+ catch (e) {
681
+ logWarning("db", `database snapshot failed: ${e.message}`);
682
+ return null;
683
+ }
684
+ }
685
+ const _transactionRunner = createDbTransactionRunner();
686
+ function createTransactionControls(db) {
687
+ return {
688
+ begin: () => db.exec("BEGIN"),
689
+ beginRead: () => db.exec("BEGIN DEFERRED"),
690
+ commit: () => db.exec("COMMIT"),
691
+ rollback: () => db.exec("ROLLBACK"),
692
+ };
693
+ }
694
+ /**
695
+ * Whether the current call is running inside an active SQLite transaction.
696
+ * Statement-time recovery paths (e.g. VACUUM retry on a malformed memory
697
+ * store) MUST gate on this — SQLite refuses VACUUM inside a transaction
698
+ * and would mask the original error with a secondary "cannot VACUUM" throw.
699
+ */
700
+ export function isInTransaction() {
701
+ return _transactionRunner.isInTransaction();
702
+ }
703
+ export function transaction(fn) {
704
+ if (!currentDb)
705
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
706
+ return _transactionRunner.transaction(createTransactionControls(currentDb), fn);
707
+ }
708
+ /**
709
+ * Wrap a block of reads in a DEFERRED transaction so that all SELECTs observe
710
+ * a consistent snapshot of the DB even if a concurrent writer commits between
711
+ * them. Use this for multi-query read flows (e.g. tool executors that query
712
+ * milestone + slices + counts and want one snapshot). Re-entrant — if already
713
+ * inside a transaction, runs fn() without starting a nested one.
714
+ */
715
+ export function readTransaction(fn) {
716
+ if (!currentDb)
717
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
718
+ return _transactionRunner.readTransaction(createTransactionControls(currentDb), fn, (rollbackErr) => {
719
+ // A failed ROLLBACK after a failed read is a split-brain signal —
720
+ // the transaction is in an indeterminate state. Surface it via the
721
+ // logger instead of swallowing it.
722
+ logError("db", "snapshotState ROLLBACK failed", {
723
+ error: rollbackErr.message,
724
+ });
725
+ });
726
+ }
727
+ export function getDbOwnerPid() {
728
+ return currentPid;
729
+ }
730
+ export function getDbPath() {
731
+ return currentPath;
732
+ }
733
+ export function _getAdapter() {
734
+ return currentDb;
735
+ }
736
+ export function _resetProvider() {
737
+ providerLoader.reset();
738
+ }
739
+ /**
740
+ * The active engine handle, or throw if no database is open. Use in write
741
+ * wrappers — replaces the historical `if (!currentDb) throw ...; currentDb.X`
742
+ * guard with `getDb().X`.
743
+ */
744
+ export function getDb() {
745
+ if (!currentDb)
746
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
747
+ return currentDb;
748
+ }
749
+ /**
750
+ * The active engine handle or null. Use in read wrappers that no-op (return
751
+ * [] / null) when no database is open.
752
+ */
753
+ export function getDbOrNull() {
754
+ return currentDb;
755
+ }