gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0-dev.710c06e97

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 (644) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/github-sync/templates.js +90 -74
  3. package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +53 -0
  5. package/dist/resources/extensions/gsd/auto/loop.js +365 -522
  6. package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
  7. package/dist/resources/extensions/gsd/auto/phases.js +55 -6
  8. package/dist/resources/extensions/gsd/auto/run-unit.js +19 -15
  9. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  10. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.js +12 -0
  11. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-iteration.js +24 -0
  12. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.js +33 -0
  13. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.js +26 -0
  14. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-retry.js +49 -0
  15. package/dist/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.js +25 -0
  16. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +48 -0
  17. package/dist/resources/extensions/gsd/auto/workflow-dispatch-ledger.js +26 -0
  18. package/dist/resources/extensions/gsd/auto/workflow-iteration-completion.js +10 -0
  19. package/dist/resources/extensions/gsd/auto/workflow-journal-reporter.js +16 -0
  20. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +263 -0
  21. package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +36 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-phase-reporter.js +9 -0
  23. package/dist/resources/extensions/gsd/auto/workflow-session-lock.js +35 -0
  24. package/dist/resources/extensions/gsd/auto/workflow-sidecar-iteration.js +24 -0
  25. package/dist/resources/extensions/gsd/auto/workflow-sidecar-queue.js +26 -0
  26. package/dist/resources/extensions/gsd/auto/workflow-turn-reporter.js +36 -0
  27. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +44 -0
  28. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +15 -0
  29. package/dist/resources/extensions/gsd/auto-dashboard.js +54 -15
  30. package/dist/resources/extensions/gsd/auto-prompts.js +168 -3
  31. package/dist/resources/extensions/gsd/auto-recovery.js +72 -55
  32. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  33. package/dist/resources/extensions/gsd/auto-verification.js +2 -11
  34. package/dist/resources/extensions/gsd/auto-worktree.js +87 -38
  35. package/dist/resources/extensions/gsd/auto.js +159 -2
  36. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +24 -2
  37. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
  38. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +22 -6
  39. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  40. package/dist/resources/extensions/gsd/commands-ship.js +23 -46
  41. package/dist/resources/extensions/gsd/commands-workflow-templates.js +12 -7
  42. package/dist/resources/extensions/gsd/component-loader.js +5 -11
  43. package/dist/resources/extensions/gsd/custom-workflow-engine.js +25 -1
  44. package/dist/resources/extensions/gsd/db-adapter.js +47 -0
  45. package/dist/resources/extensions/gsd/db-base-schema.js +337 -0
  46. package/dist/resources/extensions/gsd/db-connection-cache.js +31 -0
  47. package/dist/resources/extensions/gsd/db-coordination-schema.js +104 -0
  48. package/dist/resources/extensions/gsd/db-decision-requirement-rows.js +71 -0
  49. package/dist/resources/extensions/gsd/db-gate-rows.js +16 -0
  50. package/dist/resources/extensions/gsd/db-lightweight-query-rows.js +29 -0
  51. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +56 -0
  52. package/dist/resources/extensions/gsd/db-migration-backup.js +22 -0
  53. package/dist/resources/extensions/gsd/db-migration-steps.js +394 -0
  54. package/dist/resources/extensions/gsd/db-milestone-artifact-rows.js +35 -0
  55. package/dist/resources/extensions/gsd/db-open-state.js +32 -0
  56. package/dist/resources/extensions/gsd/db-provider.js +108 -0
  57. package/dist/resources/extensions/gsd/db-runtime-kv-schema.js +27 -0
  58. package/dist/resources/extensions/gsd/db-schema-metadata.js +23 -0
  59. package/dist/resources/extensions/gsd/db-task-slice-rows.js +86 -0
  60. package/dist/resources/extensions/gsd/db-transaction.js +63 -0
  61. package/dist/resources/extensions/gsd/db-verification-evidence-rows.js +3 -0
  62. package/dist/resources/extensions/gsd/db-verification-evidence-schema.js +19 -0
  63. package/dist/resources/extensions/gsd/escalation.js +2 -0
  64. package/dist/resources/extensions/gsd/graph.js +9 -3
  65. package/dist/resources/extensions/gsd/gsd-db.js +215 -1519
  66. package/dist/resources/extensions/gsd/legacy-telemetry.js +70 -0
  67. package/dist/resources/extensions/gsd/markdown-renderer.js +2 -0
  68. package/dist/resources/extensions/gsd/model-router.js +9 -6
  69. package/dist/resources/extensions/gsd/notification-widget.js +21 -3
  70. package/dist/resources/extensions/gsd/post-execution-checks.js +27 -6
  71. package/dist/resources/extensions/gsd/pr-evidence.js +117 -0
  72. package/dist/resources/extensions/gsd/pre-execution-checks.js +2 -0
  73. package/dist/resources/extensions/gsd/process-task-path.js +61 -0
  74. package/dist/resources/extensions/gsd/prompt-loader.js +9 -5
  75. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  76. package/dist/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  77. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  78. package/dist/resources/extensions/gsd/prompts/discuss.md +81 -181
  79. package/dist/resources/extensions/gsd/prompts/execute-task.md +40 -67
  80. package/dist/resources/extensions/gsd/prompts/forensics.md +41 -84
  81. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  82. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  83. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  84. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  85. package/dist/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  86. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  87. package/dist/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  88. package/dist/resources/extensions/gsd/prompts/queue.md +46 -53
  89. package/dist/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  90. package/dist/resources/extensions/gsd/prompts/research-slice.md +23 -23
  91. package/dist/resources/extensions/gsd/prompts/rethink.md +10 -10
  92. package/dist/resources/extensions/gsd/prompts/system.md +65 -107
  93. package/dist/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  94. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  95. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  96. package/dist/resources/extensions/gsd/state.js +4 -0
  97. package/dist/resources/extensions/gsd/tools/complete-milestone.js +14 -9
  98. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -0
  99. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +6 -1
  100. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  101. package/dist/resources/extensions/gsd/uok/kernel.js +8 -3
  102. package/dist/resources/extensions/gsd/uok/plan-v2.js +2 -0
  103. package/dist/resources/extensions/gsd/workflow-logger.js +13 -13
  104. package/dist/resources/extensions/gsd/workflow-manifest.js +2 -0
  105. package/dist/resources/extensions/gsd/workflow-projections.js +2 -0
  106. package/dist/resources/extensions/gsd/workflow-templates.js +9 -0
  107. package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
  108. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  109. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  110. package/dist/web/standalone/.next/BUILD_ID +1 -1
  111. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  112. package/dist/web/standalone/.next/build-manifest.json +3 -3
  113. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  114. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  115. package/dist/web/standalone/.next/required-server-files.json +1 -1
  116. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  117. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  125. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  134. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  174. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  182. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  186. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  192. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  196. package/dist/web/standalone/.next/server/app/index.html +1 -1
  197. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  198. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  199. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  200. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  201. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  202. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  203. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  204. package/dist/web/standalone/.next/server/chunks/167.js +2 -0
  205. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  206. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  207. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  209. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  210. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  211. package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
  212. package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
  213. package/dist/web/standalone/package.json +1 -0
  214. package/dist/web/standalone/server.js +1 -1
  215. package/package.json +15 -6
  216. package/packages/contracts/dist/index.d.ts +3 -0
  217. package/packages/contracts/dist/index.d.ts.map +1 -0
  218. package/packages/contracts/dist/index.js +5 -0
  219. package/packages/contracts/dist/index.js.map +1 -0
  220. package/packages/contracts/dist/rpc.d.ts +549 -0
  221. package/packages/contracts/dist/rpc.d.ts.map +1 -0
  222. package/packages/contracts/dist/rpc.js +53 -0
  223. package/packages/contracts/dist/rpc.js.map +1 -0
  224. package/packages/contracts/dist/rpc.test.d.ts +2 -0
  225. package/packages/contracts/dist/rpc.test.d.ts.map +1 -0
  226. package/packages/contracts/dist/rpc.test.js +47 -0
  227. package/packages/contracts/dist/rpc.test.js.map +1 -0
  228. package/packages/contracts/dist/workflow.d.ts +180 -0
  229. package/packages/contracts/dist/workflow.d.ts.map +1 -0
  230. package/packages/contracts/dist/workflow.js +201 -0
  231. package/packages/contracts/dist/workflow.js.map +1 -0
  232. package/packages/contracts/package.json +39 -0
  233. package/packages/contracts/src/index.ts +5 -0
  234. package/packages/contracts/src/rpc.test.ts +72 -0
  235. package/packages/contracts/src/rpc.ts +286 -0
  236. package/packages/contracts/src/workflow.ts +213 -0
  237. package/packages/contracts/tsconfig.json +25 -0
  238. package/packages/daemon/package.json +3 -2
  239. package/packages/daemon/src/event-bridge.test.ts +2 -1
  240. package/packages/daemon/src/event-bridge.ts +1 -1
  241. package/packages/daemon/src/event-formatter.test.ts +1 -2
  242. package/packages/daemon/src/event-formatter.ts +1 -2
  243. package/packages/daemon/src/session-manager.ts +2 -2
  244. package/packages/daemon/src/types.ts +3 -18
  245. package/packages/mcp-server/dist/server.d.ts +13 -0
  246. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  247. package/packages/mcp-server/dist/server.js +77 -0
  248. package/packages/mcp-server/dist/server.js.map +1 -1
  249. package/packages/mcp-server/dist/session-manager.js +1 -1
  250. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  251. package/packages/mcp-server/dist/types.d.ts +3 -11
  252. package/packages/mcp-server/dist/types.d.ts.map +1 -1
  253. package/packages/mcp-server/dist/types.js.map +1 -1
  254. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  255. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  256. package/packages/mcp-server/dist/workflow-tools.js +2 -40
  257. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  258. package/packages/mcp-server/package.json +3 -2
  259. package/packages/mcp-server/src/mcp-server.test.ts +138 -0
  260. package/packages/mcp-server/src/server.ts +99 -1
  261. package/packages/mcp-server/src/session-manager.ts +2 -2
  262. package/packages/mcp-server/src/types.ts +7 -18
  263. package/packages/mcp-server/src/workflow-tools.ts +2 -40
  264. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  265. package/packages/native/package.json +1 -1
  266. package/packages/pi-agent-core/package.json +1 -1
  267. package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
  268. package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
  269. package/packages/pi-ai/dist/models/fake-model.js +27 -0
  270. package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
  271. package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
  272. package/packages/pi-ai/dist/models/index.js +8 -0
  273. package/packages/pi-ai/dist/models/index.js.map +1 -1
  274. package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
  275. package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
  276. package/packages/pi-ai/dist/providers/fake.js +319 -0
  277. package/packages/pi-ai/dist/providers/fake.js.map +1 -0
  278. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  279. package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
  280. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  281. package/packages/pi-ai/package.json +1 -1
  282. package/packages/pi-ai/src/models/fake-model.ts +30 -0
  283. package/packages/pi-ai/src/models/index.ts +9 -0
  284. package/packages/pi-ai/src/providers/fake.ts +376 -0
  285. package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
  286. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  287. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  288. package/packages/pi-coding-agent/dist/core/model-registry.js +5 -0
  289. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +4 -0
  291. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  293. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  296. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +22 -0
  298. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  299. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
  305. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
  306. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
  307. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +8 -8
  308. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  309. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
  310. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  311. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
  312. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  313. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +20 -0
  314. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  315. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  316. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  317. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +48 -15
  318. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  319. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  320. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +1 -0
  321. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +40 -1
  323. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
  325. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  327. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  328. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  329. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +12 -1
  330. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  331. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +54 -10
  332. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  334. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
  335. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
  337. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
  338. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
  339. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
  340. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  341. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  342. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
  343. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  344. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
  345. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
  347. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
  349. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
  350. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
  351. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
  352. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
  353. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
  354. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +27 -0
  355. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
  356. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -512
  357. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  358. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js +3 -7
  359. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  360. package/packages/pi-coding-agent/package.json +2 -1
  361. package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
  362. package/packages/pi-coding-agent/src/core/settings-manager.ts +12 -0
  363. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  364. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +37 -2
  365. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
  366. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
  367. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +8 -8
  368. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  369. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +31 -0
  370. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +50 -15
  371. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +43 -1
  372. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +1 -0
  373. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
  374. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
  375. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +75 -9
  376. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
  377. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
  378. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
  379. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
  380. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +40 -0
  381. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
  382. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +3 -336
  383. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  384. package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
  385. package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
  386. package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
  387. package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
  388. package/packages/pi-tui/dist/__tests__/tui.test.js +24 -3
  389. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  390. package/packages/pi-tui/dist/index.d.ts +1 -0
  391. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  392. package/packages/pi-tui/dist/index.js +2 -0
  393. package/packages/pi-tui/dist/index.js.map +1 -1
  394. package/packages/pi-tui/dist/style.d.ts +41 -0
  395. package/packages/pi-tui/dist/style.d.ts.map +1 -0
  396. package/packages/pi-tui/dist/style.js +158 -0
  397. package/packages/pi-tui/dist/style.js.map +1 -0
  398. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  399. package/packages/pi-tui/dist/tui.js +1 -0
  400. package/packages/pi-tui/dist/tui.js.map +1 -1
  401. package/packages/pi-tui/package.json +1 -1
  402. package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
  403. package/packages/pi-tui/src/__tests__/tui.test.ts +29 -3
  404. package/packages/pi-tui/src/index.ts +9 -0
  405. package/packages/pi-tui/src/style.ts +225 -0
  406. package/packages/pi-tui/src/tui.ts +1 -0
  407. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  408. package/packages/rpc-client/README.md +3 -3
  409. package/packages/rpc-client/dist/index.d.ts +1 -1
  410. package/packages/rpc-client/dist/index.d.ts.map +1 -1
  411. package/packages/rpc-client/dist/index.js.map +1 -1
  412. package/packages/rpc-client/dist/rpc-client.d.ts +2 -6
  413. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -1
  414. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  415. package/packages/rpc-client/dist/rpc-types.d.ts +1 -565
  416. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -1
  417. package/packages/rpc-client/dist/rpc-types.js +3 -11
  418. package/packages/rpc-client/dist/rpc-types.js.map +1 -1
  419. package/packages/rpc-client/package.json +4 -1
  420. package/packages/rpc-client/src/index.ts +1 -1
  421. package/packages/rpc-client/src/rpc-client.ts +3 -6
  422. package/packages/rpc-client/src/rpc-types.ts +3 -398
  423. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  424. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
  425. package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  426. package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
  427. package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  428. package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
  429. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  430. package/pkg/dist/modes/interactive/theme/theme.js +18 -1
  431. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  432. package/pkg/package.json +1 -1
  433. package/src/resources/extensions/github-sync/templates.ts +93 -88
  434. package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
  435. package/src/resources/extensions/github-sync/tests/templates.test.ts +10 -2
  436. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  437. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +72 -0
  438. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  439. package/src/resources/extensions/gsd/auto/loop.ts +416 -596
  440. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  441. package/src/resources/extensions/gsd/auto/phases.ts +82 -8
  442. package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
  443. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  444. package/src/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.ts +28 -0
  445. package/src/resources/extensions/gsd/auto/workflow-custom-engine-iteration.ts +52 -0
  446. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.ts +58 -0
  447. package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.ts +71 -0
  448. package/src/resources/extensions/gsd/auto/workflow-custom-engine-retry.ts +90 -0
  449. package/src/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.ts +50 -0
  450. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +97 -0
  451. package/src/resources/extensions/gsd/auto/workflow-dispatch-ledger.ts +45 -0
  452. package/src/resources/extensions/gsd/auto/workflow-iteration-completion.ts +26 -0
  453. package/src/resources/extensions/gsd/auto/workflow-journal-reporter.ts +33 -0
  454. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +520 -0
  455. package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +58 -0
  456. package/src/resources/extensions/gsd/auto/workflow-phase-reporter.ts +22 -0
  457. package/src/resources/extensions/gsd/auto/workflow-session-lock.ts +68 -0
  458. package/src/resources/extensions/gsd/auto/workflow-sidecar-iteration.ts +46 -0
  459. package/src/resources/extensions/gsd/auto/workflow-sidecar-queue.ts +46 -0
  460. package/src/resources/extensions/gsd/auto/workflow-turn-reporter.ts +68 -0
  461. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +89 -0
  462. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +38 -0
  463. package/src/resources/extensions/gsd/auto-dashboard.ts +61 -8
  464. package/src/resources/extensions/gsd/auto-prompts.ts +170 -3
  465. package/src/resources/extensions/gsd/auto-recovery.ts +67 -53
  466. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  467. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  468. package/src/resources/extensions/gsd/auto-worktree.ts +85 -36
  469. package/src/resources/extensions/gsd/auto.ts +167 -1
  470. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +30 -2
  471. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +11 -0
  472. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +30 -6
  473. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  474. package/src/resources/extensions/gsd/commands-ship.ts +24 -51
  475. package/src/resources/extensions/gsd/commands-workflow-templates.ts +13 -0
  476. package/src/resources/extensions/gsd/component-loader.ts +5 -11
  477. package/src/resources/extensions/gsd/custom-workflow-engine.ts +29 -0
  478. package/src/resources/extensions/gsd/db-adapter.ts +75 -0
  479. package/src/resources/extensions/gsd/db-base-schema.ts +368 -0
  480. package/src/resources/extensions/gsd/db-connection-cache.ts +45 -0
  481. package/src/resources/extensions/gsd/db-coordination-schema.ts +109 -0
  482. package/src/resources/extensions/gsd/db-decision-requirement-rows.ts +77 -0
  483. package/src/resources/extensions/gsd/db-gate-rows.ts +19 -0
  484. package/src/resources/extensions/gsd/db-lightweight-query-rows.ts +50 -0
  485. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +66 -0
  486. package/src/resources/extensions/gsd/db-migration-backup.ts +34 -0
  487. package/src/resources/extensions/gsd/db-migration-steps.ts +434 -0
  488. package/src/resources/extensions/gsd/db-milestone-artifact-rows.ts +70 -0
  489. package/src/resources/extensions/gsd/db-open-state.ts +47 -0
  490. package/src/resources/extensions/gsd/db-provider.ts +148 -0
  491. package/src/resources/extensions/gsd/db-runtime-kv-schema.ts +30 -0
  492. package/src/resources/extensions/gsd/db-schema-metadata.ts +33 -0
  493. package/src/resources/extensions/gsd/db-task-slice-rows.ts +146 -0
  494. package/src/resources/extensions/gsd/db-transaction.ts +76 -0
  495. package/src/resources/extensions/gsd/db-verification-evidence-rows.ts +14 -0
  496. package/src/resources/extensions/gsd/db-verification-evidence-schema.ts +22 -0
  497. package/src/resources/extensions/gsd/escalation.ts +3 -1
  498. package/src/resources/extensions/gsd/graph.ts +12 -5
  499. package/src/resources/extensions/gsd/gsd-db.ts +260 -1659
  500. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  501. package/src/resources/extensions/gsd/legacy-telemetry.ts +99 -0
  502. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -1
  503. package/src/resources/extensions/gsd/model-router.ts +10 -6
  504. package/src/resources/extensions/gsd/notification-widget.ts +25 -4
  505. package/src/resources/extensions/gsd/post-execution-checks.ts +35 -7
  506. package/src/resources/extensions/gsd/pr-evidence.ts +182 -0
  507. package/src/resources/extensions/gsd/pre-execution-checks.ts +4 -1
  508. package/src/resources/extensions/gsd/process-task-path.ts +81 -0
  509. package/src/resources/extensions/gsd/prompt-loader.ts +9 -5
  510. package/src/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
  511. package/src/resources/extensions/gsd/prompts/complete-slice.md +23 -34
  512. package/src/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
  513. package/src/resources/extensions/gsd/prompts/discuss.md +81 -181
  514. package/src/resources/extensions/gsd/prompts/execute-task.md +40 -67
  515. package/src/resources/extensions/gsd/prompts/forensics.md +41 -84
  516. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
  517. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
  518. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
  519. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
  520. package/src/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
  521. package/src/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
  522. package/src/resources/extensions/gsd/prompts/plan-slice.md +25 -87
  523. package/src/resources/extensions/gsd/prompts/queue.md +46 -53
  524. package/src/resources/extensions/gsd/prompts/refine-slice.md +23 -23
  525. package/src/resources/extensions/gsd/prompts/research-slice.md +23 -23
  526. package/src/resources/extensions/gsd/prompts/rethink.md +10 -10
  527. package/src/resources/extensions/gsd/prompts/system.md +65 -107
  528. package/src/resources/extensions/gsd/prompts/triage-captures.md +15 -15
  529. package/src/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
  530. package/src/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
  531. package/src/resources/extensions/gsd/state.ts +6 -3
  532. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +38 -0
  533. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +98 -0
  534. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +117 -0
  535. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  536. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +19 -0
  537. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +45 -0
  538. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  539. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  540. package/src/resources/extensions/gsd/tests/commands-eval-review.test.ts +2 -2
  541. package/src/resources/extensions/gsd/tests/commands-ship-eval-warn.test.ts +3 -2
  542. package/src/resources/extensions/gsd/tests/complete-milestone-prompt-rendering.test.ts +47 -0
  543. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +19 -5
  544. package/src/resources/extensions/gsd/tests/component-loader.test.ts +2 -9
  545. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +144 -0
  546. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +139 -0
  547. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +50 -0
  548. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +3 -3
  549. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +82 -0
  550. package/src/resources/extensions/gsd/tests/db-base-schema.test.ts +62 -0
  551. package/src/resources/extensions/gsd/tests/db-connection-cache.test.ts +60 -0
  552. package/src/resources/extensions/gsd/tests/db-coordination-schema.test.ts +39 -0
  553. package/src/resources/extensions/gsd/tests/db-decision-requirement-rows.test.ts +135 -0
  554. package/src/resources/extensions/gsd/tests/db-gate-rows.test.ts +53 -0
  555. package/src/resources/extensions/gsd/tests/db-lightweight-query-rows.test.ts +45 -0
  556. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +86 -0
  557. package/src/resources/extensions/gsd/tests/db-migration-backup.test.ts +105 -0
  558. package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
  559. package/src/resources/extensions/gsd/tests/db-migration-steps.test.ts +159 -0
  560. package/src/resources/extensions/gsd/tests/db-milestone-artifact-rows.test.ts +53 -0
  561. package/src/resources/extensions/gsd/tests/db-open-state.test.ts +56 -0
  562. package/src/resources/extensions/gsd/tests/db-provider.test.ts +105 -0
  563. package/src/resources/extensions/gsd/tests/db-runtime-kv-schema.test.ts +37 -0
  564. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +115 -0
  565. package/src/resources/extensions/gsd/tests/db-task-slice-rows.test.ts +128 -0
  566. package/src/resources/extensions/gsd/tests/db-transaction.test.ts +110 -0
  567. package/src/resources/extensions/gsd/tests/db-verification-evidence-schema.test.ts +76 -0
  568. package/src/resources/extensions/gsd/tests/discuss-headless-rendering.test.ts +37 -0
  569. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +59 -0
  570. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
  571. package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
  572. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
  573. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
  574. package/src/resources/extensions/gsd/tests/forensics-prompt-rendering.test.ts +36 -0
  575. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
  576. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  577. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +43 -0
  578. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +41 -0
  579. package/src/resources/extensions/gsd/tests/guided-discuss-requirements-prompt-rendering.test.ts +45 -0
  580. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  581. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +37 -0
  582. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +5 -3
  583. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  584. package/src/resources/extensions/gsd/tests/legacy-component-format-telemetry.test.ts +62 -0
  585. package/src/resources/extensions/gsd/tests/legacy-telemetry.test.ts +144 -0
  586. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +40 -16
  587. package/src/resources/extensions/gsd/tests/model-router.test.ts +33 -12
  588. package/src/resources/extensions/gsd/tests/notification-store.test.ts +8 -0
  589. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +40 -1
  590. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  591. package/src/resources/extensions/gsd/tests/plan-milestone-rendering.test.ts +45 -0
  592. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +65 -16
  593. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  594. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  595. package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
  596. package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
  597. package/src/resources/extensions/gsd/tests/pr-evidence.test.ts +79 -0
  598. package/src/resources/extensions/gsd/tests/process-task-path.test.ts +51 -0
  599. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -1
  600. package/src/resources/extensions/gsd/tests/queue-prompt-rendering.test.ts +37 -0
  601. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +32 -9
  602. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +6 -6
  603. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +12 -0
  604. package/src/resources/extensions/gsd/tests/workflow-custom-engine-dispatch-outcome.test.ts +55 -0
  605. package/src/resources/extensions/gsd/tests/workflow-custom-engine-iteration.test.ts +93 -0
  606. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile-outcome.test.ts +108 -0
  607. package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile.test.ts +146 -0
  608. package/src/resources/extensions/gsd/tests/workflow-custom-engine-retry.test.ts +136 -0
  609. package/src/resources/extensions/gsd/tests/workflow-custom-engine-verify-outcome.test.ts +95 -0
  610. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +158 -0
  611. package/src/resources/extensions/gsd/tests/workflow-dispatch-ledger.test.ts +82 -0
  612. package/src/resources/extensions/gsd/tests/workflow-iteration-completion.test.ts +44 -0
  613. package/src/resources/extensions/gsd/tests/workflow-journal-reporter.test.ts +49 -0
  614. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +607 -0
  615. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +20 -4
  616. package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +71 -0
  617. package/src/resources/extensions/gsd/tests/workflow-phase-reporter.test.ts +40 -0
  618. package/src/resources/extensions/gsd/tests/workflow-session-lock.test.ts +135 -0
  619. package/src/resources/extensions/gsd/tests/workflow-sidecar-iteration.test.ts +110 -0
  620. package/src/resources/extensions/gsd/tests/workflow-sidecar-queue.test.ts +116 -0
  621. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +21 -0
  622. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +32 -0
  623. package/src/resources/extensions/gsd/tests/workflow-turn-reporter.test.ts +87 -0
  624. package/src/resources/extensions/gsd/tests/workflow-unit-dispatch.test.ts +160 -0
  625. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +123 -0
  626. package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
  627. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +17 -33
  628. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  629. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  630. package/src/resources/extensions/gsd/tools/complete-task.ts +4 -1
  631. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +6 -1
  632. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  633. package/src/resources/extensions/gsd/uok/kernel.ts +10 -3
  634. package/src/resources/extensions/gsd/uok/plan-v2.ts +5 -1
  635. package/src/resources/extensions/gsd/workflow-logger.ts +13 -13
  636. package/src/resources/extensions/gsd/workflow-manifest.ts +6 -15
  637. package/src/resources/extensions/gsd/workflow-projections.ts +5 -1
  638. package/src/resources/extensions/gsd/workflow-templates.ts +11 -0
  639. package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
  640. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  641. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  642. package/dist/web/standalone/.next/server/chunks/6336.js +0 -1
  643. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → CaMwumvGbBPZrJicfsCzV}/_buildManifest.js +0 -0
  644. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → CaMwumvGbBPZrJicfsCzV}/_ssgManifest.js +0 -0
