@opengsd/gsd-pi 1.8.1 → 1.9.0

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 (339) hide show
  1. package/README.md +10 -8
  2. package/dist/cli.js +5 -1
  3. package/dist/headless-events.d.ts +2 -1
  4. package/dist/headless-events.js +11 -3
  5. package/dist/headless-milestone-readiness.d.ts +29 -0
  6. package/dist/headless-milestone-readiness.js +161 -0
  7. package/dist/headless.js +25 -2
  8. package/dist/print-mode-exit.d.ts +12 -0
  9. package/dist/print-mode-exit.js +14 -0
  10. package/dist/resources/.managed-resources-content-hash +1 -1
  11. package/dist/resources/GSD-WORKFLOW.md +16 -47
  12. package/dist/resources/extensions/browser-tools/capture.js +2 -77
  13. package/dist/resources/extensions/browser-tools/screenshot-constraints.js +83 -0
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -2
  15. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +30 -2
  16. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -19
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -0
  19. package/dist/resources/extensions/gsd/auto-prompts.js +35 -1
  20. package/dist/resources/extensions/gsd/auto.js +5 -1
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +78 -4
  22. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +84 -26
  23. package/dist/resources/extensions/gsd/command-feedback.js +16 -12
  24. package/dist/resources/extensions/gsd/compat/compat-marker.js +19 -2
  25. package/dist/resources/extensions/gsd/db/command-queue.js +7 -2
  26. package/dist/resources/extensions/gsd/db/engine.js +51 -16
  27. package/dist/resources/extensions/gsd/db/writers/cascades.js +5 -1
  28. package/dist/resources/extensions/gsd/db/writers/status.js +13 -3
  29. package/dist/resources/extensions/gsd/db-base-schema.js +28 -0
  30. package/dist/resources/extensions/gsd/db-migration-steps.js +30 -0
  31. package/dist/resources/extensions/gsd/db-schema-metadata.js +4 -1
  32. package/dist/resources/extensions/gsd/docs/preferences-reference.md +4 -4
  33. package/dist/resources/extensions/gsd/flat-phase-migration.js +156 -21
  34. package/dist/resources/extensions/gsd/gsd-db.js +187 -45
  35. package/dist/resources/extensions/gsd/guided-flow.js +29 -10
  36. package/dist/resources/extensions/gsd/markdown-renderer.js +16 -2
  37. package/dist/resources/extensions/gsd/md-importer.js +102 -14
  38. package/dist/resources/extensions/gsd/migrate/command.js +19 -162
  39. package/dist/resources/extensions/gsd/migrate/execution.js +98 -0
  40. package/dist/resources/extensions/gsd/migrate/plan.js +49 -0
  41. package/dist/resources/extensions/gsd/migrate/planning-writer.js +12 -17
  42. package/dist/resources/extensions/gsd/migrate/presentation.js +64 -0
  43. package/dist/resources/extensions/gsd/parallel-eligibility.js +5 -4
  44. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +28 -43
  45. package/dist/resources/extensions/gsd/paths.js +4 -1
  46. package/dist/resources/extensions/gsd/phase-anchor.js +7 -2
  47. package/dist/resources/extensions/gsd/post-execution-checks.js +4 -4
  48. package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  49. package/dist/resources/extensions/gsd/safety/evidence-collector.js +26 -1
  50. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +30 -5
  51. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +27 -13
  52. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +13 -3
  53. package/dist/resources/extensions/gsd/stop-notice.js +15 -1
  54. package/dist/resources/extensions/gsd/sync-lock.js +13 -7
  55. package/dist/resources/extensions/gsd/tools/complete-task.js +82 -1
  56. package/dist/resources/extensions/gsd/tools/plan-task.js +13 -15
  57. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +18 -15
  58. package/dist/resources/extensions/gsd/tools/replan-task.js +128 -0
  59. package/dist/resources/extensions/gsd/tools/rework-brief.js +74 -0
  60. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -0
  61. package/dist/resources/extensions/gsd/unit-registry.js +4 -0
  62. package/dist/resources/extensions/gsd/uok/plan-v2.js +19 -3
  63. package/dist/resources/extensions/gsd/worktree-state-projection.js +6 -0
  64. package/dist/resources/extensions/mac-tools/index.js +31 -9
  65. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  66. package/dist/web/standalone/.next/BUILD_ID +1 -1
  67. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  68. package/dist/web/standalone/.next/build-manifest.json +2 -2
  69. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  94. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  96. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  97. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/static/css/3b1ceb7abfb9a230.css +1 -0
  101. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  102. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  103. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  104. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  105. package/dist/web/standalone/package.json +0 -1
  106. package/dist/web-mode.js +31 -3
  107. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  108. package/integrations/hermes/pyproject.toml +1 -1
  109. package/package.json +12 -10
  110. package/packages/cloud-mcp-gateway/package.json +2 -2
  111. package/packages/contracts/dist/workflow.d.ts +14 -0
  112. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  113. package/packages/contracts/dist/workflow.js +16 -0
  114. package/packages/contracts/dist/workflow.js.map +1 -1
  115. package/packages/contracts/package.json +1 -1
  116. package/packages/daemon/dist/cloud-config.d.ts +1 -1
  117. package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
  118. package/packages/daemon/dist/cloud-config.js +5 -1
  119. package/packages/daemon/dist/cloud-config.js.map +1 -1
  120. package/packages/daemon/dist/cloud-config.test.js +51 -1
  121. package/packages/daemon/dist/cloud-config.test.js.map +1 -1
  122. package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -1
  123. package/packages/daemon/dist/local-tool-executor.js +2 -0
  124. package/packages/daemon/dist/local-tool-executor.js.map +1 -1
  125. package/packages/daemon/package.json +6 -6
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/package.json +7 -7
  128. package/packages/gsd-cloud/dist/cli.d.ts +5 -0
  129. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -0
  130. package/packages/gsd-cloud/dist/cli.js +160 -0
  131. package/packages/gsd-cloud/dist/cli.js.map +1 -0
  132. package/packages/gsd-cloud/dist/cloud-config.d.ts +19 -0
  133. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -0
  134. package/packages/gsd-cloud/dist/cloud-config.js +231 -0
  135. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -0
  136. package/packages/gsd-cloud/dist/cloud-config.test.d.ts +2 -0
  137. package/packages/gsd-cloud/dist/cloud-config.test.d.ts.map +1 -0
  138. package/packages/gsd-cloud/dist/cloud-config.test.js +118 -0
  139. package/packages/gsd-cloud/dist/cloud-config.test.js.map +1 -0
  140. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts +2 -0
  141. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts.map +1 -0
  142. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js +37 -0
  143. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js.map +1 -0
  144. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +35 -0
  145. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -0
  146. package/packages/gsd-cloud/dist/cloud-runtime.js +258 -0
  147. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -0
  148. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts +2 -0
  149. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts.map +1 -0
  150. package/packages/gsd-cloud/dist/cloud-runtime.test.js +88 -0
  151. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -0
  152. package/packages/gsd-cloud/dist/cloud-token.d.ts +3 -0
  153. package/packages/gsd-cloud/dist/cloud-token.d.ts.map +1 -0
  154. package/packages/gsd-cloud/dist/cloud-token.js +37 -0
  155. package/packages/gsd-cloud/dist/cloud-token.js.map +1 -0
  156. package/packages/gsd-cloud/dist/config.d.ts +17 -0
  157. package/packages/gsd-cloud/dist/config.d.ts.map +1 -0
  158. package/packages/gsd-cloud/dist/config.js +146 -0
  159. package/packages/gsd-cloud/dist/config.js.map +1 -0
  160. package/packages/gsd-cloud/dist/device-flow.d.ts +24 -0
  161. package/packages/gsd-cloud/dist/device-flow.d.ts.map +1 -0
  162. package/packages/gsd-cloud/dist/device-flow.js +128 -0
  163. package/packages/gsd-cloud/dist/device-flow.js.map +1 -0
  164. package/packages/gsd-cloud/dist/device-flow.test.d.ts +2 -0
  165. package/packages/gsd-cloud/dist/device-flow.test.d.ts.map +1 -0
  166. package/packages/gsd-cloud/dist/device-flow.test.js +155 -0
  167. package/packages/gsd-cloud/dist/device-flow.test.js.map +1 -0
  168. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts +6 -0
  169. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts.map +1 -0
  170. package/packages/gsd-cloud/dist/executors/claude-executor.js +15 -0
  171. package/packages/gsd-cloud/dist/executors/claude-executor.js.map +1 -0
  172. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts +6 -0
  173. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts.map +1 -0
  174. package/packages/gsd-cloud/dist/executors/codex-executor.js +15 -0
  175. package/packages/gsd-cloud/dist/executors/codex-executor.js.map +1 -0
  176. package/packages/gsd-cloud/dist/executors/executor.d.ts +28 -0
  177. package/packages/gsd-cloud/dist/executors/executor.d.ts.map +1 -0
  178. package/packages/gsd-cloud/dist/executors/executor.js +11 -0
  179. package/packages/gsd-cloud/dist/executors/executor.js.map +1 -0
  180. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts +34 -0
  181. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts.map +1 -0
  182. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js +179 -0
  183. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js.map +1 -0
  184. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts +2 -0
  185. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts.map +1 -0
  186. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js +44 -0
  187. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js.map +1 -0
  188. package/packages/gsd-cloud/dist/executors/index.d.ts +17 -0
  189. package/packages/gsd-cloud/dist/executors/index.d.ts.map +1 -0
  190. package/packages/gsd-cloud/dist/executors/index.js +29 -0
  191. package/packages/gsd-cloud/dist/executors/index.js.map +1 -0
  192. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts +36 -0
  193. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts.map +1 -0
  194. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js +176 -0
  195. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js.map +1 -0
  196. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts +2 -0
  197. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts.map +1 -0
  198. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js +34 -0
  199. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js.map +1 -0
  200. package/packages/gsd-cloud/dist/index.d.ts +7 -0
  201. package/packages/gsd-cloud/dist/index.d.ts.map +1 -0
  202. package/packages/gsd-cloud/dist/index.js +8 -0
  203. package/packages/gsd-cloud/dist/index.js.map +1 -0
  204. package/packages/gsd-cloud/dist/inject-gateway.d.ts +16 -0
  205. package/packages/gsd-cloud/dist/inject-gateway.d.ts.map +1 -0
  206. package/packages/gsd-cloud/dist/inject-gateway.js +29 -0
  207. package/packages/gsd-cloud/dist/inject-gateway.js.map +1 -0
  208. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts +2 -0
  209. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts.map +1 -0
  210. package/packages/gsd-cloud/dist/inject-gateway.test.js +41 -0
  211. package/packages/gsd-cloud/dist/inject-gateway.test.js.map +1 -0
  212. package/packages/gsd-cloud/dist/logger.d.ts +25 -0
  213. package/packages/gsd-cloud/dist/logger.d.ts.map +1 -0
  214. package/packages/gsd-cloud/dist/logger.js +72 -0
  215. package/packages/gsd-cloud/dist/logger.js.map +1 -0
  216. package/packages/gsd-cloud/dist/types.d.ts +51 -0
  217. package/packages/gsd-cloud/dist/types.d.ts.map +1 -0
  218. package/packages/gsd-cloud/dist/types.js +9 -0
  219. package/packages/gsd-cloud/dist/types.js.map +1 -0
  220. package/packages/gsd-cloud/package.json +3 -3
  221. package/packages/mcp-server/README.md +8 -0
  222. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  223. package/packages/mcp-server/dist/workflow-tools.js +64 -2
  224. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  225. package/packages/mcp-server/package.json +4 -4
  226. package/packages/native/package.json +1 -1
  227. package/packages/pi-agent-core/package.json +1 -1
  228. package/packages/pi-ai/dist/models.generated.d.ts +60 -0
  229. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  230. package/packages/pi-ai/dist/models.generated.js +54 -0
  231. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  232. package/packages/pi-ai/dist/providers/amazon-bedrock.js +1 -0
  233. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  234. package/packages/pi-ai/dist/providers/anthropic.js +19 -0
  235. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  236. package/packages/pi-ai/package.json +1 -1
  237. package/packages/pi-coding-agent/package.json +7 -7
  238. package/packages/pi-tui/package.json +2 -2
  239. package/packages/rpc-client/package.json +2 -2
  240. package/pkg/package.json +1 -1
  241. package/src/resources/GSD-WORKFLOW.md +16 -47
  242. package/src/resources/extensions/browser-tools/capture.ts +2 -86
  243. package/src/resources/extensions/browser-tools/screenshot-constraints.ts +94 -0
  244. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -1
  245. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -0
  246. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +30 -2
  247. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
  248. package/src/resources/extensions/gsd/auto-dispatch.ts +86 -19
  249. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -0
  250. package/src/resources/extensions/gsd/auto-prompts.ts +38 -1
  251. package/src/resources/extensions/gsd/auto.ts +5 -1
  252. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +91 -4
  253. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -24
  254. package/src/resources/extensions/gsd/command-feedback.ts +19 -12
  255. package/src/resources/extensions/gsd/compat/compat-marker.ts +21 -4
  256. package/src/resources/extensions/gsd/db/command-queue.ts +7 -1
  257. package/src/resources/extensions/gsd/db/engine.ts +53 -17
  258. package/src/resources/extensions/gsd/db/writers/cascades.ts +5 -1
  259. package/src/resources/extensions/gsd/db/writers/status.ts +25 -8
  260. package/src/resources/extensions/gsd/db-base-schema.ts +30 -0
  261. package/src/resources/extensions/gsd/db-migration-steps.ts +32 -0
  262. package/src/resources/extensions/gsd/db-schema-metadata.ts +4 -1
  263. package/src/resources/extensions/gsd/docs/preferences-reference.md +4 -4
  264. package/src/resources/extensions/gsd/flat-phase-migration.ts +160 -19
  265. package/src/resources/extensions/gsd/gsd-db.ts +255 -52
  266. package/src/resources/extensions/gsd/guided-flow.ts +39 -10
  267. package/src/resources/extensions/gsd/markdown-renderer.ts +23 -7
  268. package/src/resources/extensions/gsd/md-importer.ts +109 -11
  269. package/src/resources/extensions/gsd/migrate/command.ts +21 -227
  270. package/src/resources/extensions/gsd/migrate/execution.ts +136 -0
  271. package/src/resources/extensions/gsd/migrate/plan.ts +101 -0
  272. package/src/resources/extensions/gsd/migrate/planning-writer.ts +12 -20
  273. package/src/resources/extensions/gsd/migrate/presentation.ts +83 -0
  274. package/src/resources/extensions/gsd/parallel-eligibility.ts +5 -4
  275. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +37 -40
  276. package/src/resources/extensions/gsd/paths.ts +4 -1
  277. package/src/resources/extensions/gsd/phase-anchor.ts +8 -2
  278. package/src/resources/extensions/gsd/post-execution-checks.ts +4 -4
  279. package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  280. package/src/resources/extensions/gsd/safety/evidence-collector.ts +28 -1
  281. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +31 -5
  282. package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +30 -13
  283. package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +16 -3
  284. package/src/resources/extensions/gsd/state-reconciliation/types.ts +2 -2
  285. package/src/resources/extensions/gsd/stop-notice.ts +18 -1
  286. package/src/resources/extensions/gsd/sync-lock.ts +14 -6
  287. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +96 -1
  288. package/src/resources/extensions/gsd/tests/command-feedback.test.ts +12 -0
  289. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +104 -0
  290. package/src/resources/extensions/gsd/tests/complete-task.test.ts +286 -0
  291. package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +186 -0
  292. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +29 -0
  293. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +123 -0
  294. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +50 -0
  295. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +49 -0
  296. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +81 -0
  297. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +22 -0
  298. package/src/resources/extensions/gsd/tests/external-edit-scoped-import.test.ts +262 -0
  299. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +202 -0
  300. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +29 -0
  301. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +25 -0
  302. package/src/resources/extensions/gsd/tests/import-slice-milestone-completion-preserve.test.ts +116 -0
  303. package/src/resources/extensions/gsd/tests/migrate-plan.test.ts +68 -0
  304. package/src/resources/extensions/gsd/tests/migrate-presentation.test.ts +98 -0
  305. package/src/resources/extensions/gsd/tests/migrate-safety-audit.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +55 -0
  307. package/src/resources/extensions/gsd/tests/parallel-eligibility-file-overlap.test.ts +146 -0
  308. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +59 -0
  309. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +32 -4
  310. package/src/resources/extensions/gsd/tests/plan-task.test.ts +26 -23
  311. package/src/resources/extensions/gsd/tests/planning-writer.test.ts +29 -0
  312. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +22 -0
  313. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +50 -0
  314. package/src/resources/extensions/gsd/tests/replan-task-handler.test.ts +103 -0
  315. package/src/resources/extensions/gsd/tests/rework-brief.test.ts +197 -0
  316. package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +21 -0
  317. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +17 -0
  318. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +19 -0
  319. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +34 -0
  320. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +93 -1
  321. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -0
  322. package/src/resources/extensions/gsd/tests/workflow-migration-gates.test.ts +139 -0
  323. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +36 -0
  324. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +52 -0
  325. package/src/resources/extensions/gsd/tools/complete-task.ts +120 -0
  326. package/src/resources/extensions/gsd/tools/plan-task.ts +14 -16
  327. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +18 -17
  328. package/src/resources/extensions/gsd/tools/replan-task.ts +160 -0
  329. package/src/resources/extensions/gsd/tools/rework-brief.ts +91 -0
  330. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +92 -0
  331. package/src/resources/extensions/gsd/types.ts +11 -0
  332. package/src/resources/extensions/gsd/unit-registry.ts +4 -0
  333. package/src/resources/extensions/gsd/uok/plan-v2.ts +20 -3
  334. package/src/resources/extensions/gsd/worktree-state-projection.ts +12 -0
  335. package/src/resources/extensions/mac-tools/index.ts +52 -9
  336. package/src/resources/extensions/mac-tools/tests/mac-screenshot-payload.test.cjs +70 -0
  337. package/dist/web/standalone/.next/static/css/18c2fdf261bfc1b7.css +0 -1
  338. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_buildManifest.js +0 -0
  339. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_ssgManifest.js +0 -0
