@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262

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 (325) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  4. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  5. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  6. package/dist/resources/extensions/browser-tools/index.js +68 -24
  7. package/dist/resources/extensions/browser-tools/state.js +12 -0
  8. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  9. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  10. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +87 -12
  13. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  14. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
  19. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  20. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  21. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  22. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  23. package/dist/resources/extensions/gsd/auto.js +40 -15
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  27. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  30. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  31. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  33. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  34. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  35. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  37. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  38. package/dist/resources/extensions/gsd/guided-flow.js +4 -1
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  40. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  41. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  42. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  43. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  46. package/dist/resources/extensions/gsd/preferences-models.js +111 -43
  47. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  49. package/dist/resources/extensions/gsd/preferences.js +4 -1
  50. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  55. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  56. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  57. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  58. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  59. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  63. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  64. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  65. package/dist/resources/extensions/gsd/state.js +7 -3
  66. package/dist/resources/extensions/gsd/tool-contract.js +15 -1
  67. package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
  68. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
  69. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  70. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  71. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
  72. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  73. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  74. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  75. package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
  76. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  77. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
  79. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  80. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  81. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  82. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  83. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  84. package/dist/resources/extensions/subagent/agents.js +1 -0
  85. package/dist/resources/extensions/subagent/index.js +27 -12
  86. package/dist/resources/extensions/subagent/launch.js +7 -2
  87. package/dist/web/standalone/.next/BUILD_ID +1 -1
  88. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  89. package/dist/web/standalone/.next/build-manifest.json +2 -2
  90. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  115. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  116. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  118. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  119. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  120. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  121. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  122. package/package.json +4 -4
  123. package/packages/cloud-mcp-gateway/package.json +2 -2
  124. package/packages/contracts/package.json +1 -1
  125. package/packages/daemon/package.json +4 -4
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  144. package/packages/gsd-agent-modes/package.json +7 -7
  145. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  146. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  147. package/packages/mcp-server/package.json +3 -3
  148. package/packages/native/dist/native.js +22 -0
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  152. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  153. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  154. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  155. package/packages/pi-ai/dist/models.generated.d.ts +174 -29
  156. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models.generated.js +178 -54
  158. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  161. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  162. package/packages/pi-ai/package.json +1 -1
  163. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  166. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  167. package/packages/pi-coding-agent/package.json +7 -7
  168. package/packages/pi-tui/dist/utils.d.ts +11 -0
  169. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/utils.js +119 -6
  171. package/packages/pi-tui/dist/utils.js.map +1 -1
  172. package/packages/pi-tui/package.json +2 -1
  173. package/packages/rpc-client/package.json +2 -2
  174. package/pkg/dist/theme/themes.js +1 -1
  175. package/pkg/dist/theme/themes.js.map +1 -1
  176. package/pkg/package.json +1 -1
  177. package/scripts/install/handoff.js +16 -3
  178. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  179. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  180. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  181. package/src/resources/extensions/browser-tools/index.ts +75 -27
  182. package/src/resources/extensions/browser-tools/state.ts +13 -0
  183. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  184. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  185. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  186. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  187. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  188. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  189. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  190. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  191. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  192. package/src/resources/extensions/gsd/auto/phases.ts +89 -15
  193. package/src/resources/extensions/gsd/auto/session.ts +24 -1
  194. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
  196. package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
  197. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  198. package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
  199. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  200. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  202. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  203. package/src/resources/extensions/gsd/auto.ts +41 -14
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  207. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  208. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  209. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  210. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  211. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  212. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  213. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  214. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  215. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  216. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  217. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  218. package/src/resources/extensions/gsd/guided-flow.ts +4 -1
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  220. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  221. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  222. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  223. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  224. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  225. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  226. package/src/resources/extensions/gsd/preferences-models.ts +113 -43
  227. package/src/resources/extensions/gsd/preferences-types.ts +47 -0
  228. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  229. package/src/resources/extensions/gsd/preferences.ts +5 -0
  230. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  231. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  232. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  234. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  235. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  236. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  237. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  238. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  239. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  241. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  242. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  243. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  244. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  245. package/src/resources/extensions/gsd/state.ts +7 -4
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
  247. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  249. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  250. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  251. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  252. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  253. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  254. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  255. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  256. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  257. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  258. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  259. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  260. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
  261. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  262. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  263. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  264. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  265. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  266. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
  267. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
  269. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  270. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  273. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  274. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  277. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  278. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
  279. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  280. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  281. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
  283. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  284. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  285. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  286. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  287. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  288. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  289. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  290. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  291. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  292. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  293. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  294. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
  297. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  298. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  299. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  300. package/src/resources/extensions/gsd/tool-contract.ts +29 -1
  301. package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
  302. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
  303. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  304. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
  306. package/src/resources/extensions/gsd/types.ts +1 -0
  307. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  308. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  309. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  310. package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
  311. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  312. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  313. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
  314. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  315. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  316. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  317. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  318. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  319. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  320. package/src/resources/extensions/subagent/agents.ts +4 -0
  321. package/src/resources/extensions/subagent/index.ts +28 -3
  322. package/src/resources/extensions/subagent/launch.ts +8 -0
  323. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  324. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  325. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -25,7 +25,7 @@
25
25
  import { createRequire } from "node:module";
26
26
  import { createHash } from "node:crypto";
27
27
  import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
28
- import { dirname } from "node:path";
28
+ import { dirname, join } from "node:path";
29
29
  import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
30
30
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
31
31
  import type { GsdWorkspace, MilestoneScope } from "./workspace.js";
@@ -760,6 +760,28 @@ export function checkpointDatabase(): void {
760
760
  } catch (e) { logWarning("db", `WAL checkpoint failed: ${(e as Error).message}`); }
761
761
  }
762
762
 
763
+ /**
764
+ * Copy the live database file to `.gsd/backups/<label>-<timestamp>.db` so a
765
+ * destructive operation (e.g. recover, which clears the hierarchy tables) is
766
+ * reversible. Checkpoints the WAL first so the snapshot is complete. Returns
767
+ * the backup path, or null if no DB is open or the copy failed.
768
+ */
769
+ export function backupDatabaseSnapshot(label: string): string | null {
770
+ if (!currentPath) return null;
771
+ try {
772
+ checkpointDatabase();
773
+ const backupsDir = join(dirname(currentPath), "backups");
774
+ mkdirSync(backupsDir, { recursive: true });
775
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
776
+ const dest = join(backupsDir, `${label}-${stamp}.db`);
777
+ copyFileSync(currentPath, dest);
778
+ return dest;
779
+ } catch (e) {
780
+ logWarning("db", `database snapshot failed: ${(e as Error).message}`);
781
+ return null;
782
+ }
783
+ }
784
+
763
785
  const _transactionRunner = createDbTransactionRunner();