@@ -1,3 +1,5 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: GSD database facade, schema, migrations, and single-writer write API.
1
3
  // GSD Database Abstraction Layer
2
4
  // Provides a SQLite database with provider fallback chain:
3
5
  // node:sqlite (built-in) → better-sqlite3 (npm) → null (unavailable)
@@ -28,316 +30,83 @@ import { GSDError, GSD_STALE_STATE } from "./errors.js";
28
30
  import type { GsdWorkspace, MilestoneScope } from "./workspace.js";
29
31
  import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
30
32
  import { logError, logWarning } from "./workflow-logger.js";
33
+ import { createDbAdapter, type DbAdapter } from "./db-adapter.js";
34
+ import { createBaseSchemaObjects } from "./db-base-schema.js";
35
+ import { createCoordinationTablesV24 } from "./db-coordination-schema.js";
36
+ import { createDbConnectionCache, type DbConnectionCacheEntry } from "./db-connection-cache.js";
37
+ import {
38
+ emptyTaskStatusCounts,
39
+ rowToActiveTaskSummary,
40
+ rowToIdStatusSummary,
41
+ rowToTaskStatusCounts,
42
+ rowsToStringColumn,
43
+ type ActiveTaskSummary,
44
+ type IdStatusSummary,
45
+ type TaskStatusCounts,
46
+ } from "./db-lightweight-query-rows.js";
47
+ import {
48
+ rowToActiveDecision,
49
+ rowToActiveRequirement,
50
+ rowToDecision,
51
+ rowToRequirement,
52
+ rowsToRequirementCounts,
53
+ } from "./db-decision-requirement-rows.js";
54
+ import { rowToGate } from "./db-gate-rows.js";
55
+ import { rowToArtifact, rowToMilestone, type ArtifactRow, type MilestoneRow } from "./db-milestone-artifact-rows.js";
56
+ import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
57
+ import {
58
+ applyMigrationV2Artifacts,
59
+ applyMigrationV3Memories,
60
+ applyMigrationV4DecisionMadeBy,
61
+ applyMigrationV5HierarchyTables,
62
+ applyMigrationV6SliceSummaries,
63
+ applyMigrationV7Dependencies,
64
+ applyMigrationV8PlanningFields,
65
+ applyMigrationV9Ordering,
66
+ applyMigrationV10ReplanTrigger,
67
+ applyMigrationV11TaskPlanning,
68
+ applyMigrationV12QualityGates,
69
+ applyMigrationV13HotPathIndexes,
70
+ applyMigrationV14SliceDependencies,
71
+ applyMigrationV15AuditTables,
72
+ applyMigrationV16EscalationSource,
73
+ applyMigrationV17TaskEscalation,
74
+ applyMigrationV18MemorySources,
75
+ applyMigrationV19MemoryFts,
76
+ applyMigrationV20MemoryRelations,
77
+ applyMigrationV21StructuredMemories,
78
+ applyMigrationV22QualityGateRepair,
79
+ applyMigrationV23MilestoneQueue,
80
+ } from "./db-migration-steps.js";
81
+ import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
82
+ import { createDbOpenState, type DbOpenPhase } from "./db-open-state.js";
83
+ import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
84
+ import { ensureColumn, getCurrentSchemaVersion, recordSchemaVersion } from "./db-schema-metadata.js";
85
+ import { rowToSlice, rowToTask, type SliceRow, type TaskRow } from "./db-task-slice-rows.js";
86
+ import { createDbTransactionRunner } from "./db-transaction.js";
87
+ import { ensureVerificationEvidenceDedupIndex } from "./db-verification-evidence-schema.js";
88
+ import { createSqliteProviderLoader, suppressSqliteWarning, type DbProviderName, type SqliteFallbackOpen } from "./db-provider.js";
31
89
  // Type-only import to avoid a circular runtime dep. The runtime side of
32
90
  // workflow-manifest.ts depends on this file, but the StateManifest type is
33
91
  // pure structure with no runtime coupling.
34
92
  import type { StateManifest } from "./workflow-manifest.js";
35
93
 
36
94
  const _require = createRequire(import.meta.url);
