@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
@@ -0,0 +1,197 @@
1
+ import { createTestContext } from './test-helpers.ts';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import {
6
+ openDatabase,
7
+ closeDatabase,
8
+ _getAdapter,
9
+ type ReworkBriefFindingInput,
10
+ } from '../gsd-db.ts';
11
+ import { handleReworkBriefSave } from '../tools/rework-brief.ts';
12
+
13
+ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
14
+
15
+ // ═══════════════════════════════════════════════════════════════════════════
16
+ // Helpers
17
+ // ═══════════════════════════════════════════════════════════════════════════
18
+
19
+ function tempDbPath(): string {
20
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-rework-brief-'));
21
+ return path.join(dir, 'test.db');
22
+ }
23
+
24
+ function cleanup(dbPath: string): void {
25
+ closeDatabase();
26
+ try {
27
+ const dir = path.dirname(dbPath);
28
+ for (const f of fs.readdirSync(dir)) fs.unlinkSync(path.join(dir, f));
29
+ fs.rmdirSync(dir);
30
+ } catch {
31
+ // best effort
32
+ }
33
+ }
34
+
35
+ function finding(overrides: Partial<ReworkBriefFindingInput> = {}): ReworkBriefFindingInput {
36
+ return {
37
+ findingId: 'F1',
38
+ severity: 'blocking',
39
+ description: 'Compile regression',
40
+ requiredFix: 'Fix compile error',
41
+ verificationCommands: ['pnpm run typecheck:extensions'],
42
+ ...overrides,
43
+ };
44
+ }
45
+
46
+ const baseParams = {
47
+ briefId: 'RB-001',
48
+ milestoneId: 'M001',
49
+ sliceId: 'S01',
50
+ taskId: 'T01',
51
+ };
52
+
53
+ function findingRow(): { status: string; evidence: string; decision_ref: string } | undefined {
54
+ return _getAdapter()!
55
+ .prepare("SELECT status, evidence, decision_ref FROM rework_brief_findings WHERE brief_id = 'RB-001' AND finding_id = 'F1'")
56
+ .get() as { status: string; evidence: string; decision_ref: string } | undefined;
57
+ }
58
+
59
+ // ═══════════════════════════════════════════════════════════════════════════
60
+ // rework-brief: a blocking finding saved as non-pending must carry the same
61
+ // justification a reworkResolution would, otherwise it bypasses the
62
+ // gsd_task_complete blocking gate (which only sees `pending` rows).
63
+ // ═══════════════════════════════════════════════════════════════════════════
64
+
65
+ console.log('\n=== rework-brief: rejects blocking resolved finding without evidence ===');
66
+ {
67
+ const dbPath = tempDbPath();
68
+ openDatabase(dbPath);
69
+
70
+ const result = await handleReworkBriefSave({
71
+ ...baseParams,
72
+ findings: [finding({ status: 'resolved' })],
73
+ });
74
+
75
+ assertTrue('error' in result, 'blocking resolved finding without evidence should be rejected');
76
+ if ('error' in result) {
77
+ assertMatch(result.error, /evidence is required/i, 'error should mention the evidence requirement');
78
+ }
79
+ const rows = _getAdapter()!.prepare('SELECT COUNT(*) AS n FROM rework_brief_findings').get() as { n: number };
80
+ assertEq(rows.n, 0, 'no finding rows should be persisted when validation fails');
81
+
82
+ cleanup(dbPath);
83
+ }
84
+
85
+ console.log('\n=== rework-brief: rejects blocking deferred-with-override without decisionRef ===');
86
+ {
87
+ const dbPath = tempDbPath();
88
+ openDatabase(dbPath);
89
+
90
+ const result = await handleReworkBriefSave({
91
+ ...baseParams,
92
+ findings: [finding({ status: 'deferred-with-override', evidence: 'Maintainer accepted temporary deferral.' })],
93
+ });
94
+
95
+ assertTrue('error' in result, 'deferred-with-override without decisionRef should be rejected');
96
+ if ('error' in result) {
97
+ assertMatch(result.error, /decisionRef is required/i, 'error should mention the decisionRef requirement');
98
+ }
99
+
100
+ cleanup(dbPath);
101
+ }
102
+
103
+ console.log('\n=== rework-brief: rejects invalid finding status ===');
104
+ {
105
+ const dbPath = tempDbPath();
106
+ openDatabase(dbPath);
107
+
108
+ const result = await handleReworkBriefSave({
109
+ ...baseParams,
110
+ findings: [finding({ status: 'bogus' as unknown as ReworkBriefFindingInput['status'] })],
111
+ });
112
+
113
+ assertTrue('error' in result, 'an unknown status value should be rejected');
114
+ if ('error' in result) {
115
+ assertMatch(result.error, /status must be/i, 'error should name the allowed statuses');
116
+ }
117
+
118
+ cleanup(dbPath);
119
+ }
120
+
121
+ // ═══════════════════════════════════════════════════════════════════════════
122
+ // rework-brief: legitimate saves still succeed.
123
+ // ═══════════════════════════════════════════════════════════════════════════
124
+
125
+ console.log('\n=== rework-brief: blocking finding defaults to pending ===');
126
+ {
127
+ const dbPath = tempDbPath();
128
+ openDatabase(dbPath);
129
+
130
+ const result = await handleReworkBriefSave({
131
+ ...baseParams,
132
+ findings: [finding()],
133
+ });
134
+
135
+ assertTrue(!('error' in result), 'a blocking finding without a status should be accepted');
136
+ assertEq(findingRow()?.status, 'pending', 'finding without status should default to pending');
137
+
138
+ cleanup(dbPath);
139
+ }
140
+
141
+ console.log('\n=== rework-brief: accepts blocking resolved finding with evidence ===');
142
+ {
143
+ const dbPath = tempDbPath();
144
+ openDatabase(dbPath);
145
+
146
+ const result = await handleReworkBriefSave({
147
+ ...baseParams,
148
+ findings: [finding({ status: 'resolved', evidence: 'Fixed compile error and reran pnpm run typecheck:extensions.' })],
149
+ });
150
+
151
+ assertTrue(!('error' in result), 'a resolved blocking finding with evidence should be accepted');
152
+ assertEq(findingRow()?.status, 'resolved', 'finding should persist as resolved when evidence is supplied');
153
+ assertMatch(findingRow()?.evidence ?? '', /Fixed compile error/, 'resolution evidence should be persisted');
154
+
155
+ cleanup(dbPath);
156
+ }
157
+
158
+ console.log('\n=== rework-brief: accepts blocking deferred-with-override with evidence and decisionRef ===');
159
+ {
160
+ const dbPath = tempDbPath();
161
+ openDatabase(dbPath);
162
+
163
+ const result = await handleReworkBriefSave({
164
+ ...baseParams,
165
+ findings: [finding({
166
+ status: 'deferred-with-override',
167
+ evidence: 'Maintainer accepted temporary deferral.',
168
+ decisionRef: 'DEC-2026-07-07-rework-deferral',
169
+ })],
170
+ });
171
+
172
+ assertTrue(!('error' in result), 'a deferred-with-override finding with evidence and decisionRef should be accepted');
173
+ assertEq(findingRow()?.status, 'deferred-with-override', 'finding should persist the override status');
174
+ assertEq(findingRow()?.decision_ref, 'DEC-2026-07-07-rework-deferral', 'decision reference should be persisted');
175
+
176
+ cleanup(dbPath);
177
+ }
178
+
179
+ console.log('\n=== rework-brief: advisory resolved finding does not require evidence ===');
180
+ {
181
+ const dbPath = tempDbPath();
182
+ openDatabase(dbPath);
183
+
184
+ // Advisory findings never gate gsd_task_complete, so pre-resolving one
185
+ // without evidence is harmless and stays permitted.
186
+ const result = await handleReworkBriefSave({
187
+ ...baseParams,
188
+ findings: [finding({ severity: 'advisory', status: 'resolved' })],
189
+ });
190
+
191
+ assertTrue(!('error' in result), 'an advisory resolved finding should be accepted without evidence');
192
+ assertEq(findingRow()?.status, 'resolved', 'advisory finding status should be preserved');
193
+
194
+ cleanup(dbPath);
195
+ }
196
+
197
+ report();
@@ -25,6 +25,7 @@ import {
25
25
  updateTaskStatus,
26
26
  } from "../gsd-db.ts";