@@ -19,11 +19,18 @@ import { getDbOrNull } from "../engine.js";
19
19
  import { GSDError, GSD_STALE_STATE } from "../../errors.js";
20
20
  import { isClosedStatus } from "../../status-guards.js";
21
21
 
22
- /** A single row-level status write, discriminated by entity (the faces' arity). */
22
+ /**
23
+ * A single row-level status write, discriminated by entity (the faces' arity).
24
+ *
25
+ * preserveCompletion (#1291): when true, an existing non-null completed_at on the
26
+ * row is kept rather than overwritten. Mirrors the task upsert's guard so a
27
+ * markdown re-import cannot re-stamp an already-complete slice/milestone with the
28
+ * current import time — the DB row is strictly richer than the plan.
29
+ */
23
30
  export type StatusTransition =
24
- | { entity: "task"; milestoneId: string; sliceId: string; taskId: string; status: string; completedAt?: string | null }
25
- | { entity: "slice"; milestoneId: string; sliceId: string; status: string; completedAt?: string | null }
26
- | { entity: "milestone"; milestoneId: string; status: string; completedAt?: string | null };
31
+ | { entity: "task"; milestoneId: string; sliceId: string; taskId: string; status: string; completedAt?: string | null; preserveCompletion?: boolean }
32
+ | { entity: "slice"; milestoneId: string; sliceId: string; status: string; completedAt?: string | null; preserveCompletion?: boolean }
33
+ | { entity: "milestone"; milestoneId: string; status: string; completedAt?: string | null; preserveCompletion?: boolean };
27
34
 