37
- const BETTER_SQLITE3_PACKAGE = ["better", "sqlite3"].join("-");
38
-
39
- interface DbStatement {
40
- run(...params: unknown[]): unknown;
41
- get(...params: unknown[]): Record<string, unknown> | undefined;
42
- all(...params: unknown[]): Record<string, unknown>[];
43
- }
44
-
45
- interface DbAdapter {
46
- exec(sql: string): void;
47
- prepare(sql: string): DbStatement;
48
- close(): void;
49
- }
50
-
51
- type ProviderName = "node:sqlite" | "better-sqlite3";
52
-
53
- let providerName: ProviderName | null = null;
54
- let providerModule: unknown = null;
55
- let loadAttempted = false;
56
-
57
- function suppressSqliteWarning(): void {
58
- const origEmit = process.emit;
59
- // Override via loose cast: Node's overloaded emit signature is not directly assignable.
60
- (process as any).emit = function (event: string, ...args: unknown[]): boolean {
61
- if (
62
- event === "warning" &&
63
- args[0] &&
64
- typeof args[0] === "object" &&
65
- "name" in args[0] &&
66
- (args[0] as { name: string }).name === "ExperimentalWarning" &&
67
- "message" in args[0] &&
68
- typeof (args[0] as { message: string }).message === "string" &&
69
- (args[0] as { message: string }).message.includes("SQLite")
70
- ) {
71
- return false;
72
- }
73
- return origEmit.apply(process, [event, ...args] as Parameters<typeof process.emit>) as unknown as boolean;
74
- };
75
- }
76
-
77
- function loadProvider(): void {
78
- if (loadAttempted) return;
79
- loadAttempted = true;
80
-
81
- try {
82
- suppressSqliteWarning();
83
- const mod = _require("node:sqlite");
84
- if (mod.DatabaseSync) {
85
- providerModule = mod;
86
- providerName = "node:sqlite";
87
- return;
88
- }
89
- } catch {
90
- // unavailable
91
- }
92
-
93
- try {
94
- const mod = _require(BETTER_SQLITE3_PACKAGE);
95
- if (typeof mod === "function" || (mod && mod.default)) {
96
- providerModule = mod.default || mod;
97
- providerName = "better-sqlite3";
98
- return;
99
- }
100
- } catch {
101
- // unavailable
102
- }
103
-
104
- const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
105
- const versionHint = nodeMajor < 22
106
- ? ` GSD requires Node >= 22.0.0 (current: v${process.versions.node}). Upgrade Node to fix this.`
107
- : "";
108
- process.stderr.write(
109
- `gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3).${versionHint}\n`,
110
- );
111
- }
112
-
113
- function normalizeRow(row: unknown): Record<string, unknown> | undefined {
114
- if (row == null) return undefined;
115
- if (Object.getPrototypeOf(row) === null) {
116
- return { ...(row as Record<string, unknown>) };
117
- }
118
- return row as Record<string, unknown>;
119
- }
120
-
121
- function normalizeRows(rows: unknown[]): Record<string, unknown>[] {
122
- return rows.map((r) => normalizeRow(r)!);
123
- }
124
-
125
- function createAdapter(rawDb: unknown): DbAdapter {
126
- const db = rawDb as {
127
- exec(sql: string): void;
128
- prepare(sql: string): {
129
- run(...args: unknown[]): unknown;
130
- get(...args: unknown[]): unknown;
131
- all(...args: unknown[]): unknown[];
132
- };
133
- close(): void;
134
- };
135
-
136
- const stmtCache = new Map<string, DbStatement>();
137
-
138
- function wrapStmt(raw: { run(...a: unknown[]): unknown; get(...a: unknown[]): unknown; all(...a: unknown[]): unknown[] }): DbStatement {
139
- return {
140
- run(...params: unknown[]): unknown {
141
- return raw.run(...params);
142
- },
143
- get(...params: unknown[]): Record<string, unknown> | undefined {
144
- return normalizeRow(raw.get(...params));
145
- },
146
- all(...params: unknown[]): Record<string, unknown>[] {
147
- return normalizeRows(raw.all(...params));
148
- },
149
- };
150
- }
151
-
152
- return {
153
- exec(sql: string): void {
154
- db.exec(sql);
155
- },
156
- prepare(sql: string): DbStatement {
157
- let cached = stmtCache.get(sql);
158
- if (cached) return cached;
159
- cached = wrapStmt(db.prepare(sql));
160
- stmtCache.set(sql, cached);
161
- return cached;
162
- },
163
- close(): void {
164
- stmtCache.clear();
165
- db.close();
166
- },
167
- };
168
- }
169
-
170
- function openRawDb(path: string): unknown {
171
- loadProvider();
172
- if (!providerModule || !providerName) return null;
95
+ type ProviderName = DbProviderName;
173
96
 
174
- if (providerName === "node:sqlite") {
175
- const { DatabaseSync } = providerModule as {
176
- DatabaseSync: new (path: string) => unknown;
177
- };
178
- return new DatabaseSync(path);
179
- }
97
+ export type { ArtifactRow, MilestoneRow } from "./db-milestone-artifact-rows.js";
98
+ export type { ActiveTaskSummary, IdStatusSummary, TaskStatusCounts } from "./db-lightweight-query-rows.js";
99
+ export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
180
100
 
181
- const Database = providerModule as new (path: string) => unknown;
182
- return new Database(path);
183
- }
101
+ const providerLoader = createSqliteProviderLoader({
102
+ requireModule: (id: string) => _require(id),
103
+ suppressSqliteWarning,
104
+ nodeVersion: process.versions.node,
105
+ writeStderr: (message: string) => process.stderr.write(message),
106
+ });
184
107
 
185
108
  export const SCHEMA_VERSION = 25;
186
109
 