764
786
 
765
787
  function createTransactionControls(db: DbAdapter) {
@@ -1260,14 +1282,14 @@ export function insertTask(t: {
1260
1282
  milestone_id, slice_id, id, title, status, one_liner, narrative,
1261
1283
  verification_result, duration, completed_at, blocker_discovered,
1262
1284
  deviations, known_issues, key_files, key_decisions, full_summary_md,
1263
- description, estimate, files, verify, inputs, expected_output, observability_impact, sequence
1264
- , target_repositories
1285
+ description, estimate, files, verify, inputs, expected_output,
1286
+ observability_impact, full_plan_md, target_repositories, sequence
1265
1287
  ) VALUES (
1266
1288
  :milestone_id, :slice_id, :id, :title, :status, :one_liner, :narrative,
1267
1289
  :verification_result, :duration, :completed_at, :blocker_discovered,
1268
1290
  :deviations, :known_issues, :key_files, :key_decisions, :full_summary_md,
1269
- :description, :estimate, :files, :verify, :inputs, :expected_output, :observability_impact, :sequence
1270
- , :target_repositories
1291
+ :description, :estimate, :files, :verify, :inputs, :expected_output,
1292
+ :observability_impact, :full_plan_md, :target_repositories, :sequence
1271
1293
  )
1272
1294
  ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
1273
1295
  title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
@@ -1290,6 +1312,7 @@ export function insertTask(t: {
1290
1312
  inputs = CASE WHEN NULLIF(:inputs, '[]') IS NOT NULL THEN :inputs ELSE tasks.inputs END,
1291
1313
  expected_output = CASE WHEN NULLIF(:expected_output, '[]') IS NOT NULL THEN :expected_output ELSE tasks.expected_output END,
1292
1314
  observability_impact = CASE WHEN NULLIF(:observability_impact, '') IS NOT NULL THEN :observability_impact ELSE tasks.observability_impact END,
1315
+ full_plan_md = CASE WHEN NULLIF(:full_plan_md, '') IS NOT NULL THEN :full_plan_md ELSE tasks.full_plan_md END,
1293
1316
  sequence = :sequence,
1294
1317
  target_repositories = CASE
1295
1318
  WHEN :raw_target_repositories IS NOT NULL THEN :target_repositories
@@ -1319,6 +1342,7 @@ export function insertTask(t: {
1319
1342
  ":inputs": JSON.stringify(t.planning?.inputs ?? []),
1320
1343
  ":expected_output": JSON.stringify(t.planning?.expectedOutput ?? []),
1321
1344
  ":observability_impact": t.planning?.observabilityImpact ?? "",
1345
+ ":full_plan_md": t.planning?.fullPlanMd ?? "",
1322
1346
  ":sequence": t.sequence ?? 0,
1323
1347
  ":target_repositories": JSON.stringify(t.planning?.targetRepositories ?? []),
1324
1348
  ":raw_target_repositories":
@@ -1855,7 +1879,13 @@ export interface ReconcileResult {
1855
1879
  slices: number;
1856
1880
  tasks: number;
1857
1881
  memories: number;
1882
+ replan_history: number;
1883
+ assessments: number;
1884
+ quality_gates: number;
1885
+ slice_dependencies: number;
1858
1886
  verification_evidence: number;
1887
+ gate_runs: number;
1888
+ milestone_commit_attributions: number;
1859
1889
  conflicts: string[];
1860
1890
  }
1861
1891
 
@@ -1863,7 +1893,23 @@ export function reconcileWorktreeDb(
1863
1893
  mainDbPath: string,
1864
1894
  worktreeDbPath: string,
1865
1895
  ): ReconcileResult {
1866
- const zero: ReconcileResult = { decisions: 0, requirements: 0, artifacts: 0, milestones: 0, slices: 0, tasks: 0, memories: 0, verification_evidence: 0, conflicts: [] };
1896
+ const zero: ReconcileResult = {
1897
+ decisions: 0,
1898
+ requirements: 0,
1899
+ artifacts: 0,
1900
+ milestones: 0,
1901
+ slices: 0,
1902
+ tasks: 0,
1903
+ memories: 0,
1904
+ replan_history: 0,
1905
+ assessments: 0,
1906
+ quality_gates: 0,
1907
+ slice_dependencies: 0,
1908
+ verification_evidence: 0,
1909
+ gate_runs: 0,
1910
+ milestone_commit_attributions: 0,
1911
+ conflicts: [],
1912
+ };
1867
1913
  if (!existsSync(worktreeDbPath)) return zero;
1868
1914
  // Guard: bail when both paths resolve to the same physical file.
1869
1915
  // ATTACHing a WAL-mode DB to itself corrupts the WAL (#2823).
@@ -1889,224 +1935,398 @@ export function reconcileWorktreeDb(
1889
1935
  try {
1890
1936
  adapter.exec(`ATTACH DATABASE '${worktreeDbPath}' AS wt`);
1891
1937
  try {
1892
- const wtInfo = adapter.prepare("PRAGMA wt.table_info('decisions')").all();
1938
+ function countChanges(result: unknown): number {
1939
+ return typeof result === "object" && result !== null ? ((result as { changes?: number }).changes ?? 0) : 0;
1940
+ }
1941
+
1942
+ function wtTableInfo(tableName: string): Array<Record<string, unknown>> {
1943
+ return adapter.prepare(`PRAGMA wt.table_info('${tableName}')`).all() as Array<Record<string, unknown>>;
1944
+ }
1945
+
1946
+ const wtInfo = wtTableInfo("decisions");
1947
+ const hasWtDecisions = wtInfo.length > 0;
1893
1948
  const hasMadeBy = wtInfo.some((col) => col["name"] === "made_by");
1894
1949
  // ADR-011: worktree may predate schema v16/v17. For missing columns we
1895
1950
  // fall through to the main DB's existing value (not a literal default)
1896
1951
  // so reconcile never silently clears state the main tree has recorded.
1897
1952
  const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
1898
- const wtMilestoneInfo = adapter.prepare("PRAGMA wt.table_info('milestones')").all();
1953
+ const wtRequirementInfo = wtTableInfo("requirements");
1954
+ const hasWtRequirements = wtRequirementInfo.length > 0;
1955
+ const wtMilestoneInfo = wtTableInfo("milestones");
1956
+ const hasWtMilestones = wtMilestoneInfo.length > 0;
1899
1957
  const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
1900
- const wtSliceInfo = adapter.prepare("PRAGMA wt.table_info('slices')").all();
1958
+ const wtSliceInfo = wtTableInfo("slices");
1959
+ const hasWtSlices = wtSliceInfo.length > 0;
1901
1960
  const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
1902
1961
  const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
1903
1962
  const hasSliceTargetRepositories = wtSliceInfo.some((col) => col["name"] === "target_repositories");
1904
- const wtTaskInfo = adapter.prepare("PRAGMA wt.table_info('tasks')").all();
1963
+ const wtTaskInfo = wtTableInfo("tasks");
1964
+ const hasWtTasks = wtTaskInfo.length > 0;
1905
1965
  const hasTaskTargetRepositories = wtTaskInfo.some((col) => col["name"] === "target_repositories");
1906
1966
  const hasBlockerSource = wtTaskInfo.some((col) => col["name"] === "blocker_source");
1907
1967
  const hasEscalationPending = wtTaskInfo.some((col) => col["name"] === "escalation_pending");
1908
1968
  const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
1909
1969
  const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
1910
1970
  const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
1911
- const wtArtifactInfo = adapter.prepare("PRAGMA wt.table_info('artifacts')").all();
1912
- const hasArtifactContentHash = wtArtifactInfo.some((col) => col["name"] === "content_hash");
1913
- const wtMemoryInfo = adapter.prepare("PRAGMA wt.table_info('memories')").all();
1971
+ const wtArtifactInfo = wtTableInfo("artifacts");
1972
+ const hasWtArtifacts = wtArtifactInfo.length > 0;
1973
+ const wtMemoryInfo = wtTableInfo("memories");
1974
+ const hasWtMemories = wtMemoryInfo.length > 0;
1914
1975
  const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
1915
1976
  const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
1916
1977
  const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
1917
1978
  const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
1979
+ const hasWtReplanHistory = wtTableInfo("replan_history").length > 0;
1980
+ const hasWtAssessments = wtTableInfo("assessments").length > 0;
1981
+ const hasWtQualityGates = wtTableInfo("quality_gates").length > 0;
1982
+ const hasWtSliceDependencies = wtTableInfo("slice_dependencies").length > 0;
1983
+ const hasWtVerificationEvidence = wtTableInfo("verification_evidence").length > 0;
1984
+ const hasWtGateRuns = wtTableInfo("gate_runs").length > 0;
1985
+ const hasWtMilestoneCommitAttributions = wtTableInfo("milestone_commit_attributions").length > 0;
1986
+
1987
+ if (hasWtDecisions) {
1988
+ const decConf = adapter.prepare(
1989
+ `SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${
1990
+ hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"
1991
+ } OR m.superseded_by IS NOT w.superseded_by`,
1992
+ ).all();
1993
+ for (const row of decConf) conflicts.push(`decision ${(row as Record<string, unknown>)["id"]}: modified in both`);
1994
+ }
1918
1995
 
1919
- const decConf = adapter.prepare(
1920
- `SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${
1921
- hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"
1922
- } OR m.superseded_by IS NOT w.superseded_by`,
1923
- ).all();
1924
- for (const row of decConf) conflicts.push(`decision ${(row as Record<string, unknown>)["id"]}: modified in both`);
1925
-
1926
- const reqConf = adapter.prepare(
1927
- `SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`,
1928
- ).all();
1929
- for (const row of reqConf) conflicts.push(`requirement ${(row as Record<string, unknown>)["id"]}: modified in both`);
1930
-
1931
- const merged: Omit<ReconcileResult, "conflicts"> = { decisions: 0, requirements: 0, artifacts: 0, milestones: 0, slices: 0, tasks: 0, memories: 0, verification_evidence: 0 };
1932
-
1933
- function countChanges(result: unknown): number {
1934
- return typeof result === "object" && result !== null ? ((result as { changes?: number }).changes ?? 0) : 0;
1996
+ if (hasWtRequirements) {
1997
+ const reqConf = adapter.prepare(
1998
+ `SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`,
1999
+ ).all();
2000
+ for (const row of reqConf) conflicts.push(`requirement ${(row as Record<string, unknown>)["id"]}: modified in both`);
1935
2001
  }
1936
2002
 
2003
+ const merged: Omit<ReconcileResult, "conflicts"> = {
2004
+ decisions: 0,
2005
+ requirements: 0,
2006
+ artifacts: 0,
2007
+ milestones: 0,
2008
+ slices: 0,
2009
+ tasks: 0,
2010
+ memories: 0,
2011
+ replan_history: 0,
2012
+ assessments: 0,
2013
+ quality_gates: 0,
2014
+ slice_dependencies: 0,
2015
+ verification_evidence: 0,
2016
+ gate_runs: 0,
2017
+ milestone_commit_attributions: 0,
2018
+ };
2019
+ const sliceTargetRepositoriesSql = hasSliceTargetRepositories
2020
+ ? `CASE
2021
+ WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
2022
+ THEN m.target_repositories
2023
+ ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
2024
+ END`
2025
+ : "COALESCE(m.target_repositories, '[]')";
2026
+ const taskTargetRepositoriesSql = hasTaskTargetRepositories
2027
+ ? `CASE
2028
+ WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
2029
+ THEN m.target_repositories
2030
+ ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
2031
+ END`
2032
+ : "COALESCE(m.target_repositories, '[]')";
2033
+
1937
2034
  adapter.exec("BEGIN");
1938
2035
  try {
1939
2036
  // Join the target decisions so we can prefer an existing main.source
1940
2037
  // when the worktree predates v16 — otherwise a write-through reconcile
1941
2038
  // would clobber 'escalation'-sourced decisions with the literal default.
1942
- merged.decisions = countChanges(adapter.prepare(`
1943
- INSERT OR REPLACE INTO decisions (
1944
- id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by
1945
- )
1946
- SELECT w.id, w.when_context, w.scope, w.decision, w.choice, w.rationale, w.revisable, ${
1947
- hasMadeBy ? "w.made_by" : "COALESCE(m.made_by, 'agent')"
1948
- }, ${
1949
- hasDecisionSource ? "w.source" : "COALESCE(m.source, 'discussion')"
1950
- }, w.superseded_by
1951
- FROM wt.decisions w
1952
- LEFT JOIN decisions m ON m.id = w.id
1953
- `).run());
1954
-
1955
- merged.requirements = countChanges(adapter.prepare(`
1956
- INSERT OR REPLACE INTO requirements (
1957
- id, class, status, description, why, source, primary_owner,
1958
- supporting_slices, validation, notes, full_content, superseded_by
1959
- )
1960
- SELECT id, class, status, description, why, source, primary_owner,
1961
- supporting_slices, validation, notes, full_content, superseded_by
1962
- FROM wt.requirements
1963
- `).run());
1964
-
1965
- // V27: preserve content_hash. If the worktree predates V27 (no column),
1966
- // fall back to the main DB's existing hash so reconcile doesn't null
1967
- // out integrity fingerprints on artifacts that were unchanged in wt.
1968
- merged.artifacts = countChanges(adapter.prepare(`
1969
- INSERT OR REPLACE INTO artifacts (
1970
- path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
1971
- )
1972
- SELECT w.path, w.artifact_type, w.milestone_id, w.slice_id, w.task_id, w.full_content, w.imported_at,
1973
- ${hasArtifactContentHash ? "w.content_hash" : "m.content_hash"}
1974
- FROM wt.artifacts w
1975
- LEFT JOIN artifacts m ON m.path = w.path
1976
- `).run());
2039
+ if (hasWtDecisions) {
2040
+ merged.decisions = countChanges(adapter.prepare(`
2041
+ INSERT INTO decisions (
2042
+ id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by
2043
+ )
2044
+ SELECT w.id, w.when_context, w.scope, w.decision, w.choice, w.rationale, w.revisable, ${
2045
+ hasMadeBy ? "w.made_by" : "COALESCE(m.made_by, 'agent')"
2046
+ }, ${
2047
+ hasDecisionSource ? "w.source" : "COALESCE(m.source, 'discussion')"
2048
+ }, w.superseded_by
2049
+ FROM wt.decisions w
2050
+ LEFT JOIN decisions m ON m.id = w.id
2051
+ WHERE true
2052
+ ON CONFLICT(id) DO UPDATE SET
2053
+ when_context = excluded.when_context,
2054
+ scope = excluded.scope,
2055
+ decision = excluded.decision,
2056
+ choice = excluded.choice,
2057
+ rationale = excluded.rationale,
2058
+ revisable = excluded.revisable,
2059
+ made_by = excluded.made_by,
2060
+ source = excluded.source,
2061
+ superseded_by = excluded.superseded_by
2062
+ `).run());
2063
+ }
2064
+
2065
+ if (hasWtRequirements) {
2066
+ merged.requirements = countChanges(adapter.prepare(`
2067
+ INSERT OR REPLACE INTO requirements (
2068
+ id, class, status, description, why, source, primary_owner,
2069
+ supporting_slices, validation, notes, full_content, superseded_by
2070
+ )
2071
+ SELECT id, class, status, description, why, source, primary_owner,
2072
+ supporting_slices, validation, notes, full_content, superseded_by
2073
+ FROM wt.requirements
2074
+ `).run());
2075
+ }
2076
+
2077
+ // Always recompute artifact hashes from the content being merged. Older
2078
+ // worktree DBs may not have content_hash at all, and migrated old DBs can
2079
+ // carry stale default/null hashes after their content changed.
2080
+ if (hasWtArtifacts) {
2081
+ const artifactRows = adapter.prepare(`
2082
+ SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
2083
+ FROM wt.artifacts
2084
+ `).all() as Array<Record<string, unknown>>;
2085
+ const artifactStmt = adapter.prepare(`
2086
+ INSERT OR REPLACE INTO artifacts (
2087
+ path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
2088
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2089
+ `);
2090
+ for (const row of artifactRows) {
2091
+ const fullContent = String(row["full_content"] ?? "");
2092
+ merged.artifacts += countChanges(artifactStmt.run(
2093
+ row["path"],
2094
+ row["artifact_type"],
2095
+ row["milestone_id"] ?? null,
2096
+ row["slice_id"] ?? null,
2097
+ row["task_id"] ?? null,
2098
+ fullContent,
2099
+ row["imported_at"],
2100
+ createHash("sha256").update(fullContent).digest("hex"),
2101
+ ));
2102
+ }
2103
+ }
1977
2104
 
1978
2105
  // Merge milestones — worktree may have updated status/planning fields.
1979
2106
  // Never downgrade status: complete > active > pre-planning (#4372).
1980
2107
  // A stale worktree may carry an older 'active' status for a milestone
1981
2108
  // that the main DB has already marked 'complete'; preserve the higher status.
1982
- merged.milestones = countChanges(adapter.prepare(`
1983
- INSERT OR REPLACE INTO milestones (
1984
- id, title, status, depends_on, created_at, completed_at,
1985
- vision, success_criteria, key_risks, proof_strategy,
1986
- verification_contract, verification_integration, verification_operational, verification_uat,
1987
- definition_of_done, requirement_coverage, boundary_map_markdown, sequence
1988
- )
1989
- SELECT w.id, w.title,
1990
- CASE
1991
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
1992
- THEN m.status ELSE w.status
1993
- END,
1994
- w.depends_on,
1995
- CASE
1996
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
1997
- THEN m.created_at ELSE w.created_at
1998
- END,
1999
- CASE
2000
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2001
- THEN m.completed_at ELSE w.completed_at
2002
- END,
2003
- w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
2004
- w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
2005
- w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
2006
- ${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
2007
- FROM wt.milestones w
2008
- LEFT JOIN milestones m ON m.id = w.id
2009
- `).run());
2109
+ if (hasWtMilestones) {
2110
+ merged.milestones = countChanges(adapter.prepare(`
2111
+ INSERT OR REPLACE INTO milestones (
2112
+ id, title, status, depends_on, created_at, completed_at,
2113
+ vision, success_criteria, key_risks, proof_strategy,
2114
+ verification_contract, verification_integration, verification_operational, verification_uat,
2115
+ definition_of_done, requirement_coverage, boundary_map_markdown, sequence
2116
+ )
2117
+ SELECT w.id, w.title,
2118
+ CASE
2119
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2120
+ THEN m.status ELSE w.status
2121
+ END,
2122
+ w.depends_on,
2123
+ CASE
2124
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2125
+ THEN m.created_at ELSE w.created_at
2126
+ END,
2127
+ CASE
2128
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2129
+ THEN m.completed_at ELSE w.completed_at
2130
+ END,
2131
+ w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
2132
+ w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
2133
+ w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
2134
+ ${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
2135
+ FROM wt.milestones w
2136
+ LEFT JOIN milestones m ON m.id = w.id
2137
+ `).run());
2138
+ }
2010
2139
 
2011
2140
  // Merge slices — preserve worktree progress but never downgrade completed status (#2558).
2012
2141
  // ADR-011 Phase 1: carry is_sketch + sketch_scope so reconcile doesn't
2013
2142
  // silently clear sketch metadata. When the worktree predates v16,
2014
2143
  // fall back to the main DB's existing value rather than a literal 0/''.
2015
- merged.slices = countChanges(adapter.prepare(`
2016
- INSERT OR REPLACE INTO slices (
2017
- milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
2018
- full_summary_md, full_uat_md, goal, success_criteria, proof_level,
2019
- integration_closure, observability_impact, target_repositories, sequence, replan_triggered_at,
2020
- is_sketch, sketch_scope
2021
- )
2022
- SELECT w.milestone_id, w.id, w.title,
2023
- CASE
2024
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2025
- THEN m.status ELSE w.status
2026
- END,
2027
- w.risk, w.depends, w.demo, w.created_at,
2028
- CASE
2029
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2030
- THEN m.completed_at ELSE w.completed_at
2031
- END,
2032
- w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
2033
- w.integration_closure, w.observability_impact,
2034
- ${hasSliceTargetRepositories ? "COALESCE(w.target_repositories, m.target_repositories, '[]')" : "COALESCE(m.target_repositories, '[]')"},
2035
- w.sequence, w.replan_triggered_at,
2036
- ${hasIsSketch ? "w.is_sketch" : "COALESCE(m.is_sketch, 0)"},
2037
- ${hasSketchScope ? "w.sketch_scope" : "COALESCE(m.sketch_scope, '')"}
2038
- FROM wt.slices w
2039
- LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
2040
- `).run());
2144
+ if (hasWtSlices) {
2145
+ merged.slices = countChanges(adapter.prepare(`
2146
+ INSERT OR REPLACE INTO slices (
2147
+ milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
2148
+ full_summary_md, full_uat_md, goal, success_criteria, proof_level,
2149
+ integration_closure, observability_impact, target_repositories, sequence, replan_triggered_at,
2150
+ is_sketch, sketch_scope
2151
+ )
2152
+ SELECT w.milestone_id, w.id, w.title,
2153
+ CASE
2154
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2155
+ THEN m.status ELSE w.status
2156
+ END,
2157
+ w.risk, w.depends, w.demo, w.created_at,
2158
+ CASE
2159
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2160
+ THEN m.completed_at ELSE w.completed_at
2161
+ END,
2162
+ w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
2163
+ w.integration_closure, w.observability_impact,
2164
+ ${sliceTargetRepositoriesSql},
2165
+ w.sequence, w.replan_triggered_at,
2166
+ ${hasIsSketch ? "w.is_sketch" : "COALESCE(m.is_sketch, 0)"},
2167
+ ${hasSketchScope ? "w.sketch_scope" : "COALESCE(m.sketch_scope, '')"}
2168
+ FROM wt.slices w
2169
+ LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
2170
+ `).run());
2171
+ }
2041
2172
 
2042
2173
  // Merge tasks — preserve execution results, never downgrade completed status (#2558).
2043
2174
  // ADR-011 P2: carry blocker_source + escalation_* columns so worktree reconcile
2044
2175
  // doesn't silently clear escalation state back to defaults.
2045
- merged.tasks = countChanges(adapter.prepare(`
2046
- INSERT OR REPLACE INTO tasks (
2047
- milestone_id, slice_id, id, title, status, one_liner, narrative,
2048
- verification_result, duration, completed_at, blocker_discovered,
2049
- deviations, known_issues, key_files, key_decisions, full_summary_md,
2050
- description, estimate, files, verify, inputs, expected_output,
2051
- observability_impact, full_plan_md, target_repositories, sequence,
2052
- blocker_source, escalation_pending, escalation_awaiting_review,
2053
- escalation_artifact_path, escalation_override_applied_at
2054
- )
2055
- SELECT w.milestone_id, w.slice_id, w.id, w.title,
2056
- CASE
2057
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2058
- THEN m.status ELSE w.status
2059
- END,
2060
- w.one_liner, w.narrative,
2061
- w.verification_result, w.duration,
2062
- CASE
2063
- WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2064
- THEN m.completed_at ELSE w.completed_at
2065
- END,
2066
- w.blocker_discovered,
2067
- w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
2068
- w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
2069
- w.observability_impact, w.full_plan_md,
2070
- ${hasTaskTargetRepositories ? "COALESCE(w.target_repositories, m.target_repositories, '[]')" : "COALESCE(m.target_repositories, '[]')"},
2071
- w.sequence,
2072
- ${hasBlockerSource ? "w.blocker_source" : "COALESCE(m.blocker_source, '')"},
2073
- ${hasEscalationPending ? "w.escalation_pending" : "COALESCE(m.escalation_pending, 0)"},
2074
- ${hasEscalationAwaiting ? "w.escalation_awaiting_review" : "COALESCE(m.escalation_awaiting_review, 0)"},
2075
- ${hasEscalationArtifact ? "w.escalation_artifact_path" : "m.escalation_artifact_path"},
2076
- ${hasEscalationOverride ? "w.escalation_override_applied_at" : "m.escalation_override_applied_at"}
2077
- FROM wt.tasks w
2078
- LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
2079
- `).run());
2176
+ if (hasWtTasks) {
2177
+ merged.tasks = countChanges(adapter.prepare(`
2178
+ INSERT OR REPLACE INTO tasks (
2179
+ milestone_id, slice_id, id, title, status, one_liner, narrative,
2180
+ verification_result, duration, completed_at, blocker_discovered,
2181
+ deviations, known_issues, key_files, key_decisions, full_summary_md,
2182
+ description, estimate, files, verify, inputs, expected_output,
2183
+ observability_impact, full_plan_md, target_repositories, sequence,
2184
+ blocker_source, escalation_pending, escalation_awaiting_review,
2185
+ escalation_artifact_path, escalation_override_applied_at
2186
+ )
2187
+ SELECT w.milestone_id, w.slice_id, w.id, w.title,
2188
+ CASE
2189
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2190
+ THEN m.status ELSE w.status
2191
+ END,
2192
+ w.one_liner, w.narrative,
2193
+ w.verification_result, w.duration,
2194
+ CASE
2195
+ WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
2196
+ THEN m.completed_at ELSE w.completed_at
2197
+ END,
2198
+ w.blocker_discovered,
2199
+ w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
2200
+ w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
2201
+ w.observability_impact, w.full_plan_md,
2202
+ ${taskTargetRepositoriesSql},
2203
+ w.sequence,
2204
+ ${hasBlockerSource ? "w.blocker_source" : "COALESCE(m.blocker_source, '')"},
2205
+ ${hasEscalationPending ? "w.escalation_pending" : "COALESCE(m.escalation_pending, 0)"},
2206
+ ${hasEscalationAwaiting ? "w.escalation_awaiting_review" : "COALESCE(m.escalation_awaiting_review, 0)"},
2207
+ ${hasEscalationArtifact ? "w.escalation_artifact_path" : "m.escalation_artifact_path"},
2208
+ ${hasEscalationOverride ? "w.escalation_override_applied_at" : "m.escalation_override_applied_at"}
2209
+ FROM wt.tasks w
2210
+ LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
2211
+ `).run());
2212
+ }
2080
2213
 
2081
2214
  // Merge memories — keep worktree-learned insights.
2082
2215
  // V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
2083
2216
  // column the wt may not yet have (older worktree DB), fall back to the
2084
2217
  // main DB's existing value via LEFT JOIN so reconcile never silently
2085
2218
  // resets these fields to defaults on rows that already had them.
2086
- merged.memories = countChanges(adapter.prepare(`
2087
- INSERT OR REPLACE INTO memories (
2088
- seq, id, category, content, confidence, source_unit_type, source_unit_id,
2089
- created_at, updated_at, superseded_by, hit_count,
2090
- scope, tags, structured_fields, last_hit_at
2091
- )
2092
- SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
2093
- w.created_at, w.updated_at, w.superseded_by, w.hit_count,
2094
- ${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
2095
- ${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
2096
- ${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
2097
- ${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
2098
- FROM wt.memories w
2099
- LEFT JOIN memories m ON m.id = w.id
2100
- `).run());
2219
+ if (hasWtMemories) {
2220
+ merged.memories = countChanges(adapter.prepare(`
2221
+ INSERT OR REPLACE INTO memories (
2222
+ seq, id, category, content, confidence, source_unit_type, source_unit_id,
2223
+ created_at, updated_at, superseded_by, hit_count,
2224
+ scope, tags, structured_fields, last_hit_at
2225
+ )
2226
+ SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
2227
+ w.created_at, w.updated_at, w.superseded_by, w.hit_count,
2228
+ ${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
2229
+ ${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
2230
+ ${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
2231
+ ${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
2232
+ FROM wt.memories w
2233
+ LEFT JOIN memories m ON m.id = w.id
2234
+ `).run());
2235
+ }
2236
+
2237
+ if (hasWtReplanHistory) {
2238
+ merged.replan_history = countChanges(adapter.prepare(`
2239
+ INSERT INTO replan_history (
2240
+ milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
2241
+ )
2242
+ SELECT w.milestone_id, w.slice_id, w.task_id, w.summary, w.previous_artifact_path, w.replacement_artifact_path, w.created_at
2243
+ FROM wt.replan_history w
2244
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
2245
+ AND NOT EXISTS (
2246
+ SELECT 1 FROM replan_history m
2247
+ WHERE m.milestone_id = w.milestone_id
2248
+ AND m.slice_id IS w.slice_id
2249
+ AND m.task_id IS w.task_id
2250
+ AND m.summary = w.summary
2251
+ AND m.previous_artifact_path IS w.previous_artifact_path
2252
+ AND m.replacement_artifact_path IS w.replacement_artifact_path
2253
+ )
2254
+ `).run());
2255
+ }
2256
+
2257
+ if (hasWtAssessments) {
2258
+ merged.assessments = countChanges(adapter.prepare(`
2259
+ INSERT OR REPLACE INTO assessments (
2260
+ path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
2261
+ )
2262
+ SELECT w.path, w.milestone_id, w.slice_id, w.task_id, w.status, w.scope, w.full_content, w.created_at
2263
+ FROM wt.assessments w
2264
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
2265
+ `).run());
2266
+ }
2267
+
2268
+ if (hasWtQualityGates) {
2269
+ merged.quality_gates = countChanges(adapter.prepare(`
2270
+ INSERT OR REPLACE INTO quality_gates (
2271
+ milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
2272
+ )
2273
+ SELECT w.milestone_id, w.slice_id, w.gate_id, w.scope, COALESCE(w.task_id, ''), w.status, w.verdict, w.rationale, w.findings, w.evaluated_at
2274
+ FROM wt.quality_gates w
2275
+ WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
2276
+ `).run());
2277
+ }
2278
+
2279
+ if (hasWtSliceDependencies) {
2280
+ merged.slice_dependencies = countChanges(adapter.prepare(`
2281
+ INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id)
2282
+ SELECT w.milestone_id, w.slice_id, w.depends_on_slice_id
2283
+ FROM wt.slice_dependencies w
2284
+ WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
2285
+ AND EXISTS (SELECT 1 FROM slices d WHERE d.milestone_id = w.milestone_id AND d.id = w.depends_on_slice_id)
2286
+ `).run());
2287
+ }
2101
2288
 
2102
2289
  // Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
2103
- merged.verification_evidence = countChanges(adapter.prepare(`
2104
- INSERT OR IGNORE INTO verification_evidence (
2105
- task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
2106
- )
2107
- SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
2108
- FROM wt.verification_evidence
2109
- `).run());
2290
+ if (hasWtVerificationEvidence) {
2291
+ merged.verification_evidence = countChanges(adapter.prepare(`
2292
+ INSERT OR IGNORE INTO verification_evidence (
2293
+ task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
2294
+ )
2295
+ SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
2296
+ FROM wt.verification_evidence
2297
+ `).run());
2298
+ }
2299
+
2300
+ if (hasWtGateRuns) {
2301
+ merged.gate_runs = countChanges(adapter.prepare(`
2302
+ INSERT INTO gate_runs (
2303
+ trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
2304
+ outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
2305
+ )
2306
+ SELECT w.trace_id, w.turn_id, w.gate_id, w.gate_type, w.unit_type, w.unit_id, w.milestone_id, w.slice_id, w.task_id,
2307
+ w.outcome, w.failure_class, w.rationale, w.findings, w.attempt, w.max_attempts, w.retryable, w.evaluated_at
2308
+ FROM wt.gate_runs w
2309
+ WHERE NOT EXISTS (
2310
+ SELECT 1 FROM gate_runs m
2311
+ WHERE m.trace_id = w.trace_id
2312
+ AND m.turn_id = w.turn_id
2313
+ AND m.gate_id = w.gate_id
2314
+ AND m.attempt = w.attempt
2315
+ AND m.evaluated_at = w.evaluated_at
2316
+ )
2317
+ `).run());
2318
+ }
2319
+
2320
+ if (hasWtMilestoneCommitAttributions) {
2321
+ merged.milestone_commit_attributions = countChanges(adapter.prepare(`
2322
+ INSERT OR REPLACE INTO milestone_commit_attributions (
2323
+ commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
2324
+ )
2325
+ SELECT w.commit_sha, w.milestone_id, w.slice_id, w.task_id, w.source, w.confidence, w.files_json, w.created_at
2326
+ FROM wt.milestone_commit_attributions w
2327
+ WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
2328
+ `).run());
2329
+ }
2110
2330
 
2111
2331
  adapter.exec("COMMIT");
2112
2332
  } catch (txErr) {
@@ -2364,12 +2584,13 @@ export function saveGateResult(g: {
2364
2584
  findings: string;
2365
2585
  }): void {
2366
2586
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2367
- currentDb.prepare(
2587
+ const evaluatedAt = new Date().toISOString();
2588
+ const result = currentDb.prepare(
2368
2589
  `UPDATE quality_gates
2369
2590
  SET status = 'complete', verdict = :verdict, rationale = :rationale,
2370
2591
  findings = :findings, evaluated_at = :evaluated_at
2371
2592
  WHERE milestone_id = :mid AND slice_id = :sid AND gate_id = :gid
2372
- AND task_id = :tid`,
2593
+ AND (task_id = :tid OR (:tid = '' AND task_id IS NULL))`,
2373
2594
  ).run({
2374
2595
  ":mid": g.milestoneId,
2375
2596
  ":sid": g.sliceId,
@@ -2378,8 +2599,15 @@ export function saveGateResult(g: {
2378
2599
  ":verdict": g.verdict,
2379
2600
  ":rationale": g.rationale,
2380
2601
  ":findings": g.findings,
2381
- ":evaluated_at": new Date().toISOString(),
2382
- });
2602
+ ":evaluated_at": evaluatedAt,
2603
+ }) as { changes?: number };
2604
+
2605
+ if ((result.changes ?? 0) === 0) {
2606
+ throw new GSDError(
2607
+ GSD_STALE_STATE,
2608
+ `quality gate row not found for ${g.milestoneId}/${g.sliceId}/${g.gateId}${g.taskId ? `/${g.taskId}` : ""}`,
2609
+ );
2610
+ }
2383
2611
 
2384
2612
  const outcome =
2385
2613
  g.verdict === "pass"
@@ -2402,7 +2630,7 @@ export function saveGateResult(g: {
2402
2630
  attempt: 1,
2403
2631
  maxAttempts: 1,
2404
2632
  retryable: false,
2405
- evaluatedAt: new Date().toISOString(),
2633
+ evaluatedAt,
2406
2634
  });
2407
2635
  }
2408
2636
 
@@ -2438,6 +2666,30 @@ export function markAllGatesOmitted(milestoneId: string, sliceId: string): void
2438
2666
  });
2439
2667
  }
2440
2668
 
2669
+ export function markPendingGatesOmittedForTurn(
2670
+ milestoneId: string,
2671
+ sliceId: string,
2672
+ turn: OwnerTurn,
2673
+ ): void {
2674
+ if (!currentDb) return;
2675
+ const gateIds = [...getGateIdsForTurn(turn)];
2676
+ if (gateIds.length === 0) return;
2677
+ const placeholders = gateIds.map((_, i) => `:gid${i}`).join(",");
2678
+ const params: Record<string, unknown> = {
2679
+ ":mid": milestoneId,
2680
+ ":sid": sliceId,
2681
+ ":now": new Date().toISOString(),
2682
+ };
2683
+ gateIds.forEach((id, index) => {
2684
+ params[`:gid${index}`] = id;
2685
+ });
2686
+ currentDb.prepare(
2687
+ `UPDATE quality_gates SET status = 'complete', verdict = 'omitted', evaluated_at = :now
2688
+ WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'
2689
+ AND gate_id IN (${placeholders})`,
2690
+ ).run(params);
2691
+ }
2692
+
2441
2693
  export function getPendingSliceGateCount(milestoneId: string, sliceId: string): number {
2442
2694
  if (!currentDb) return 0;
2443
2695
  const row = currentDb.prepare(
@@ -2858,21 +3110,75 @@ export function upsertQualityGate(g: {
2858
3110
 
2859
3111
  /**
2860
3112
  * Atomically replace all workflow state from a manifest. Lifted verbatim from
2861
- * workflow-manifest.ts so the single-writer invariant holds. Only touches
2862
- * engine tables + decisions. Does NOT modify artifacts or memories.
3113
+ * workflow-manifest.ts so the single-writer invariant holds. Restores
3114
+ * correctness-bearing workflow tables; runtime soft state and append-only audit
3115
+ * streams stay outside this recovery path.
2863
3116
  */
2864
3117
  export function restoreManifest(manifest: StateManifest): void {
2865
3118
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2866
3119
  const db = currentDb;
2867
3120
 
2868
3121
  transaction(() => {
2869
- // Clear engine tables (order matters for foreign-key-like consistency)
3122
+ const restoredMilestoneIds = new Set(manifest.milestones.map((m) => m.id));
3123
+ const restoredSliceKeys = new Set(manifest.slices.map((s) => JSON.stringify([s.milestone_id, s.id])));
3124
+ const preservedReplanHistory = manifest.replan_history === undefined
3125
+ ? db.prepare("SELECT * FROM replan_history ORDER BY id").all() as unknown as NonNullable<StateManifest["replan_history"]>
3126
+ : [];
3127
+ const preservedAssessments = manifest.assessments === undefined
3128
+ ? db.prepare("SELECT * FROM assessments ORDER BY path").all() as unknown as NonNullable<StateManifest["assessments"]>
3129
+ : [];
3130
+ const preservedQualityGates = manifest.quality_gates === undefined
3131
+ ? db.prepare("SELECT * FROM quality_gates ORDER BY milestone_id, slice_id, gate_id, task_id").all() as unknown as NonNullable<StateManifest["quality_gates"]>
3132
+ : [];
3133
+ const preservedCommitAttributions = manifest.milestone_commit_attributions === undefined
3134
+ ? db.prepare("SELECT * FROM milestone_commit_attributions ORDER BY milestone_id, commit_sha").all() as unknown as NonNullable<StateManifest["milestone_commit_attributions"]>
3135
+ : [];
3136
+
3137
+ // Clear workflow tables in dependency order.
2870
3138
  db.exec("DELETE FROM verification_evidence");
3139
+ db.exec("DELETE FROM quality_gates");
3140
+ db.exec("DELETE FROM slice_dependencies");
3141
+ db.exec("DELETE FROM assessments");
3142
+ db.exec("DELETE FROM replan_history");
3143
+ db.exec("DELETE FROM milestone_commit_attributions");
2871
3144
  db.exec("DELETE FROM tasks");
2872
3145
  db.exec("DELETE FROM slices");
2873
3146
  db.exec("DELETE FROM milestone_leases");
2874
3147
  db.exec("DELETE FROM milestones");
2875
3148
  db.exec("DELETE FROM decisions WHERE 1=1");
3149
+ db.exec(`DELETE FROM memories WHERE category = 'architecture' AND structured_fields LIKE '%"sourceDecisionId":"%'`);
3150
+ if (manifest.artifacts !== undefined) db.exec("DELETE FROM artifacts");
3151
+ if (manifest.requirements !== undefined) db.exec("DELETE FROM requirements");
3152
+
3153
+ if (manifest.requirements !== undefined) {
3154
+ const reqStmt = db.prepare(
3155
+ `INSERT INTO requirements (
3156
+ id, class, status, description, why, source, primary_owner,
3157
+ supporting_slices, validation, notes, full_content, superseded_by
3158
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3159
+ );
3160
+ for (const r of manifest.requirements) {
3161
+ reqStmt.run(
3162
+ r.id, r.class, r.status, r.description, r.why, r.source, r.primary_owner,
3163
+ r.supporting_slices, r.validation, r.notes, r.full_content, r.superseded_by,
3164
+ );
3165
+ }
3166
+ }
3167
+
3168
+ if (manifest.artifacts !== undefined) {
3169
+ const artStmt = db.prepare(
3170
+ `INSERT INTO artifacts (
3171
+ path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
3172
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3173
+ );
3174
+ for (const a of manifest.artifacts) {
3175
+ const fullContent = a.full_content ?? "";
3176
+ artStmt.run(
3177
+ a.path, a.artifact_type, a.milestone_id, a.slice_id, a.task_id,
3178
+ fullContent, a.imported_at, a.content_hash ?? createHash("sha256").update(fullContent).digest("hex"),
3179
+ );
3180
+ }
3181
+ }
2876
3182
 
2877
3183
  // Restore milestones
2878
3184
  const msStmt = db.prepare(
@@ -2914,16 +3220,25 @@ export function restoreManifest(manifest: StateManifest): void {
2914
3220
  );
2915
3221
  }
2916
3222
 
3223
+ const depStmt = db.prepare(
3224
+ "INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (?, ?, ?)",
3225
+ );
3226
+ for (const s of manifest.slices) {
3227
+ for (const dep of s.depends ?? []) {
3228
+ depStmt.run(s.milestone_id, s.id, dep);
3229
+ }
3230
+ }
3231
+
2917
3232
  // Restore tasks (ADR-011 P2: includes blocker_source + escalation_* columns)
2918
3233
  const tkStmt = db.prepare(
2919
3234
  `INSERT INTO tasks (milestone_id, slice_id, id, title, status,
2920
3235
  one_liner, narrative, verification_result, duration, completed_at,
2921
3236
  blocker_discovered, deviations, known_issues, key_files, key_decisions,
2922
3237
  full_summary_md, description, estimate, files, verify,
2923
- inputs, expected_output, observability_impact, target_repositories, sequence,
3238
+ inputs, expected_output, observability_impact, full_plan_md, target_repositories, sequence,
2924
3239
  blocker_source, escalation_pending, escalation_awaiting_review,
2925
3240
  escalation_artifact_path, escalation_override_applied_at)
2926
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3241
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2927
3242
  );
2928
3243
  for (const t of manifest.tasks) {
2929
3244
  tkStmt.run(
@@ -2933,7 +3248,7 @@ export function restoreManifest(manifest: StateManifest): void {
2933
3248
  JSON.stringify(t.key_files), JSON.stringify(t.key_decisions),
2934
3249
  t.full_summary_md, t.description, t.estimate, JSON.stringify(t.files), t.verify,
2935
3250
  JSON.stringify(t.inputs), JSON.stringify(t.expected_output),
2936
- t.observability_impact, JSON.stringify(t.target_repositories ?? []), t.sequence,
3251
+ t.observability_impact, t.full_plan_md ?? "", JSON.stringify(t.target_repositories ?? []), t.sequence,
2937
3252
  t.blocker_source ?? "",
2938
3253
  t.escalation_pending ?? 0,
2939
3254
  t.escalation_awaiting_review ?? 0,
@@ -2951,6 +3266,69 @@ export function restoreManifest(manifest: StateManifest): void {
2951
3266
  dcStmt.run(d.seq, d.id, d.when_context, d.scope, d.decision, d.choice, d.rationale, d.revisable, d.made_by, d.source ?? "discussion", d.superseded_by);
2952
3267
  }
2953
3268
 
3269
+ const replanHistoryRows = manifest.replan_history ?? preservedReplanHistory.filter((r) => restoredMilestoneIds.has(r.milestone_id));
3270
+ if (replanHistoryRows.length > 0) {
3271
+ const replStmt = db.prepare(
3272
+ `INSERT INTO replan_history (
3273
+ id, milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
3274
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3275
+ );
3276
+ for (const r of replanHistoryRows) {
3277
+ replStmt.run(
3278
+ r.id, r.milestone_id, r.slice_id, r.task_id, r.summary,
3279
+ r.previous_artifact_path, r.replacement_artifact_path, r.created_at,
3280
+ );
3281
+ }
3282
+ }
3283
+
3284
+ const assessmentRows = manifest.assessments ?? preservedAssessments.filter((a) => restoredMilestoneIds.has(a.milestone_id));
3285
+ if (assessmentRows.length > 0) {
3286
+ const assessStmt = db.prepare(
3287
+ `INSERT INTO assessments (
3288
+ path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
3289
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3290
+ );
3291
+ for (const a of assessmentRows) {
3292
+ assessStmt.run(
3293
+ a.path, a.milestone_id, a.slice_id, a.task_id,
3294
+ a.status, a.scope, a.full_content, a.created_at,
3295
+ );
3296
+ }
3297
+ }
3298
+
3299
+ const qualityGateRows = manifest.quality_gates ?? preservedQualityGates.filter((g) => (
3300
+ restoredSliceKeys.has(JSON.stringify([g.milestone_id, g.slice_id]))
3301
+ ));
3302
+ if (qualityGateRows.length > 0) {
3303
+ const gateStmt = db.prepare(
3304
+ `INSERT INTO quality_gates (
3305
+ milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
3306
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3307
+ );
3308
+ for (const g of qualityGateRows) {
3309
+ gateStmt.run(
3310
+ g.milestone_id, g.slice_id, g.gate_id, g.scope, g.task_id,
3311
+ g.status, g.verdict ?? "", g.rationale, g.findings, g.evaluated_at,
3312
+ );
3313
+ }
3314
+ }
3315
+
3316
+ const commitAttributionRows = manifest.milestone_commit_attributions ??
3317
+ preservedCommitAttributions.filter((a) => restoredMilestoneIds.has(a.milestone_id));
3318
+ if (commitAttributionRows.length > 0) {
3319
+ const attrStmt = db.prepare(
3320
+ `INSERT OR REPLACE INTO milestone_commit_attributions (
3321
+ commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
3322
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3323
+ );
3324
+ for (const a of commitAttributionRows) {
3325
+ attrStmt.run(
3326
+ a.commit_sha, a.milestone_id, a.slice_id, a.task_id,
3327
+ a.source, a.confidence, a.files_json, a.created_at,
3328
+ );
3329
+ }
3330
+ }
3331
+
2954
3332
  // Restore verification evidence
2955
3333
  const evStmt = db.prepare(
2956
3334
  `INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)