28
35
  function requireDb() {
29
36
  const db = getDbOrNull();
@@ -44,15 +51,19 @@ function requireDb() {
44
51
  export function applyStatusTransition(t: StatusTransition): void {
45
52
  const db = requireDb();
46
53
  const completedAt = t.completedAt ?? null;
54
+ const preserve = t.preserveCompletion ? 1 : 0;
47
55
 
48
56
  switch (t.entity) {
49
57
  case "task":
50
58
  db.prepare(
51
- `UPDATE tasks SET status = :status, completed_at = :completed_at
59
+ `UPDATE tasks SET status = :status,
60
+ completed_at = CASE WHEN :preserve_completion = 1 AND tasks.completed_at IS NOT NULL
61
+ THEN tasks.completed_at ELSE :completed_at END
52
62
  WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`,
53
63
  ).run({
54
64
  ":status": t.status,
55
65
  ":completed_at": completedAt,
66
+ ":preserve_completion": preserve,
56
67
  ":milestone_id": t.milestoneId,
57
68
  ":slice_id": t.sliceId,
58
69
  ":id": t.taskId,
@@ -61,11 +72,14 @@ export function applyStatusTransition(t: StatusTransition): void {
61
72
 
62
73
  case "slice":
63
74
  db.prepare(
64
- `UPDATE slices SET status = :status, completed_at = :completed_at
75
+ `UPDATE slices SET status = :status,
76
+ completed_at = CASE WHEN :preserve_completion = 1 AND slices.completed_at IS NOT NULL
77
+ THEN slices.completed_at ELSE :completed_at END
65
78
  WHERE milestone_id = :milestone_id AND id = :id`,
66
79
  ).run({
67
80
  ":status": t.status,
68
81
  ":completed_at": completedAt,
82
+ ":preserve_completion": preserve,
69
83
  ":milestone_id": t.milestoneId,
70
84
  ":id": t.sliceId,
71
85
  });
@@ -80,8 +94,11 @@ export function applyStatusTransition(t: StatusTransition): void {
80
94
  );
81
95
  }
82
96
  db.prepare(
83
- `UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`,
84
- ).run({ ":status": t.status, ":completed_at": completedAt, ":id": t.milestoneId });
97
+ `UPDATE milestones SET status = :status,
98
+ completed_at = CASE WHEN :preserve_completion = 1 AND milestones.completed_at IS NOT NULL
99
+ THEN milestones.completed_at ELSE :completed_at END
100
+ WHERE id = :id`,
101
+ ).run({ ":status": t.status, ":completed_at": completedAt, ":preserve_completion": preserve, ":id": t.milestoneId });
85
102
  return;
86
103
  }
87
104
  }