187
- function indexExists(db: DbAdapter, name: string): boolean {
188
- return !!db.prepare(
189
- "SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?",
190
- ).get(name);
191
- }
192
-
193
- /**
194
- * Create the v24 coordination tables (workers, milestone_leases,
195
- * unit_dispatches, cancellation_requests, command_queue) and their indexes.
196
- *
197
- * Idempotent — uses IF NOT EXISTS throughout. Called from both the
198
- * fresh-install path and the v24 migration block in migrateSchema().
199
- *
200
- * Single-host invariant: these tables coordinate concurrent auto-mode
201
- * workers via shared SQLite WAL on local disk only. NFS / network
202
- * filesystems break the coordination semantics — multi-host execution
203
- * needs a real coordinator (etcd, Postgres) and is out of scope.
204
- */
205
- function createCoordinationTablesV24(db: DbAdapter): void {
206
- const ddl = [
207
- `CREATE TABLE IF NOT EXISTS workers (
208
- worker_id TEXT PRIMARY KEY,
209
- host TEXT NOT NULL,
210
- pid INTEGER NOT NULL,
211
- started_at TEXT NOT NULL,
212
- version TEXT NOT NULL,
213
- last_heartbeat_at TEXT NOT NULL,
214
- status TEXT NOT NULL,
215
- project_root_realpath TEXT NOT NULL
216
- )`,
217
- `CREATE TABLE IF NOT EXISTS milestone_leases (
218
- milestone_id TEXT PRIMARY KEY,
219
- worker_id TEXT NOT NULL,
220
- fencing_token INTEGER NOT NULL,
221
- acquired_at TEXT NOT NULL,
222
- expires_at TEXT NOT NULL,
223
- status TEXT NOT NULL,
224
- FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
225
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
226
- )`,
227
- `CREATE TABLE IF NOT EXISTS unit_dispatches (
228
- id INTEGER PRIMARY KEY AUTOINCREMENT,
229
- trace_id TEXT NOT NULL,
230
- turn_id TEXT,
231
- worker_id TEXT NOT NULL,
232
- milestone_lease_token INTEGER NOT NULL,
233
- milestone_id TEXT NOT NULL,
234
- slice_id TEXT,
235
- task_id TEXT,
236
- unit_type TEXT NOT NULL,
237
- unit_id TEXT NOT NULL,
238
- status TEXT NOT NULL,
239
- attempt_n INTEGER NOT NULL DEFAULT 1,
240
- started_at TEXT NOT NULL,
241
- ended_at TEXT,
242
- exit_reason TEXT,
243
- error_summary TEXT,
244
- verification_evidence_id INTEGER,
245
- next_run_at TEXT,
246
- retry_after_ms INTEGER,
247
- max_attempts INTEGER NOT NULL DEFAULT 3,
248
- last_error_code TEXT,
249
- last_error_at TEXT,
250
- FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
251
- FOREIGN KEY (verification_evidence_id) REFERENCES verification_evidence(id)
252
- )`,
253
- `CREATE TABLE IF NOT EXISTS cancellation_requests (
254
- id INTEGER PRIMARY KEY AUTOINCREMENT,
255
- requested_at TEXT NOT NULL,
256
- requested_by TEXT NOT NULL,
257
- scope TEXT NOT NULL,
258
- scope_id TEXT NOT NULL,
259
- dispatch_id INTEGER,
260
- reason TEXT NOT NULL,
261
- status TEXT NOT NULL,
262
- acked_at TEXT,
263
- acked_worker_id TEXT,
264
- FOREIGN KEY (dispatch_id) REFERENCES unit_dispatches(id),
265
- FOREIGN KEY (acked_worker_id) REFERENCES workers(worker_id)
266
- )`,
267
- `CREATE TABLE IF NOT EXISTS command_queue (
268
- id INTEGER PRIMARY KEY AUTOINCREMENT,
269
- target_worker TEXT,
270
- command TEXT NOT NULL,
271
- args_json TEXT NOT NULL DEFAULT '{}',
272
- enqueued_at TEXT NOT NULL,
273
- claimed_at TEXT,
274
- claimed_by TEXT,
275
- completed_at TEXT,
276
- result_json TEXT
277
- )`,
278
- ];
279
- for (const stmt of ddl) db.exec(stmt);
280
-
281
- // Indexes — created here so both fresh-install and v24-migration paths
282
- // produce identical structure.
283
- db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_active ON unit_dispatches(milestone_id, status)");
284
- db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_trace ON unit_dispatches(trace_id, turn_id)");
285
- // Partial unique index — prevents two workers from claiming the same
286
- // unit concurrently. Codex review MEDIUM B2: enforces double-claim guard
287
- // at the DB level.
288
- db.exec(
289
- "CREATE UNIQUE INDEX IF NOT EXISTS idx_unit_dispatches_active_per_unit "
290
- + "ON unit_dispatches(unit_id) WHERE status IN ('claimed','running')",
291
- );
292
- // command_queue index — SQLite indexes NULLs in B-trees, so this single
293
- // index serves both targeted (target_worker = ?) and broadcast
294
- // (target_worker IS NULL) queries. Codex review LOW B4 documented.
295
- db.exec("CREATE INDEX IF NOT EXISTS idx_command_queue_pending ON command_queue(target_worker, claimed_at)");
296
- }
297
-
298
- /**
299
- * Create the v25 runtime_kv table. Idempotent — uses IF NOT EXISTS.
300
- *
301
- * STRICT INVARIANT: runtime_kv is NON-CORRECTNESS-CRITICAL. UI cursors,
302
- * dashboard caches, last-seen-version markers, resume cursors, and other
303
- * "soft" state are OK. Anything that drives auto-mode control flow gets
304
- * typed columns in unit_dispatches / workers / milestone_leases — never
305
- * a bag of JSON in runtime_kv.
306
- *
307
- * Scope partitioning: ('global', '', key) for project-wide values;
308
- * ('worker', worker_id, key) for per-worker state (resume cursors);
309
- * ('milestone', milestone_id, key) for per-milestone soft state.
310
- */
311
- function createRuntimeKvTableV25(db: DbAdapter): void {
312
- db.exec(`
313
- CREATE TABLE IF NOT EXISTS runtime_kv (
314
- scope TEXT NOT NULL,
315
- scope_id TEXT NOT NULL DEFAULT '',
316
- key TEXT NOT NULL,
317
- value_json TEXT NOT NULL,
318
- updated_at TEXT NOT NULL,
319
- PRIMARY KEY (scope, scope_id, key)
320
- )
321
- `);
322
- }
323
-
324
- function dedupeVerificationEvidenceRows(db: DbAdapter): void {
325
- db.exec(`
326
- DELETE FROM verification_evidence
327
- WHERE rowid NOT IN (
328
- SELECT MIN(rowid)
329
- FROM verification_evidence
330
- GROUP BY task_id, slice_id, milestone_id, command, verdict
331
- )
332
- `);
333
- }
334
-
335
- function ensureVerificationEvidenceDedupIndex(db: DbAdapter): void {
336
- if (indexExists(db, "idx_verification_evidence_dedup")) return;
337
- dedupeVerificationEvidenceRows(db);
338
- db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
339
- }
340
-
341
110
  function initSchema(db: DbAdapter, fileBacked: boolean): void {
342
111
  if (fileBacked) db.exec("PRAGMA journal_mode=WAL");
343
112
  if (fileBacked) db.exec("PRAGMA busy_timeout = 5000");
@@ -350,370 +119,10 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
350
119
 
351
120
  db.exec("BEGIN");
352
121
  try {
353
- db.exec(`
354
- CREATE TABLE IF NOT EXISTS schema_version (
355
- version INTEGER NOT NULL,
356
- applied_at TEXT NOT NULL
357
- )
358
- `);
359
-
360
- db.exec(`
361
- CREATE TABLE IF NOT EXISTS decisions (
362
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
363
- id TEXT NOT NULL UNIQUE,
364
- when_context TEXT NOT NULL DEFAULT '',
365
- scope TEXT NOT NULL DEFAULT '',
366
- decision TEXT NOT NULL DEFAULT '',
367
- choice TEXT NOT NULL DEFAULT '',
368
- rationale TEXT NOT NULL DEFAULT '',
369
- revisable TEXT NOT NULL DEFAULT '',
370
- made_by TEXT NOT NULL DEFAULT 'agent',
371
- source TEXT NOT NULL DEFAULT 'discussion', -- ADR-011 P2: 'discussion' | 'planning' | 'escalation'
372
- superseded_by TEXT DEFAULT NULL
373
- )
374
- `);
375
-
376
- db.exec(`
377
- CREATE TABLE IF NOT EXISTS requirements (
378
- id TEXT PRIMARY KEY,
379
- class TEXT NOT NULL DEFAULT '',
380
- status TEXT NOT NULL DEFAULT '',
381
- description TEXT NOT NULL DEFAULT '',
382
- why TEXT NOT NULL DEFAULT '',
383
- source TEXT NOT NULL DEFAULT '',
384
- primary_owner TEXT NOT NULL DEFAULT '',
385
- supporting_slices TEXT NOT NULL DEFAULT '',
386
- validation TEXT NOT NULL DEFAULT '',
387
- notes TEXT NOT NULL DEFAULT '',
388
- full_content TEXT NOT NULL DEFAULT '',
389
- superseded_by TEXT DEFAULT NULL
390
- )
391
- `);
392
-
393
- db.exec(`
394
- CREATE TABLE IF NOT EXISTS artifacts (
395
- path TEXT PRIMARY KEY,
396
- artifact_type TEXT NOT NULL DEFAULT '',
397
- milestone_id TEXT DEFAULT NULL,
398
- slice_id TEXT DEFAULT NULL,
399
- task_id TEXT DEFAULT NULL,
400
- full_content TEXT NOT NULL DEFAULT '',
401
- imported_at TEXT NOT NULL DEFAULT ''
402
- )
403
- `);
404
-
405
- db.exec(`
406
- CREATE TABLE IF NOT EXISTS memories (
407
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
408
- id TEXT NOT NULL UNIQUE,
409
- category TEXT NOT NULL,
410
- content TEXT NOT NULL,
411
- confidence REAL NOT NULL DEFAULT 0.8,
412
- source_unit_type TEXT,
413
- source_unit_id TEXT,
414
- created_at TEXT NOT NULL,
415
- updated_at TEXT NOT NULL,
416
- superseded_by TEXT DEFAULT NULL,
417
- hit_count INTEGER NOT NULL DEFAULT 0,
418
- scope TEXT NOT NULL DEFAULT 'project',
419
- tags TEXT NOT NULL DEFAULT '[]',
420
- structured_fields TEXT DEFAULT NULL
421
- )
422
- `);
423
-
424
- db.exec(`
425
- CREATE TABLE IF NOT EXISTS memory_processed_units (
426
- unit_key TEXT PRIMARY KEY,
427
- activity_file TEXT,
428
- processed_at TEXT NOT NULL
429
- )
430
- `);
431
-
432
- db.exec(`
433
- CREATE TABLE IF NOT EXISTS memory_sources (
434
- id TEXT PRIMARY KEY,
435
- kind TEXT NOT NULL,
436
- uri TEXT,
437
- title TEXT,
438
- content TEXT NOT NULL,
439
- content_hash TEXT NOT NULL UNIQUE,
440
- imported_at TEXT NOT NULL,
441
- scope TEXT NOT NULL DEFAULT 'project',
442
- tags TEXT NOT NULL DEFAULT '[]'
443
- )
444
- `);
445
-
446
- db.exec(`
447
- CREATE TABLE IF NOT EXISTS memory_embeddings (
448
- memory_id TEXT PRIMARY KEY,
449
- model TEXT NOT NULL,
450
- dim INTEGER NOT NULL,
451
- vector BLOB NOT NULL,
452
- updated_at TEXT NOT NULL
453
- )
454
- `);
455
-
456
- db.exec(`
457
- CREATE TABLE IF NOT EXISTS memory_relations (
458
- from_id TEXT NOT NULL,
459
- to_id TEXT NOT NULL,
460
- rel TEXT NOT NULL,
461
- confidence REAL NOT NULL DEFAULT 0.8,
462
- created_at TEXT NOT NULL,
463
- PRIMARY KEY (from_id, to_id, rel)
464
- )
465
- `);
466
-
467
- // FTS5 virtual table mirroring memories.content for fast keyword search.
468
- // Optional — if the SQLite build lacks FTS5, we fall back to LIKE scans.
469
- tryCreateMemoriesFts(db);
470
-
471
- db.exec(`
472
- CREATE TABLE IF NOT EXISTS milestones (
473
- id TEXT PRIMARY KEY,
474
- title TEXT NOT NULL DEFAULT '',
475
- status TEXT NOT NULL DEFAULT 'active',
476
- depends_on TEXT NOT NULL DEFAULT '[]',
477
- created_at TEXT NOT NULL DEFAULT '',
478
- completed_at TEXT DEFAULT NULL,
479
- vision TEXT NOT NULL DEFAULT '',
480
- success_criteria TEXT NOT NULL DEFAULT '[]',
481
- key_risks TEXT NOT NULL DEFAULT '[]',
482
- proof_strategy TEXT NOT NULL DEFAULT '[]',
483
- verification_contract TEXT NOT NULL DEFAULT '',
484
- verification_integration TEXT NOT NULL DEFAULT '',
485
- verification_operational TEXT NOT NULL DEFAULT '',
486
- verification_uat TEXT NOT NULL DEFAULT '',
487
- definition_of_done TEXT NOT NULL DEFAULT '[]',
488
- requirement_coverage TEXT NOT NULL DEFAULT '',
489
- boundary_map_markdown TEXT NOT NULL DEFAULT '',
490
- sequence INTEGER DEFAULT 0
491
- )
492
- `);
493
-
494
- db.exec(`
495
- CREATE TABLE IF NOT EXISTS slices (
496
- milestone_id TEXT NOT NULL,
497
- id TEXT NOT NULL,
498
- title TEXT NOT NULL DEFAULT '',
499
- status TEXT NOT NULL DEFAULT 'pending',
500
- risk TEXT NOT NULL DEFAULT 'medium',
501
- depends TEXT NOT NULL DEFAULT '[]',
502
- demo TEXT NOT NULL DEFAULT '',
503
- created_at TEXT NOT NULL DEFAULT '',
504
- completed_at TEXT DEFAULT NULL,
505
- full_summary_md TEXT NOT NULL DEFAULT '',
506
- full_uat_md TEXT NOT NULL DEFAULT '',
507
- goal TEXT NOT NULL DEFAULT '',
508
- success_criteria TEXT NOT NULL DEFAULT '',
509
- proof_level TEXT NOT NULL DEFAULT '',
510
- integration_closure TEXT NOT NULL DEFAULT '',
511
- observability_impact TEXT NOT NULL DEFAULT '',
512
- sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
513
- replan_triggered_at TEXT DEFAULT NULL,
514
- is_sketch INTEGER NOT NULL DEFAULT 0, -- ADR-011: 1 = slice is a sketch awaiting refinement
515
- sketch_scope TEXT NOT NULL DEFAULT '', -- ADR-011: 2-3 sentence rough scope from plan-milestone
516
- PRIMARY KEY (milestone_id, id),
517
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
518
- )
519
- `);
520
-
521
- db.exec(`
522
- CREATE TABLE IF NOT EXISTS tasks (
523
- milestone_id TEXT NOT NULL,
524
- slice_id TEXT NOT NULL,
525
- id TEXT NOT NULL,
526
- title TEXT NOT NULL DEFAULT '',
527
- status TEXT NOT NULL DEFAULT 'pending',
528
- one_liner TEXT NOT NULL DEFAULT '',
529
- narrative TEXT NOT NULL DEFAULT '',
530
- verification_result TEXT NOT NULL DEFAULT '',
531
- duration TEXT NOT NULL DEFAULT '',
532
- completed_at TEXT DEFAULT NULL,
533
- blocker_discovered INTEGER DEFAULT 0,
534
- blocker_source TEXT NOT NULL DEFAULT '', -- ADR-011 P2: provenance for blocker_discovered (e.g. 'reject-escalation')
535
- escalation_pending INTEGER NOT NULL DEFAULT 0, -- ADR-011 P2: pause-on-escalation flag
536
- escalation_awaiting_review INTEGER NOT NULL DEFAULT 0, -- ADR-011 P2: artifact exists but continueWithDefault=true (no pause)
537
- escalation_artifact_path TEXT DEFAULT NULL, -- ADR-011 P2: path to T##-ESCALATION.json
538
- escalation_override_applied_at TEXT DEFAULT NULL, -- ADR-011 P2: DB claim lock for idempotent override injection
539
- deviations TEXT NOT NULL DEFAULT '',
540
- known_issues TEXT NOT NULL DEFAULT '',
541
- key_files TEXT NOT NULL DEFAULT '[]',
542
- key_decisions TEXT NOT NULL DEFAULT '[]',
543
- full_summary_md TEXT NOT NULL DEFAULT '',
544
- description TEXT NOT NULL DEFAULT '',
545
- estimate TEXT NOT NULL DEFAULT '',
546
- files TEXT NOT NULL DEFAULT '[]',
547
- verify TEXT NOT NULL DEFAULT '',
548
- inputs TEXT NOT NULL DEFAULT '[]',
549
- expected_output TEXT NOT NULL DEFAULT '[]',
550
- observability_impact TEXT NOT NULL DEFAULT '',
551
- full_plan_md TEXT NOT NULL DEFAULT '',
552
- sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
553
- PRIMARY KEY (milestone_id, slice_id, id),
554
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
555
- )
556
- `);
557
-
558
- db.exec(`
559
- CREATE TABLE IF NOT EXISTS verification_evidence (
560
- id INTEGER PRIMARY KEY AUTOINCREMENT,
561
- task_id TEXT NOT NULL DEFAULT '',
562
- slice_id TEXT NOT NULL DEFAULT '',
563
- milestone_id TEXT NOT NULL DEFAULT '',
564
- command TEXT NOT NULL DEFAULT '',
565
- exit_code INTEGER DEFAULT 0,
566
- verdict TEXT NOT NULL DEFAULT '',
567
- duration_ms INTEGER DEFAULT 0,
568
- created_at TEXT NOT NULL DEFAULT '',
569
- FOREIGN KEY (milestone_id, slice_id, task_id) REFERENCES tasks(milestone_id, slice_id, id)
570
- )
571
- `);
572
-
573
- db.exec(`
574
- CREATE TABLE IF NOT EXISTS replan_history (
575
- id INTEGER PRIMARY KEY AUTOINCREMENT,
576
- milestone_id TEXT NOT NULL DEFAULT '',
577
- slice_id TEXT DEFAULT NULL,
578
- task_id TEXT DEFAULT NULL,
579
- summary TEXT NOT NULL DEFAULT '',
580
- previous_artifact_path TEXT DEFAULT NULL,
581
- replacement_artifact_path TEXT DEFAULT NULL,
582
- created_at TEXT NOT NULL DEFAULT '',
583
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
584
- )
585
- `);
586
-
587
- db.exec(`
588
- CREATE TABLE IF NOT EXISTS assessments (
589
- path TEXT PRIMARY KEY,
590
- milestone_id TEXT NOT NULL DEFAULT '',
591
- slice_id TEXT DEFAULT NULL,
592
- task_id TEXT DEFAULT NULL,
593
- status TEXT NOT NULL DEFAULT '',
594
- scope TEXT NOT NULL DEFAULT '',
595
- full_content TEXT NOT NULL DEFAULT '',
596
- created_at TEXT NOT NULL DEFAULT '',
597
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
598
- )
599
- `);
600
-
601
- db.exec(`
602
- CREATE TABLE IF NOT EXISTS quality_gates (
603
- milestone_id TEXT NOT NULL,
604
- slice_id TEXT NOT NULL,
605
- gate_id TEXT NOT NULL,
606
- scope TEXT NOT NULL DEFAULT 'slice',
607
- task_id TEXT NOT NULL DEFAULT '',
608
- status TEXT NOT NULL DEFAULT 'pending',
609
- verdict TEXT NOT NULL DEFAULT '',
610
- rationale TEXT NOT NULL DEFAULT '',
611
- findings TEXT NOT NULL DEFAULT '',
612
- evaluated_at TEXT DEFAULT NULL,
613
- PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
614
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
615
- )
616
- `);
617
-
618
- // Slice dependency junction table (v14)
619
- db.exec(`
620
- CREATE TABLE IF NOT EXISTS slice_dependencies (
621
- milestone_id TEXT NOT NULL,
622
- slice_id TEXT NOT NULL,
623
- depends_on_slice_id TEXT NOT NULL,
624
- PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
625
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
626
- FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
627
- )
628
- `);
629
-
630
- db.exec(`
631
- CREATE TABLE IF NOT EXISTS gate_runs (
632
- id INTEGER PRIMARY KEY AUTOINCREMENT,
633
- trace_id TEXT NOT NULL,
634
- turn_id TEXT NOT NULL,
635
- gate_id TEXT NOT NULL,
636
- gate_type TEXT NOT NULL DEFAULT '',
637
- unit_type TEXT DEFAULT NULL,
638
- unit_id TEXT DEFAULT NULL,
639
- milestone_id TEXT DEFAULT NULL,
640
- slice_id TEXT DEFAULT NULL,
641
- task_id TEXT DEFAULT NULL,
642
- outcome TEXT NOT NULL DEFAULT 'pass',
643
- failure_class TEXT NOT NULL DEFAULT 'none',
644
- rationale TEXT NOT NULL DEFAULT '',
645
- findings TEXT NOT NULL DEFAULT '',
646
- attempt INTEGER NOT NULL DEFAULT 1,
647
- max_attempts INTEGER NOT NULL DEFAULT 1,
648
- retryable INTEGER NOT NULL DEFAULT 0,
649
- evaluated_at TEXT NOT NULL DEFAULT ''
650
- )
651
- `);
652
-
653
- db.exec(`
654
- CREATE TABLE IF NOT EXISTS turn_git_transactions (
655
- trace_id TEXT NOT NULL,
656
- turn_id TEXT NOT NULL,
657
- unit_type TEXT DEFAULT NULL,
658
- unit_id TEXT DEFAULT NULL,
659
- stage TEXT NOT NULL DEFAULT 'turn-start',
660
- action TEXT NOT NULL DEFAULT 'status-only',
661
- push INTEGER NOT NULL DEFAULT 0,
662
- status TEXT NOT NULL DEFAULT 'ok',
663
- error TEXT DEFAULT NULL,
664
- metadata_json TEXT NOT NULL DEFAULT '{}',
665
- updated_at TEXT NOT NULL DEFAULT '',
666
- PRIMARY KEY (trace_id, turn_id, stage)
667
- )
668
- `);
669
-
670
- db.exec(`
671
- CREATE TABLE IF NOT EXISTS audit_events (
672
- event_id TEXT PRIMARY KEY,
673
- trace_id TEXT NOT NULL,
674
- turn_id TEXT DEFAULT NULL,
675
- caused_by TEXT DEFAULT NULL,
676
- category TEXT NOT NULL,
677
- type TEXT NOT NULL,
678
- ts TEXT NOT NULL,
679
- payload_json TEXT NOT NULL DEFAULT '{}'
680
- )
681
- `);
682
-
683
- db.exec(`
684
- CREATE TABLE IF NOT EXISTS audit_turn_index (
685
- trace_id TEXT NOT NULL,
686
- turn_id TEXT NOT NULL,
687
- first_ts TEXT NOT NULL,
688
- last_ts TEXT NOT NULL,
689
- event_count INTEGER NOT NULL DEFAULT 0,
690
- PRIMARY KEY (trace_id, turn_id)
691
- )
692
- `);
693
-
694
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
695
-
696
- db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
697
-
698
- // v13 indexes — hot-path dispatch queries
699
- db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
700
- db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
701
- db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
702
- db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
703
- db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
704
- ensureVerificationEvidenceDedupIndex(db);
705
-
706
- // v14 index — slice dependency lookups
707
- db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
708
- db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
709
- db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
710
- db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
711
- db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
712
- db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
713
-
714
- db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
715
- db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
716
- db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
122
+ createBaseSchemaObjects(db, {
123
+ tryCreateMemoriesFts,
124
+ ensureVerificationEvidenceDedupIndex,
125
+ });
717
126
 
718
127
  const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
719
128
  if (existing && (existing["cnt"] as number) === 0) {
@@ -731,12 +140,7 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
731
140
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
732
141
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
733
142
 
734
- db.prepare(
735
- "INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
736
- ).run({
737
- ":version": SCHEMA_VERSION,
738
- ":applied_at": new Date().toISOString(),
739
- });
143
+ recordSchemaVersion(db, SCHEMA_VERSION);
740
144
  }
741
145
 
742
146
  db.exec("COMMIT");
@@ -748,11 +152,6 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
748
152
  migrateSchema(db);
749
153
  }
750
154
 
751
- function columnExists(db: DbAdapter, table: string, column: string): boolean {
752
- const rows = db.prepare(`PRAGMA table_info(${table})`).all();
753
- return rows.some((row) => row["name"] === column);
754
- }
755
-
756
155
  /**
757
156
  * Create the FTS5 virtual table for memories plus the triggers that keep it
758
157
  * in sync with the base table. FTS5 may be unavailable on stripped-down
@@ -760,317 +159,88 @@ function columnExists(db: DbAdapter, table: string, column: string): boolean {
760
159
  * to LIKE-based scans in `memory-store.queryMemoriesRanked`.
761
160
  */
762
161
  export function tryCreateMemoriesFts(db: DbAdapter): boolean {
763
- try {
764
- db.exec(`
765
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
766
- USING fts5(content, content='memories', content_rowid='seq', tokenize='porter unicode61')
767
- `);
768
- // Triggers mirror inserts / updates / deletes on the base memories table.
769
- db.exec(`
770
- CREATE TRIGGER IF NOT EXISTS memories_ai
771
- AFTER INSERT ON memories BEGIN
772
- INSERT INTO memories_fts(rowid, content) VALUES (new.seq, new.content);
773
- END
774
- `);
775
- db.exec(`
776
- CREATE TRIGGER IF NOT EXISTS memories_ad
777
- AFTER DELETE ON memories BEGIN
778
- INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.seq, old.content);
779
- END
780
- `);
781
- db.exec(`
782
- CREATE TRIGGER IF NOT EXISTS memories_au
783
- AFTER UPDATE OF content ON memories BEGIN
784
- INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.seq, old.content);
785
- INSERT INTO memories_fts(rowid, content) VALUES (new.seq, new.content);
786
- END
787
- `);
788
- return true;
789
- } catch (err) {
790
- logWarning("db", `FTS5 unavailable — memory queries will use LIKE fallback: ${(err as Error).message}`);
791
- return false;
792
- }
162
+ return tryCreateMemoriesFtsSchema(db, {
163
+ onUnavailable: (message) => logWarning("db", message),
164
+ });
793
165
  }
794
166
 
795
167
  export function isMemoriesFtsAvailable(db: DbAdapter): boolean {
796
- try {
797
- const row = db
798
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'")
799
- .get();
800
- return !!row;
801
- } catch {
802
- return false;
803
- }
168
+ return isMemoriesFtsAvailableSchema(db);
804
169
  }
805
170
 
806
- function ensureColumn(db: DbAdapter, table: string, column: string, ddl: string): void {
807
- if (!columnExists(db, table, column)) db.exec(ddl);
171
+ function backfillMemoriesFts(db: DbAdapter): void {
172
+ db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
173
+ }
174
+
175
+ function copyQualityGateRowsToRepairedTable(db: DbAdapter): void {
176
+ db.exec(`
177
+ INSERT OR IGNORE INTO quality_gates_new
178
+ (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
179
+ SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
180
+ FROM quality_gates
181
+ `);
808
182
  }
809
183
 
810
184
  function migrateSchema(db: DbAdapter): void {
811
- const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get();
812
- const currentVersion = row ? (row["v"] as number) : 0;
185
+ const currentVersion = getCurrentSchemaVersion(db);
813
186
  if (currentVersion >= SCHEMA_VERSION) return;
814
187
 
815
- // Backup database before migration so a mid-migration crash doesn't
816
- // leave a partially-migrated DB with no recovery path.
817
- // WAL-safe: checkpoint first to flush WAL into the main DB file, then copy.
818
- if (currentPath && currentPath !== ":memory:" && existsSync(currentPath)) {
819
- try {
820
- const backupPath = `${currentPath}.backup-v${currentVersion}`;
821
- if (!existsSync(backupPath)) {
822
- // Flush WAL to main DB file before copying — without this, the backup
823
- // may be missing committed data that only exists in the -wal file.
824
- try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* checkpoint is best-effort */ }
825
- copyFileSync(currentPath, backupPath);
826
- }
827
- } catch (backupErr) {
828
- // Log but proceed — blocking migration leaves the DB stuck at an old
829
- // schema version permanently on read-only or full filesystems.
830
- logWarning("db", `Pre-migration backup failed: ${backupErr instanceof Error ? backupErr.message : String(backupErr)}`);
831
- }
832
- }
188
+ backupDatabaseBeforeMigration(db, currentPath, currentVersion, {
189
+ existsSync,
190
+ copyFileSync,
191
+ logWarning,
192
+ });
833
193
 
834
194
  db.exec("BEGIN");
835
195
  try {
836
196
  if (currentVersion < 2) {
837
- db.exec(`
838
- CREATE TABLE IF NOT EXISTS artifacts (
839
- path TEXT PRIMARY KEY,
840
- artifact_type TEXT NOT NULL DEFAULT '',
841
- milestone_id TEXT DEFAULT NULL,
842
- slice_id TEXT DEFAULT NULL,
843
- task_id TEXT DEFAULT NULL,
844
- full_content TEXT NOT NULL DEFAULT '',
845
- imported_at TEXT NOT NULL DEFAULT ''
846
- )
847
- `);
848
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
849
- ":version": 2,
850
- ":applied_at": new Date().toISOString(),
851
- });
197
+ applyMigrationV2Artifacts(db);
198
+ recordSchemaVersion(db, 2);
852
199
  }
853
200
 
854
201
  if (currentVersion < 3) {
855
- db.exec(`
856
- CREATE TABLE IF NOT EXISTS memories (
857
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
858
- id TEXT NOT NULL UNIQUE,
859
- category TEXT NOT NULL,
860
- content TEXT NOT NULL,
861
- confidence REAL NOT NULL DEFAULT 0.8,
862
- source_unit_type TEXT,
863
- source_unit_id TEXT,
864
- created_at TEXT NOT NULL,
865
- updated_at TEXT NOT NULL,
866
- superseded_by TEXT DEFAULT NULL,
867
- hit_count INTEGER NOT NULL DEFAULT 0
868
- )
869
- `);
870
- db.exec(`
871
- CREATE TABLE IF NOT EXISTS memory_processed_units (
872
- unit_key TEXT PRIMARY KEY,
873
- activity_file TEXT,
874
- processed_at TEXT NOT NULL
875
- )
876
- `);
877
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
878
- db.exec("DROP VIEW IF EXISTS active_memories");
879
- db.exec("CREATE VIEW active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL");
880
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
881
- ":version": 3,
882
- ":applied_at": new Date().toISOString(),
883
- });
202
+ applyMigrationV3Memories(db);
203
+ recordSchemaVersion(db, 3);
884
204
  }