27
27
  import { handleSkipSlice } from "../tools/skip-slice.ts";
28
+ import { skipSliceCascade } from "../db/writers/cascades.ts";
28
29
 
29
30
  describe("handleSkipSlice cascades skip to tasks (#4375)", () => {
30
31
  let dir: string;
@@ -122,4 +123,24 @@ describe("handleSkipSlice cascades skip to tasks (#4375)", () => {
122
123
  assert.match(result.error!, /not found/i);
123
124
  assert.equal(result.errorCode, "slice_not_found");
124
125
  });
126
+
127
+ test("skipSliceCascade rejects a slice in the legacy 'closed' status without nulling completed_at", () => {
128
+ const completedAt = "2026-01-01T00:00:00.000Z";
129
+ updateSliceStatus("M001", "S03", "closed", completedAt);
130
+
131
+ const outcome = skipSliceCascade("M001", "S03");
132
+ assert.deepEqual(outcome, { ok: false, reason: "slice-already-complete" });
133
+
134
+ const slice = getSlice("M001", "S03");
135
+ assert.equal(slice?.status, "closed", "closed slice must not be downgraded to skipped");
136
+ assert.equal(slice?.completed_at, completedAt, "completed_at must be preserved");
137
+ });
138
+
139
+ test("skipSliceCascade still re-skips an already-skipped slice (ok, wasAlreadySkipped)", () => {
140
+ updateSliceStatus("M001", "S03", "skipped");
141
+
142
+ const outcome = skipSliceCascade("M001", "S03");
143
+ assert.equal(outcome.ok, true);
144
+ if (outcome.ok) assert.equal(outcome.wasAlreadySkipped, true);
145
+ });
125
146
  });