@@ -245,6 +245,34 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
245
245
  )
246
246
  `);
247
247
 
248
+ db.exec(`
249
+ CREATE TABLE IF NOT EXISTS rework_briefs (
250
+ id TEXT PRIMARY KEY,
251
+ milestone_id TEXT NOT NULL DEFAULT '',
252
+ slice_id TEXT NOT NULL DEFAULT '',
253
+ task_id TEXT NOT NULL DEFAULT '',
254
+ created_at TEXT NOT NULL DEFAULT '',
255
+ updated_at TEXT NOT NULL DEFAULT ''
256
+ )
257
+ `);
258
+
259
+ db.exec(`
260
+ CREATE TABLE IF NOT EXISTS rework_brief_findings (
261
+ brief_id TEXT NOT NULL,
262
+ finding_id TEXT NOT NULL,
263
+ severity TEXT NOT NULL DEFAULT 'blocking',
264
+ description TEXT NOT NULL DEFAULT '',
265
+ required_fix TEXT NOT NULL DEFAULT '',
266
+ verification_commands TEXT NOT NULL DEFAULT '[]',
267
+ status TEXT NOT NULL DEFAULT 'pending',
268
+ evidence TEXT NOT NULL DEFAULT '',
269
+ decision_ref TEXT NOT NULL DEFAULT '',
270
+ updated_at TEXT NOT NULL DEFAULT '',
271
+ PRIMARY KEY (brief_id, finding_id),
272
+ FOREIGN KEY (brief_id) REFERENCES rework_briefs(id)
273
+ )
274
+ `);
275
+
248
276
  db.exec(`