885
205
 
886
206
  if (currentVersion < 4) {
887
- ensureColumn(db, "decisions", "made_by", `ALTER TABLE decisions ADD COLUMN made_by TEXT NOT NULL DEFAULT 'agent'`);
888
- db.exec("DROP VIEW IF EXISTS active_decisions");
889
- db.exec("CREATE VIEW active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL");
890
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
891
- ":version": 4,
892
- ":applied_at": new Date().toISOString(),
893
- });
207
+ applyMigrationV4DecisionMadeBy(db);
208
+ recordSchemaVersion(db, 4);
894
209
  }
895
210
 
896
211
  if (currentVersion < 5) {
897
- db.exec(`
898
- CREATE TABLE IF NOT EXISTS milestones (
899
- id TEXT PRIMARY KEY,
900
- title TEXT NOT NULL DEFAULT '',
901
- status TEXT NOT NULL DEFAULT 'active',
902
- created_at TEXT NOT NULL,
903
- completed_at TEXT DEFAULT NULL
904
- )
905
- `);
906
- db.exec(`
907
- CREATE TABLE IF NOT EXISTS slices (
908
- milestone_id TEXT NOT NULL,
909
- id TEXT NOT NULL,
910
- title TEXT NOT NULL DEFAULT '',
911
- status TEXT NOT NULL DEFAULT 'pending',
912
- risk TEXT NOT NULL DEFAULT 'medium',
913
- created_at TEXT NOT NULL DEFAULT '',
914
- completed_at TEXT DEFAULT NULL,
915
- PRIMARY KEY (milestone_id, id),
916
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
917
- )
918
- `);
919
- db.exec(`
920
- CREATE TABLE IF NOT EXISTS tasks (
921
- milestone_id TEXT NOT NULL,
922
- slice_id TEXT NOT NULL,
923
- id TEXT NOT NULL,
924
- title TEXT NOT NULL DEFAULT '',
925
- status TEXT NOT NULL DEFAULT 'pending',
926
- one_liner TEXT NOT NULL DEFAULT '',
927
- narrative TEXT NOT NULL DEFAULT '',
928
- verification_result TEXT NOT NULL DEFAULT '',
929
- duration TEXT NOT NULL DEFAULT '',
930
- completed_at TEXT DEFAULT NULL,
931
- blocker_discovered INTEGER DEFAULT 0,
932
- deviations TEXT NOT NULL DEFAULT '',
933
- known_issues TEXT NOT NULL DEFAULT '',
934
- key_files TEXT NOT NULL DEFAULT '[]',
935
- key_decisions TEXT NOT NULL DEFAULT '[]',
936
- full_summary_md TEXT NOT NULL DEFAULT '',
937
- PRIMARY KEY (milestone_id, slice_id, id),
938
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
939
- )
940
- `);
941
- db.exec(`
942
- CREATE TABLE IF NOT EXISTS verification_evidence (
943
- id INTEGER PRIMARY KEY AUTOINCREMENT,
944
- task_id TEXT NOT NULL DEFAULT '',
945
- slice_id TEXT NOT NULL DEFAULT '',
946
- milestone_id TEXT NOT NULL DEFAULT '',
947
- command TEXT NOT NULL DEFAULT '',
948
- exit_code INTEGER DEFAULT 0,
949
- verdict TEXT NOT NULL DEFAULT '',
950
- duration_ms INTEGER DEFAULT 0,
951
- created_at TEXT NOT NULL DEFAULT '',
952
- FOREIGN KEY (milestone_id, slice_id, task_id) REFERENCES tasks(milestone_id, slice_id, id)
953
- )
954
- `);
955
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
956
- ":version": 5,
957
- ":applied_at": new Date().toISOString(),
958
- });
212
+ applyMigrationV5HierarchyTables(db);
213
+ recordSchemaVersion(db, 5);
959
214
  }
960
215
 
961
216
  if (currentVersion < 6) {
962
- ensureColumn(db, "slices", "full_summary_md", `ALTER TABLE slices ADD COLUMN full_summary_md TEXT NOT NULL DEFAULT ''`);
963
- ensureColumn(db, "slices", "full_uat_md", `ALTER TABLE slices ADD COLUMN full_uat_md TEXT NOT NULL DEFAULT ''`);
964
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
965
- ":version": 6,
966
- ":applied_at": new Date().toISOString(),
967
- });
217
+ applyMigrationV6SliceSummaries(db);
218
+ recordSchemaVersion(db, 6);
968
219
  }
969
220
 
970
221
  if (currentVersion < 7) {
971
- ensureColumn(db, "slices", "depends", `ALTER TABLE slices ADD COLUMN depends TEXT NOT NULL DEFAULT '[]'`);
972
- ensureColumn(db, "slices", "demo", `ALTER TABLE slices ADD COLUMN demo TEXT NOT NULL DEFAULT ''`);
973
- ensureColumn(db, "milestones", "depends_on", `ALTER TABLE milestones ADD COLUMN depends_on TEXT NOT NULL DEFAULT '[]'`);
974
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
975
- ":version": 7,
976
- ":applied_at": new Date().toISOString(),
977
- });
222
+ applyMigrationV7Dependencies(db);
223
+ recordSchemaVersion(db, 7);
978
224
  }
979
225
 
980
226
  if (currentVersion < 8) {
981
- ensureColumn(db, "milestones", "vision", `ALTER TABLE milestones ADD COLUMN vision TEXT NOT NULL DEFAULT ''`);
982
- ensureColumn(db, "milestones", "success_criteria", `ALTER TABLE milestones ADD COLUMN success_criteria TEXT NOT NULL DEFAULT '[]'`);
983
- ensureColumn(db, "milestones", "key_risks", `ALTER TABLE milestones ADD COLUMN key_risks TEXT NOT NULL DEFAULT '[]'`);
984
- ensureColumn(db, "milestones", "proof_strategy", `ALTER TABLE milestones ADD COLUMN proof_strategy TEXT NOT NULL DEFAULT '[]'`);
985
- ensureColumn(db, "milestones", "verification_contract", `ALTER TABLE milestones ADD COLUMN verification_contract TEXT NOT NULL DEFAULT ''`);
986
- ensureColumn(db, "milestones", "verification_integration", `ALTER TABLE milestones ADD COLUMN verification_integration TEXT NOT NULL DEFAULT ''`);
987
- ensureColumn(db, "milestones", "verification_operational", `ALTER TABLE milestones ADD COLUMN verification_operational TEXT NOT NULL DEFAULT ''`);
988
- ensureColumn(db, "milestones", "verification_uat", `ALTER TABLE milestones ADD COLUMN verification_uat TEXT NOT NULL DEFAULT ''`);
989
- ensureColumn(db, "milestones", "definition_of_done", `ALTER TABLE milestones ADD COLUMN definition_of_done TEXT NOT NULL DEFAULT '[]'`);
990
- ensureColumn(db, "milestones", "requirement_coverage", `ALTER TABLE milestones ADD COLUMN requirement_coverage TEXT NOT NULL DEFAULT ''`);
991
- ensureColumn(db, "milestones", "boundary_map_markdown", `ALTER TABLE milestones ADD COLUMN boundary_map_markdown TEXT NOT NULL DEFAULT ''`);
992
-
993
- ensureColumn(db, "slices", "goal", `ALTER TABLE slices ADD COLUMN goal TEXT NOT NULL DEFAULT ''`);
994
- ensureColumn(db, "slices", "success_criteria", `ALTER TABLE slices ADD COLUMN success_criteria TEXT NOT NULL DEFAULT ''`);
995
- ensureColumn(db, "slices", "proof_level", `ALTER TABLE slices ADD COLUMN proof_level TEXT NOT NULL DEFAULT ''`);
996
- ensureColumn(db, "slices", "integration_closure", `ALTER TABLE slices ADD COLUMN integration_closure TEXT NOT NULL DEFAULT ''`);
997
- ensureColumn(db, "slices", "observability_impact", `ALTER TABLE slices ADD COLUMN observability_impact TEXT NOT NULL DEFAULT ''`);
998
-
999
- ensureColumn(db, "tasks", "description", `ALTER TABLE tasks ADD COLUMN description TEXT NOT NULL DEFAULT ''`);
1000
- ensureColumn(db, "tasks", "estimate", `ALTER TABLE tasks ADD COLUMN estimate TEXT NOT NULL DEFAULT ''`);
1001
- ensureColumn(db, "tasks", "files", `ALTER TABLE tasks ADD COLUMN files TEXT NOT NULL DEFAULT '[]'`);
1002
- ensureColumn(db, "tasks", "verify", `ALTER TABLE tasks ADD COLUMN verify TEXT NOT NULL DEFAULT ''`);
1003
- ensureColumn(db, "tasks", "inputs", `ALTER TABLE tasks ADD COLUMN inputs TEXT NOT NULL DEFAULT '[]'`);
1004
- ensureColumn(db, "tasks", "expected_output", `ALTER TABLE tasks ADD COLUMN expected_output TEXT NOT NULL DEFAULT '[]'`);
1005
- ensureColumn(db, "tasks", "observability_impact", `ALTER TABLE tasks ADD COLUMN observability_impact TEXT NOT NULL DEFAULT ''`);
1006
-
1007
- db.exec(`
1008
- CREATE TABLE IF NOT EXISTS replan_history (
1009
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1010
- milestone_id TEXT NOT NULL DEFAULT '',
1011
- slice_id TEXT DEFAULT NULL,
1012
- task_id TEXT DEFAULT NULL,
1013
- summary TEXT NOT NULL DEFAULT '',
1014
- previous_artifact_path TEXT DEFAULT NULL,
1015
- replacement_artifact_path TEXT DEFAULT NULL,
1016
- created_at TEXT NOT NULL DEFAULT '',
1017
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
1018
- )
1019
- `);
1020
- db.exec(`
1021
- CREATE TABLE IF NOT EXISTS assessments (
1022
- path TEXT PRIMARY KEY,
1023
- milestone_id TEXT NOT NULL DEFAULT '',
1024
- slice_id TEXT DEFAULT NULL,
1025
- task_id TEXT DEFAULT NULL,
1026
- status TEXT NOT NULL DEFAULT '',
1027
- scope TEXT NOT NULL DEFAULT '',
1028
- full_content TEXT NOT NULL DEFAULT '',
1029
- created_at TEXT NOT NULL DEFAULT '',
1030
- FOREIGN KEY (milestone_id) REFERENCES milestones(id)
1031
- )
1032
- `);
1033
- db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
1034
-
1035
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1036
- ":version": 8,
1037
- ":applied_at": new Date().toISOString(),
1038
- });
227
+ applyMigrationV8PlanningFields(db);
228
+ recordSchemaVersion(db, 8);
1039
229
  }
1040
230
 
1041
231
  if (currentVersion < 9) {
1042
- ensureColumn(db, "slices", "sequence", `ALTER TABLE slices ADD COLUMN sequence INTEGER DEFAULT 0`);
1043
- ensureColumn(db, "tasks", "sequence", `ALTER TABLE tasks ADD COLUMN sequence INTEGER DEFAULT 0`);
1044
-
1045
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1046
- ":version": 9,
1047
- ":applied_at": new Date().toISOString(),
1048
- });
232
+ applyMigrationV9Ordering(db);
233
+ recordSchemaVersion(db, 9);
1049
234
  }
1050
235
 
1051
236
  if (currentVersion < 10) {
1052
- ensureColumn(db, "slices", "replan_triggered_at", `ALTER TABLE slices ADD COLUMN replan_triggered_at TEXT DEFAULT NULL`);
1053
-
1054
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1055
- ":version": 10,
1056
- ":applied_at": new Date().toISOString(),
1057
- });
237
+ applyMigrationV10ReplanTrigger(db);
238
+ recordSchemaVersion(db, 10);
1058
239
  }
1059
240
 
1060
241
  if (currentVersion < 11) {
1061
- ensureColumn(db, "tasks", "full_plan_md", `ALTER TABLE tasks ADD COLUMN full_plan_md TEXT NOT NULL DEFAULT ''`);
1062
- // Add unique constraint to replan_history for idempotency:
1063
- // one replan record per blocker task per slice per milestone.
1064
- db.exec(`
1065
- CREATE UNIQUE INDEX IF NOT EXISTS idx_replan_history_unique
1066
- ON replan_history(milestone_id, slice_id, task_id)
1067
- WHERE slice_id IS NOT NULL AND task_id IS NOT NULL
1068
- `);
1069
-
1070
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1071
- ":version": 11,
1072
- ":applied_at": new Date().toISOString(),
1073
- });
242
+ applyMigrationV11TaskPlanning(db);
243
+ recordSchemaVersion(db, 11);
1074
244
  }
1075
245
 
1076
246
  if (currentVersion < 12) {
@@ -1079,305 +249,68 @@ function migrateSchema(db: DbAdapter): void {
1079
249
  // DBs that migrate through v12. The corrected DDL uses
1080
250
  // task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
1081
251
  // were created with the broken DDL are repaired by the v22 migration below.
1082
- db.exec(`
1083
- CREATE TABLE IF NOT EXISTS quality_gates (
1084
- milestone_id TEXT NOT NULL,
1085
- slice_id TEXT NOT NULL,
1086
- gate_id TEXT NOT NULL,
1087
- scope TEXT NOT NULL DEFAULT 'slice',
1088
- task_id TEXT NOT NULL DEFAULT '',
1089
- status TEXT NOT NULL DEFAULT 'pending',
1090
- verdict TEXT NOT NULL DEFAULT '',
1091
- rationale TEXT NOT NULL DEFAULT '',
1092
- findings TEXT NOT NULL DEFAULT '',
1093
- evaluated_at TEXT DEFAULT NULL,
1094
- PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
1095
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
1096
- )
1097
- `);
1098
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1099
- ":version": 12,
1100
- ":applied_at": new Date().toISOString(),
1101
- });
252
+ applyMigrationV12QualityGates(db);
253
+ recordSchemaVersion(db, 12);
1102
254
  }
1103
255
 
1104
256
  if (currentVersion < 13) {
1105
- // Hot-path indexes for auto-loop dispatch queries
1106
- db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
1107
- db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
1108
- db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
1109
- db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
1110
- db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
1111
- ensureVerificationEvidenceDedupIndex(db);
1112
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1113
- ":version": 13,
1114
- ":applied_at": new Date().toISOString(),
1115
- });
257
+ applyMigrationV13HotPathIndexes(db, ensureVerificationEvidenceDedupIndex);
258
+ recordSchemaVersion(db, 13);
1116
259
  }
1117
260
 