@@ -14,6 +14,7 @@ import {
14
14
  isPauseNotice,
15
15
  isBlockedNoticeMessage,
16
16
  isManualResolutionNotice,
17
+ isInteractiveMenuUnavailableNotice,
17
18
  PAUSED_NOTICE_PREFIXES,
18
19
  TERMINAL_NOTICE_PREFIXES,
19
20
  } from "../stop-notice.js";
@@ -62,6 +63,22 @@ describe("emitter↔detector round-trip", () => {
62
63
  assert.ok(isBlockedNoticeMessage(message));
63
64
  });
64
65
 
66
+ test("un-showable menu notices classify as blocked (#1294)", () => {
67
+ // Emitted verbatim by notifyCommandMenuUnavailable (next-action-ui.ts / command-feedback.ts).
68
+ const menuUnavailable =
69
+ "gsd — m002: editorial hn menu could not be shown in this session.\nrun /gsd when ready.";
70
+ assert.ok(isInteractiveMenuUnavailableNotice(menuUnavailable));
71
+ assert.ok(isBlockedNoticeMessage(menuUnavailable));
72
+
73
+ // Emitted by notifyPickerCommandNeedsInteractiveMenu (command-feedback.ts).
74
+ const pickerGuidance = "/gsd did not start: milestone menu needs an interactive session";
75
+ assert.ok(isInteractiveMenuUnavailableNotice(pickerGuidance));
76
+ assert.ok(isBlockedNoticeMessage(pickerGuidance));
77
+
78
+ // Unrelated notices are not swept up.
79
+ assert.equal(isInteractiveMenuUnavailableNotice("auto-mode complete"), false);
80
+ });
81
+
65
82
  test("terminal prefixes cover the known stop vocabulary", () => {
66
83
  for (const message of ["auto-mode stopped.", "auto-mode complete", "auto-mode idle", "no active milestone"]) {
67
84
  assert.ok(TERMINAL_NOTICE_PREFIXES.some((prefix) => message.startsWith(prefix)), message);
@@ -174,3 +174,22 @@ test('sync-lock: contended fresh lock fails fast without honoring long sync time
174
174
  cleanupDir(base);
175
175
  }
176
176
  });
177
+
178
+ test('sync-lock: uncreatable lock path fails open instead of spinning forever', () => {
179
+ const base = tempDir();
180
+ try {
181
+ // `.gsd` is a dangling symlink to a missing target: the O_EXCL open lands
182
+ // on a missing parent and throws non-EEXIST on every attempt. Without the
183
+ // fail-open guard the retry loop spins forever, blocking the event loop.
184
+ fs.symlinkSync(path.join(base, 'missing-external-state'), path.join(base, '.gsd'), 'junction');
185
+
186
+ const startedAt = performance.now();
187
+ const result = acquireSyncLock(base, 0, 'write-gate.lock');
188
+ const elapsedMs = performance.now() - startedAt;
189
+
190
+ assert.strictEqual(result.acquired, false, 'uncreatable lock path must fail open, not acquire');
191
+ assert.ok(elapsedMs < 250, `must return promptly, not spin; elapsed=${elapsedMs.toFixed(1)}ms`);
192
+ } finally {
193
+ cleanupDir(base);
194
+ }
195
+ });
@@ -43,6 +43,8 @@ import {
43
43
  insertMilestone,
44
44
  upsertMilestonePlanning,
45
45
  insertSlice,
46
+ insertTask,
47
+ saveReworkBrief,
46
48
  } from "../gsd-db.ts";
47
49
 
48
50
  // ─── Pure composer tests ──────────────────────────────────────────────────
@@ -518,6 +520,38 @@ test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context wi
518
520
  assert.ok(!prompt.includes("Slice Context (from discussion)"));
519
521
  });
