@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
@@ -4,12 +4,12 @@
4
4
  import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { renderAllFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
7
- import { deleteArtifactsByPathPrefix, getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
7
+ import { deleteArtifactByPath, deleteArtifactsByPathPrefix, getAllMilestones, getArtifactsByPathPrefix, getMilestoneSlices, } from "./gsd-db.js";
8
8
  import { migrateFromMarkdown } from "./md-importer.js";
9
9
  import { countDbHierarchy } from "./migration-auto-check.js";
10
10
  import { logWarning } from "./workflow-logger.js";
11
11
  import { LAYOUT_SEGMENTS } from "./layout-policy.js";
12
- import { canonicalPhaseDirName, dirIsContentBearingLegacyMilestone, milestonesDir, resolveMilestonePath, } from "./paths.js";
12
+ import { canonicalPhaseDirName, dirIsContentBearingLegacyMilestone, gsdProjectionRoot, milestonesDir, resolveMilestonePath, } from "./paths.js";
13
13
  const LEGACY_MIGRATING_SEGMENT = "milestones.migrating";
14
14
  const RETRYABLE_FS_ERROR_CODES = new Set(["EPERM", "EBUSY", "ENOTEMPTY"]);
15
15
  const RM_RETRY_OPTIONS = { recursive: true, force: true, maxRetries: 5, retryDelay: 100 };
@@ -60,6 +60,46 @@ function expectedPhaseDirs(basePath) {
60
60
  return getAllMilestones().map((milestone) => resolveMilestonePath(basePath, milestone.id) ??
61
61
  join(milestonesDir(basePath), canonicalPhaseDirName(milestone.id, milestone.title)));
62
62
  }
63
+ /**
64
+ * Return the most-recent existing `.gsd-backups/migrate-<ts>/` snapshot, or null.
65
+ *
66
+ * The flat-phase migration can re-fire on later dispatches when the legacy
67
+ * `.gsd/milestones/` layout reappears (e.g. a marker-key mismatch re-triggers a
68
+ * whole-tree re-import — issue #1292). The DB was already reconciled from that
69
+ * tree before the backup step runs, so re-snapshotting an identical legacy tree
70
+ * on every dispatch only leaks a fresh `migrate-<ts>/` directory each time. When
71
+ * a prior snapshot already exists we reuse it as the rollback fallback instead
72
+ * of creating a duplicate, bounding the accumulation to one recovery copy.
73
+ */
74
+ function existingMigrateBackup(basePath) {
75
+ const backupRoot = join(basePath, ".gsd-backups");
76
+ if (!existsSync(backupRoot))
77
+ return null;
78
+ try {
79
+ let latest = null;
80
+ for (const entry of readdirSync(backupRoot)) {
81
+ if (!entry.startsWith("migrate-"))
82
+ continue;
83
+ const dirPath = join(backupRoot, entry);
84
+ try {
85
+ const st = statSync(dirPath);
86
+ if (!st.isDirectory())
87
+ continue;
88
+ if (!hasLegacyMilestoneSubdirs(dirPath))
89
+ continue;
90
+ if (!latest || st.mtimeMs > latest.mtimeMs)
91
+ latest = { path: dirPath, mtimeMs: st.mtimeMs };
92
+ }
93
+ catch {
94
+ // Non-fatal: skip unreadable entries.
95
+ }
96
+ }
97
+ return latest?.path ?? null;
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ }
63
103
  function hasLegacyMilestoneSubdirs(dirPath) {
64
104
  if (!existsSync(dirPath))
65
105
  return false;
@@ -70,7 +110,58 @@ function hasLegacyMilestoneSubdirs(dirPath) {
70
110
  return false;
71
111
  }
72
112
  }
73
- function rollbackPartialMigration(basePath, backupDir, migratingPath) {
113
+ function backupFlatProjectionIfPresent(basePath, phasesPath, backupDir) {
114
+ if (!existsSync(phasesPath))
115
+ return;
116
+ const phaseBackupDir = join(backupDir, "__phases");
117
+ try {
118
+ mkdirSync(backupDir, { recursive: true });
119
+ if (existsSync(phaseBackupDir)) {
120
+ removePathWithRetries(phaseBackupDir);
121
+ }
122
+ fsOps.cpSync(phasesPath, phaseBackupDir, { recursive: true, force: true });
123
+ }
124
+ catch (err) {
125
+ logWarning("migration", `flat-phase projection backup failed: ${err.message}`);
126
+ throw err;
127
+ }
128
+ }
129
+ function isInsideFlatProjectionBackup(backupDir, src) {
130
+ const phaseBackupDir = join(backupDir, "__phases");
131
+ return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
132
+ }
133
+ function restoreFlatProjectionFromBackup(basePath, backupDir) {
134
+ const phaseBackupDir = join(backupDir, "__phases");
135
+ if (!existsSync(phaseBackupDir))
136
+ return;
137
+ const phasesPath = join(basePath, ".gsd", LAYOUT_SEGMENTS.level1);
138
+ try {
139
+ if (existsSync(phasesPath)) {
140
+ removePathWithRetries(phasesPath);
141
+ }
142
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
143
+ cpSync(phaseBackupDir, phasesPath, { recursive: true });
144
+ }
145
+ catch (restoreErr) {
146
+ logWarning("migration", `rollback: could not restore ${LAYOUT_SEGMENTS.level1}/ from backup: ${restoreErr.message}`);
147
+ }
148
+ }
149
+ function pruneStaleFlatPhaseArtifactRows(basePath) {
150
+ const projectionRoot = gsdProjectionRoot(basePath);
151
+ let pruned = 0;
152
+ for (const row of getArtifactsByPathPrefix(`${LAYOUT_SEGMENTS.level1}/`)) {
153
+ if (existsSync(join(projectionRoot, row.path)))
154
+ continue;
155
+ const staleTaskPlan = row.artifact_type.toUpperCase() === "PLAN" && Boolean(row.task_id);
156
+ const skippedEmptyArtifact = row.full_content.trim() === "";
157
+ if (!staleTaskPlan && !skippedEmptyArtifact)
158
+ continue;
159
+ deleteArtifactByPath(row.path);
160
+ pruned++;
161
+ }
162
+ return pruned;
163
+ }
164
+ function rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun = true) {
74
165
  // Remove the partially-written phases/ dir.
75
166
  try {
76
167
  removePathWithRetries(join(basePath, ".gsd", LAYOUT_SEGMENTS.level1));
@@ -83,7 +174,7 @@ function rollbackPartialMigration(basePath, backupDir, migratingPath) {
83
174
  // preserved migrating tree (the sole surviving data source) and must never be
84
175
  // removed here. Deleting the backup after a successful rollback keeps the gate
85
176
  // from leaking one .gsd-backups/migrate-<ts>/ per session_start.
86
- const isDisposableBackup = Boolean(backupDir) && backupDir !== migratingPath;
177
+ const isDisposableBackup = Boolean(backupDir) && backupDir !== migratingPath && backupCreatedThisRun;
87
178
  const cleanupBackup = () => {
88
179
  if (!isDisposableBackup || !existsSync(backupDir))
89
180
  return;
@@ -99,11 +190,16 @@ function rollbackPartialMigration(basePath, backupDir, migratingPath) {
99
190
  try {
100
191
  if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
101
192
  movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
193
+ restoreFlatProjectionFromBackup(basePath, backupDir);
102
194
  cleanupBackup();
103
195
  return;
104
196
  }
105
197
  if (existsSync(backupDir)) {
106
- cpSync(backupDir, milestonesPath, { recursive: true });
198
+ cpSync(backupDir, milestonesPath, {
199
+ recursive: true,
200
+ filter: (src) => !isInsideFlatProjectionBackup(backupDir, src),
201
+ });
202
+ restoreFlatProjectionFromBackup(basePath, backupDir);
107
203
  cleanupBackup();
108
204
  }
109
205
  if (migratingPath && existsSync(migratingPath)) {
@@ -204,17 +300,33 @@ export async function migrateToFlatPhase(basePath) {
204
300
  return;
205
301
  }
206
302
  let backupDir = migratingPath;
303
+ let backupCreatedThisRun = false;
207
304
  if (!resumingInterrupted) {
208
- // 2. Backup (only reached when the DB has rows and migration will proceed)
209
- const ts = Date.now();
210
- backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
211
- try {
212
- mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
213
- cpSync(milestonesPath, backupDir, { recursive: true });
305
+ // 2. Backup (only reached when the DB has rows and migration will proceed).
306
+ // migrateFromMarkdown above already reconciled the legacy tree into the DB,
307
+ // so its content is safely persisted. If a prior successful migration
308
+ // already snapshotted the legacy tree, a re-fire of this gate (issue #1292:
309
+ // marker-key mismatch re-importing the whole tree at dispatch boundaries)
310
+ // must not leak a fresh .gsd-backups/migrate-<ts>/ every dispatch. Treat it
311
+ // as a marker-refresh re-projection: reuse the existing snapshot as the
312
+ // rollback fallback instead of creating a duplicate.
313
+ const priorBackup = existingMigrateBackup(basePath);
314
+ if (priorBackup) {
315
+ backupDir = priorBackup;
316
+ logWarning("migration", `flat-phase migration re-fired; reusing existing backup ${priorBackup} instead of re-snapshotting (issue #1292)`);
214
317
  }
215
- catch (err) {
216
- logWarning("migration", `flat-phase migration backup failed: ${err.message}`);
217
- throw err;
318
+ else {
319
+ const ts = Date.now();
320
+ backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
321
+ try {
322
+ mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
323
+ cpSync(milestonesPath, backupDir, { recursive: true });
324
+ backupCreatedThisRun = true;
325
+ }
326
+ catch (err) {
327
+ logWarning("migration", `flat-phase migration backup failed: ${err.message}`);
328
+ throw err;
329
+ }
218
330
  }
219
331
  if (existsSync(migratingPath)) {
220
332
  removePathWithRetries(migratingPath);
@@ -230,14 +342,34 @@ export async function migrateToFlatPhase(basePath) {
230
342
  throw err;
231
343
  }
232
344
  }
345
+ else {
346
+ const priorBackup = existingMigrateBackup(basePath);
347
+ if (priorBackup) {
348
+ backupDir = priorBackup;
349
+ }
350
+ else {
351
+ const ts = Date.now();
352
+ backupDir = join(basePath, ".gsd-backups", `migrate-${ts}`);
353
+ try {
354
+ mkdirSync(join(basePath, ".gsd-backups"), { recursive: true });
355
+ cpSync(migratingPath, backupDir, { recursive: true });
356
+ backupCreatedThisRun = true;
357
+ }
358
+ catch (err) {
359
+ logWarning("migration", `flat-phase migration backup failed during resume: ${err.message}`);
360
+ throw err;
361
+ }
362
+ }
363
+ }
233
364
  // Clear any stale or partially-rendered flat projection before this run
234
365
  // writes. Verification below checks the current DB render, not leftovers.
235
366
  try {
367
+ backupFlatProjectionIfPresent(basePath, phasesPath, backupDir);
236
368
  removePathWithRetries(phasesPath);
237
369
  }
238
370
  catch (err) {
239
371
  logWarning("migration", `failed to clear stale phases/ before render: ${err.message}`);
240
- rollbackPartialMigration(basePath, backupDir, migratingPath);
372
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
241
373
  throw err;
242
374
  }
243
375
  // 4. Render flat-phase from DB
@@ -260,35 +392,38 @@ export async function migrateToFlatPhase(basePath) {
260
392
  }
261
393
  catch (err) {
262
394
  logWarning("migration", `flat-phase render failed: ${err.message}`);
263
- rollbackPartialMigration(basePath, backupDir, migratingPath);
395
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
264
396
  throw err;
265
397
  }
266
398
  // 5. Verify render succeeded and flat-phase projection was written
267
399
  if (renderResult.errors.length > 0) {
268
400
  logWarning("migration", `flat-phase render had ${renderResult.errors.length} error(s): ${renderResult.errors.join("; ")}`);
269
- rollbackPartialMigration(basePath, backupDir, migratingPath);
401
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
270
402
  throw new Error(`flat-phase migration render failed: ${renderResult.errors.slice(0, 3).join("; ")}`);
271
403
  }
272
404
  const db = countDbHierarchy();
273
405
  const missingPhaseDirs = expectedPhaseDirs(basePath).filter((phaseDir) => !existsSync(phaseDir));
274
406
  if (db.milestones > 0 && missingPhaseDirs.length > 0) {
275
407
  logWarning("migration", `flat-phase migration missing ${missingPhaseDirs.length} expected phase dir(s): ${missingPhaseDirs.join(", ")}`);
276
- rollbackPartialMigration(basePath, backupDir, migratingPath);
408
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
277
409
  throw new Error("flat-phase migration verification failed: missing rendered phase directories");
278
410
  }
279
411
  if (db.slices > 0 && renderResult.rendered === 0) {
280
412
  logWarning("migration", "flat-phase migration verification failed: render produced no artifacts for populated DB");
281
- rollbackPartialMigration(basePath, backupDir, migratingPath);
413
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
282
414
  throw new Error("flat-phase migration verification failed: no artifacts rendered");
283
415
  }
284
416
  // 6. Verified — prune legacy artifact rows now that renderAllFromDb has
285
- // re-inserted flat-phase rows for artifacts that still have files.
417
+ // re-inserted flat-phase rows for artifacts that still have files. Also
418
+ // prune flat-phase rows whose files the renderer intentionally no longer
419
+ // materializes, such as task PLAN files and empty-content artifacts.
286
420
  try {
287
421
  deleteArtifactsByPathPrefix("milestones/");
422
+ pruneStaleFlatPhaseArtifactRows(basePath);
288
423
  }
289
424
  catch (err) {
290
425
  logWarning("migration", `flat-phase migration could not prune legacy artifact rows: ${err.message}`);
291
- rollbackPartialMigration(basePath, backupDir, migratingPath);
426
+ rollbackPartialMigration(basePath, backupDir, migratingPath, backupCreatedThisRun);
292
427
  throw err;
293
428
  }
294
429
  // 7. Remove the renamed legacy tree (backup already on disk).
@@ -19,7 +19,8 @@ import { createHash } from "node:crypto";
19
19
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
20
20
  import { getGateIdsForTurn } from "./gate-registry.js";
21
21
  import { logWarning } from "./workflow-logger.js";
22
- import { isClosedStatus } from "./status-guards.js";
22
+ import { rowToArtifact } from "./db-milestone-artifact-rows.js";
23
+ import { isClosedStatus, toStatus } from "./status-guards.js";
23
24
  // Connection ownership, lifecycle, schema/migrations and transaction
24
25
  // primitives now live in the engine; re-export the full public surface so
25
26
  // existing `from "./gsd-db.js"` imports keep working.
@@ -336,6 +337,11 @@ export function upsertSlicePlanning(milestoneId, sliceId, planning) {
336
337
  export function insertTask(t) {
337
338
  if (!getDbOrNull())
338
339
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
340
+ // Stamp completed_at for every terminal-complete alias (complete/done/closed),
341
+ // not just two literals — a task imported as "closed" is completed and must
342
+ // carry a timestamp. NOT for "skipped": a skipped task was never completed
343
+ // (cascade writers set its completed_at = NULL).
344
+ const isCompleteAlias = t.status != null && toStatus(t.status) === "complete";
339
345
  getDbOrNull().prepare(`INSERT INTO tasks (
340
346
  milestone_id, slice_id, id, title, status, one_liner, narrative,
341
347
  verification_result, duration, completed_at, blocker_discovered,
@@ -385,7 +391,7 @@ export function insertTask(t) {
385
391
  ":narrative": t.narrative ?? "",
386
392
  ":verification_result": t.verificationResult ?? "",
387
393
  ":duration": t.duration ?? "",
388
- ":completed_at": t.status === "done" || t.status === "complete" ? new Date().toISOString() : null,
394
+ ":completed_at": isCompleteAlias ? new Date().toISOString() : null,
389
395
  ":blocker_discovered": t.blockerDiscovered ? 1 : 0,
390
396
  ":deviations": t.deviations ?? "",
391
397
  ":known_issues": t.knownIssues ?? "",
@@ -401,7 +407,7 @@ export function insertTask(t) {
401
407
  ":observability_impact": t.planning?.observabilityImpact ?? "",
402
408
  ":full_plan_md": t.planning?.fullPlanMd ?? "",
403
409
  ":sequence": t.sequence ?? 0,
404
- ":preserve_completion": t.preserveCompletionMetadata && (t.status === "complete" || t.status === "done") ? 1 : 0,
410
+ ":preserve_completion": t.preserveCompletionMetadata && isCompleteAlias ? 1 : 0,
405
411
  ":target_repositories": JSON.stringify(t.planning?.targetRepositories ?? []),
406
412
  ":raw_target_repositories": t.planning && "targetRepositories" in t.planning
407
413
  ? JSON.stringify(t.planning.targetRepositories ?? [])
@@ -498,8 +504,8 @@ export function upsertTaskPlanning(milestoneId, sliceId, taskId, planning) {
498
504
  ":target_repositories": planning.targetRepositories ? JSON.stringify(planning.targetRepositories) : null,
499
505
  });
500
506
  }
501
- export function updateSliceStatus(milestoneId, sliceId, status, completedAt) {
502
- applyStatusTransition({ entity: "slice", milestoneId, sliceId, status, completedAt });
507
+ export function updateSliceStatus(milestoneId, sliceId, status, completedAt, preserveCompletion) {
508
+ applyStatusTransition({ entity: "slice", milestoneId, sliceId, status, completedAt, preserveCompletion });
503
509
  }
504
510
  export function setTaskSummaryMd(milestoneId, sliceId, taskId, md) {
505
511
  if (!getDbOrNull())
@@ -611,8 +617,8 @@ function writeMilestoneStatus(milestoneId, status, completedAt) {
611
617
  * advance planned milestones. They may not reopen a closed milestone; callers
612
618
  * must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
613
619
  */
614
- export function updateMilestoneStatus(milestoneId, status, completedAt) {
615
- applyStatusTransition({ entity: "milestone", milestoneId, status, completedAt });
620
+ export function updateMilestoneStatus(milestoneId, status, completedAt, preserveCompletion) {
621
+ applyStatusTransition({ entity: "milestone", milestoneId, status, completedAt, preserveCompletion });
616
622
  }
617
623
  /**
618
624
  * Explicit closed -> active transition for gsd_milestone_reopen only.
@@ -657,6 +663,128 @@ export function insertReplanHistory(entry) {
657
663
  ":created_at": new Date().toISOString(),
658
664
  });
659
665
  }
666
+ function reworkBriefIdFromTask(milestoneId, sliceId, taskId) {
667
+ return `RB-${milestoneId}-${sliceId}-${taskId}`;
668
+ }
669
+ function normalizeReworkStatus(status) {
670
+ const normalized = String(status ?? "pending");
671
+ if (normalized === "resolved" || normalized === "deferred-with-override") {
672
+ return normalized;
673
+ }
674
+ return "pending";
675
+ }
676
+ function rowToReworkFinding(row) {
677
+ let verificationCommands = [];
678
+ try {
679
+ const parsed = JSON.parse(String(row["verification_commands"] ?? "[]"));
680
+ verificationCommands = Array.isArray(parsed) ? parsed.map(String) : [];
681
+ }
682
+ catch {
683
+ verificationCommands = [];
684
+ }
685
+ return {
686
+ brief_id: String(row["brief_id"] ?? ""),
687
+ finding_id: String(row["finding_id"] ?? ""),
688
+ severity: String(row["severity"] ?? "blocking") === "advisory" ? "advisory" : "blocking",
689
+ description: String(row["description"] ?? ""),
690
+ required_fix: String(row["required_fix"] ?? ""),
691
+ verification_commands: verificationCommands,
692
+ status: normalizeReworkStatus(row["status"]),
693
+ evidence: String(row["evidence"] ?? ""),
694
+ decision_ref: String(row["decision_ref"] ?? ""),
695
+ };
696
+ }
697
+ export function saveReworkBrief(entry) {
698
+ if (!getDbOrNull())
699
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
700
+ const briefId = entry.briefId?.trim() || reworkBriefIdFromTask(entry.milestoneId, entry.sliceId, entry.taskId);
701
+ const now = new Date().toISOString();
702
+ transaction(() => {
703
+ getDbOrNull().prepare(`INSERT INTO rework_briefs (id, milestone_id, slice_id, task_id, created_at, updated_at)
704
+ VALUES (:id, :mid, :sid, :tid, :created_at, :updated_at)
705
+ ON CONFLICT(id) DO UPDATE SET
706
+ milestone_id = :mid,
707
+ slice_id = :sid,
708
+ task_id = :tid,
709
+ updated_at = :updated_at`).run({
710
+ ":id": briefId,
711
+ ":mid": entry.milestoneId,
712
+ ":sid": entry.sliceId,
713
+ ":tid": entry.taskId,
714
+ ":created_at": now,
715
+ ":updated_at": now,
716
+ });
717
+ getDbOrNull().prepare("DELETE FROM rework_brief_findings WHERE brief_id = :id").run({ ":id": briefId });
718
+ const stmt = getDbOrNull().prepare(`INSERT INTO rework_brief_findings (
719
+ brief_id, finding_id, severity, description, required_fix, verification_commands,
720
+ status, evidence, decision_ref, updated_at
721
+ ) VALUES (
722
+ :brief_id, :finding_id, :severity, :description, :required_fix, :verification_commands,
723
+ :status, :evidence, :decision_ref, :updated_at
724
+ )`);
725
+ for (const finding of entry.findings) {
726
+ stmt.run({
727
+ ":brief_id": briefId,
728
+ ":finding_id": finding.findingId,
729
+ ":severity": finding.severity,
730
+ ":description": finding.description,
731
+ ":required_fix": finding.requiredFix,
732
+ ":verification_commands": JSON.stringify(finding.verificationCommands),
733
+ ":status": finding.status ?? "pending",
734
+ ":evidence": finding.evidence ?? "",
735
+ ":decision_ref": finding.decisionRef ?? "",
736
+ ":updated_at": now,
737
+ });
738
+ }
739
+ });
740
+ return { briefId };
741
+ }
742
+ export function getBlockingReworkFindingsForTask(milestoneId, sliceId, taskId) {
743
+ if (!getDbOrNull())
744
+ return [];
745
+ const rows = getDbOrNull().prepare(`SELECT f.*
746
+ FROM rework_brief_findings f
747
+ JOIN rework_briefs b ON b.id = f.brief_id
748
+ WHERE b.milestone_id = :mid
749
+ AND b.slice_id = :sid
750
+ AND b.task_id = :tid
751
+ AND f.severity = 'blocking'
752
+ ORDER BY b.created_at, f.finding_id`).all({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
753
+ return rows.map(rowToReworkFinding);
754
+ }
755
+ export function getUnresolvedBlockingReworkFindingsForTask(milestoneId, sliceId, taskId) {
756
+ return getBlockingReworkFindingsForTask(milestoneId, sliceId, taskId)
757
+ .filter((finding) => finding.status === "pending");
758
+ }
759
+ export function applyReworkResolutions(resolutions) {
760
+ if (!getDbOrNull())
761
+ throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
762
+ const now = new Date().toISOString();
763
+ const stmt = getDbOrNull().prepare(`UPDATE rework_brief_findings
764
+ SET status = :status,
765
+ evidence = :evidence,
766
+ decision_ref = :decision_ref,
767
+ updated_at = :updated_at
768
+ WHERE finding_id = :finding_id
769
+ AND brief_id IN (
770
+ SELECT id FROM rework_briefs
771
+ WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid
772
+ )`);
773
+ transaction(() => {
774
+ for (const resolution of resolutions) {
775
+ stmt.run({
776
+ ":status": resolution.status,
777
+ ":evidence": resolution.evidence,
778
+ ":decision_ref": resolution.decisionRef ?? "",
779
+ ":updated_at": now,
780
+ ":finding_id": resolution.findingId,
781
+ ":mid": resolution.milestoneId,
782
+ ":sid": resolution.sliceId,
783
+ ":tid": resolution.taskId,
784
+ });
785
+ }
786
+ });
787
+ }
660
788
  export function insertAssessment(entry) {
661
789
  if (!getDbOrNull())
662
790
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
@@ -767,44 +895,51 @@ export function saveGateResult(g) {
767
895
  if (!getDbOrNull())
768
896
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
769
897
  const evaluatedAt = new Date().toISOString();
770
- const result = getDbOrNull().prepare(`UPDATE quality_gates
771
- SET status = 'complete', verdict = :verdict, rationale = :rationale,
772
- findings = :findings, evaluated_at = :evaluated_at
773
- WHERE milestone_id = :mid AND slice_id = :sid AND gate_id = :gid
774
- AND (task_id = :tid OR (:tid = '' AND task_id IS NULL))`).run({
775
- ":mid": g.milestoneId,
776
- ":sid": g.sliceId,
777
- ":gid": g.gateId,
778
- ":tid": g.taskId ?? "",
779
- ":verdict": g.verdict,
780
- ":rationale": g.rationale,
781
- ":findings": g.findings,
782
- ":evaluated_at": evaluatedAt,
783
- });
784
- if ((result.changes ?? 0) === 0) {
785
- throw new GSDError(GSD_STALE_STATE, `quality gate row not found for ${g.milestoneId}/${g.sliceId}/${g.gateId}${g.taskId ? `/${g.taskId}` : ""}`);
786
- }
787
- const outcome = g.verdict === "pass"
788
- ? "pass"
789
- : g.verdict === "omitted"
790
- ? "manual-attention"
791
- : "fail";
792
- insertGateRun({
793
- traceId: `quality-gate:${g.milestoneId}:${g.sliceId}`,
794
- turnId: `gate:${g.gateId}:${g.taskId ?? "slice"}`,
795
- gateId: g.gateId,
796
- gateType: "quality-gate",
797
- milestoneId: g.milestoneId,
798
- sliceId: g.sliceId,
799
- taskId: g.taskId ?? undefined,
800
- outcome,
801
- failureClass: outcome === "fail" ? "verification" : outcome === "manual-attention" ? "manual-attention" : "none",
802
- rationale: g.rationale,
803
- findings: g.findings,
804
- attempt: 1,
805
- maxAttempts: 1,
806
- retryable: false,
807
- evaluatedAt,
898
+ // Atomic: the gate verdict UPDATE and the gate_runs ledger INSERT must commit
899
+ // together. As two autocommits, a crash between them records a completed gate
900
+ // with no ledger row (the row Recovery Classification reads). transaction()
901
+ // is re-entrant, so callers already inside a transaction are safe. The
902
+ // throw-on-zero-changes stays inside so a missing gate row rolls back cleanly.
903
+ transaction(() => {
904
+ const result = getDbOrNull().prepare(`UPDATE quality_gates
905
+ SET status = 'complete', verdict = :verdict, rationale = :rationale,
906
+ findings = :findings, evaluated_at = :evaluated_at
907
+ WHERE milestone_id = :mid AND slice_id = :sid AND gate_id = :gid
908
+ AND (task_id = :tid OR (:tid = '' AND task_id IS NULL))`).run({
909
+ ":mid": g.milestoneId,
910
+ ":sid": g.sliceId,
911
+ ":gid": g.gateId,
912
+ ":tid": g.taskId ?? "",
913
+ ":verdict": g.verdict,
914
+ ":rationale": g.rationale,
915
+ ":findings": g.findings,
916
+ ":evaluated_at": evaluatedAt,
917
+ });
918
+ if ((result.changes ?? 0) === 0) {
919
+ throw new GSDError(GSD_STALE_STATE, `quality gate row not found for ${g.milestoneId}/${g.sliceId}/${g.gateId}${g.taskId ? `/${g.taskId}` : ""}`);
920
+ }
921
+ const outcome = g.verdict === "pass"
922
+ ? "pass"
923
+ : g.verdict === "omitted"
924
+ ? "manual-attention"
925
+ : "fail";
926
+ insertGateRun({
927
+ traceId: `quality-gate:${g.milestoneId}:${g.sliceId}`,
928
+ turnId: `gate:${g.gateId}:${g.taskId ?? "slice"}`,
929
+ gateId: g.gateId,
930
+ gateType: "quality-gate",
931
+ milestoneId: g.milestoneId,
932
+ sliceId: g.sliceId,
933
+ taskId: g.taskId ?? undefined,
934
+ outcome,
935
+ failureClass: outcome === "fail" ? "verification" : outcome === "manual-attention" ? "manual-attention" : "none",
936
+ rationale: g.rationale,
937
+ findings: g.findings,
938
+ attempt: 1,
939
+ maxAttempts: 1,
940
+ retryable: false,
941
+ evaluatedAt,
942
+ });
808
943
  });
809
944
  }
810
945
  export function markAllGatesOmitted(milestoneId, sliceId) {
@@ -1012,6 +1147,13 @@ export function deleteArtifactsByPathPrefix(prefix) {
1012
1147
  return Number(countRow?.["count"] ?? 0);
1013
1148
  });
1014
1149
  }
1150
+ /** List artifact rows whose paths share a DB-relative prefix. */
1151
+ export function getArtifactsByPathPrefix(prefix) {
1152
+ if (!getDbOrNull())
1153
+ return [];
1154
+ const rows = getDbOrNull().prepare("SELECT * FROM artifacts WHERE path LIKE :prefix ORDER BY path").all({ ":prefix": `${prefix}%` });
1155
+ return rows.map(rowToArtifact);
1156
+ }
1015
1157
  /**
1016
1158
  * Drop hierarchy rows in dependency order inside a transaction. Used by
1017
1159
  * `gsd recover --confirm` to rebuild engine state from markdown.
@@ -603,6 +603,9 @@ export const _dispatchWorkflowForTest = dispatchWorkflow;
603
603
  export function getDiscussableFutureMilestones(registry, activeMilestoneId) {
604
604
  return registry.filter((m) => m.id !== activeMilestoneId && m.status !== "complete" && m.status !== "parked");
605
605
  }
606
+ function findDiscussTargetMilestone(registry, milestoneId) {
607
+ return registry.find((m) => m.id === milestoneId || m.id.startsWith(`${milestoneId}-`));
608
+ }
606
609
  function getStructuredQuestionsAvailability(pi, ctx) {
607
610
  if (!ctx)
608
611
  return "false";
@@ -964,12 +967,20 @@ export async function showDiscuss(ctx, pi, basePath, options) {
964
967
  if (slash > 0) {
965
968
  const mid = target.slice(0, slash);
966
969
  const sid = target.slice(slash + 1);
967
- const targetMilestone = state.registry.find((m) => m.id === mid);
968
- if (!targetMilestone || targetMilestone.status === "complete" || targetMilestone.status === "parked") {
969
- ctx.ui.notify(`Milestone ${mid} is not discussable.`, "warning");
970
+ const targetMilestone = findDiscussTargetMilestone(state.registry, mid);
971
+ if (!targetMilestone) {
972
+ ctx.ui.notify(`Milestone ${mid} was not found in the roadmap. Use /gsd new-milestone to add it, or /gsd status to see available milestones.`, "warning");
973
+ return;
974
+ }
975
+ if (targetMilestone.status === "complete") {
976
+ ctx.ui.notify(`Milestone ${targetMilestone.id} is already complete.`, "info");
977
+ return;
978
+ }
979
+ if (targetMilestone.status === "parked") {
980
+ ctx.ui.notify(`Milestone ${targetMilestone.id} is parked. Run /gsd unpark ${targetMilestone.id} to reactivate.`, "warning");
970
981
  return;
971
982
  }
972
- const slices = await loadDiscussNormSlices(basePath, mid);
983
+ const slices = await loadDiscussNormSlices(basePath, targetMilestone.id);
973
984
  const chosen = slices.find((s) => s.id.toUpperCase() === sid.toUpperCase());
974
985
  if (!chosen) {
975
986
  ctx.ui.notify(`Slice ${target} was not found in discussable slices.`, "warning");
@@ -979,19 +990,27 @@ export async function showDiscuss(ctx, pi, basePath, options) {
979
990
  ctx.ui.notify(`Slice ${target} is already complete; nothing to discuss.`, "info");
980
991
  return;
981
992
  }
982
- const discussBasePath = resolveDiscussSliceBasePath(basePath, mid);
983
- const contextFile = resolveSliceFile(discussBasePath, mid, sid, "CONTEXT");
993
+ const discussBasePath = resolveDiscussSliceBasePath(basePath, targetMilestone.id);
994
+ const contextFile = resolveSliceFile(discussBasePath, targetMilestone.id, sid, "CONTEXT");
984
995
  const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
985
- const prompt = await buildDiscussSlicePrompt(mid, sid, chosen.title, discussBasePath, {
996
+ const prompt = await buildDiscussSlicePrompt(targetMilestone.id, sid, chosen.title, discussBasePath, {
986
997
  rediscuss: !!contextFile,
987
998
  structuredQuestionsAvailable: sqAvail,
988
999
  });
989
1000
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice", { basePath: discussBasePath });
990
1001
  return;
991
1002
  }
992
- const targetMilestone = state.registry.find((m) => m.id === target);
993
- if (!targetMilestone || targetMilestone.status === "complete" || targetMilestone.status === "parked") {
994
- ctx.ui.notify(`Milestone ${target} is not discussable.`, "warning");
1003
+ const targetMilestone = findDiscussTargetMilestone(state.registry, target);
1004
+ if (!targetMilestone) {
1005
+ ctx.ui.notify(`Milestone ${target} was not found in the roadmap. Use /gsd new-milestone to add it, or /gsd status to see available milestones.`, "warning");
1006
+ return;
1007
+ }
1008
+ if (targetMilestone.status === "complete") {
1009
+ ctx.ui.notify(`Milestone ${targetMilestone.id} is already complete.`, "info");
1010
+ return;
1011
+ }
1012
+ if (targetMilestone.status === "parked") {
1013
+ ctx.ui.notify(`Milestone ${targetMilestone.id} is parked. Run /gsd unpark ${targetMilestone.id} to reactivate.`, "warning");
995
1014
  return;
996
1015
  }
997
1016
  await dispatchDiscussForMilestone(ctx, pi, basePath, targetMilestone.id, targetMilestone.title, {});