1118
261
  if (currentVersion < 14) {
1119
- db.exec(`
1120
- CREATE TABLE IF NOT EXISTS slice_dependencies (
1121
- milestone_id TEXT NOT NULL,
1122
- slice_id TEXT NOT NULL,
1123
- depends_on_slice_id TEXT NOT NULL,
1124
- PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
1125
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
1126
- FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
1127
- )
1128
- `);
1129
- db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
1130
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1131
- ":version": 14,
1132
- ":applied_at": new Date().toISOString(),
1133
- });
262
+ applyMigrationV14SliceDependencies(db);
263
+ recordSchemaVersion(db, 14);
1134
264
  }
1135
265
 
1136
266
  if (currentVersion < 15) {
1137
- db.exec(`
1138
- CREATE TABLE IF NOT EXISTS gate_runs (
1139
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1140
- trace_id TEXT NOT NULL,
1141
- turn_id TEXT NOT NULL,
1142
- gate_id TEXT NOT NULL,
1143
- gate_type TEXT NOT NULL DEFAULT '',
1144
- unit_type TEXT DEFAULT NULL,
1145
- unit_id TEXT DEFAULT NULL,
1146
- milestone_id TEXT DEFAULT NULL,
1147
- slice_id TEXT DEFAULT NULL,
1148
- task_id TEXT DEFAULT NULL,
1149
- outcome TEXT NOT NULL DEFAULT 'pass',
1150
- failure_class TEXT NOT NULL DEFAULT 'none',
1151
- rationale TEXT NOT NULL DEFAULT '',
1152
- findings TEXT NOT NULL DEFAULT '',
1153
- attempt INTEGER NOT NULL DEFAULT 1,
1154
- max_attempts INTEGER NOT NULL DEFAULT 1,
1155
- retryable INTEGER NOT NULL DEFAULT 0,
1156
- evaluated_at TEXT NOT NULL DEFAULT ''
1157
- )
1158
- `);
1159
- db.exec(`
1160
- CREATE TABLE IF NOT EXISTS turn_git_transactions (
1161
- trace_id TEXT NOT NULL,
1162
- turn_id TEXT NOT NULL,
1163
- unit_type TEXT DEFAULT NULL,
1164
- unit_id TEXT DEFAULT NULL,
1165
- stage TEXT NOT NULL DEFAULT 'turn-start',
1166
- action TEXT NOT NULL DEFAULT 'status-only',
1167
- push INTEGER NOT NULL DEFAULT 0,
1168
- status TEXT NOT NULL DEFAULT 'ok',
1169
- error TEXT DEFAULT NULL,
1170
- metadata_json TEXT NOT NULL DEFAULT '{}',
1171
- updated_at TEXT NOT NULL DEFAULT '',
1172
- PRIMARY KEY (trace_id, turn_id, stage)
1173
- )
1174
- `);
1175
- db.exec(`
1176
- CREATE TABLE IF NOT EXISTS audit_events (
1177
- event_id TEXT PRIMARY KEY,
1178
- trace_id TEXT NOT NULL,
1179
- turn_id TEXT DEFAULT NULL,
1180
- caused_by TEXT DEFAULT NULL,
1181
- category TEXT NOT NULL,
1182
- type TEXT NOT NULL,
1183
- ts TEXT NOT NULL,
1184
- payload_json TEXT NOT NULL DEFAULT '{}'
1185
- )
1186
- `);
1187
- db.exec(`
1188
- CREATE TABLE IF NOT EXISTS audit_turn_index (
1189
- trace_id TEXT NOT NULL,
1190
- turn_id TEXT NOT NULL,
1191
- first_ts TEXT NOT NULL,
1192
- last_ts TEXT NOT NULL,
1193
- event_count INTEGER NOT NULL DEFAULT 0,
1194
- PRIMARY KEY (trace_id, turn_id)
1195
- )
1196
- `);
1197
- db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
1198
- db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
1199
- db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
1200
- db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
1201
- db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
1202
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1203
- ":version": 15,
1204
- ":applied_at": new Date().toISOString(),
1205
- });
267
+ applyMigrationV15AuditTables(db);
268
+ recordSchemaVersion(db, 15);
1206
269
  }
1207
270
 
1208
271
  if (currentVersion < 16) {
1209
- // ADR-011 Phase 1: sketch-then-refine progressive planning — sketch columns on slices.
1210
- ensureColumn(db, "slices", "is_sketch", `ALTER TABLE slices ADD COLUMN is_sketch INTEGER NOT NULL DEFAULT 0`);
1211
- ensureColumn(db, "slices", "sketch_scope", `ALTER TABLE slices ADD COLUMN sketch_scope TEXT NOT NULL DEFAULT ''`);
1212
- // ADR-011 Phase 2: decisions can now be sourced from escalation resolutions.
1213
- ensureColumn(db, "decisions", "source", `ALTER TABLE decisions ADD COLUMN source TEXT NOT NULL DEFAULT 'discussion'`);
1214
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1215
- ":version": 16,
1216
- ":applied_at": new Date().toISOString(),
1217
- });
272
+ applyMigrationV16EscalationSource(db);
273
+ recordSchemaVersion(db, 16);
1218
274
  }
1219
275
 
1220
276
  if (currentVersion < 17) {
1221
- // ADR-011 Phase 2: mid-execution escalation — columns on the tasks table.
1222
- ensureColumn(db, "tasks", "blocker_source", `ALTER TABLE tasks ADD COLUMN blocker_source TEXT NOT NULL DEFAULT ''`);
1223
- ensureColumn(db, "tasks", "escalation_pending", `ALTER TABLE tasks ADD COLUMN escalation_pending INTEGER NOT NULL DEFAULT 0`);
1224
- ensureColumn(db, "tasks", "escalation_awaiting_review", `ALTER TABLE tasks ADD COLUMN escalation_awaiting_review INTEGER NOT NULL DEFAULT 0`);
1225
- ensureColumn(db, "tasks", "escalation_artifact_path", `ALTER TABLE tasks ADD COLUMN escalation_artifact_path TEXT DEFAULT NULL`);
1226
- ensureColumn(db, "tasks", "escalation_override_applied_at", `ALTER TABLE tasks ADD COLUMN escalation_override_applied_at TEXT DEFAULT NULL`);
1227
- db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
1228
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1229
- ":version": 17,
1230
- ":applied_at": new Date().toISOString(),
1231
- });
277
+ applyMigrationV17TaskEscalation(db);
278
+ recordSchemaVersion(db, 17);
1232
279
  }
1233
280
 
1234
281
  if (currentVersion < 18) {
1235
- // Memory system Phase 2: scope + tags on memories, plus memory_sources
1236
- // table for raw ingested content (notes, files, URLs, artifacts).
1237
- ensureColumn(db, "memories", "scope", `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'`);
1238
- ensureColumn(db, "memories", "tags", `ALTER TABLE memories ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`);
1239
- db.exec(`
1240
- CREATE TABLE IF NOT EXISTS memory_sources (
1241
- id TEXT PRIMARY KEY,
1242
- kind TEXT NOT NULL,
1243
- uri TEXT,
1244
- title TEXT,
1245
- content TEXT NOT NULL,
1246
- content_hash TEXT NOT NULL UNIQUE,
1247
- imported_at TEXT NOT NULL,
1248
- scope TEXT NOT NULL DEFAULT 'project',
1249
- tags TEXT NOT NULL DEFAULT '[]'
1250
- )
1251
- `);
1252
- // If memory_sources already existed before v18 (created by an earlier
1253
- // version of initSchema that lacked scope/tags), add the missing columns.
1254
- ensureColumn(db, "memory_sources", "scope", `ALTER TABLE memory_sources ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'`);
1255
- ensureColumn(db, "memory_sources", "tags", `ALTER TABLE memory_sources ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`);
1256
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
1257
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
1258
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
1259
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1260
- ":version": 18,
1261
- ":applied_at": new Date().toISOString(),
1262
- });
282
+ applyMigrationV18MemorySources(db);
283
+ recordSchemaVersion(db, 18);
1263
284
  }
1264
285
 
1265
286
  if (currentVersion < 19) {
1266
- // Memory system Phase 3: embeddings + FTS5 for hybrid retrieval.
1267
- db.exec(`
1268
- CREATE TABLE IF NOT EXISTS memory_embeddings (
1269
- memory_id TEXT PRIMARY KEY,
1270
- model TEXT NOT NULL,
1271
- dim INTEGER NOT NULL,
1272
- vector BLOB NOT NULL,
1273
- updated_at TEXT NOT NULL
1274
- )
1275
- `);
1276
- tryCreateMemoriesFts(db);
1277
- // Backfill FTS5 with any existing memories (triggers only cover future writes).
1278
- if (isMemoriesFtsAvailable(db)) {
1279
- try {
1280
- db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
1281
- } catch (err) {
1282
- logWarning("db", `FTS5 backfill failed: ${(err as Error).message}`);
1283
- }
1284
- }
1285
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1286
- ":version": 19,
1287
- ":applied_at": new Date().toISOString(),
287
+ applyMigrationV19MemoryFts(db, {
288
+ tryCreateMemoriesFts,
289
+ isMemoriesFtsAvailable,
290
+ backfillMemoriesFts,
291
+ logWarning,
1288
292
  });
293
+ recordSchemaVersion(db, 19);
1289
294
  }
1290
295
 
1291
296
  if (currentVersion < 20) {
1292
- // Memory system Phase 4: knowledge-graph relations between memories.
1293
- db.exec(`
1294
- CREATE TABLE IF NOT EXISTS memory_relations (
1295
- from_id TEXT NOT NULL,
1296
- to_id TEXT NOT NULL,
1297
- rel TEXT NOT NULL,
1298
- confidence REAL NOT NULL DEFAULT 0.8,
1299
- created_at TEXT NOT NULL,
1300
- PRIMARY KEY (from_id, to_id, rel)
1301
- )
1302
- `);
1303
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
1304
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
1305
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1306
- ":version": 20,
1307
- ":applied_at": new Date().toISOString(),
1308
- });
297
+ applyMigrationV20MemoryRelations(db);
298
+ recordSchemaVersion(db, 20);
1309
299
  }
1310
300
 
1311
301
  if (currentVersion < 21) {
1312
- // ADR-013 Step 2: preserve structured fields (gsd_save_decision's
1313
- // scope/decision/choice/rationale/made_by/revisable) on memories rows so
1314
- // the eventual decisions->memories cutover does not lose schema fidelity.
1315
- // Nullable JSON column — existing rows stay NULL until backfilled in Step 5.
1316
- // Use ensureColumn for race-safety (matches v15-v18 pattern; bare ALTER
1317
- // throws "duplicate column" on the loser of a concurrent open race even
1318
- // though the transaction wrapper protects the schema_version row).
1319
- ensureColumn(db, "memories", "structured_fields", "ALTER TABLE memories ADD COLUMN structured_fields TEXT DEFAULT NULL");
1320
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1321
- ":version": 21,
1322
- ":applied_at": new Date().toISOString(),
1323
- });
302
+ applyMigrationV21StructuredMemories(db);
303
+ recordSchemaVersion(db, 21);
1324
304
  }
1325
305
 
1326
306
  if (currentVersion < 22) {
1327
- // v22: Repair quality_gates tables that were created by the broken v12
1328
- // migration (which used COALESCE(task_id, '') as a PK expression — invalid
1329
- // SQLite DDL). Those DBs have task_id nullable (dflt_value NULL, notnull 0).
1330
- // Rebuild the table with the correct schema, migrating existing rows via
1331
- // COALESCE so no data is lost.
1332
- const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
1333
- const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
1334
- const needsRepair = taskIdCol && (taskIdCol["notnull"] === 0 || taskIdCol["notnull"] === "0");
1335
- if (needsRepair) {
1336
- db.exec(`
1337
- CREATE TABLE quality_gates_new (
1338
- milestone_id TEXT NOT NULL,
1339
- slice_id TEXT NOT NULL,
1340
- gate_id TEXT NOT NULL,
1341
- scope TEXT NOT NULL DEFAULT 'slice',
1342
- task_id TEXT NOT NULL DEFAULT '',
1343
- status TEXT NOT NULL DEFAULT 'pending',
1344
- verdict TEXT NOT NULL DEFAULT '',
1345
- rationale TEXT NOT NULL DEFAULT '',
1346
- findings TEXT NOT NULL DEFAULT '',
1347
- evaluated_at TEXT DEFAULT NULL,
1348
- PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
1349
- FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
1350
- )
1351
- `);
1352
- db.exec(`
1353
- INSERT OR IGNORE INTO quality_gates_new
1354
- (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
1355
- SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
1356
- FROM quality_gates
1357
- `);
1358
- db.exec("DROP TABLE quality_gates");
1359
- db.exec("ALTER TABLE quality_gates_new RENAME TO quality_gates");
1360
- db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
1361
- }
1362
- // Ensure scope column exists on quality_gates and assessments (guard
1363
- // against DBs that somehow lack it after a partial migration).
1364
- ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
1365
- ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
1366
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1367
- ":version": 22,
1368
- ":applied_at": new Date().toISOString(),
1369
- });
307
+ applyMigrationV22QualityGateRepair(db, { copyQualityGateRowsToRepairedTable });
308
+ recordSchemaVersion(db, 22);
1370
309
  }
1371
310
 
1372
311
  if (currentVersion < 23) {
1373
- // v23: milestone queue ordering moves into the canonical DB. The
1374
- // historical QUEUE-ORDER.json file remains a projection, but runtime
1375
- // derivation must not read it as authoritative state.
1376
- ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
1377
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1378
- ":version": 23,
1379
- ":applied_at": new Date().toISOString(),
1380
- });
312
+ applyMigrationV23MilestoneQueue(db);
313
+ recordSchemaVersion(db, 23);
1381
314
  }
1382
315
 
1383
316
  if (currentVersion < 24) {
@@ -1386,20 +319,14 @@ function migrateSchema(db: DbAdapter): void {
1386
319
  // helper runs in the fresh-install path); for upgraded DBs this is
1387
320
  // the only place these tables get created.
1388
321
  createCoordinationTablesV24(db);
1389
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1390
- ":version": 24,
1391
- ":applied_at": new Date().toISOString(),
1392
- });
322
+ recordSchemaVersion(db, 24);
1393
323
  }
1394
324
 
1395
325
  if (currentVersion < 25) {
1396
326
  // v25: runtime_kv non-correctness-critical key-value storage. See
1397
327
  // createRuntimeKvTableV25 for the full schema + invariants.
1398
328
  createRuntimeKvTableV25(db);
1399
- db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1400
- ":version": 25,
1401
- ":applied_at": new Date().toISOString(),
1402
- });
329
+ recordSchemaVersion(db, 25);
1403
330
  }
1404
331
 
1405
332
  db.exec("COMMIT");
@@ -1413,9 +340,7 @@ let currentDb: DbAdapter | null = null;
1413
340
  let currentPath: string | null = null;
1414
341
  let currentPid: number = 0;
1415
342
  let _exitHandlerRegistered = false;
1416
- let _dbOpenAttempted = false;
1417
- let _lastDbError: Error | null = null;
1418
- let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
343
+ const _dbOpenState = createDbOpenState();
1419
344
  /**
1420
345
  * Identity key of the workspace whose connection is currently active
1421
346
  * (currentDb). Set by openDatabaseByWorkspace(); null when the active
@@ -1436,11 +361,29 @@ let _currentIdentityKey: string | null = null;
1436
361
  * The cache allows fast re-activation of a previously opened connection when
1437
362
  * callers switch between known workspaces via openDatabaseByWorkspace().
1438
363
  */
1439
- const _dbCache = new Map<string, { dbPath: string; db: DbAdapter }>();
364
+ const _dbCache = createDbConnectionCache();
1440
365
 
1441
366
  /** Test helper: expose the internal cache for inspection. Not for production use. */