249
277
  CREATE TABLE IF NOT EXISTS assessments (
250
278
  path TEXT PRIMARY KEY,
@@ -367,6 +395,8 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
367
395
 
368
396
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
369
397
  db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
398
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
399
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
370
400
  db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
371
401
  db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
372
402
  db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
@@ -462,3 +462,35 @@ export function applyMigrationV22QualityGateRepair(db: DbAdapter, hooks: Migrati
462
462
  ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
463
463
  ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
464
464
  }
465
+
466
+
467
+ export function applyMigrationV30ReworkBriefs(db: DbAdapter): void {
468
+ db.exec(`
469
+ CREATE TABLE IF NOT EXISTS rework_briefs (
470
+ id TEXT PRIMARY KEY,
471
+ milestone_id TEXT NOT NULL DEFAULT '',
472
+ slice_id TEXT NOT NULL DEFAULT '',
473
+ task_id TEXT NOT NULL DEFAULT '',
474
+ created_at TEXT NOT NULL DEFAULT '',
475
+ updated_at TEXT NOT NULL DEFAULT ''
476
+ )
477
+ `);
478
+ db.exec(`
479
+ CREATE TABLE IF NOT EXISTS rework_brief_findings (
480
+ brief_id TEXT NOT NULL,
481
+ finding_id TEXT NOT NULL,
482
+ severity TEXT NOT NULL DEFAULT 'blocking',
483
+ description TEXT NOT NULL DEFAULT '',
484
+ required_fix TEXT NOT NULL DEFAULT '',
485
+ verification_commands TEXT NOT NULL DEFAULT '[]',
486
+ status TEXT NOT NULL DEFAULT 'pending',
487
+ evidence TEXT NOT NULL DEFAULT '',
488
+ decision_ref TEXT NOT NULL DEFAULT '',
489
+ updated_at TEXT NOT NULL DEFAULT '',
490
+ PRIMARY KEY (brief_id, finding_id),
491
+ FOREIGN KEY (brief_id) REFERENCES rework_briefs(id)
492
+ )
493
+ `);
494
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
495
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
496
+ }
@@ -19,8 +19,11 @@ export function ensureColumn(db: DbAdapter, table: string, column: string, ddl:
19
19
  }
20
20
 
21
21
  export function getCurrentSchemaVersion(db: DbAdapter): number {
22
+ // MAX(version) on an empty table returns a row whose value is NULL —
23
+ // treat that (and any non-numeric value) as version 0, not NULL-as-number.
22
24
  const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get();
23
- return row ? (row["v"] as number) : 0;
25
+ const v = row?.["v"];
26
+ return typeof v === "number" ? v : 0;
24
27
  }
25
28
 
26
29
  export function recordSchemaVersion(db: DbAdapter, version: number): void {
@@ -128,7 +128,7 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
128
128
  - Two equivalent ways to set it: inline as `models.<phase>.thinking`, or as a separate `thinking:` block keyed by phase. For the same phase, the inline value wins over the block; project preferences win over global; a phase that's unset inherits via the same sibling chain as `models` (e.g. `discuss → planning`).
129
129
  - If no thinking is configured for a phase, the session level (set via `/model`) is used, exactly as before — this is fully backward-compatible.
130
130
  - `execute-task` (code-writing) has a measured reasoning floor of `medium`: at lower levels models stop planning edits and thrash on file re-reads. The floor applies to the session/default path. An **explicit** `execution` thinking level bypasses the floor and is honored verbatim (with a one-time advisory) — set `execution: low` deliberately if you want it.
131
- - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` is only honored by select model families.
131
+ - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` support is model-dependent; current examples include OpenAI Codex Max models and Claude Sonnet 5 on Amazon Bedrock.
132
132
 
133
133
  - `skill_staleness_days`: number — skills unused for this many days get deprioritized during discovery. Set to `0` to disable staleness tracking. Default: `60`.
134
134
 
@@ -482,13 +482,13 @@ Inline form — thinking pinned alongside the model:
482
482
  version: 1
483
483
  models:
484
484
  planning:
485
- model: openai-codex/gpt-5.4
485
+ model: bedrock/global.anthropic.claude-sonnet-5
486
486
  thinking: xhigh
487
487
  execution:
488
- model: openai-codex/gpt-5.4
488
+ model: bedrock/global.anthropic.claude-sonnet-5
489
489
  thinking: low # explicit → bypasses the execute-task medium floor
490
490
  validation:
491
- model: openai-codex/gpt-5.4
491
+ model: bedrock/global.anthropic.claude-sonnet-5
492
492
  thinking: high
493
493
  ---
494
494
  ```
@@ -6,7 +6,13 @@ import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSyn
6
6
  import { join } from "node:path";
7
7
 
8
8
  import { renderAllFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
9
- import { deleteArtifactsByPathPrefix, getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
9
+ import {
10
+ deleteArtifactByPath,
11
+ deleteArtifactsByPathPrefix,
12
+ getAllMilestones,
13
+ getArtifactsByPathPrefix,
14
+ getMilestoneSlices,
15
+ } from "./gsd-db.js";
10
16
  import { migrateFromMarkdown } from "./md-importer.js";
11
17
  import { countDbHierarchy } from "./migration-auto-check.js";
12
18
  import { logWarning } from "./workflow-logger.js";
@@ -14,6 +20,7 @@ import { LAYOUT_SEGMENTS } from "./layout-policy.js";
14
20
  import {
15
21
  canonicalPhaseDirName,
16
22
  dirIsContentBearingLegacyMilestone,
23
+ gsdProjectionRoot,
17
24
  milestonesDir,
18
25
  resolveMilestonePath,
19
26
  } from "./paths.js";
@@ -83,6 +90,40 @@ function expectedPhaseDirs(basePath: string): string[] {
83
90
  );
84
91
  }
85
92
 
93
+ /**
94
+ * Return the most-recent existing `.gsd-backups/migrate-<ts>/` snapshot, or null.
95
+ *
96
+ * The flat-phase migration can re-fire on later dispatches when the legacy
97
+ * `.gsd/milestones/` layout reappears (e.g. a marker-key mismatch re-triggers a
98
+ * whole-tree re-import — issue #1292). The DB was already reconciled from that
99
+ * tree before the backup step runs, so re-snapshotting an identical legacy tree
100
+ * on every dispatch only leaks a fresh `migrate-<ts>/` directory each time. When
101
+ * a prior snapshot already exists we reuse it as the rollback fallback instead
102
+ * of creating a duplicate, bounding the accumulation to one recovery copy.
103
+ */
104
+ function existingMigrateBackup(basePath: string): string | null {
105
+ const backupRoot = join(basePath, ".gsd-backups");
106
+ if (!existsSync(backupRoot)) return null;
107
+ try {
108
+ let latest: { path: string; mtimeMs: number } | null = null;
109
+ for (const entry of readdirSync(backupRoot)) {
110
+ if (!entry.startsWith("migrate-")) continue;
111
+ const dirPath = join(backupRoot, entry);
112
+ try {
113
+ const st = statSync(dirPath);
114
+ if (!st.isDirectory()) continue;
115
+ if (!hasLegacyMilestoneSubdirs(dirPath)) continue;
116
+ if (!latest || st.mtimeMs > latest.mtimeMs) latest = { path: dirPath, mtimeMs: st.mtimeMs };
117
+ } catch {
118
+ // Non-fatal: skip unreadable entries.
119
+ }
120
+ }
121
+ return latest?.path ?? null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
86
127
  function hasLegacyMilestoneSubdirs(dirPath: string): boolean {
87
128
  if (!existsSync(dirPath)) return false;
88
129
  try {
@@ -94,10 +135,63 @@ function hasLegacyMilestoneSubdirs(dirPath: string): boolean {
94
135
  }
95
136
  }
96
137
 
138
+ function backupFlatProjectionIfPresent(basePath: string, phasesPath: string, backupDir: string): void {
139
+ if (!existsSync(phasesPath)) return;
140
+ const phaseBackupDir = join(backupDir, "__phases");
141
+ try {
142
+ mkdirSync(backupDir, { recursive: true });
143
+ if (existsSync(phaseBackupDir)) {
144
+ removePathWithRetries(phaseBackupDir);
145
+ }
146
+ fsOps.cpSync(phasesPath, phaseBackupDir, { recursive: true, force: true });
147
+ } catch (err) {
148
+ logWarning("migration", `flat-phase projection backup failed: ${(err as Error).message}`);
149
+ throw err;
150
+ }
151
+ }
152
+
153
+ function isInsideFlatProjectionBackup(backupDir: string, src: string): boolean {
154
+ const phaseBackupDir = join(backupDir, "__phases");
155
+ return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
156
+ }
157
+
158
+ function restoreFlatProjectionFromBackup(basePath: string, backupDir: string): void {
159
+ const phaseBackupDir = join(backupDir, "__phases");
160
+ if (!existsSync(phaseBackupDir)) return;
161
+ const phasesPath = join(basePath, ".gsd", LAYOUT_SEGMENTS.level1);
162
+ try {
163
+ if (existsSync(phasesPath)) {
164
+ removePathWithRetries(phasesPath);
165
+ }
166
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
167
+ cpSync(phaseBackupDir, phasesPath, { recursive: true });
168
+ } catch (restoreErr) {
169
+ logWarning(
170
+ "migration",
171
+ `rollback: could not restore ${LAYOUT_SEGMENTS.level1}/ from backup: ${(restoreErr as Error).message}`,
172
+ );
173
+ }
174
+ }
175
+
176
+ function pruneStaleFlatPhaseArtifactRows(basePath: string): number {
177
+ const projectionRoot = gsdProjectionRoot(basePath);
178
+ let pruned = 0;
179
+ for (const row of getArtifactsByPathPrefix(`${LAYOUT_SEGMENTS.level1}/`)) {
180
+ if (existsSync(join(projectionRoot, row.path))) continue;
181
+ const staleTaskPlan = row.artifact_type.toUpperCase() === "PLAN" && Boolean(row.task_id);
182
+ const skippedEmptyArtifact = row.full_content.trim() === "";
183
+ if (!staleTaskPlan && !skippedEmptyArtifact) continue;
184
+ deleteArtifactByPath(row.path);
185
+ pruned++;
186
+ }
187
+ return pruned;
188
+ }
189
+
97
190
  function rollbackPartialMigration(
98
191
  basePath: string,
99
192
  backupDir: string,
100
193
  migratingPath?: string,
194
+ backupCreatedThisRun = true,
101
195
  ): void {
102
196
  // Remove the partially-written phases/ dir.
103
197
  try {
@@ -113,7 +207,8 @@ function rollbackPartialMigration(
113
207
  // preserved migrating tree (the sole surviving data source) and must never be
114
208
  // removed here. Deleting the backup after a successful rollback keeps the gate
115
209
  // from leaking one .gsd-backups/migrate-<ts>/ per session_start.
116
- const isDisposableBackup = Boolean(backupDir) && backupDir !== migratingPath;
210
+ const isDisposableBackup =
211
+ Boolean(backupDir) && backupDir !== migratingPath && backupCreatedThisRun;
117
212
  const cleanupBackup = (): void => {
118
213
  if (!isDisposableBackup || !existsSync(backupDir)) return;
119
214
  try {
@@ -131,11 +226,16 @@ function rollbackPartialMigration(
131
226
  try {
132
227
  if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
133
228
  movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
229
+ restoreFlatProjectionFromBackup(basePath, backupDir);
134
230
  cleanupBackup();
135
231
  return;
136
232
  }
137
233
  if (existsSync(backupDir)) {
138
- cpSync(backupDir, milestonesPath, { recursive: true });
234
+ cpSync(backupDir, milestonesPath, {
235
+ recursive: true,
236
+ filter: (src) => !isInsideFlatProjectionBackup(backupDir, src),
237
+ });
238
+ restoreFlatProjectionFromBackup(basePath, backupDir);
139
239
  cleanupBackup();
140
240
  }
141
241
  if (migratingPath && existsSync(migratingPath)) {
@@ -244,16 +344,34 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
244
344
  }
245
345
 
246
346
  let backupDir = migratingPath;
347
+ let backupCreatedThisRun = false;
247
348
  if (!resumingInterrupted) {
248
- // 2. Backup (only reached when the DB has rows and migration will proceed)
249
- const ts = Date.now();
250
- backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
251
- try {
252
- mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
253
- cpSync(milestonesPath, backupDir, { recursive: true });
254
- } catch (err) {
255
- logWarning("migration", `flat-phase migration backup failed: ${(err as Error).message}`);
256
- throw err;
349
+ // 2. Backup (only reached when the DB has rows and migration will proceed).
350
+ // migrateFromMarkdown above already reconciled the legacy tree into the DB,
351
+ // so its content is safely persisted. If a prior successful migration
352
+ // already snapshotted the legacy tree, a re-fire of this gate (issue #1292:
353
+ // marker-key mismatch re-importing the whole tree at dispatch boundaries)
354
+ // must not leak a fresh .gsd-backups/migrate-<ts>/ every dispatch. Treat it
355
+ // as a marker-refresh re-projection: reuse the existing snapshot as the
356
+ // rollback fallback instead of creating a duplicate.
357
+ const priorBackup = existingMigrateBackup(basePath);
358
+ if (priorBackup) {
359
+ backupDir = priorBackup;
360
+ logWarning(
361
+ "migration",
362
+ `flat-phase migration re-fired; reusing existing backup ${priorBackup} instead of re-snapshotting (issue #1292)`,
363
+ );
364
+ } else {
365
+ const ts = Date.now();
366
+ backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
367
+ try {
368
+ mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
369
+ cpSync(milestonesPath, backupDir, { recursive: true });
370
+ backupCreatedThisRun = true;
371
+ } catch (err) {
372
+ logWarning("migration", `flat-phase migration backup failed: ${(err as Error).message}`);
373
+ throw err;
374
+ }
257
375
  }
258
376
 
259
377
  if (existsSync(migratingPath)) {
@@ -269,15 +387,35 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
269
387
  logWarning("migration", `failed to rename legacy milestones/ before render: ${(err as Error).message}`);
270
388
  throw err;
271
389
  }
390
+ } else {
391
+ const priorBackup = existingMigrateBackup(basePath);
392
+ if (priorBackup) {
393
+ backupDir = priorBackup;
394
+ } else {
395
+ const ts = Date.now();
396
+ backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
397
+ try {
398
+ mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
399
+ cpSync(migratingPath, backupDir, { recursive: true });
400
+ backupCreatedThisRun = true;
401
+ } catch (err) {
402
+ logWarning(
403
+ "migration",
404
+ `flat-phase migration backup failed during resume: ${(err as Error).message}`,
405
+ );
406
+ throw err;
407
+ }
408
+ }
272
409
  }