520
522
 
523
+
524
+ test("execute-task prompt injects unresolved blocking rework findings", async (t) => {
525
+ const base = makeFixtureBase();
526
+ t.after(() => cleanup(base));
527
+ invalidateAllCaches();
528
+
529
+ seed(base, "M001");
530
+ writeArtifacts(base);
531
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Task", status: "pending" });
532
+ saveReworkBrief({
533
+ briefId: "RB-001",
534
+ milestoneId: "M001",
535
+ sliceId: "S01",
536
+ taskId: "T01",
537
+ findings: [{
538
+ findingId: "F1",
539
+ severity: "blocking",
540
+ description: "Compile regression",
541
+ requiredFix: "Restore typecheck",
542
+ verificationCommands: ["pnpm run typecheck:extensions"],
543
+ }],
544
+ });
545
+
546
+ const prompt = await buildExecuteTaskPrompt("M001", "S01", "First", "T01", "Task", base);
547
+
548
+ assert.match(prompt, /Blocking Rework Findings \(non-optional\)/);
549
+ assert.match(prompt, /F1/);
550
+ assert.match(prompt, /Compile regression/);
551
+ assert.match(prompt, /pnpm run typecheck:extensions/);
552
+ assert.match(prompt, /reworkResolution/);
553
+ });
554
+
521
555
  test("execute-task prompt omits on-demand slice research when the artifact is absent", async (t) => {
522
556
  const base = makeFixtureBase();
523
557
  t.after(() => cleanup(base));
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
@@ -239,6 +239,98 @@ test("plan-v2 ensure rejects empty executable graph", () => {
239
239
  assert.equal(isEmptyPlanV2GraphResult(compiled), true);
240
240
  });
241
241
 
242
+ function seedMultiSliceRows(): void {
243
+ insertMilestone({ id: MILESTONE_ID, title: "Milestone", status: "active" });
244
+ const slices: Array<[string, number, Array<[string, number]>]> = [
245
+ ["S01", 1, [["T01", 1], ["T02", 2]]],
246
+ ["S02", 2, [["T03", 1], ["T04", 2]]],
247
+ ];
248
+ for (const [sid, sseq, tasks] of slices) {
249
+ insertSlice({ id: sid, milestoneId: MILESTONE_ID, title: `Slice ${sid}`, status: "in_progress", sequence: sseq });
250
+ for (const [tid, tseq] of tasks) {
251
+ insertTask({
252
+ id: tid,
253
+ milestoneId: MILESTONE_ID,
254
+ sliceId: sid,
255
+ title: `Task ${tid}`,
256
+ status: "pending",
257
+ keyFiles: [`src/${tid}.ts`],
258
+ sequence: tseq,
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ test("plan-v2 batched compile preserves node ids, order, and count", () => {
265
+ const basePath = createBasePath();
266
+ seedMultiSliceRows();
267
+ writeMilestoneFile(basePath, "CONTEXT", "Finalized context.");
268
+
269
+ const compiled = ensurePlanV2Graph(basePath, buildState("executing"));
270
+ assert.equal(compiled.ok, true);
271
+
272
+ const graph = JSON.parse(readFileSync(compiled.graphPath ?? "", "utf-8")) as {
273
+ nodes: Array<{ id: string }>;
274
+ };
275
+ assert.deepEqual(
276
+ graph.nodes.map((n) => n.id),
277
+ [
278
+ "execute-task:M001:S01:T01",
279
+ "execute-task:M001:S01:T02",
280
+ "complete-slice:M001:S01",
281
+ "execute-task:M001:S02:T03",
282
+ "execute-task:M001:S02:T04",
283
+ "complete-slice:M001:S02",
284
+ ],
285
+ );
286
+ });
287
+
288
+ test("plan-v2 skips the disk write when the graph is unchanged", () => {
289
+ const basePath = createBasePath();
290
+ seedMultiSliceRows();
291
+ writeMilestoneFile(basePath, "CONTEXT", "Finalized context.");
292
+
293
+ const first = ensurePlanV2Graph(basePath, buildState("executing"));
294
+ assert.equal(first.ok, true);
295
+ const graphPath = first.graphPath ?? "";
296
+ const before = readFileSync(graphPath, "utf-8");
297
+
298
+ // Backdate mtime so a rewrite is detectable even within the same millisecond.
299
+ utimesSync(graphPath, 1000, 1000);
300
+ const backdatedMtime = statSync(graphPath).mtimeMs;
301
+
302
+ const second = ensurePlanV2Graph(basePath, buildState("executing"));
303
+ assert.equal(second.ok, true);
304
+ assert.equal(statSync(graphPath).mtimeMs, backdatedMtime, "identical state must not rewrite the graph");
305
+ assert.equal(readFileSync(graphPath, "utf-8"), before);
306
+ });
307
+
308
+ test("plan-v2 rewrites the graph when it changes", () => {
309
+ const basePath = createBasePath();
310
+ seedMultiSliceRows();
311
+ writeMilestoneFile(basePath, "CONTEXT", "Finalized context.");
312
+
313
+ const first = ensurePlanV2Graph(basePath, buildState("executing"));
314
+ const graphPath = first.graphPath ?? "";
315
+ const before = readFileSync(graphPath, "utf-8");
316
+ utimesSync(graphPath, 1000, 1000);
317
+
318
+ insertTask({
319
+ id: "T05",
320
+ milestoneId: MILESTONE_ID,
321
+ sliceId: "S02",
322
+ title: "Added task",
323
+ status: "pending",
324
+ keyFiles: ["src/T05.ts"],
325
+ sequence: 3,
326
+ });
327
+
328
+ const second = ensurePlanV2Graph(basePath, buildState("executing"));
329
+ assert.equal(second.ok, true);
330
+ assert.notEqual(statSync(graphPath).mtimeMs, 1000000, "changed graph must rewrite");
331
+ assert.notEqual(readFileSync(graphPath, "utf-8"), before);
332
+ });
333
+
242
334
  test("plan-v2 allows empty graph for milestone terminal phases", () => {
243
335
  const basePath = createBasePath();
244
336
  writeMilestoneFile(basePath, "CONTEXT", "Finalized context.");
@@ -94,6 +94,8 @@ test("complete-slice requires status, closeout, and execution handoff tools", ()
94
94
  "gsd_slice_complete",
95
95
  "gsd_task_reopen",
96
96
  "gsd_replan_slice",
97
+ "gsd_replan_task",
98
+ "gsd_rework_brief_save",
97
99
  "gsd_requirement_update",
98
100
  "gsd_summary_save",
99
101
  ];
@@ -0,0 +1,139 @@
1
+ // GSD Extension - Auto-migration gate tests (plan 026).
2
+ // Covers needsAutoMigration's trigger (empty milestones table + .gsd/milestones
3
+ // present) and validateMigration's engine-vs-markdown count comparison.
4
+
5
+ import { describe, test } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { tmpdir } from 'node:os';
10
+
11
+ import { openDatabase, closeDatabase, insertMilestone, insertSlice } from '../gsd-db.ts';
12
+ import { needsAutoMigration, validateMigration } from '../workflow-migration.ts';
13
+
14
+ function makeProject(): string {
15
+ return mkdtempSync(join(tmpdir(), 'gsd-migration-gates-'));
16
+ }
17
+
18
+ function openProjectDb(base: string): string {
19
+ const gsdDir = join(base, '.gsd');
20
+ mkdirSync(gsdDir, { recursive: true });
21
+ const dbPath = join(gsdDir, 'gsd.db');
22
+ assert.ok(openDatabase(dbPath), 'openDatabase should succeed');
23
+ return dbPath;
24
+ }
25
+
26
+ function cleanup(base: string): void {
27
+ closeDatabase();
28
+ rmSync(base, { recursive: true, force: true });
29
+ }
30
+
31
+ function writeRoadmap(base: string, mId: string, sliceLines: string[]): void {
32
+ const mDir = join(base, '.gsd', 'milestones', mId);
33
+ mkdirSync(mDir, { recursive: true });
34
+ writeFileSync(join(mDir, 'ROADMAP.md'), [
35
+ `# ${mId}: Test`,
36
+ '',
37
+ '## Slices',
38
+ '',
39
+ ...sliceLines,
40
+ '',
41
+ ].join('\n'));
42
+ }
43
+
44
+ describe('needsAutoMigration', () => {
45
+ test('false for a fresh project with no legacy markdown', () => {
46
+ const base = makeProject();
47
+ try {
48
+ openProjectDb(base);
49
+ assert.equal(needsAutoMigration(base), false, 'no .gsd/milestones dir → no migration');
50
+ } finally {
51
+ cleanup(base);
52
+ }
53
+ });
54
+
55
+ test('true when engine tables are empty and .gsd/milestones exists', () => {
56
+ const base = makeProject();
57
+ try {
58
+ openProjectDb(base);
59
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
60
+ assert.equal(needsAutoMigration(base), true, 'legacy markdown + empty engine → migrate');
61
+ } finally {
62
+ cleanup(base);
63
+ }
64
+ });
65
+
66
+ test('false once the milestones table has rows (migration already done)', () => {
67
+ const base = makeProject();
68
+ try {
69
+ openProjectDb(base);
70
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
71
+ insertMilestone({ id: 'M001', title: 'Migrated' });
72
+ assert.equal(needsAutoMigration(base), false, 'engine rows present → migration already done');
73
+ } finally {
74
+ cleanup(base);
75
+ }
76
+ });
77
+
78
+ test('false when no database connection is open', () => {
79
+ const base = makeProject();
80
+ try {
81
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
82
+ closeDatabase();
83
+ assert.equal(needsAutoMigration(base), false, 'no DB → cannot migrate');
84
+ } finally {
85
+ cleanup(base);
86
+ }
87
+ });
88
+ });
89
+
90
+ describe('validateMigration', () => {
91
+ test('reports no discrepancies when engine counts match markdown', () => {
92
+ const base = makeProject();
93
+ try {
94
+ openProjectDb(base);
95
+ writeRoadmap(base, 'M001', ['- [ ] **S01: Test Slice** `risk:low` `depends:[]`']);
96
+ insertMilestone({ id: 'M001', title: 'Test' });
97
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
98
+
99
+ const result = validateMigration(base);
100
+ assert.deepEqual(result.discrepancies, [], 'matching engine/markdown counts → clean');
101
+ } finally {
102
+ cleanup(base);
103
+ }
104
+ });
105
+
106
+ test('reports discrepancies when engine counts diverge from markdown', () => {
107
+ const base = makeProject();
108
+ try {
109
+ openProjectDb(base);
110
+ writeRoadmap(base, 'M001', [
111
+ '- [ ] **S01: Test Slice** `risk:low` `depends:[]`',
112
+ '- [ ] **S02: Second Slice** `risk:low` `depends:[]`',
113
+ ]);
114
+ // Engine has no rows at all → milestone and slice counts both diverge.
115
+ const result = validateMigration(base);
116
+ assert.ok(
117
+ result.discrepancies.some((d) => d.includes('Milestone count mismatch')),
118
+ `expected milestone mismatch, got: ${result.discrepancies.join('; ')}`,
119
+ );
120
+ assert.ok(
121
+ result.discrepancies.some((d) => d.includes('Slice count mismatch')),
122
+ `expected slice mismatch, got: ${result.discrepancies.join('; ')}`,
123
+ );
124
+ } finally {
125
+ cleanup(base);
126
+ }
127
+ });
128
+
129
+ test('reports a validation failure when no database connection is open', () => {
130
+ const base = makeProject();
131
+ try {
132
+ closeDatabase();
133
+ const result = validateMigration(base);
134
+ assert.deepEqual(result.discrepancies, ['No database connection for validation']);
135
+ } finally {
136
+ cleanup(base);
137
+ }
138
+ });
139
+ });
@@ -97,6 +97,42 @@ test("projectRootToWorktree forwards root PROJECT.md into isolated worktrees", (
97
97
  }
98
98
  });
99
99
 
100
+ test("projectRootToWorktree projects flat-phase discuss artifacts before dispatch", () => {
101
+ const { dir, cleanup } = makeProjectRoot();
102
+ try {
103
+ const worktree = join(dir, ".gsd", "worktrees", "M001");
104
+ mkdirSync(join(worktree, ".gsd", "phases", "01-foundation"), { recursive: true });
105
+
106
+ const projectPhaseDir = join(dir, ".gsd", "phases", "01-foundation");
107
+ mkdirSync(projectPhaseDir, { recursive: true });
108
+ writeFileSync(join(projectPhaseDir, "01-CONTEXT.md"), "# M001 Context\n");
109
+ writeFileSync(join(projectPhaseDir, "01-DISCUSSION.md"), "# M001 Discussion\n");
110
+
111
+ const metaDir = join(dir, ".gsd", "milestones", "M001");
112
+ mkdirSync(metaDir, { recursive: true });
113
+ writeFileSync(join(metaDir, "M001-META.json"), '{"branch":"milestone/M001"}');
114
+
115
+ const workspace = createWorkspace(worktree);
116
+ const scope = scopeMilestone(workspace, "M001");
117
+ new WorktreeStateProjection().projectRootToWorktree(scope);
118
+
119
+ assert.ok(
120
+ existsSync(join(worktree, ".gsd", "milestones", "M001", "M001-META.json")),
121
+ "milestone metadata is projected into the worktree",
122
+ );
123
+ assert.ok(
124
+ existsSync(join(worktree, ".gsd", "phases", "01-foundation", "01-CONTEXT.md")),
125
+ "flat-phase CONTEXT.md is projected into the worktree",
126
+ );
127
+ assert.ok(
128
+ existsSync(join(worktree, ".gsd", "phases", "01-foundation", "01-DISCUSSION.md")),
129
+ "flat-phase DISCUSSION.md is projected into the worktree",
130
+ );
131
+ } finally {
132
+ cleanup();
133
+ }
134
+ });
135
+
100
136
  test("projectRootToWorktree projects prior-milestone slice/task SUMMARY.md, not just top-level files", () => {
101
137
  // Regression: a worktree opened for the CURRENT milestone (M002) must also
102
138
  // receive the full subtree of OTHER, already-completed milestones (M001) so