1442
- export function _getDbCache(): ReadonlyMap<string, { dbPath: string; db: DbAdapter }> {
1443
- return _dbCache;
367
+ export function _getDbCache(): ReadonlyMap<string, DbConnectionCacheEntry> {
368
+ return _dbCache.asReadonlyMap();
369
+ }
370
+
371
+ function closeCachedConnection(entry: DbConnectionCacheEntry, source: "all" | "workspace"): void {
372
+ try {
373
+ entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
374
+ } catch (e) {
375
+ if (source === "workspace") logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`);
376
+ }
377
+ try {
378
+ entry.db.exec("PRAGMA incremental_vacuum(64)");
379
+ } catch (e) {
380
+ if (source === "workspace") logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`);
381
+ }
382
+ try {
383
+ entry.db.close();
384
+ } catch (e) {
385
+ if (source === "workspace") logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`);
386
+ }
1444
387
  }
1445
388
 
1446
389
  /**
@@ -1453,13 +396,7 @@ export function _getDbCache(): ReadonlyMap<string, { dbPath: string; db: DbAdapt
1453
396
  */
1454
397
  export function closeAllDatabases(): void {
1455
398
  // Close all non-active cached connections first.
1456
- for (const [key, entry] of _dbCache) {
1457
- if (entry.db === currentDb) continue; // handled by closeDatabase() below
1458
- _dbCache.delete(key);
1459
- try { entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* best-effort */ }
1460
- try { entry.db.exec("PRAGMA incremental_vacuum(64)"); } catch { /* best-effort */ }
1461
- try { entry.db.close(); } catch { /* best-effort */ }
1462
- }
399
+ _dbCache.closeNonActive(currentDb, (entry) => closeCachedConnection(entry, "all"));
1463
400
  closeDatabase();
1464
401
  }
1465
402
 
@@ -1489,7 +426,7 @@ export function openDatabaseByWorkspace(workspace: GsdWorkspace): boolean {
1489
426
  currentDb = cached.db;
1490
427
  currentPath = cached.dbPath;
1491
428
  currentPid = process.pid;
1492
- _dbOpenAttempted = true;
429
+ _dbOpenState.markAttempted();
1493
430
  _currentIdentityKey = key;
1494
431
  return true;
1495
432
  }
@@ -1581,21 +518,13 @@ export function closeDatabaseByWorkspace(workspace: GsdWorkspace): void {
1581
518
  closeDatabase();
1582
519
  } else {
1583
520
  // Connection was displaced by a later open; close the adapter directly.
1584
- try {
1585
- cached.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1586
- } catch (e) { logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`); }
1587
- try {
1588
- cached.db.exec("PRAGMA incremental_vacuum(64)");
1589
- } catch (e) { logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`); }
1590
- try {
1591
- cached.db.close();
1592
- } catch (e) { logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`); }
521
+ closeCachedConnection(cached, "workspace");
1593
522
  }
1594
523
  }
1595
524
 
1596
525
  export function getDbProvider(): ProviderName | null {
1597
- loadProvider();
1598
- return providerName;
526
+ providerLoader.load();
527
+ return providerLoader.getProviderName();
1599
528
  }
1600
529
 