273
410
 
274
411
  // Clear any stale or partially-rendered flat projection before this run
275
412
  // writes. Verification below checks the current DB render, not leftovers.
276
413
  try {
414
+ backupFlatProjectionIfPresent(basePath, phasesPath, backupDir);
277
415
  removePathWithRetries(phasesPath);
278
416
  } catch (err) {
279
417
  logWarning("migration", `failed to clear stale phases/ before render: ${(err as Error).message}`);
280
- rollbackPartialMigration(basePath, backupDir, migratingPath);
418
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
281
419
  throw err;
282
420
  }
283
421
 
@@ -299,7 +437,7 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
299
437
  }
300
438
  } catch (err) {
301
439
  logWarning("migration", `flat-phase render failed: ${(err as Error).message}`);
302
- rollbackPartialMigration(basePath, backupDir, migratingPath);
440
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
303
441
  throw err;
304
442
  }
305
443
 
@@ -309,7 +447,7 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
309
447
  "migration",
310
448
  `flat-phase render had ${renderResult.errors.length} error(s): ${renderResult.errors.join("; ")}`,
311
449
  );
312
- rollbackPartialMigration(basePath, backupDir, migratingPath);
450
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
313
451
  throw new Error(
314
452
  `flat-phase migration render failed: ${renderResult.errors.slice(0, 3).join("; ")}`,
315
453
  );
