@opengsd/gsd-pi 1.8.1 → 1.9.0-dev.5c1a3ff2

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 (546) 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 +18 -49
  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/artifact-verification.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-artifact-paths.js +16 -10
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -19
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -0
  20. package/dist/resources/extensions/gsd/auto-prompts.js +125 -18
  21. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  22. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
  24. package/dist/resources/extensions/gsd/auto.js +5 -1
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +80 -4
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
  27. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
  28. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -50
  29. package/dist/resources/extensions/gsd/command-feedback.js +16 -12
  30. package/dist/resources/extensions/gsd/compat/compat-marker.js +19 -2
  31. package/dist/resources/extensions/gsd/constants.js +12 -13
  32. package/dist/resources/extensions/gsd/context-masker.js +97 -21
  33. package/dist/resources/extensions/gsd/db/command-queue.js +7 -2
  34. package/dist/resources/extensions/gsd/db/engine.js +51 -16
  35. package/dist/resources/extensions/gsd/db/writers/cascades.js +5 -1
  36. package/dist/resources/extensions/gsd/db/writers/status.js +13 -3
  37. package/dist/resources/extensions/gsd/db-base-schema.js +28 -0
  38. package/dist/resources/extensions/gsd/db-migration-steps.js +30 -0
  39. package/dist/resources/extensions/gsd/db-schema-metadata.js +4 -1
  40. package/dist/resources/extensions/gsd/docs/preferences-reference.md +26 -4
  41. package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
  42. package/dist/resources/extensions/gsd/flat-phase-migration.js +156 -21
  43. package/dist/resources/extensions/gsd/git-service.js +19 -7
  44. package/dist/resources/extensions/gsd/gsd-db.js +187 -45
  45. package/dist/resources/extensions/gsd/guided-flow.js +38 -13
  46. package/dist/resources/extensions/gsd/markdown-renderer.js +17 -13
  47. package/dist/resources/extensions/gsd/md-importer.js +107 -20
  48. package/dist/resources/extensions/gsd/migrate/command.js +19 -162
  49. package/dist/resources/extensions/gsd/migrate/execution.js +98 -0
  50. package/dist/resources/extensions/gsd/migrate/plan.js +49 -0
  51. package/dist/resources/extensions/gsd/migrate/planning-writer.js +12 -17
  52. package/dist/resources/extensions/gsd/migrate/presentation.js +64 -0
  53. package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
  54. package/dist/resources/extensions/gsd/model-router.js +11 -0
  55. package/dist/resources/extensions/gsd/parallel-eligibility.js +5 -4
  56. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +28 -43
  57. package/dist/resources/extensions/gsd/paths.js +55 -11
  58. package/dist/resources/extensions/gsd/phase-anchor.js +7 -2
  59. package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
  60. package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
  61. package/dist/resources/extensions/gsd/post-execution-checks.js +4 -4
  62. package/dist/resources/extensions/gsd/preferences-types.js +5 -0
  63. package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
  64. package/dist/resources/extensions/gsd/preferences.js +17 -0
  65. package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  66. package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
  67. package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
  68. package/dist/resources/extensions/gsd/safety/evidence-collector.js +26 -1
  69. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +30 -5
  70. package/dist/resources/extensions/gsd/skill-activation.js +17 -3
  71. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +27 -13
  72. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +13 -3
  73. package/dist/resources/extensions/gsd/stop-notice.js +15 -1
  74. package/dist/resources/extensions/gsd/sync-lock.js +13 -7
  75. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  76. package/dist/resources/extensions/gsd/tools/complete-task.js +94 -6
  77. package/dist/resources/extensions/gsd/tools/plan-task.js +13 -15
  78. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +18 -15
  79. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
  80. package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
  81. package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
  82. package/dist/resources/extensions/gsd/tools/replan-task.js +128 -0
  83. package/dist/resources/extensions/gsd/tools/rework-brief.js +74 -0
  84. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -0
  85. package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
  86. package/dist/resources/extensions/gsd/unit-registry.js +4 -0
  87. package/dist/resources/extensions/gsd/uok/plan-v2.js +19 -3
  88. package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
  89. package/dist/resources/extensions/gsd/workspace.js +2 -1
  90. package/dist/resources/extensions/gsd/worktree-state-projection.js +6 -0
  91. package/dist/resources/extensions/mac-tools/index.js +31 -9
  92. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  93. package/dist/web/standalone/.next/BUILD_ID +1 -1
  94. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  95. package/dist/web/standalone/.next/build-manifest.json +3 -3
  96. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  97. package/dist/web/standalone/.next/required-server-files.json +3 -3
  98. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  99. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  109. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  111. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  118. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  125. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  137. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  159. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  169. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  175. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  191. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  195. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app/index.html +1 -1
  205. package/dist/web/standalone/.next/server/app/index.rsc +5 -5
  206. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  207. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
  208. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  209. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
  210. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  211. package/dist/web/standalone/.next/server/app/page.js +2 -2
  212. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  213. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  214. package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
  215. package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
  216. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  217. package/dist/web/standalone/.next/server/middleware.js +3 -3
  218. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  219. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  220. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  221. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +1 -0
  222. package/dist/web/standalone/.next/static/chunks/app/{layout-b35cbfff38aaf4cf.js → layout-4ae2d68984392bbf.js} +1 -1
  223. package/dist/web/standalone/.next/static/chunks/app/page-72a856634ad14c10.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/{main-app-590a74400e35f685.js → main-app-90d1d8d5e5d2dc6b.js} +1 -1
  225. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +1 -0
  226. package/dist/web/standalone/.next/static/css/3b1ceb7abfb9a230.css +1 -0
  227. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  228. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  229. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  230. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  231. package/dist/web/standalone/package.json +0 -1
  232. package/dist/web/standalone/server.js +1 -1
  233. package/dist/web-mode.js +31 -3
  234. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  235. package/integrations/hermes/pyproject.toml +1 -1
  236. package/package.json +12 -10
  237. package/packages/cloud-mcp-gateway/package.json +2 -2
  238. package/packages/contracts/dist/workflow.d.ts +14 -0
  239. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  240. package/packages/contracts/dist/workflow.js +16 -0
  241. package/packages/contracts/dist/workflow.js.map +1 -1
  242. package/packages/contracts/package.json +1 -1
  243. package/packages/daemon/dist/cloud-config.d.ts +1 -1
  244. package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
  245. package/packages/daemon/dist/cloud-config.js +5 -1
  246. package/packages/daemon/dist/cloud-config.js.map +1 -1
  247. package/packages/daemon/dist/cloud-config.test.js +51 -1
  248. package/packages/daemon/dist/cloud-config.test.js.map +1 -1
  249. package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -1
  250. package/packages/daemon/dist/local-tool-executor.js +2 -0
  251. package/packages/daemon/dist/local-tool-executor.js.map +1 -1
  252. package/packages/daemon/package.json +6 -6
  253. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  254. package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
  255. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  256. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  257. package/packages/gsd-agent-core/dist/sdk.js +1 -0
  258. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  259. package/packages/gsd-agent-core/package.json +5 -5
  260. package/packages/gsd-agent-modes/package.json +7 -7
  261. package/packages/gsd-cloud/dist/cli.d.ts +5 -0
  262. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -0
  263. package/packages/gsd-cloud/dist/cli.js +160 -0
  264. package/packages/gsd-cloud/dist/cli.js.map +1 -0
  265. package/packages/gsd-cloud/dist/cloud-config.d.ts +19 -0
  266. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -0
  267. package/packages/gsd-cloud/dist/cloud-config.js +231 -0
  268. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -0
  269. package/packages/gsd-cloud/dist/cloud-config.test.d.ts +2 -0
  270. package/packages/gsd-cloud/dist/cloud-config.test.d.ts.map +1 -0
  271. package/packages/gsd-cloud/dist/cloud-config.test.js +118 -0
  272. package/packages/gsd-cloud/dist/cloud-config.test.js.map +1 -0
  273. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts +2 -0
  274. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts.map +1 -0
  275. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js +37 -0
  276. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js.map +1 -0
  277. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +35 -0
  278. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -0
  279. package/packages/gsd-cloud/dist/cloud-runtime.js +258 -0
  280. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -0
  281. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts +2 -0
  282. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts.map +1 -0
  283. package/packages/gsd-cloud/dist/cloud-runtime.test.js +88 -0
  284. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -0
  285. package/packages/gsd-cloud/dist/cloud-token.d.ts +3 -0
  286. package/packages/gsd-cloud/dist/cloud-token.d.ts.map +1 -0
  287. package/packages/gsd-cloud/dist/cloud-token.js +37 -0
  288. package/packages/gsd-cloud/dist/cloud-token.js.map +1 -0
  289. package/packages/gsd-cloud/dist/config.d.ts +17 -0
  290. package/packages/gsd-cloud/dist/config.d.ts.map +1 -0
  291. package/packages/gsd-cloud/dist/config.js +146 -0
  292. package/packages/gsd-cloud/dist/config.js.map +1 -0
  293. package/packages/gsd-cloud/dist/device-flow.d.ts +24 -0
  294. package/packages/gsd-cloud/dist/device-flow.d.ts.map +1 -0
  295. package/packages/gsd-cloud/dist/device-flow.js +128 -0
  296. package/packages/gsd-cloud/dist/device-flow.js.map +1 -0
  297. package/packages/gsd-cloud/dist/device-flow.test.d.ts +2 -0
  298. package/packages/gsd-cloud/dist/device-flow.test.d.ts.map +1 -0
  299. package/packages/gsd-cloud/dist/device-flow.test.js +155 -0
  300. package/packages/gsd-cloud/dist/device-flow.test.js.map +1 -0
  301. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts +6 -0
  302. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts.map +1 -0
  303. package/packages/gsd-cloud/dist/executors/claude-executor.js +15 -0
  304. package/packages/gsd-cloud/dist/executors/claude-executor.js.map +1 -0
  305. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts +6 -0
  306. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts.map +1 -0
  307. package/packages/gsd-cloud/dist/executors/codex-executor.js +15 -0
  308. package/packages/gsd-cloud/dist/executors/codex-executor.js.map +1 -0
  309. package/packages/gsd-cloud/dist/executors/executor.d.ts +28 -0
  310. package/packages/gsd-cloud/dist/executors/executor.d.ts.map +1 -0
  311. package/packages/gsd-cloud/dist/executors/executor.js +11 -0
  312. package/packages/gsd-cloud/dist/executors/executor.js.map +1 -0
  313. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts +34 -0
  314. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts.map +1 -0
  315. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js +179 -0
  316. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js.map +1 -0
  317. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts +2 -0
  318. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts.map +1 -0
  319. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js +44 -0
  320. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js.map +1 -0
  321. package/packages/gsd-cloud/dist/executors/index.d.ts +17 -0
  322. package/packages/gsd-cloud/dist/executors/index.d.ts.map +1 -0
  323. package/packages/gsd-cloud/dist/executors/index.js +29 -0
  324. package/packages/gsd-cloud/dist/executors/index.js.map +1 -0
  325. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts +36 -0
  326. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts.map +1 -0
  327. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js +176 -0
  328. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js.map +1 -0
  329. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts +2 -0
  330. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts.map +1 -0
  331. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js +34 -0
  332. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js.map +1 -0
  333. package/packages/gsd-cloud/dist/index.d.ts +7 -0
  334. package/packages/gsd-cloud/dist/index.d.ts.map +1 -0
  335. package/packages/gsd-cloud/dist/index.js +8 -0
  336. package/packages/gsd-cloud/dist/index.js.map +1 -0
  337. package/packages/gsd-cloud/dist/inject-gateway.d.ts +16 -0
  338. package/packages/gsd-cloud/dist/inject-gateway.d.ts.map +1 -0
  339. package/packages/gsd-cloud/dist/inject-gateway.js +29 -0
  340. package/packages/gsd-cloud/dist/inject-gateway.js.map +1 -0
  341. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts +2 -0
  342. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts.map +1 -0
  343. package/packages/gsd-cloud/dist/inject-gateway.test.js +41 -0
  344. package/packages/gsd-cloud/dist/inject-gateway.test.js.map +1 -0
  345. package/packages/gsd-cloud/dist/logger.d.ts +25 -0
  346. package/packages/gsd-cloud/dist/logger.d.ts.map +1 -0
  347. package/packages/gsd-cloud/dist/logger.js +72 -0
  348. package/packages/gsd-cloud/dist/logger.js.map +1 -0
  349. package/packages/gsd-cloud/dist/types.d.ts +51 -0
  350. package/packages/gsd-cloud/dist/types.d.ts.map +1 -0
  351. package/packages/gsd-cloud/dist/types.js +9 -0
  352. package/packages/gsd-cloud/dist/types.js.map +1 -0
  353. package/packages/gsd-cloud/package.json +3 -3
  354. package/packages/mcp-server/README.md +15 -2
  355. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  356. package/packages/mcp-server/dist/server.js +5 -5
  357. package/packages/mcp-server/dist/server.js.map +1 -1
  358. package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
  359. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  360. package/packages/mcp-server/dist/workflow-tools.js +64 -2
  361. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  362. package/packages/mcp-server/package.json +4 -4
  363. package/packages/native/package.json +1 -1
  364. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  365. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  366. package/packages/pi-agent-core/dist/agent.js +2 -0
  367. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  368. package/packages/pi-agent-core/package.json +1 -1
  369. package/packages/pi-ai/dist/models.generated.d.ts +119 -0
  370. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  371. package/packages/pi-ai/dist/models.generated.js +107 -0
  372. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  373. package/packages/pi-ai/dist/providers/amazon-bedrock.js +1 -0
  374. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  375. package/packages/pi-ai/dist/providers/anthropic.js +25 -1
  376. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  377. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  378. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  379. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  380. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  381. package/packages/pi-ai/package.json +1 -1
  382. package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
  383. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  384. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  385. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  386. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
  387. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  388. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  389. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  390. package/packages/pi-coding-agent/package.json +7 -7
  391. package/packages/pi-tui/package.json +2 -2
  392. package/packages/rpc-client/package.json +2 -2
  393. package/pkg/package.json +1 -1
  394. package/src/resources/GSD-WORKFLOW.md +18 -49
  395. package/src/resources/extensions/browser-tools/capture.ts +2 -86
  396. package/src/resources/extensions/browser-tools/screenshot-constraints.ts +94 -0
  397. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -1
  398. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -0
  399. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +30 -2
  400. package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
  401. package/src/resources/extensions/gsd/auto-artifact-paths.ts +16 -9
  402. package/src/resources/extensions/gsd/auto-dispatch.ts +86 -19
  403. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -0
  404. package/src/resources/extensions/gsd/auto-prompts.ts +142 -16
  405. package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
  406. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
  407. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
  408. package/src/resources/extensions/gsd/auto.ts +5 -1
  409. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +92 -4
  410. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
  411. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
  412. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +87 -53
  413. package/src/resources/extensions/gsd/command-feedback.ts +19 -12
  414. package/src/resources/extensions/gsd/compat/compat-marker.ts +21 -4
  415. package/src/resources/extensions/gsd/constants.ts +14 -13
  416. package/src/resources/extensions/gsd/context-masker.ts +98 -2
  417. package/src/resources/extensions/gsd/db/command-queue.ts +7 -1
  418. package/src/resources/extensions/gsd/db/engine.ts +53 -17
  419. package/src/resources/extensions/gsd/db/writers/cascades.ts +5 -1
  420. package/src/resources/extensions/gsd/db/writers/status.ts +25 -8
  421. package/src/resources/extensions/gsd/db-base-schema.ts +30 -0
  422. package/src/resources/extensions/gsd/db-migration-steps.ts +32 -0
  423. package/src/resources/extensions/gsd/db-schema-metadata.ts +4 -1
  424. package/src/resources/extensions/gsd/docs/preferences-reference.md +26 -4
  425. package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
  426. package/src/resources/extensions/gsd/flat-phase-migration.ts +160 -19
  427. package/src/resources/extensions/gsd/git-service.ts +19 -6
  428. package/src/resources/extensions/gsd/gsd-db.ts +255 -52
  429. package/src/resources/extensions/gsd/guided-flow.ts +48 -12
  430. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -21
  431. package/src/resources/extensions/gsd/md-importer.ts +114 -16
  432. package/src/resources/extensions/gsd/migrate/command.ts +21 -227
  433. package/src/resources/extensions/gsd/migrate/execution.ts +136 -0
  434. package/src/resources/extensions/gsd/migrate/plan.ts +101 -0
  435. package/src/resources/extensions/gsd/migrate/planning-writer.ts +12 -20
  436. package/src/resources/extensions/gsd/migrate/presentation.ts +83 -0
  437. package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
  438. package/src/resources/extensions/gsd/model-router.ts +12 -0
  439. package/src/resources/extensions/gsd/parallel-eligibility.ts +5 -4
  440. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +37 -40
  441. package/src/resources/extensions/gsd/paths.ts +62 -11
  442. package/src/resources/extensions/gsd/phase-anchor.ts +8 -2
  443. package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
  444. package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
  445. package/src/resources/extensions/gsd/post-execution-checks.ts +4 -4
  446. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  447. package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
  448. package/src/resources/extensions/gsd/preferences.ts +25 -0
  449. package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  450. package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
  451. package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
  452. package/src/resources/extensions/gsd/safety/evidence-collector.ts +28 -1
  453. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +31 -5
  454. package/src/resources/extensions/gsd/skill-activation.ts +17 -3
  455. package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +30 -13
  456. package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +16 -3
  457. package/src/resources/extensions/gsd/state-reconciliation/types.ts +2 -2
  458. package/src/resources/extensions/gsd/stop-notice.ts +18 -1
  459. package/src/resources/extensions/gsd/sync-lock.ts +14 -6
  460. package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  461. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
  462. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
  463. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
  464. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +96 -1
  465. package/src/resources/extensions/gsd/tests/command-feedback.test.ts +12 -0
  466. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +104 -0
  467. package/src/resources/extensions/gsd/tests/complete-task.test.ts +362 -1
  468. package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
  469. package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +186 -0
  470. package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
  471. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +29 -0
  472. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
  473. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
  474. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +123 -0
  475. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +50 -0
  476. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +49 -0
  477. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +81 -0
  478. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +22 -0
  479. package/src/resources/extensions/gsd/tests/external-edit-scoped-import.test.ts +262 -0
  480. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +202 -0
  481. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +29 -0
  482. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +25 -0
  483. package/src/resources/extensions/gsd/tests/import-slice-milestone-completion-preserve.test.ts +116 -0
  484. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
  485. package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
  486. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
  487. package/src/resources/extensions/gsd/tests/migrate-plan.test.ts +68 -0
  488. package/src/resources/extensions/gsd/tests/migrate-presentation.test.ts +98 -0
  489. package/src/resources/extensions/gsd/tests/migrate-safety-audit.test.ts +1 -1
  490. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +55 -0
  491. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  492. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  493. package/src/resources/extensions/gsd/tests/parallel-eligibility-file-overlap.test.ts +146 -0
  494. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +59 -0
  495. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
  496. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +32 -4
  497. package/src/resources/extensions/gsd/tests/plan-task.test.ts +26 -23
  498. package/src/resources/extensions/gsd/tests/planning-writer.test.ts +29 -0
  499. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +22 -0
  500. package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
  501. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
  502. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
  503. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
  504. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +50 -0
  505. package/src/resources/extensions/gsd/tests/replan-task-handler.test.ts +103 -0
  506. package/src/resources/extensions/gsd/tests/rework-brief.test.ts +197 -0
  507. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  508. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
  509. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
  510. package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +21 -0
  511. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +17 -0
  512. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +19 -0
  513. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
  514. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
  515. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +73 -0
  516. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +93 -1
  517. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -0
  518. package/src/resources/extensions/gsd/tests/workflow-migration-gates.test.ts +139 -0
  519. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
  520. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +36 -0
  521. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
  522. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +52 -0
  523. package/src/resources/extensions/gsd/tools/complete-task.ts +140 -5
  524. package/src/resources/extensions/gsd/tools/plan-task.ts +14 -16
  525. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +18 -17
  526. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
  527. package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
  528. package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
  529. package/src/resources/extensions/gsd/tools/replan-task.ts +160 -0
  530. package/src/resources/extensions/gsd/tools/rework-brief.ts +91 -0
  531. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +92 -0
  532. package/src/resources/extensions/gsd/types.ts +11 -0
  533. package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
  534. package/src/resources/extensions/gsd/unit-registry.ts +4 -0
  535. package/src/resources/extensions/gsd/uok/plan-v2.ts +20 -3
  536. package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
  537. package/src/resources/extensions/gsd/workspace.ts +2 -1
  538. package/src/resources/extensions/gsd/worktree-state-projection.ts +12 -0
  539. package/src/resources/extensions/mac-tools/index.ts +52 -9
  540. package/src/resources/extensions/mac-tools/tests/mac-screenshot-payload.test.cjs +70 -0
  541. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +0 -1
  542. package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +0 -1
  543. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +0 -1
  544. package/dist/web/standalone/.next/static/css/18c2fdf261bfc1b7.css +0 -1
  545. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
  546. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