1601
530
  export function isDbAvailable(): boolean {
@@ -1609,7 +538,7 @@ export function isDbAvailable(): boolean {
1609
538
  * trigger a false degraded-mode warning.
1610
539
  */
1611
540
  export function wasDbOpenAttempted(): boolean {
1612
- return _dbOpenAttempted;
541
+ return _dbOpenState.snapshot().attempted;
1613
542
  }
1614
543
 
1615
544
  export function getDbStatus(): {
@@ -1617,56 +546,44 @@ export function getDbStatus(): {
1617
546
  provider: ProviderName | null;
1618
547
  attempted: boolean;
1619
548
  lastError: Error | null;
1620
- lastPhase: "open" | "initSchema" | "vacuum-recovery" | null;
549
+ lastPhase: DbOpenPhase | null;
1621
550
  } {
1622
- loadProvider();
551
+ providerLoader.load();
552
+ const openState = _dbOpenState.snapshot();
1623
553
  return {
1624
554
  available: currentDb !== null,
1625
- provider: providerName,
1626
- attempted: _dbOpenAttempted,
1627
- lastError: _lastDbError,
1628
- lastPhase: _lastDbPhase,
555
+ provider: providerLoader.getProviderName(),
556
+ attempted: openState.attempted,
557
+ lastError: openState.lastError,
558
+ lastPhase: openState.lastPhase,
1629
559
  };
1630
560
  }
1631
561
 
1632
562
  export function openDatabase(path: string): boolean {
1633
- _dbOpenAttempted = true;
563
+ _dbOpenState.markAttempted();
1634
564
  if (currentDb && currentPath !== path) closeDatabase();
1635
565
  if (currentDb && currentPath === path) return true;
1636
566
 
1637
567
  // Reset error state only when a new open attempt is actually going to run.
1638
- _lastDbError = null;
1639
- _lastDbPhase = null;
568
+ _dbOpenState.clearError();
1640
569
 
1641
570
  let rawDb: unknown;
1642
- let fallbackProvider: ProviderName | null = null;
1643
- let fallbackModule: unknown = null;
571
+ let fallbackOpen: SqliteFallbackOpen | null = null;
1644
572
  try {
1645
- rawDb = openRawDb(path);
573
+ rawDb = providerLoader.openRaw(path);
1646
574
  } catch (primaryErr) {
1647
- _lastDbPhase = "open";
1648
- _lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
575
+ _dbOpenState.recordError("open", primaryErr);
1649
576
  // node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
1650
- if (providerName === "node:sqlite") {
1651
- try {
1652
- const mod = _require(BETTER_SQLITE3_PACKAGE);
1653
- const Db = (mod && mod.default) ? mod.default : mod;
1654
- if (typeof Db === "function") {
1655
- rawDb = new Db(path);
1656
- fallbackProvider = "better-sqlite3";
1657
- fallbackModule = Db;
1658
- _lastDbError = null;
1659
- _lastDbPhase = null;
1660
- }
1661
- } catch {
1662
- // fallback unavailable; surface original error
1663
- }
577
+ fallbackOpen = providerLoader.tryOpenBetterSqliteFallback(path);
578
+ if (fallbackOpen) {
579
+ rawDb = fallbackOpen.rawDb;
580
+ _dbOpenState.clearError();
1664
581
  }
1665
582
  if (!rawDb) throw primaryErr;
1666
583
  }
1667
584
  if (!rawDb) return false;
1668
585
 
1669
- const adapter = createAdapter(rawDb);
586
+ const adapter = createDbAdapter(rawDb);
1670
587
  const fileBacked = path !== ":memory:";
1671
588
  try {
1672
589
  initSchema(adapter, fileBacked);
@@ -1679,24 +596,19 @@ export function openDatabase(path: string): boolean {
1679
596
  initSchema(adapter, fileBacked);
1680
597
  process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
1681
598
  } catch (retryErr) {
1682
- _lastDbPhase = "vacuum-recovery";
1683
- _lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
599
+ _dbOpenState.recordError("vacuum-recovery", retryErr);
1684
600
  try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
1685
601
  throw retryErr;
1686
602
  }
1687
603
  } else {
1688
- _lastDbPhase = "initSchema";
1689
- _lastDbError = err instanceof Error ? err : new Error(String(err));
604
+ _dbOpenState.recordError("initSchema", err);
1690
605
  try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
1691
606
  throw err;
1692
607
  }
1693
608
  }
1694
609
 
1695
610
  // Commit fallback provider switch only after open + schema both succeeded.
1696
- if (fallbackProvider) {
1697
- providerName = fallbackProvider;
1698
- providerModule = fallbackModule;
1699
- }
611
+ if (fallbackOpen) providerLoader.commitFallback(fallbackOpen);
1700
612
 
1701
613
  currentDb = adapter;
1702
614
  currentPath = path;
@@ -1735,9 +647,36 @@ export function closeDatabase(): void {
1735
647
  }
1736
648
  // Reset session-scoped state unconditionally so stale error info from a
1737
649
  // failed open doesn't persist into the next open attempt or status check.
1738
- _dbOpenAttempted = false;
1739
- _lastDbError = null;
1740
- _lastDbPhase = null;
650
+ _dbOpenState.reset();
651
+ }
652
+
653
+ /**
654
+ * Re-open the active database connection from disk.
655
+ *
656
+ * Auto-mode can observe artifacts written by a workflow server running in a
657
+ * different process before its long-lived singleton has re-synchronized. The
658
+ * recovery path uses this to force the next state derivation to read from the
659
+ * current on-disk database instead of continuing with a possibly stale handle.
660
+ */
661
+ export function refreshOpenDatabaseFromDisk(): boolean {
662
+ if (!currentDb || !currentPath) return false;
663
+ if (currentPath === ":memory:") return false;
664
+
665
+ const dbPath = currentPath;
666
+ const identityKey = _currentIdentityKey;
667
+
668
+ try {
669
+ closeDatabase();
670
+ const opened = openDatabase(dbPath);
671
+ if (opened && identityKey && currentDb) {
672
+ _dbCache.set(identityKey, { dbPath, db: currentDb });
673
+ _currentIdentityKey = identityKey;
674
+ }
675
+ return opened;
676
+ } catch (e) {
677
+ logWarning("db", `database refresh failed: ${(e as Error).message}`);
678
+ return false;
679
+ }
1741
680
  }
1742
681
 
1743
682
  /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
@@ -1756,7 +695,16 @@ export function checkpointDatabase(): void {
1756
695
  } catch (e) { logWarning("db", `WAL checkpoint failed: ${(e as Error).message}`); }
1757
696
  }
1758
697
 
1759
- let _txDepth = 0;
698
+ const _transactionRunner = createDbTransactionRunner();
699
+
700
+ function createTransactionControls(db: DbAdapter) {
701
+ return {
702
+ begin: () => db.exec("BEGIN"),
703
+ beginRead: () => db.exec("BEGIN DEFERRED"),
704
+ commit: () => db.exec("COMMIT"),
705
+ rollback: () => db.exec("ROLLBACK"),
706
+ };
707
+ }
1760
708
 
1761
709
  /**
1762
710
  * Whether the current call is running inside an active SQLite transaction.
@@ -1765,35 +713,12 @@ let _txDepth = 0;
1765
713
  * and would mask the original error with a secondary "cannot VACUUM" throw.
1766
714
  */
1767
715
  export function isInTransaction(): boolean {
1768
- return _txDepth > 0;
716
+ return _transactionRunner.isInTransaction();
1769
717
  }
1770
718
 
1771
719
  export function transaction<T>(fn: () => T): T {
1772
720
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1773
-
1774
- // Re-entrant: if already inside a transaction, just run fn() without
1775
- // starting a new one. SQLite does not support nested BEGIN/COMMIT.
1776
- if (_txDepth > 0) {
1777
- _txDepth++;
1778
- try {
1779
- return fn();
1780
- } finally {
1781
- _txDepth--;
1782
- }
1783
- }
1784
-
1785
- currentDb.exec("BEGIN");
1786
- _txDepth++;
1787
- try {
1788
- const result = fn();
1789
- currentDb.exec("COMMIT");
1790
- return result;
1791
- } catch (err) {
1792
- currentDb.exec("ROLLBACK");
1793
- throw err;
1794
- } finally {
1795
- _txDepth--;
1796
- }
721
+ return _transactionRunner.transaction(createTransactionControls(currentDb), fn);
1797
722
  }
1798
723
 
1799
724
  /**
@@ -1806,36 +731,14 @@ export function transaction<T>(fn: () => T): T {
1806
731
  export function readTransaction<T>(fn: () => T): T {
1807
732
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1808
733
 
1809
- if (_txDepth > 0) {
1810
- _txDepth++;
1811
- try {
1812
- return fn();
1813
- } finally {
1814
- _txDepth--;
1815
- }
1816
- }
1817
-
1818
- currentDb.exec("BEGIN DEFERRED");
1819
- _txDepth++;
1820
- try {
1821
- const result = fn();
1822
- currentDb.exec("COMMIT");
1823
- return result;
1824
- } catch (err) {
1825
- try {
1826
- currentDb.exec("ROLLBACK");
1827
- } catch (rollbackErr) {
1828
- // A failed ROLLBACK after a failed read is a split-brain signal —
1829
- // the transaction is in an indeterminate state. Surface it via the
1830
- // logger instead of swallowing it.
1831
- logError("db", "snapshotState ROLLBACK failed", {
1832
- error: (rollbackErr as Error).message,
1833
- });
1834
- }
1835
- throw err;
1836
- } finally {
1837
- _txDepth--;
1838
- }
734
+ return _transactionRunner.readTransaction(createTransactionControls(currentDb), fn, (rollbackErr) => {
735
+ // A failed ROLLBACK after a failed read is a split-brain signal —
736
+ // the transaction is in an indeterminate state. Surface it via the
737
+ // logger instead of swallowing it.
738
+ logError("db", "snapshotState ROLLBACK failed", {
739
+ error: rollbackErr.message,
740
+ });
741
+ });
1839
742
  }
1840
743
 
1841
744
  export function insertDecision(d: Omit<Decision, "seq">): void {
@@ -1861,37 +764,13 @@ export function getDecisionById(id: string): Decision | null {
1861
764
  if (!currentDb) return null;
1862
765
  const row = currentDb.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
1863
766
  if (!row) return null;
1864
- return {
1865
- seq: row["seq"] as number,
1866
- id: row["id"] as string,
1867
- when_context: row["when_context"] as string,
1868
- scope: row["scope"] as string,
1869
- decision: row["decision"] as string,
1870
- choice: row["choice"] as string,
1871
- rationale: row["rationale"] as string,
1872
- revisable: row["revisable"] as string,
1873
- made_by: (row["made_by"] as string as import("./types.js").DecisionMadeBy) ?? "agent",
1874
- source: (row["source"] as string) ?? "discussion",
1875
- superseded_by: (row["superseded_by"] as string) ?? null,
1876
- };
767
+ return rowToDecision(row);
1877
768
  }
1878
769
 
1879
770
  export function getActiveDecisions(): Decision[] {
1880
771
  if (!currentDb) return [];
1881
772
  const rows = currentDb.prepare("SELECT * FROM active_decisions").all();
1882
- return rows.map((row) => ({
1883
- seq: row["seq"] as number,
1884
- id: row["id"] as string,
1885
- when_context: row["when_context"] as string,
1886
- scope: row["scope"] as string,
1887
- decision: row["decision"] as string,
1888
- choice: row["choice"] as string,
1889
- rationale: row["rationale"] as string,
1890
- revisable: row["revisable"] as string,
1891
- made_by: (row["made_by"] as string as import("./types.js").DecisionMadeBy) ?? "agent",
1892
- source: (row["source"] as string) ?? "discussion",
1893
- superseded_by: null,
1894
- }));
773
+ return rows.map(rowToActiveDecision);
1895
774
  }
1896
775
 
1897
776
  export function insertRequirement(r: Requirement): void {
@@ -1919,39 +798,13 @@ export function getRequirementById(id: string): Requirement | null {
1919
798
  if (!currentDb) return null;
1920
799
  const row = currentDb.prepare("SELECT * FROM requirements WHERE id = ?").get(id);
1921
800
  if (!row) return null;
1922
- return {
1923
- id: row["id"] as string,
1924
- class: row["class"] as string,
1925
- status: row["status"] as string,
1926
- description: row["description"] as string,
1927
- why: row["why"] as string,
1928
- source: row["source"] as string,
1929
- primary_owner: row["primary_owner"] as string,
1930
- supporting_slices: row["supporting_slices"] as string,
1931
- validation: row["validation"] as string,
1932
- notes: row["notes"] as string,
1933
- full_content: row["full_content"] as string,
1934
- superseded_by: (row["superseded_by"] as string) ?? null,
1935
- };
801
+ return rowToRequirement(row);
1936
802
  }
1937
803
 
1938
804
  export function getActiveRequirements(): Requirement[] {
1939
805
  if (!currentDb) return [];
1940
806
  const rows = currentDb.prepare("SELECT * FROM active_requirements").all();
1941
- return rows.map((row) => ({
1942
- id: row["id"] as string,
1943
- class: row["class"] as string,
1944
- status: row["status"] as string,
1945
- description: row["description"] as string,
1946
- why: row["why"] as string,
1947
- source: row["source"] as string,
1948
- primary_owner: row["primary_owner"] as string,
1949
- supporting_slices: row["supporting_slices"] as string,
1950
- validation: row["validation"] as string,
1951
- notes: row["notes"] as string,
1952
- full_content: row["full_content"] as string,
1953
- superseded_by: null,
1954
- }));
807
+ return rows.map(rowToActiveRequirement);
1955
808
  }
1956
809
 
1957
810
  export function getRequirementCounts(): {
@@ -1968,18 +821,7 @@ export function getRequirementCounts(): {
1968
821
  const rows = currentDb
1969
822
  .prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
1970
823
  .all();
1971
- const counts = { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
1972
- for (const row of rows) {
1973
- const status = String(row["status"] ?? "");
1974
- const count = Number(row["count"] ?? 0);
1975
- counts.total += count;
1976
- if (status === "active") counts.active += count;
1977
- else if (status === "validated") counts.validated += count;
1978
- else if (status === "deferred") counts.deferred += count;
1979
- else if (status === "out-of-scope" || status === "out_of_scope") counts.outOfScope += count;
1980
- else if (status === "blocked") counts.blocked += count;
1981
- }
1982
- return counts;
824
+ return rowsToRequirementCounts(rows);
1983
825
  }
1984
826
 
1985
827
  export function getDbOwnerPid(): number {
@@ -1995,9 +837,7 @@ export function _getAdapter(): DbAdapter | null {
1995
837
  }
1996
838
 
1997
839
  export function _resetProvider(): void {
1998
- loadAttempted = false;
1999
- providerModule = null;
2000
- providerName = null;
840
+ providerLoader.reset();
2001
841
  }
2002
842
 
2003
843
  export function upsertDecision(d: Omit<Decision, "seq">): void {
@@ -2458,54 +1298,6 @@ export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId:
2458
1298
  });
2459
1299
  }
2460
1300
 
2461
- export interface SliceRow {
2462
- milestone_id: string;
2463
- id: string;
2464
- title: string;
2465
- status: string;
2466
- risk: string;
2467
- depends: string[];
2468
- demo: string;
2469
- created_at: string;
2470
- completed_at: string | null;
2471
- full_summary_md: string;
2472
- full_uat_md: string;
2473
- goal: string;
2474
- success_criteria: string;
2475
- proof_level: string;
2476
- integration_closure: string;
2477
- observability_impact: string;
2478
- sequence: number;
2479
- replan_triggered_at: string | null;
2480
- is_sketch: number;
2481
- sketch_scope: string;
2482
- }
2483
-
2484
- function rowToSlice(row: Record<string, unknown>): SliceRow {
2485
- return {
2486
- milestone_id: row["milestone_id"] as string,
2487
- id: row["id"] as string,
2488
- title: row["title"] as string,
2489
- status: row["status"] as string,
2490
- risk: row["risk"] as string,
2491
- depends: JSON.parse((row["depends"] as string) || "[]"),
2492
- demo: (row["demo"] as string) ?? "",
2493
- created_at: row["created_at"] as string,
2494
- completed_at: (row["completed_at"] as string) ?? null,
2495
- full_summary_md: (row["full_summary_md"] as string) ?? "",
2496
- full_uat_md: (row["full_uat_md"] as string) ?? "",
2497
- goal: (row["goal"] as string) ?? "",
2498
- success_criteria: (row["success_criteria"] as string) ?? "",
2499
- proof_level: (row["proof_level"] as string) ?? "",
2500
- integration_closure: (row["integration_closure"] as string) ?? "",
2501
- observability_impact: (row["observability_impact"] as string) ?? "",
2502
- sequence: (row["sequence"] as number) ?? 0,
2503
- replan_triggered_at: (row["replan_triggered_at"] as string) ?? null,
2504
- is_sketch: (row["is_sketch"] as number) ?? 0,
2505
- sketch_scope: (row["sketch_scope"] as string) ?? "",
2506
- };
2507
- }
2508
-
2509
1301
  export function getSlice(milestoneId: string, sliceId: string): SliceRow | null {
2510
1302
  if (!currentDb) return null;
2511
1303
  const row = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid AND id = :sid").get({ ":mid": milestoneId, ":sid": sliceId });
@@ -2540,116 +1332,6 @@ export function setSliceSummaryMd(milestoneId: string, sliceId: string, summaryM
2540
1332
  ).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
2541
1333
  }
2542
1334
 
2543
- export interface TaskRow {
2544
- milestone_id: string;
2545
- slice_id: string;
2546
- id: string;
2547
- title: string;
2548
- status: string;
2549
- one_liner: string;
2550
- narrative: string;
2551
- verification_result: string;
2552
- duration: string;
2553
- completed_at: string | null;
2554
- blocker_discovered: boolean;
2555
- deviations: string;
2556
- known_issues: string;
2557
- key_files: string[];
2558
- key_decisions: string[];
2559
- full_summary_md: string;
2560
- description: string;
2561
- estimate: string;
2562
- files: string[];
2563
- verify: string;
2564
- inputs: string[];
2565
- expected_output: string[];
2566
- observability_impact: string;
2567
- full_plan_md: string;
2568
- sequence: number;
2569
- // ADR-011 Phase 2 escalation fields
2570
- blocker_source: string;
2571
- escalation_pending: number;
2572
- escalation_awaiting_review: number;
2573
- escalation_artifact_path: string | null;
2574
- escalation_override_applied_at: string | null;
2575
- }
2576
-
2577
- function parseTaskArrayColumn(raw: unknown): string[] {
2578
- if (typeof raw !== "string" || raw.trim() === "") return [];
2579
-
2580
- try {
2581
- const parsed = JSON.parse(raw);
2582
- if (Array.isArray(parsed)) return parsed.map((value) => String(value));
2583
- if (parsed === null || parsed === undefined || parsed === "") return [];
2584
- return [String(parsed)];
2585
- } catch {
2586
- // Older/corrupt rows may contain comma-separated strings instead of JSON.
2587
- return raw
2588
- .split(",")
2589
- .map((value) => value.trim())
2590
- .filter(Boolean);
2591
- }
2592
- }
2593
-
2594
- function rowToTask(row: Record<string, unknown>): TaskRow {
2595
- const parseTaskArray = (value: unknown): string[] => {
2596
- if (Array.isArray(value)) {
2597
- return value.filter((entry): entry is string => typeof entry === "string");
2598
- }
2599
- if (typeof value !== "string") return [];
2600
-
2601
- const trimmed = value.trim();
2602
- if (!trimmed) return [];
2603
-
2604
- try {
2605
- const parsed = JSON.parse(trimmed);
2606
- if (Array.isArray(parsed)) {
2607
- return parsed.filter((entry): entry is string => typeof entry === "string");
2608
- }
2609
- if (typeof parsed === "string" && parsed.trim()) {
2610
- return [parsed.trim()];
2611
- }
2612
- } catch {
2613
- // Older/corrupt DB rows may contain raw comma-separated paths instead of JSON arrays.
2614
- }
2615
-
2616
- return trimmed.split(",").map((entry) => entry.trim()).filter(Boolean);
2617
- };
2618
-
2619
- return {
2620
- milestone_id: row["milestone_id"] as string,
2621
- slice_id: row["slice_id"] as string,
2622
- id: row["id"] as string,
2623
- title: row["title"] as string,
2624
- status: row["status"] as string,
2625
- one_liner: row["one_liner"] as string,
2626
- narrative: row["narrative"] as string,
2627
- verification_result: row["verification_result"] as string,
2628
- duration: row["duration"] as string,
2629
- completed_at: (row["completed_at"] as string) ?? null,
2630
- blocker_discovered: (row["blocker_discovered"] as number) === 1,
2631
- deviations: row["deviations"] as string,
2632
- known_issues: row["known_issues"] as string,
2633
- key_files: parseTaskArrayColumn(row["key_files"]),
2634
- key_decisions: parseTaskArrayColumn(row["key_decisions"]),
2635
- full_summary_md: row["full_summary_md"] as string,
2636
- description: (row["description"] as string) ?? "",
2637
- estimate: (row["estimate"] as string) ?? "",
2638
- files: parseTaskArray(row["files"]),
2639
- verify: (row["verify"] as string) ?? "",
2640
- inputs: parseTaskArray(row["inputs"]),
2641
- expected_output: parseTaskArray(row["expected_output"]),
2642
- observability_impact: (row["observability_impact"] as string) ?? "",
2643
- full_plan_md: (row["full_plan_md"] as string) ?? "",
2644
- sequence: (row["sequence"] as number) ?? 0,
2645
- blocker_source: (row["blocker_source"] as string) ?? "",
2646
- escalation_pending: (row["escalation_pending"] as number) ?? 0,
2647
- escalation_awaiting_review: (row["escalation_awaiting_review"] as number) ?? 0,
2648
- escalation_artifact_path: (row["escalation_artifact_path"] as string) ?? null,
2649
- escalation_override_applied_at: (row["escalation_override_applied_at"] as string) ?? null,
2650
- };
2651
- }
2652
-
2653
1335
  export function getTask(milestoneId: string, sliceId: string, taskId: string): TaskRow | null {
2654
1336
  if (!currentDb) return null;
2655
1337
  const row = currentDb.prepare(
@@ -2831,72 +1513,6 @@ export function getVerificationEvidence(milestoneId: string, sliceId: string, ta
2831
1513
  return rows as unknown as VerificationEvidenceRow[];
2832
1514
  }
2833
1515
 
2834
- export interface MilestoneRow {
2835
- id: string;
2836
- title: string;
2837
- status: string;
2838
- depends_on: string[];
2839
- created_at: string;
2840
- completed_at: string | null;
2841
- vision: string;
2842
- success_criteria: string[];
2843
- key_risks: Array<{ risk: string; whyItMatters: string }>;
2844
- proof_strategy: Array<{ riskOrUnknown: string; retireIn: string; whatWillBeProven: string }>;
2845
- verification_contract: string;
2846
- verification_integration: string;
2847
- verification_operational: string;
2848
- verification_uat: string;
2849
- definition_of_done: string[];
2850
- requirement_coverage: string;
2851
- boundary_map_markdown: string;
2852
- sequence: number;
2853
- }
2854
-
2855
- function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
2856
- return {
2857
- id: row["id"] as string,
2858
- title: row["title"] as string,
2859
- status: row["status"] as string,
2860
- depends_on: JSON.parse((row["depends_on"] as string) || "[]"),
2861
- created_at: row["created_at"] as string,
2862
- completed_at: (row["completed_at"] as string) ?? null,
2863
- vision: (row["vision"] as string) ?? "",
2864
- success_criteria: JSON.parse((row["success_criteria"] as string) || "[]"),
2865
- key_risks: JSON.parse((row["key_risks"] as string) || "[]"),
2866
- proof_strategy: JSON.parse((row["proof_strategy"] as string) || "[]"),
2867
- verification_contract: (row["verification_contract"] as string) ?? "",
2868
- verification_integration: (row["verification_integration"] as string) ?? "",
2869
- verification_operational: (row["verification_operational"] as string) ?? "",
2870
- verification_uat: (row["verification_uat"] as string) ?? "",
2871
- definition_of_done: JSON.parse((row["definition_of_done"] as string) || "[]"),
2872
- requirement_coverage: (row["requirement_coverage"] as string) ?? "",
2873
- boundary_map_markdown: (row["boundary_map_markdown"] as string) ?? "",
2874
- sequence: Number(row["sequence"] ?? 0),
2875
- };
2876
- }
2877
-
2878
- export interface ArtifactRow {
2879
- path: string;
2880
- artifact_type: string;
2881
- milestone_id: string | null;
2882
- slice_id: string | null;
2883
- task_id: string | null;
2884
- full_content: string;
2885
- imported_at: string;
2886
- }
2887
-
2888
- function rowToArtifact(row: Record<string, unknown>): ArtifactRow {
2889
- return {
2890
- path: row["path"] as string,
2891
- artifact_type: row["artifact_type"] as string,
2892
- milestone_id: (row["milestone_id"] as string) ?? null,
2893
- slice_id: (row["slice_id"] as string) ?? null,
2894
- task_id: (row["task_id"] as string) ?? null,
2895
- full_content: row["full_content"] as string,
2896
- imported_at: row["imported_at"] as string,
2897
- };
2898
- }
2899
-
2900
1516
  export function getAllMilestones(): MilestoneRow[] {
2901
1517
  if (!currentDb) return [];
2902
1518
  const rows = currentDb.prepare(
@@ -2996,36 +1612,36 @@ export function getArtifact(path: string): ArtifactRow | null {
2996
1612
  // ─── Lightweight Query Variants (hot-path optimized) ─────────────────────
2997
1613
 
2998
1614
  /** Fast milestone status check — avoids deserializing JSON planning fields. */
2999
- export function getActiveMilestoneIdFromDb(): { id: string; status: string } | null {
1615
+ export function getActiveMilestoneIdFromDb(): IdStatusSummary | null {
3000
1616
  if (!currentDb) return null;
3001
1617
  const row = currentDb.prepare(
3002
1618
  "SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'parked') ORDER BY id LIMIT 1",
3003
1619
  ).get();
3004
1620
  if (!row) return null;
3005
- return { id: row["id"] as string, status: row["status"] as string };
1621
+ return rowToIdStatusSummary(row);
3006
1622
  }
3007
1623
 
3008
1624
  /** Fast slice status check — avoids deserializing JSON depends/planning fields. */
3009
- export function getSliceStatusSummary(milestoneId: string): Array<{ id: string; status: string }> {
1625
+ export function getSliceStatusSummary(milestoneId: string): IdStatusSummary[] {
3010
1626
  if (!currentDb) return [];
3011
1627
  return currentDb.prepare(
3012
1628
  "SELECT id, status FROM slices WHERE milestone_id = :mid ORDER BY sequence, id",
3013
- ).all({ ":mid": milestoneId }).map((r) => ({ id: r["id"] as string, status: r["status"] as string }));
1629
+ ).all({ ":mid": milestoneId }).map(rowToIdStatusSummary);
3014
1630
  }
3015
1631
 
3016
1632
  /** Fast task status check — avoids deserializing JSON arrays and large text fields. */
3017
- export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): { id: string; status: string; title: string } | null {
1633
+ export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): ActiveTaskSummary | null {
3018
1634
  if (!currentDb) return null;
3019
1635
  const row = currentDb.prepare(
3020
1636
  "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",
3021
1637
  ).get({ ":mid": milestoneId, ":sid": sliceId });
3022
1638
  if (!row) return null;
3023
- return { id: row["id"] as string, status: row["status"] as string, title: row["title"] as string };
1639
+ return rowToActiveTaskSummary(row);
3024
1640
  }
3025
1641
 
3026
1642
  /** Count tasks by status for a slice — useful for progress reporting without full row load. */
3027
- export function getSliceTaskCounts(milestoneId: string, sliceId: string): { total: number; done: number; pending: number } {
3028
- if (!currentDb) return { total: 0, done: 0, pending: 0 };
1643
+ export function getSliceTaskCounts(milestoneId: string, sliceId: string): TaskStatusCounts {
1644
+ if (!currentDb) return emptyTaskStatusCounts();
3029
1645
  const row = currentDb.prepare(
3030
1646
  `SELECT
3031
1647
  COUNT(*) as total,
@@ -3033,8 +1649,7 @@ export function getSliceTaskCounts(milestoneId: string, sliceId: string): { tota
3033
1649
  SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
3034
1650
  FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
3035
1651
  ).get({ ":mid": milestoneId, ":sid": sliceId });
3036
- if (!row) return { total: 0, done: 0, pending: 0 };
3037
- return { total: (row["total"] as number) ?? 0, done: (row["done"] as number) ?? 0, pending: (row["pending"] as number) ?? 0 };
1652
+ return rowToTaskStatusCounts(row);
3038
1653
  }
3039
1654
 
3040
1655
  // ─── Slice Dependencies (junction table) ─────────────────────────────────
@@ -3055,9 +1670,10 @@ export function syncSliceDependencies(milestoneId: string, sliceId: string, depe
3055
1670
  /** Get all slices that depend on a given slice. */
3056
1671
  export function getDependentSlices(milestoneId: string, sliceId: string): string[] {
3057
1672
  if (!currentDb) return [];
3058
- return currentDb.prepare(
1673
+ const rows = currentDb.prepare(
3059
1674
  "SELECT slice_id FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid",
3060
- ).all({ ":mid": milestoneId, ":sid": sliceId }).map((r) => r["slice_id"] as string);
1675
+ ).all({ ":mid": milestoneId, ":sid": sliceId });
1676
+ return rowsToStringColumn(rows, "slice_id");
3061
1677
  }
3062
1678
 
3063
1679
  // ─── Worktree DB Helpers ──────────────────────────────────────────────────
@@ -3526,21 +2142,6 @@ export function getLatestAssessmentByScope(
3526
2142
 
3527
2143
  // ─── Quality Gates ───────────────────────────────────────────────────────
3528
2144
 
3529
- function rowToGate(row: Record<string, unknown>): GateRow {
3530
- return {
3531
- milestone_id: row["milestone_id"] as string,
3532
- slice_id: row["slice_id"] as string,
3533
- gate_id: row["gate_id"] as GateId,
3534
- scope: row["scope"] as GateScope,
3535
- task_id: (row["task_id"] as string) ?? "",
3536
- status: row["status"] as GateStatus,
3537
- verdict: row["status"] === "pending" ? null : (row["verdict"] as GateVerdict),
3538
- rationale: (row["rationale"] as string) || "",
3539
- findings: (row["findings"] as string) || "",
3540
- evaluated_at: (row["evaluated_at"] as string) ?? null,
3541
- };
3542
- }
3543
-
3544
2145
  export function insertGateRow(g: {
3545
2146
  milestoneId: string;
3546
2147
  sliceId: string;