@@ -322,7 +460,7 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
322
460
  "migration",
323
461
  `flat-phase migration missing ${missingPhaseDirs.length} expected phase dir(s): ${missingPhaseDirs.join(", ")}`,
324
462
  );
325
- rollbackPartialMigration(basePath, backupDir, migratingPath);
463
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
326
464
  throw new Error("flat-phase migration verification failed: missing rendered phase directories");
327
465
  }
328
466
  if (db.slices > 0 && renderResult.rendered === 0) {
@@ -330,17 +468,20 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
330
468
  "migration",
331
469
  "flat-phase migration verification failed: render produced no artifacts for populated DB",
332
470
  );
333
- rollbackPartialMigration(basePath, backupDir, migratingPath);
471
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
334
472
  throw new Error("flat-phase migration verification failed: no artifacts rendered");
335
473
  }
336
474
 
337
475
  // 6. Verified — prune legacy artifact rows now that renderAllFromDb has
338
- // re-inserted flat-phase rows for artifacts that still have files.
476
+ // re-inserted flat-phase rows for artifacts that still have files. Also
477
+ // prune flat-phase rows whose files the renderer intentionally no longer
478
+ // materializes, such as task PLAN files and empty-content artifacts.
339
479
  try {
340
480
  deleteArtifactsByPathPrefix("milestones/");
481
+ pruneStaleFlatPhaseArtifactRows(basePath);
341
482
  } catch (err) {
342
483
  logWarning("migration", `flat-phase migration could not prune legacy artifact rows: ${(err as Error).message}`);
343
- rollbackPartialMigration(basePath, backupDir, migratingPath);
484
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
344
485
  throw err;
345
486
  }
346
487