@@ -54,6 +54,87 @@ test("passing retry evidence is not invalidated by an earlier failed run of the
54
54
  assert.deepEqual(mismatches, []);
55
55
  });
56
56
 
57
+ test("newer script-wrapped pass is not shadowed by a stale exact failing run", () => {
58
+ const command = "npm test";
59
+ const mismatches = crossReferenceEvidence(
60
+ [{ command, exitCode: 0, verdict: "passed after retry" }],
61
+ [
62
+ {
63
+ kind: "bash",
64
+ toolCallId: "call-1",
65
+ command,
66
+ exitCode: 1,
67
+ outputSnippet: "failed",
68
+ timestamp: 1,
69
+ },
70
+ {
71
+ kind: "bash",
72
+ toolCallId: "call-2",
73
+ command: `cd /work && ${command}`,
74
+ exitCode: 0,
75
+ outputSnippet: "passed",
76
+ timestamp: 2,
77
+ },
78
+ ] as EvidenceEntry[],
79
+ );
80
+
81
+ assert.deepEqual(mismatches, []);
82
+ });
83
+
84
+ test("later containing script does not override an exact successful run", () => {
85
+ const command = "npm test";
86
+ const mismatches = crossReferenceEvidence(
87
+ [{ command, exitCode: 0, verdict: "passed" }],
88
+ [
89
+ {
90
+ kind: "bash",
91
+ toolCallId: "call-1",
92
+ command,
93
+ exitCode: 0,
94
+ outputSnippet: "passed",
95
+ timestamp: 1,
96
+ },
97
+ {
98
+ kind: "bash",
99
+ toolCallId: "call-2",
100
+ command: `cd /work && ${command} && npm run lint`,
101
+ exitCode: 1,
102
+ outputSnippet: "lint failed",
103
+ timestamp: 2,
104
+ },
105
+ ] as EvidenceEntry[],
106
+ );
107
+
108
+ assert.deepEqual(mismatches, []);
109
+ });
110
+
111
+ test("same-timestamp retry evidence prefers the later recorded run", () => {
112
+ const command = "npm test";
113
+ const mismatches = crossReferenceEvidence(
114
+ [{ command, exitCode: 0, verdict: "passed after retry" }],
115
+ [
116
+ {
117
+ kind: "bash",
118
+ toolCallId: "call-1",
119
+ command,
120
+ exitCode: 1,
121
+ outputSnippet: "failed",
122
+ timestamp: 1,
123
+ },
124
+ {
125
+ kind: "bash",
126
+ toolCallId: "call-2",
127
+ command,
128
+ exitCode: 0,
129
+ outputSnippet: "passed",
130
+ timestamp: 1,
131
+ },
132
+ ] as EvidenceEntry[],
133
+ );
134
+
135
+ assert.deepEqual(mismatches, []);
136
+ });
137
+
57
138
  test("stale verification evidence batches are ignored when a newer completion batch exists", () => {
58
139
  const command = "node todo.js add 'Task A' && node todo.js add 'Task B' && node todo.js done 1";
59
140
  const resetCommand = `rm -f "$HOME/.config/todo/data.json" && ${command}`;
@@ -60,6 +60,28 @@ test("evidence-xref: verification run through gsd_exec script matches the claime
60
60
  assert.deepEqual(mismatches, [], "gsd_exec-executed verification must not be flagged as missing");
61
61
  });
62
62
 
63
+ test("evidence-xref: gsd_exec runtime-purpose label matches the recorded sandbox run", () => {
64
+ resetEvidence();
65
+
66
+ recordToolCall("tc-exec-label", "gsd_exec", {
67
+ runtime: "node",
68
+ code: "const plan = 'T02-PLAN'; if (!plan.includes('T02')) process.exit(1);",
69
+ purpose: "validate T02-PLAN contains canonical Cargo command, state seam, d",
70
+ });
71
+ recordToolResult("tc-exec-label", "gsd_exec", gsdExecResult(0), false);
72
+
73
+ const mismatches = crossReferenceEvidence(
74
+ [{
75
+ command: "gsd_exec node: validate T02-PLAN contains canonical Cargo command, state seam, d",
76
+ exitCode: 0,
77
+ verdict: "passed",
78
+ }],
79
+ getEvidence(),
80
+ );
81
+
82
+ assert.deepEqual(mismatches, [], "gsd_exec purpose labels must match sandbox evidence");
83
+ });
84
+
63
85
  test("evidence-xref: multi-line gsd_exec script matches claims for each embedded command", () => {
64
86
  resetEvidence();
65
87
 
@@ -0,0 +1,262 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: #027 regression — external-edit drift repair must scope status
3
+ // authority to the drifted milestone(s) so a stale checkbox in an unrelated
4
+ // projection can't revert a reopened slice/milestone in the DB.
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+
12
+ import {
13
+ closeDatabase,
14
+ getSliceTasks,
15
+ getSlice,
16
+ openDatabase,
17
+ updateTaskStatus,
18
+ updateSliceStatus,
19
+ } from "../gsd-db.ts";
20
+ import { migrateHierarchyToDb, milestoneIdsFromEntities } from "../md-importer.ts";
21
+ import { externalMarkdownEditHandler } from "../state-reconciliation/drift/external-markdown-edit.ts";
22
+ import {
23
+ computeProjectionSha,
24
+ writeCompatMarker,
25
+ } from "../compat/compat-marker.ts";
26
+ import type { DriftContext } from "../state-reconciliation/types.ts";
27
+ import type { GSDState } from "../types.ts";
28
+
29
+ const stubState = { phase: "idle" } as unknown as GSDState;
30
+
31
+ function makeBase(): string {
32
+ const base = mkdtempSync(join(tmpdir(), "gsd-scoped-import-"));
33
+ mkdirSync(join(base, ".gsd"), { recursive: true });
34
+ return base;
35
+ }
36
+
37
+ function cleanup(base: string): void {
38
+ try {
39
+ closeDatabase();
40
+ } catch {
41
+ /* noop */
42
+ }
43
+ rmSync(base, { recursive: true, force: true });
44
+ }
45
+
46
+ const roadmapRel = (mid: string): string => join("milestones", mid, `${mid}-ROADMAP.md`);
47
+
48
+ /** Render a single-slice roadmap whose one slice is checked (`done`). */
49
+ function roadmapContent(mid: string, sliceTitle: string, done: boolean): string {
50
+ return [
51
+ `# ${mid}: ${mid} Milestone`,
52
+ "",
53
+ `**Vision:** ${mid} vision`,
54
+ "",
55
+ "## Slices",
56
+ `- [${done ? "x" : " "}] **S01: ${sliceTitle}** \`risk:low\` \`depends:[]\``,
57
+ "",
58
+ ].join("\n");
59
+ }
60
+
61
+ function writeRoadmap(base: string, mid: string, sliceTitle: string, done: boolean): string {
62
+ const dir = join(base, ".gsd", "milestones", mid);
63
+ mkdirSync(dir, { recursive: true });
64
+ const content = roadmapContent(mid, sliceTitle, done);
65
+ writeFileSync(join(dir, `${mid}-ROADMAP.md`), content, "utf-8");
66
+ return content;
67
+ }
68
+
69
+ function planContent(taskDone: boolean): string {
70
+ return [
71
+ "# S01: Slice Plan",
72
+ "",
73
+ "**Goal:** Exercise scoped task status imports.",
74
+ "",
75
+ "## Tasks",
76
+ "",
77
+ `- [${taskDone ? "x" : " "}] **T01: Scoped Task** \`est:10m\``,
78
+ " Task body.",
79
+ "",
80
+ ].join("\n");
81
+ }
82
+
83
+ function writePlan(base: string, mid: string, taskDone: boolean): void {
84
+ const dir = join(base, ".gsd", "milestones", mid, "slices", "S01");
85
+ mkdirSync(dir, { recursive: true });
86
+ writeFileSync(join(dir, "S01-PLAN.md"), planContent(taskDone), "utf-8");
87
+ }
88
+
89
+ function taskStatus(mid: string): string | undefined {
90
+ return getSliceTasks(mid, "S01").find((task) => task.id === "T01")?.status;
91
+ }
92
+
93
+ /**
94
+ * Build two legacy-layout milestones (M001, M002) each with one checked slice,
95
+ * import them so both slices land `complete`, then reopen M002/S01 to `pending`
96
+ * while leaving M002's roadmap checkbox checked (a stale projection).
97
+ */
98
+ function seedTwoMilestonesWithReopenedB(base: string): void {
99
+ writeRoadmap(base, "M001", "Alpha Slice", true);
100
+ writeRoadmap(base, "M002", "Beta Slice", true);
101
+ openDatabase(join(base, ".gsd", "gsd.db"));
102
+ migrateHierarchyToDb(base);
103
+ assert.equal(getSlice("M001", "S01")?.status, "complete");
104
+ assert.equal(getSlice("M002", "S01")?.status, "complete");
105
+ updateSliceStatus("M002", "S01", "pending");
106
+ assert.equal(getSlice("M002", "S01")?.status, "pending", "precondition: B/S01 reopened");
107
+ }
108
+
109
+ /**
110
+ * Same as seedTwoMilestonesWithReopenedB, but with checked task boxes too. The
111
+ * reopened DB task is the authority for out-of-scope milestones.
112
+ */
113
+ function seedTwoMilestonesWithReopenedBTask(base: string): void {
114
+ writeRoadmap(base, "M001", "Alpha Slice", true);
115
+ writePlan(base, "M001", true);
116
+ writeRoadmap(base, "M002", "Beta Slice", true);
117
+ writePlan(base, "M002", true);
118
+ openDatabase(join(base, ".gsd", "gsd.db"));
119
+ migrateHierarchyToDb(base);
120
+ assert.equal(getSlice("M002", "S01")?.status, "complete");
121
+ assert.equal(taskStatus("M002"), "complete");
122
+ updateSliceStatus("M002", "S01", "pending");
123
+ updateTaskStatus("M002", "S01", "T01", "pending");
124
+ assert.equal(getSlice("M002", "S01")?.status, "pending", "precondition: B/S01 reopened");
125
+ assert.equal(taskStatus("M002"), "pending", "precondition: B/S01/T01 reopened");
126
+ }
127
+
128
+ test("milestoneIdsFromEntities derives milestone ids from DB entity ids (layout-independent)", () => {
129
+ assert.deepEqual(
130
+ [...milestoneIdsFromEntities(["M001", "M001/S01", "M001/S01/T01"])],
131
+ ["M001"],
132
+ );
133
+ assert.deepEqual(
134
+ [...milestoneIdsFromEntities(["M001/S01", "M002/S03/T02"])].sort(),
135
+ ["M001", "M002"],
136
+ );
137
+ // Entity-less / blank entries yield an empty set → repair preserves DB status.
138
+ assert.equal(milestoneIdsFromEntities([]).size, 0);
139
+ assert.equal(milestoneIdsFromEntities([""]).size, 0);
140
+ });
141
+
142
+ test("scoped import preserves a reopened out-of-scope slice (the #027 bug, direct)", (t) => {
143
+ const base = makeBase();
144
+ t.after(() => cleanup(base));
145
+ seedTwoMilestonesWithReopenedB(base);
146
+
147
+ // Only M001 drifted; M002 keeps DB status authority.
148
+ migrateHierarchyToDb(base, { statusAuthoritativeMilestones: new Set(["M001"]) });
149
+
150
+ assert.equal(
151
+ getSlice("M002", "S01")?.status,
152
+ "pending",
153
+ "reopened out-of-scope slice must NOT be reverted to complete by a stale checkbox",
154
+ );
155
+ });
156
+
157
+ test("scoped import preserves reopened tasks in an out-of-scope slice", (t) => {
158
+ const base = makeBase();
159
+ t.after(() => cleanup(base));
160
+ seedTwoMilestonesWithReopenedBTask(base);
161
+
162
+ // Only M001 drifted; M002's stale checked roadmap and plan boxes must not
163
+ // re-complete either its reopened slice or its reopened task.
164
+ migrateHierarchyToDb(base, { statusAuthoritativeMilestones: new Set(["M001"]) });
165
+
166
+ assert.equal(
167
+ getSlice("M002", "S01")?.status,
168
+ "pending",
169
+ "reopened out-of-scope slice must stay pending",
170
+ );
171
+ assert.equal(
172
+ taskStatus("M002"),
173
+ "pending",
174
+ "reopened out-of-scope task must NOT be reverted to complete by a stale plan checkbox",
175
+ );
176
+ });
177
+
178
+ test("scoped import keeps markdown authority for the in-scope (drifted) milestone", (t) => {
179
+ const base = makeBase();
180
+ t.after(() => cleanup(base));
181
+ seedTwoMilestonesWithReopenedB(base);
182
+
183
+ // Sanctioned interop: M002's own roadmap was externally edited — the checked
184
+ // box IS authoritative for the drifted milestone.
185
+ migrateHierarchyToDb(base, { statusAuthoritativeMilestones: new Set(["M002"]) });
186
+
187
+ assert.equal(
188
+ getSlice("M002", "S01")?.status,
189
+ "complete",
190
+ "the drifted milestone's checkbox must close its slice (markdown authority)",
191
+ );
192
+ });
193
+
194
+ test("no-opts import is unchanged: markdown checkbox wins (pins the default)", (t) => {
195
+ const base = makeBase();
196
+ t.after(() => cleanup(base));
197
+ seedTwoMilestonesWithReopenedB(base);
198
+
199
+ // No scope → full markdown authority, exactly as before #027. Initial-migration
200
+ // and gsd-core full-import semantics must not silently change.
201
+ migrateHierarchyToDb(base);
202
+
203
+ assert.equal(
204
+ getSlice("M002", "S01")?.status,
205
+ "complete",
206
+ "without a scope, the stale checkbox closes the slice — today's behavior",
207
+ );
208
+ });
209
+
210
+ test("scoped import still imports NEW out-of-scope content with its parsed status", (t) => {
211
+ const base = makeBase();
212
+ t.after(() => cleanup(base));
213
+ // Both milestones are on disk but neither is in the DB yet. M002 is out of the
214
+ // authority scope, but a row that does not exist yet takes the parsed status
215
+ // regardless of scope (new content is new content).
216
+ writeRoadmap(base, "M001", "Alpha Slice", true);
217
+ writeRoadmap(base, "M002", "Beta Slice", true);
218
+ openDatabase(join(base, ".gsd", "gsd.db"));
219
+
220
+ migrateHierarchyToDb(base, { statusAuthoritativeMilestones: new Set(["M001"]) });
221
+
222
+ assert.equal(getSlice("M002", "S01")?.status, "complete", "new out-of-scope slice imports as parsed");
223
+ });
224
+
225
+ test("external-markdown-edit repair scopes authority to the drifted milestone (end-to-end)", async (t) => {
226
+ const base = makeBase();
227
+ t.after(() => cleanup(base));
228
+ seedTwoMilestonesWithReopenedB(base);
229
+
230
+ // Externally edit M001's roadmap (change the slice title) so its sha drifts;
231
+ // leave M002's roadmap byte-identical to its marker baseline (not drifted).
232
+ writeRoadmap(base, "M001", "Alpha Slice EDITED", true);
233
+ const m002Content = roadmapContent("M002", "Beta Slice", true);
234
+
235
+ writeCompatMarker(base, {
236
+ schema: 2,
237
+ lastWriter: "gsd-pi",
238
+ lastProjectedAt: "2026-07-07T00:00:00.000Z",
239
+ projections: {
240
+ [roadmapRel("M001")]: { sha: "stale000000000000", entities: ["M001"] },
241
+ [roadmapRel("M002")]: { sha: computeProjectionSha(m002Content), entities: ["M002"] },
242
+ },
243
+ piVersion: "1.8.1",
244
+ });
245
+
246
+ const ctx: DriftContext = { basePath: base, state: stubState };
247
+ const drift = await externalMarkdownEditHandler.detect(stubState, ctx);
248
+ assert.equal(drift.length, 1, "only M001's roadmap should be detected as drifted");
249
+ assert.deepEqual(drift[0].entities, ["M001"]);
250
+
251
+ await externalMarkdownEditHandler.repair(drift[0], ctx);
252
+
253
+ // B was NOT the drifted file — its reopened slice must survive the repair.
254
+ assert.equal(
255
+ getSlice("M002", "S01")?.status,
256
+ "pending",
257
+ "repair of an unrelated file must not revert B/S01",
258
+ );
259
+ // A WAS drifted — its edit is imported (title updated, status stays complete).
260
+ assert.equal(getSlice("M001", "S01")?.title, "Alpha Slice EDITED", "A's external edit is imported");
261
+ assert.equal(getSlice("M001", "S01")?.status, "complete");
262
+ });
@@ -57,6 +57,19 @@ test("needsFlatPhaseMigration returns false when no .gsd/milestones/", () => {
57
57
  assert.equal(needsFlatPhaseMigration(base), false);
58
58
  });
59
59
 
60
+ test("needsFlatPhaseMigration ignores legacy anchor runtime scaffolding", () => {
61
+ const base = mkdtempSync(join(tmpdir(), `gsd-anchor-nomig-${randomUUID()}`));
62
+ mkdirSync(join(base, ".gsd", "phases"), { recursive: true });
63
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "anchors"), { recursive: true });
64
+ writeFileSync(
65
+ join(base, ".gsd", "milestones", "M001", "anchors", "research-slice.json"),
66
+ "{}",
67
+ "utf-8",
68
+ );
69
+ tmpDirs.push(base);
70
+ assert.equal(needsFlatPhaseMigration(base), false);
71
+ });
72
+
60
73
  test("migrateToFlatPhase moves content from milestones/ to phases/", async () => {
61
74
  const base = makeTmp();
62
75
  await migrateToFlatPhase(base);
@@ -109,6 +122,59 @@ test("migrateToFlatPhase creates a backup", async () => {
109
122
  assert.ok(backups.length >= 1, "at least one migrate-* backup dir should exist");
110
123
  });
111
124
 
125
+ test("migrateToFlatPhase backs up existing phases projection before clearing it", async () => {
126
+ const base = makeTmp();
127
+ const reviewPath = join(base, ".gsd", "phases", "01-foundation", "PLAN-REVIEW.md");
128
+ mkdirSync(join(base, ".gsd", "phases", "01-foundation"), { recursive: true });
129
+ writeFileSync(reviewPath, "# Plan Review\n\nHand-authored review.", "utf-8");
130
+
131
+ await migrateToFlatPhase(base);
132
+
133
+ const backupRoot = join(base, ".gsd-backups");
134
+ const backups = readdirSync(backupRoot).filter(d => d.startsWith("migrate-"));
135
+ assert.equal(backups.length, 1, "one migrate backup should exist");
136
+ const backedUpReview = join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md");
137
+ assert.equal(
138
+ readFileSync(backedUpReview, "utf-8"),
139
+ "# Plan Review\n\nHand-authored review.",
140
+ "pre-existing phases/ files should have a recovery copy",
141
+ );
142
+ });
143
+
144
+ test("failed migration restores pre-existing phases projection from backup", async (t) => {
145
+ const base = makeTmp();
146
+ const phasesPath = join(base, ".gsd", "phases");
147
+ const reviewPath = join(phasesPath, "01-foundation", "PLAN-REVIEW.md");
148
+ mkdirSync(join(phasesPath, "01-foundation"), { recursive: true });
149
+ writeFileSync(reviewPath, "# Plan Review\n\nHand-authored review.", "utf-8");
150
+
151
+ let sabotagedClear = false;
152
+ const restoreFsOps = _setFlatPhaseMigrationFsOpsForTest({
153
+ rmSync(target, opts) {
154
+ rmSync(target, opts as never);
155
+ if (target === phasesPath && !sabotagedClear) {
156
+ sabotagedClear = true;
157
+ writeFileSync(phasesPath, "not a directory", "utf-8");
158
+ }
159
+ },
160
+ });
161
+ t.after(restoreFsOps);
162
+
163
+ await assert.rejects(migrateToFlatPhase(base), /flat-phase migration render failed/);
164
+
165
+ assert.equal(sabotagedClear, true, "test should force a post-clear render failure");
166
+ assert.equal(
167
+ readFileSync(reviewPath, "utf-8"),
168
+ "# Plan Review\n\nHand-authored review.",
169
+ "rollback should restore the pre-existing phases/ files",
170
+ );
171
+ const backupRoot = join(base, ".gsd-backups");
172
+ const leaked = existsSync(backupRoot)
173
+ ? readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"))
174
+ : [];
175
+ assert.equal(leaked.length, 0, "rollback should still clean the backup it created");
176
+ });
177
+
112
178
  test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir", async (t) => {
113
179
  const base = makeTmp();
114
180
  const milestonesPath = join(base, ".gsd", "milestones");
@@ -136,6 +202,33 @@ test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir",
136
202
  assert.equal(leaked.length, 0, "rollback must delete the migrate-* backup it created");
137
203
  });
138
204
 
205
+ test("resumed migration stores phases backup in retained migrate snapshot", async () => {
206
+ const base = makeTmp();
207
+ const milestonesPath = join(base, ".gsd", "milestones");
208
+ const migratingPath = join(base, ".gsd", "milestones.migrating");
209
+ const reviewPath = join(base, ".gsd", "phases", "01-foundation", "PLAN-REVIEW.md");
210
+ mkdirSync(join(base, ".gsd", "phases", "01-foundation"), { recursive: true });
211
+ writeFileSync(reviewPath, "# Plan Review\n\nResume recovery copy.", "utf-8");
212
+ renameSync(milestonesPath, migratingPath);
213
+
214
+ await migrateToFlatPhase(base);
215
+
216
+ assert.equal(existsSync(migratingPath), false, "resume staging dir should be removed after success");
217
+ const backupRoot = join(base, ".gsd-backups");
218
+ const backups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
219
+ assert.equal(backups.length, 1, "resumed migration should create one retained migrate backup");
220
+ assert.equal(
221
+ readFileSync(join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md"), "utf-8"),
222
+ "# Plan Review\n\nResume recovery copy.",
223
+ "resume should retain the phases snapshot under .gsd-backups",
224
+ );
225
+ assert.equal(
226
+ existsSync(join(migratingPath, "__phases")),
227
+ false,
228
+ "resume should not store the phases snapshot inside the disposable migrating tree",
229
+ );
230
+ });
231
+
139
232
  test("migrateToFlatPhase preserves milestone/slice/task counts in DB", async () => {
140
233
  const base = makeTmp();
141
234
  const msBefore = getAllMilestones().length;
@@ -190,6 +283,42 @@ test("migrateToFlatPhase prunes legacy milestones artifact rows after flat rende
190
283
  );
191
284
  });
192
285
 
286
+ test("migrateToFlatPhase prunes stale flat-phase artifact rows that renderAll intentionally skips", async () => {
287
+ const base = makeTmp();
288
+ insertArtifact({
289
+ path: "phases/01-foundation/T01-PLAN.md",
290
+ artifact_type: "PLAN",
291
+ milestone_id: "M001",
292
+ slice_id: "S01",
293
+ task_id: "T01",
294
+ full_content: "# Legacy standalone task plan\n",
295
+ });
296
+ insertArtifact({
297
+ path: "phases/01-foundation/01-01-RESEARCH.md",
298
+ artifact_type: "RESEARCH",
299
+ milestone_id: "M001",
300
+ slice_id: "S01",
301
+ task_id: null,
302
+ full_content: "",
303
+ });
304
+
305
+ await migrateToFlatPhase(base);
306
+
307
+ const rows = _getAdapter()!
308
+ .prepare("SELECT path FROM artifacts ORDER BY path")
309
+ .all() as Array<{ path: string }>;
310
+ assert.equal(
311
+ rows.some((row) => row.path === "phases/01-foundation/T01-PLAN.md"),
312
+ false,
313
+ "standalone task PLAN rows should be pruned when the flat renderer does not recreate the file",
314
+ );
315
+ assert.equal(
316
+ rows.some((row) => row.path === "phases/01-foundation/01-01-RESEARCH.md"),
317
+ false,
318
+ "empty-content artifact rows should be pruned when the flat renderer skips the file",
319
+ );
320
+ });
321
+
193
322
  test("migrateToFlatPhase is idempotent (second run is a no-op)", async () => {
194
323
  const base = makeTmp();
195
324
  await migrateToFlatPhase(base);
@@ -199,6 +328,79 @@ test("migrateToFlatPhase is idempotent (second run is a no-op)", async () => {
199
328
  assert.equal(backups.length, 1, "should only have one backup");
200
329
  });
201
330
 
331
+ test("re-fired migration reuses the existing backup instead of leaking a new migrate-* dir (#1292)", async () => {
332
+ const base = makeTmp();
333
+ await migrateToFlatPhase(base);
334
+ const backupRoot = join(base, ".gsd-backups");
335
+ const firstBackups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
336
+ assert.equal(firstBackups.length, 1, "first migration snapshots exactly one backup");
337
+
338
+ // Simulate the re-fire: the legacy .gsd/milestones/ layout reappears (e.g. a
339
+ // marker-key mismatch re-imports the whole tree). The DB rows still exist, so
340
+ // the migration gate proceeds again — it must not snapshot a second backup.
341
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01"), { recursive: true });
342
+ assert.equal(needsFlatPhaseMigration(base), true, "reappeared legacy layout re-triggers migration");
343
+
344
+ await migrateToFlatPhase(base);
345
+
346
+ const afterBackups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
347
+ assert.deepEqual(afterBackups, firstBackups, "re-fire must not leak a second migrate-* backup");
348
+ assert.equal(existsSync(join(base, ".gsd", "milestones")), false, "legacy milestones/ removed again");
349
+ });
350
+
351
+ test("rollback after a re-fired migration preserves the reused migrate-* backup", async (t) => {
352
+ const base = makeTmp();
353
+ await migrateToFlatPhase(base);
354
+ const backupRoot = join(base, ".gsd-backups");
355
+ const firstBackups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
356
+ assert.equal(firstBackups.length, 1, "first migration snapshots exactly one backup");
357
+
358
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01"), { recursive: true });
359
+ const milestonesPath = join(base, ".gsd", "milestones");
360
+ const phasesPath = join(base, ".gsd", "phases");
361
+
362
+ const restoreFsOps = _setFlatPhaseMigrationFsOpsForTest({
363
+ rmSync(target, opts) {
364
+ if (target === phasesPath) {
365
+ throw Object.assign(new Error("simulated locked phases/"), { code: "EPERM" });
366
+ }
367
+ return rmSync(target, opts as never);
368
+ },
369
+ });
370
+ t.after(restoreFsOps);
371
+
372
+ await assert.rejects(migrateToFlatPhase(base), /simulated locked/);
373
+
374
+ const afterBackups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
375
+ assert.deepEqual(afterBackups, firstBackups, "rollback must preserve the reused backup");
376
+ assert.ok(existsSync(milestonesPath), "legacy milestones/ should be restored on rollback");
377
+ });
378
+
379
+ test("migration ignores an empty/partial leftover backup and writes a complete one (#1292)", async () => {
380
+ const base = makeTmp();
381
+
382
+ // Simulate a prior first-time backup that crashed mid-cpSync: an empty
383
+ // migrate-<ts>/ leftover with no milestones/ content. It is the newest entry,
384
+ // so mtime-based selection would reuse it as the rollback copy if content were
385
+ // not validated — silently retaining a recovery copy missing the legacy tree.
386
+ const backupRoot = join(base, ".gsd-backups");
387
+ const emptyLeftover = join(backupRoot, "migrate-crashed");
388
+ mkdirSync(emptyLeftover, { recursive: true });
389
+
390
+ await migrateToFlatPhase(base);
391
+
392
+ assert.ok(existsSync(join(base, ".gsd", "phases", "01-foundation")), "flat phase should render");
393
+ // The empty leftover must not have been reused; a fresh, content-bearing
394
+ // backup that actually captured the legacy milestones/ tree must be created.
395
+ const contentBackups = readdirSync(backupRoot)
396
+ .filter((d) => d.startsWith("migrate-"))
397
+ .filter((d) => existsSync(join(backupRoot, d, "M001")));
398
+ assert.ok(
399
+ contentBackups.length >= 1,
400
+ "a complete backup containing the legacy milestones/ tree must be created, not the empty leftover",
401
+ );
402
+ });
403
+
202
404
  test("migrateToFlatPhase preserves slice sidecar artifacts and skips recovery placeholder PLAN", async () => {
203
405
  const base = makeTmp({ withTask: false });
204
406
  const legacySliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
@@ -19,6 +19,11 @@ import {
19
19
  insertMilestone,
20
20
  insertSlice,
21
21
  } from "../gsd-db.ts";
22
+ import { getDb } from "../db/engine.ts";
23
+
24
+ function gateRunCount(gateId: string): number {
25
+ return (getDb().prepare("SELECT count(*) AS cnt FROM gate_runs WHERE gate_id = :gid").get({ ":gid": gateId }) as { cnt: number }).cnt;
26
+ }
22
27
 
23
28
  describe("quality_gates CRUD", () => {
24
29
  let tmpDir: string;
@@ -83,6 +88,30 @@ describe("quality_gates CRUD", () => {
83
88
  assert.ok(results[0].evaluated_at);
84
89
  });
85
90
 
91
+ test("saveGateResult commits the verdict and the gate_runs ledger row together", () => {
92
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
93
+ saveGateResult({
94
+ milestoneId: "M001", sliceId: "S01", gateId: "Q3",
95
+ verdict: "pass", rationale: "OK", findings: "",
96
+ });
97
+ assert.equal(getGateResults("M001", "S01")[0].status, "complete");
98
+ assert.equal(gateRunCount("Q3"), 1, "exactly one ledger row must accompany the verdict");
99
+ });
100
+
101
+ test("saveGateResult rolls back the verdict UPDATE if the ledger INSERT fails", () => {
102
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
103
+ // Force the insertGateRun step to throw after the UPDATE by removing the
104
+ // ledger table. Atomicity means the gate row must stay pending.
105
+ getDb().exec("DROP TABLE gate_runs");
106
+ assert.throws(() => saveGateResult({
107
+ milestoneId: "M001", sliceId: "S01", gateId: "Q3",
108
+ verdict: "pass", rationale: "OK", findings: "",
109
+ }));
110
+ const gate = getGateResults("M001", "S01")[0];
111
+ assert.equal(gate.status, "pending", "gate verdict UPDATE must roll back when the ledger INSERT fails");
112
+ assert.equal(gate.verdict ?? "", "", "verdict must not persist after rollback");
113
+ });
114
+
86
115
  test("saveGateResult throws when the gate row does not exist", () => {
87
116
  assert.throws(
88
117
  () => saveGateResult({