@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,83 @@
1
+ // sharp is an optional native dependency. Load it lazily so that screenshot
2
+ // emitters still work on platforms where sharp is unavailable (e.g. bunx on
3
+ // Raspberry Pi). constrainScreenshot falls back to returning the raw buffer
4
+ // when sharp is not installed.
5
+ let _sharp;
6
+ async function getSharp() {
7
+ if (_sharp !== undefined)
8
+ return _sharp;
9
+ try {
10
+ _sharp = (await import("sharp")).default;
11
+ }
12
+ catch {
13
+ _sharp = null;
14
+ }
15
+ return _sharp;
16
+ }
17
+ /**
18
+ * Test-only seam: override the cached sharp module. Pass `null` to simulate
19
+ * an environment where the sharp native dep is unavailable; pass `undefined`
20
+ * to clear the cache and let the next getSharp() call re-import. See
21
+ * tests/capture-sharp-optional.test.cjs.
22
+ */
23
+ export function __setSharpForTesting(value) {
24
+ _sharp = value;
25
+ }
26
+ // Anthropic vision: 1568px is the recommended optimal width. Height is capped
27
+ // generously at 8000px so tall full-page screenshots remain readable rather
28
+ // than being squished into a square constraint.
29
+ //
30
+ // Override via environment variables:
31
+ // SCREENSHOT_MAX_WIDTH=0 -> uncap width (use raw resolution)
32
+ // SCREENSHOT_MAX_HEIGHT=0 -> uncap height
33
+ export const MAX_SCREENSHOT_WIDTH = parseScreenshotDimension(process.env.SCREENSHOT_MAX_WIDTH, 1568);
34
+ export const MAX_SCREENSHOT_HEIGHT = parseScreenshotDimension(process.env.SCREENSHOT_MAX_HEIGHT, 8000);
35
+ /** Parse a dimension env var: positive int = that value, 0 = Infinity (uncapped), absent/invalid = default. */
36
+ function parseScreenshotDimension(value, fallback) {
37
+ if (value === undefined || value === "")
38
+ return fallback;
39
+ const n = parseInt(value, 10);
40
+ if (isNaN(n) || n < 0)
41
+ return fallback;
42
+ if (n === 0)
43
+ return Infinity;
44
+ return n;
45
+ }
46
+ export async function getScreenshotBufferDimensions(buffer) {
47
+ const sharp = await getSharp();
48
+ if (!sharp)
49
+ return null;
50
+ const meta = await sharp(buffer).metadata();
51
+ if (meta.width === undefined || meta.height === undefined)
52
+ return null;
53
+ return { width: meta.width, height: meta.height };
54
+ }
55
+ /**
56
+ * Constrain screenshot dimensions for the Anthropic vision API.
57
+ * Width is capped at 1568px (optimal) and height at 8000px, each
58
+ * independently, using `fit: "inside"` so aspect ratio is preserved.
59
+ * Small images are never upscaled.
60
+ *
61
+ * `page` parameter is retained for ToolDeps signature stability (D008)
62
+ * but is no longer used — all processing is server-side via sharp.
63
+ */
64
+ export async function constrainScreenshot(_page, buffer, mimeType, quality) {
65
+ const sharp = await getSharp();
66
+ if (!sharp)
67
+ return buffer;
68
+ const dimensions = await getScreenshotBufferDimensions(buffer);
69
+ const width = dimensions?.width;
70
+ const height = dimensions?.height;
71
+ if (width === undefined || height === undefined)
72
+ return buffer;
73
+ if (width <= MAX_SCREENSHOT_WIDTH && height <= MAX_SCREENSHOT_HEIGHT)
74
+ return buffer;
75
+ const resizer = sharp(buffer).resize(MAX_SCREENSHOT_WIDTH, MAX_SCREENSHOT_HEIGHT, {
76
+ fit: "inside",
77
+ withoutEnlargement: true,
78
+ });
79
+ if (mimeType === "image/png") {
80
+ return Buffer.from(await resizer.png().toBuffer());
81
+ }
82
+ return Buffer.from(await resizer.jpeg({ quality }).toBuffer());
83
+ }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { EventStream } from "@gsd/pi-ai";
12
12
  import { execSync } from "node:child_process";
13
+ import { createHash } from "node:crypto";
13
14
  import { existsSync, readFileSync } from "node:fs";
14
15
  import { homedir } from "node:os";
15
16
  import { createRequire } from "node:module";
@@ -368,6 +369,7 @@ function inferMimeTypeFromDataUri(value) {
368
369
  /** Collect all base64 image blocks from user messages in the context for inclusion in the SDK prompt. */
369
370
  export function extractImageBlocksFromContext(context) {
370
371
  const imageBlocks = [];
372
+ const seenImageKeys = new Set();
371
373
  for (const msg of context.messages) {
372
374
  if (msg.role !== "user" || !Array.isArray(msg.content))
373
375
  continue;
@@ -382,12 +384,17 @@ export function extractImageBlocksFromContext(context) {
382
384
  : inferMimeTypeFromDataUri(block.data);
383
385
  if (!mimeType)
384
386
  continue;
387
+ const data = stripDataUriPrefix(block.data);
388
+ const imageKey = `${mimeType}\0${createHash("sha1").update(data).digest("hex")}`;
389
+ if (seenImageKeys.has(imageKey))
390
+ continue;
391
+ seenImageKeys.add(imageKey);
385
392
  imageBlocks.push({
386
393
  type: "image",
387
394
  source: {
388
395
  type: "base64",
389
396
  media_type: mimeType,
390
- data: stripDataUriPrefix(block.data),
397
+ data,
391
398
  },
392
399
  });
393
400
  }
@@ -1831,6 +1838,7 @@ function createSdkAttemptMessageState() {
1831
1838
  return {
1832
1839
  builder: null,
1833
1840
  intermediateToolBlocks: [],
1841
+ intermediateTextBlocks: [],
1834
1842
  toolResultsById: new Map(),
1835
1843
  toolCompletionTargetsById: new Map(),
1836
1844
  emittedExternalToolResultIds: new Set(),
@@ -1939,7 +1947,7 @@ async function pumpSdkMessages(model, context, options, stream) {
1939
1947
  }
1940
1948
  }
1941
1949
  sdkAttemptLoop: for (let readinessAttempt = 0;; readinessAttempt++) {
1942
- let { builder, intermediateToolBlocks, toolResultsById, toolCompletionTargetsById, emittedExternalToolResultIds, } = createSdkAttemptMessageState();
1950
+ let { builder, intermediateToolBlocks, intermediateTextBlocks, toolResultsById, toolCompletionTargetsById, emittedExternalToolResultIds, } = createSdkAttemptMessageState();
1943
1951
  const controller = new AbortController();
1944
1952
  const forwardAbort = () => controller.abort();
1945
1953
  if (options?.signal) {
@@ -2061,9 +2069,15 @@ async function pumpSdkMessages(model, context, options, stream) {
2061
2069
  for (const [contentIndex, block] of builder.message.content.entries()) {
2062
2070
  if (block.type === "text" && block.text) {
2063
2071
  lastTextContent = block.text;
2072
+ // Accumulate completed prose in order — a multi-question
2073
+ // turn commits [prose][elicitation] segments across several
2074
+ // synthetic-user boundaries, and overwriting a single
2075
+ // scalar would drop every explanation but the last.
2076
+ intermediateTextBlocks.push({ type: "text", text: block.text });
2064
2077
  }
2065
2078
  else if (block.type === "thinking" && block.thinking) {
2066
2079
  lastThinkingContent = block.thinking;
2080
+ intermediateTextBlocks.push({ type: "thinking", thinking: block.thinking });
2067
2081
  }
2068
2082
  else if (block.type === "toolCall" || block.type === "serverToolUse") {
2069
2083
  // Collect tool blocks for externalToolExecution rendering
@@ -2161,6 +2175,7 @@ async function pumpSdkMessages(model, context, options, stream) {
2161
2175
  const result = msg;
2162
2176
  const finalContent = buildFinalAssistantContent({
2163
2177
  intermediateToolBlocks,
2178
+ intermediateTextBlocks,
2164
2179
  pendingContent: builder?.message.content,
2165
2180
  toolResultsById,
2166
2181
  lastThinkingContent,
@@ -198,6 +198,15 @@ export function buildFinalAssistantContent(params) {
198
198
  }
199
199
  attachExternalResultsToToolBlocks(mergedToolBlocks, params.toolResultsById);
200
200
  const finalContent = mergedToolBlocks.filter((block) => !shouldSuppressDuplicateToolUnavailableBlock(block, mergedToolBlocks));
201
+ // Emit prose/thinking captured at earlier turn-boundaries (each successive
202
+ // `ask_user_questions` elicitation completes a turn) before the final
203
+ // segment. Capturing these in an ordered accumulator — symmetric with
204
+ // `intermediateToolBlocks` — is what keeps intermediate explanations from
205
+ // being collapsed to a single overwritten scalar and silently dropped.
206
+ const intermediateTextBlocks = params.intermediateTextBlocks ?? [];
207
+ for (const block of intermediateTextBlocks) {
208
+ finalContent.push(block);
209
+ }
201
210
  if (params.pendingContent && params.pendingContent.length > 0) {
202
211
  for (const block of params.pendingContent) {
203
212
  if (block.type === "text" || block.type === "thinking") {
@@ -206,11 +215,30 @@ export function buildFinalAssistantContent(params) {
206
215
  }
207
216
  }
208
217
  else {
218
+ // No builder content survived to this turn boundary, so fall back to the
219
+ // last scalar thinking/text captured off the stream. When the turn ends
220
+ // on an elicitation these scalars just mirror the final intermediate
221
+ // block (they are overwritten at each synthetic-user boundary), so only
222
+ // emit them when they carry content not already accumulated — otherwise
223
+ // a non-streaming `assistant` message's prose/thinking is silently lost.
224
+ const lastBlockOfType = (type) => {
225
+ for (let i = intermediateTextBlocks.length - 1; i >= 0; i--) {
226
+ if (intermediateTextBlocks[i].type === type)
227
+ return intermediateTextBlocks[i];
228
+ }
229
+ return undefined;
230
+ };
209
231
  if (params.lastThinkingContent) {
210
- finalContent.push({ type: "thinking", thinking: params.lastThinkingContent });
232
+ const lastThinking = lastBlockOfType("thinking");
233
+ if (lastThinking?.thinking !== params.lastThinkingContent) {
234
+ finalContent.push({ type: "thinking", thinking: params.lastThinkingContent });
235
+ }
211
236
  }
212
237
  if (params.lastTextContent) {
213
- finalContent.push({ type: "text", text: params.lastTextContent });
238
+ const lastText = lastBlockOfType("text");
239
+ if (lastText?.text !== params.lastTextContent) {
240
+ finalContent.push({ type: "text", text: params.lastTextContent });
241
+ }
214
242
  }
215
243
  }
216
244
  if (finalContent.length === 0 && params.fallbackResultText) {
@@ -263,9 +263,9 @@ export function diagnoseExpectedArtifact(unitType, unitId, base) {
263
263
  }
264
264
  return `${relSliceFile(base, mid, sid, "RESEARCH")} (slice research)`;
265
265
  case "plan-slice":
266
- return `${relSliceFile(base, mid, sid, "PLAN")} plus tasks/T##-PLAN.md files (slice plan and task plans)`;
266
+ return `${relSliceFile(base, mid, sid, "PLAN")} with embedded task plans`;
267
267
  case "refine-slice":
268
- return `${relSliceFile(base, mid, sid, "PLAN")} plus tasks/T##-PLAN.md files (refined slice plan and task plans)`;
268
+ return `${relSliceFile(base, mid, sid, "PLAN")} with embedded refined task plans`;
269
269
  case "execute-task": {
270
270
  return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid, "PLAN")} + summary written`;
271
271
  }
@@ -209,7 +209,8 @@ export function shouldRunDeepProjectSetup(state, prefs, basePath, options = {})
209
209
  }
210
210
  function resolveArtifactBasePath(basePath, mid, session) {
211
211
  if (session?.basePath &&
212
- session.currentMilestoneId === mid &&
212
+ session.currentMilestoneId &&
213
+ milestoneIdsDispatchCompatible(session.currentMilestoneId, mid) &&
213
214
  existsSync(session.basePath)) {
214
215
  return session.basePath;
215
216
  }
@@ -231,6 +232,24 @@ function normalizeMilestoneScope(value) {
231
232
  return null;
232
233
  return trimmed;
233
234
  }
235
+ function dispatchMilestoneIdentity(value) {
236
+ const normalized = normalizeMilestoneScope(value);
237
+ const match = normalized?.match(/^(M\d{3})(?:-[a-z0-9]{6})?$/);
238
+ if (!match)
239
+ return null;
240
+ return { baseId: match[1], hasSuffix: normalized !== match[1] };
241
+ }
242
+ function isBareSuffixedMilestoneAlias(left, right) {
243
+ const leftId = dispatchMilestoneIdentity(left);
244
+ const rightId = dispatchMilestoneIdentity(right);
245
+ return Boolean(leftId &&
246
+ rightId &&
247
+ leftId.baseId === rightId.baseId &&
248
+ leftId.hasSuffix !== rightId.hasSuffix);
249
+ }
250
+ function milestoneIdsDispatchCompatible(left, right) {
251
+ return left === right || isBareSuffixedMilestoneAlias(left, right);
252
+ }
234
253
  function resolveDispatchMilestoneScope(ctx) {
235
254
  const sessionMilestone = normalizeMilestoneScope(ctx.session?.currentMilestoneId);
236
255
  if (sessionMilestone)
@@ -243,6 +262,31 @@ function resolveDispatchMilestoneScope(ctx) {
243
262
  return { id: baseWorktree, source: "basePath worktree" };
244
263
  return null;
245
264
  }
265
+ function resolveEffectiveDispatchMilestoneId(ctx, scopedMilestone) {
266
+ if (scopedMilestone && isBareSuffixedMilestoneAlias(ctx.mid, scopedMilestone.id)) {
267
+ return dispatchMilestoneIdentity(scopedMilestone.id)?.hasSuffix ? scopedMilestone.id : ctx.mid;
268
+ }
269
+ const activeMid = ctx.state.activeMilestone?.id;
270
+ if (activeMid && isBareSuffixedMilestoneAlias(ctx.mid, activeMid)) {
271
+ return dispatchMilestoneIdentity(activeMid)?.hasSuffix ? activeMid : ctx.mid;
272
+ }
273
+ return ctx.mid;
274
+ }
275
+ function withEffectiveDispatchMilestone(ctx, effectiveMid) {
276
+ if (effectiveMid === ctx.mid)
277
+ return ctx;
278
+ const activeMilestone = ctx.state.activeMilestone;
279
+ const state = activeMilestone && milestoneIdsDispatchCompatible(activeMilestone.id, effectiveMid)
280
+ ? {
281
+ ...ctx.state,
282
+ activeMilestone: {
283
+ ...activeMilestone,
284
+ id: effectiveMid,
285
+ },
286
+ }
287
+ : ctx.state;
288
+ return { ...ctx, mid: effectiveMid, state };
289
+ }
246
290
  function hasMilestonePassedDiscuss(basePath, mid) {
247
291
  if (isDbAvailable()) {
248
292
  try {
@@ -510,18 +554,25 @@ export const DISPATCH_RULES = [
510
554
  // Fire BEFORE the execution-entry phase rules so we redispatch to
511
555
  // `discuss-milestone` instead of hitting the plan-v2 gate.
512
556
  name: "execution-entry phase (no context) → discuss-milestone",
513
- match: async ({ state, mid, midTitle, basePath, prefs, structuredQuestionsAvailable }) => {
557
+ match: async ({ state, mid, midTitle, basePath, session, prefs, structuredQuestionsAvailable }) => {
514
558
  if (!EXECUTION_ENTRY_PHASES.has(state.phase))
515
559
  return null;
516
560
  if (!MILESTONE_ID_RE.test(mid))
517
561
  return null;
518
562
  if (isRegistryMilestoneComplete(state, mid))
519
563
  return null;
520
- if (hasMilestonePassedDiscuss(basePath, mid))
564
+ // Resolve discuss/context artifacts against the active session worktree,
565
+ // mirroring the executing rules below. For a suffixed-worktree milestone
566
+ // the CONTEXT and slice plans live under the worktree, not the project
567
+ // root; checking the raw project-root basePath misreads the milestone as
568
+ // "never discussed" and re-dispatches discuss-milestone after task
569
+ // closeout instead of continuing execution (#1317).
570
+ const artifactBasePath = resolveArtifactBasePath(basePath, mid, session);
571
+ if (hasMilestonePassedDiscuss(artifactBasePath, mid))
521
572
  return null;
522
573
  // Align with the plan-v2 gate's lookup semantics: whitespace-only counts
523
574
  // as missing, and an auto worktree may fall back to GSD_PROJECT_ROOT.
524
- if (hasFinalizedMilestoneContext(basePath, mid))
575
+ if (hasFinalizedMilestoneContext(artifactBasePath, mid))
525
576
  return null;
526
577
  // H6 fix (#4973): non-deep auto-mode has no human to answer the
527
578
  // depth-verification question, so pre-marking avoids a write-gate
@@ -854,12 +905,14 @@ export const DISPATCH_RULES = [
854
905
  },
855
906
  {
856
907
  name: "pre-planning (no context) → discuss-milestone",
857
- match: async ({ state, mid, midTitle, basePath, prefs, structuredQuestionsAvailable }) => {
908
+ match: async ({ state, mid, midTitle, basePath, prefs, session, structuredQuestionsAvailable }) => {
858
909
  if (state.phase !== "pre-planning")
859
910
  return null;
860
911
  if (isRegistryMilestoneComplete(state, mid))
861
912
  return null;
862
- const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
913
+ const contextBasePath = resolveWorktreeProjectRoot(basePath, session?.originalBasePath);
914
+ const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT") ??
915
+ (contextBasePath !== basePath ? resolveMilestoneFile(contextBasePath, mid, "CONTEXT") : null);
863
916
  const hasContext = !!(contextFile && (await loadFile(contextFile)));
864
917
  if (hasContext)
865
918
  return null; // fall through to next rule
@@ -1562,31 +1615,33 @@ function applyLanguageDirectiveToDispatch(action, prefs) {
1562
1615
  * resolveDispatch directly without registry initialization).
1563
1616
  */
1564
1617
  export async function resolveDispatch(ctx) {
1565
- if (ctx.mid && isDbAvailable()) {
1566
- const milestone = getMilestone(ctx.mid);
1618
+ const scopedMilestone = resolveDispatchMilestoneScope(ctx);
1619
+ const effectiveMid = resolveEffectiveDispatchMilestoneId(ctx, scopedMilestone);
1620
+ const dispatchCtx = withEffectiveDispatchMilestone(ctx, effectiveMid);
1621
+ if (dispatchCtx.mid && isDbAvailable()) {
1622
+ const milestone = getMilestone(dispatchCtx.mid);
1567
1623
  if (milestone && isClosedStatus(milestone.status)) {
1568
1624
  return {
1569
1625
  action: "stop",
1570
- reason: `Milestone ${ctx.mid} is closed (status: ${milestone.status}); auto-mode will not reopen or recover it implicitly. ` +
1626
+ reason: `Milestone ${dispatchCtx.mid} is closed (status: ${milestone.status}); auto-mode will not reopen or recover it implicitly. ` +
1571
1627
  "Use an explicit reopen command before planning or executing more work for this milestone.",
1572
1628
  level: "warning",
1573
1629
  };
1574
1630
  }
1575
1631
  }
1576
- const activeMid = ctx.state.activeMilestone?.id;
1577
- if (activeMid && ctx.mid !== activeMid) {
1632
+ const activeMid = dispatchCtx.state.activeMilestone?.id;
1633
+ if (activeMid && !milestoneIdsDispatchCompatible(dispatchCtx.mid, activeMid)) {
1578
1634
  return {
1579
1635
  action: "stop",
1580
- reason: `Dispatch milestone mismatch: context mid "${ctx.mid}" does not match active milestone "${activeMid}". ` +
1636
+ reason: `Dispatch milestone mismatch: context mid "${dispatchCtx.mid}" does not match active milestone "${activeMid}". ` +
1581
1637
  "This usually means a project-level deep setup pseudo-id leaked into milestone dispatch; rerun /gsd auto after setup state is reconciled.",
1582
1638
  level: "warning",
1583
1639
  };
1584
1640
  }
1585
- const scopedMilestone = resolveDispatchMilestoneScope(ctx);
1586
- if (scopedMilestone && ctx.mid !== scopedMilestone.id) {
1641
+ if (scopedMilestone && !milestoneIdsDispatchCompatible(dispatchCtx.mid, scopedMilestone.id)) {
1587
1642
  return {
1588
1643
  action: "stop",
1589
- reason: `Dispatch milestone mismatch: context mid "${ctx.mid}" does not match ${scopedMilestone.source} "${scopedMilestone.id}". ` +
1644
+ reason: `Dispatch milestone mismatch: context mid "${dispatchCtx.mid}" does not match ${scopedMilestone.source} "${scopedMilestone.id}". ` +
1590
1645
  "The active worktree/session and derived project state disagree; recover, park, or discard the stranded milestone before continuing.",
1591
1646
  level: "warning",
1592
1647
  };
@@ -1594,9 +1649,9 @@ export async function resolveDispatch(ctx) {
1594
1649
  // Delegate to registry when available
1595
1650
  try {
1596
1651
  const registry = getRegistry();
1597
- const action = annotateBackgroundable(await registry.evaluateDispatch(ctx));
1652
+ const action = annotateBackgroundable(await registry.evaluateDispatch(dispatchCtx));
1598
1653
  if (action.action === "dispatch" &&
1599
- ctx.session?.exhaustedVerificationUnits?.has(`${action.unitType}:${action.unitId}`)) {
1654
+ dispatchCtx.session?.exhaustedVerificationUnits?.has(`${action.unitType}:${action.unitId}`)) {
1600
1655
  return {
1601
1656
  action: "stop",
1602
1657
  reason: `Unit ${action.unitId} exhausted verification retries this session.`,
@@ -1610,13 +1665,13 @@ export async function resolveDispatch(ctx) {
1610
1665
  logWarning("dispatch", `registry dispatch failed, falling back to inline rules: ${err instanceof Error ? err.message : String(err)}`);
1611
1666
  }
1612
1667
  for (const rule of DISPATCH_RULES) {
1613
- const result = await rule.match(ctx);
1668
+ const result = await rule.match(dispatchCtx);
1614
1669
  if (result) {
1615
1670
  if (result.action !== "skip")
1616
1671
  result.matchedRule = rule.name;
1617
1672
  const action = annotateBackgroundable(result);
1618
1673
  if (action.action === "dispatch" &&
1619
- ctx.session?.exhaustedVerificationUnits?.has(`${action.unitType}:${action.unitId}`)) {
1674
+ dispatchCtx.session?.exhaustedVerificationUnits?.has(`${action.unitType}:${action.unitId}`)) {
1620
1675
  return {
1621
1676
  action: "stop",
1622
1677
  reason: `Unit ${action.unitId} exhausted verification retries this session.`,
@@ -1411,6 +1411,10 @@ export async function postUnitPreVerification(pctx, opts) {
1411
1411
  const blockingMismatch = mismatches.find((mismatch) => mismatch.severity === "error");
1412
1412
  if (blockingMismatch) {
1413
1413
  ctx.ui.notify(`Safety: task ${sTid} claimed passing verification that failed in recorded execution`, "error");
1414
+ const gitActionResult = await runCloseoutGitAction(pctx, s.currentUnit);
1415
+ if (gitActionResult === "dispatched") {
1416
+ return "dispatched";
1417
+ }
1414
1418
  await pauseAuto(ctx, pi);
1415
1419
  return "dispatched";
1416
1420
  }
@@ -18,7 +18,7 @@ import { parseRoadmap } from "./parsers-legacy.js";
18
18
  import { join, relative, sep } from "node:path";
19
19
  import { existsSync } from "node:fs";
20
20
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
21
- import { getGateResults, getPendingGatesForTurn } from "./gsd-db.js";
21
+ import { getBlockingReworkFindingsForTask, getGateResults, getPendingGatesForTurn } from "./gsd-db.js";
22
22
  import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
23
23
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
24
24
  import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
@@ -76,6 +76,32 @@ function resolvePromptBudgets() {
76
76
  function resolveSummaryBudgetChars() {
77
77
  return resolvePromptBudgets().summaryBudgetChars;
78
78
  }
79
+ function renderBlockingReworkFindingsBlock(mid, sid, tid) {
80
+ const findings = getBlockingReworkFindingsForTask(mid, sid, tid)
81
+ .filter((finding) => finding.status === "pending");
82
+ if (findings.length === 0)
83
+ return "";
84
+ const lines = [
85
+ "## Blocking Rework Findings (non-optional)",
86
+ "",
87
+ "This task has structured blocking rework findings. Address every finding before calling `gsd_task_complete`. Include `reworkResolution` entries with each `findingId`, `status: \"resolved\"`, and concrete evidence. If deferring a finding, use `status: \"deferred-with-override\"` with concrete evidence and `decisionRef`.",
88
+ "",
89
+ ];
90
+ for (const finding of findings) {
91
+ lines.push(`### ${finding.finding_id} (${finding.severity})`);
92
+ lines.push(`- Status: ${finding.status}`);
93
+ lines.push(`- Finding: ${finding.description}`);
94
+ lines.push(`- Required fix: ${finding.required_fix}`);
95
+ if (finding.verification_commands.length > 0) {
96
+ lines.push("- Required verification:");
97
+ for (const command of finding.verification_commands) {
98
+ lines.push(` - \`${command}\``);
99
+ }
100
+ }
101
+ lines.push("");
102
+ }
103
+ return lines.join("\n").trim();
104
+ }
79
105
  function formatProjectClassificationForPlanning(classification) {
80
106
  const sampleFiles = classification.contentFiles.slice(0, 8);
81
107
  const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
@@ -2319,6 +2345,14 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
2319
2345
  catch (reopenErr) {
2320
2346
  logWarning("prompt", `reopen reason injection failed: ${reopenErr.message}`);
2321
2347
  }
2348
+ const reworkFindingsBlock = renderBlockingReworkFindingsBlock(mid, sid, tid);
2349
+ if (reworkFindingsBlock) {
2350
+ const block = reworkFindingsBlock + "\n\n---\n\n";
2351
+ phaseAnchorSection = phaseAnchorSection
2352
+ ? `${block}${phaseAnchorSection}`
2353
+ : block;
2354
+ trackPromptContext(contextTelemetry, "rework-findings", "inline", reworkFindingsBlock);
2355
+ }
2322
2356
  // ADR-011 Phase 2: inject any resolved-but-unapplied escalation override
2323
2357
  // into this task's prompt. Claim is atomic via DB UPDATE WHERE IS NULL, so
2324
2358
  // if a parallel build already injected it, we skip. Feature-gated by
@@ -11,7 +11,7 @@
11
11
  * next unit type, creates a fresh session, and injects a focused prompt
12
12
  * telling the LLM which files to read and what to do.
13
13
  */
14
- import { deriveState } from "./state.js";
14
+ import { deriveState, invalidateStateCache } from "./state.js";
15
15
  import { buildRequirementsBacklogSummaryLines, countUnmappedActiveRequirements, getUnmappedActiveRequirements, } from "./requirements-backlog.js";
16
16
  import { parseUnitId } from "./unit-id.js";
17
17
  import { assessInterruptedSession, readPausedSessionMetadata, PAUSED_SESSION_KV_KEY, } from "./interrupted-session.js";
@@ -214,6 +214,9 @@ function captureMilestoneLockEnv(milestoneId) {
214
214
  else {
215
215
  delete process.env.GSD_MILESTONE_LOCK;
216
216
  }
217
+ // The derive cache is keyed on basePath only; GSD_MILESTONE_LOCK changes
218
+ // what deriveState computes, so a lock flip must not serve a cached result.
219
+ invalidateStateCache();
217
220
  }
218
221
  function restoreMilestoneLockEnv() {
219
222
  if (!s.milestoneLockEnvCaptured)
@@ -227,6 +230,7 @@ function restoreMilestoneLockEnv() {
227
230
  s.previousMilestoneLockEnv = null;
228
231
  s.hadMilestoneLockEnv = false;
229
232
  s.milestoneLockEnvCaptured = false;
233
+ invalidateStateCache();
230
234
  }
231
235
  /**
232
236
  * Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
@@ -731,12 +731,12 @@ export function registerDbTools(pi) {
731
731
  const planTaskTool = {
732
732
  name: "gsd_plan_task",
733
733
  label: "Plan Task",
734
- description: "Write task planning state to the GSD database, render tasks/T##-PLAN.md from DB, and clear caches after a successful render.",
735
- promptSnippet: "Plan a task via DB write + task PLAN render + cache invalidation",
734
+ description: "Write task planning state to the GSD database, render the slice PLAN from DB, and clear caches after a successful render.",
735
+ promptSnippet: "Plan a task via DB write + slice PLAN render + cache invalidation",
736
736
  promptGuidelines: [
737
- "Use gsd_plan_task for task planning instead of writing tasks/T##-PLAN.md directly.",
737
+ "Use gsd_plan_task for task planning instead of writing task PLAN files directly.",
738
738
  "Keep parameters flat and provide the full task planning payload.",
739
- "The tool validates input, requires an existing parent slice, writes task planning data, renders the task PLAN file from DB, and clears both state and parse caches after success.",
739
+ "The tool validates input, requires an existing parent slice, writes task planning data, renders the slice PLAN file from DB, and clears both state and parse caches after success.",
740
740
  "Use the canonical name gsd_plan_task; gsd_task_plan is only an alias.",
741
741
  ],
742
742
  parameters: Type.Object({
@@ -812,6 +812,12 @@ export function registerDbTools(pi) {
812
812
  verdict: Type.String({ description: "Pass/fail verdict (e.g. '✅ pass', '❌ fail')" }),
813
813
  durationMs: Type.Number({ description: "Duration of the command in milliseconds" }),
814
814
  }), { description: "Array of verification evidence entries (structured objects only)" })),
815
+ reworkResolution: Type.Optional(Type.Array(Type.Object({
816
+ findingId: Type.String({ description: "Rework finding ID being resolved (e.g. F1)" }),
817
+ status: StringEnum(["resolved", "deferred-with-override"], { description: "Resolution status for this finding" }),
818
+ evidence: Type.String({ description: "Concrete evidence that the required fix was completed" }),
819
+ decisionRef: Type.Optional(Type.String({ description: "Decision reference required for deferred-with-override findings" })),
820
+ }), { description: "Resolution evidence for structured rework findings linked to this task" })),
815
821
  // Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
816
822
  actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
817
823
  triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered (e.g. 'task verified after retry')" })),
@@ -1084,6 +1090,74 @@ export function registerDbTools(pi) {
1084
1090
  execute: replanSliceExecute,
1085
1091
  };
1086
1092
  registerWorkflowTool(pi, replanSliceTool);
1093
+ // ─── gsd_replan_task ───────────────────────────────────────────────────
1094
+ const replanTaskExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
1095
+ const { executeReplanTask } = await loadWorkflowExecutors();
1096
+ return executeReplanTask(params, resolveWorkflowToolBasePath(_ctx, params));
1097
+ };
1098
+ const replanTaskTool = {
1099
+ name: "gsd_replan_task",
1100
+ label: "Replan Task",
1101
+ description: "Update one pending task's planning contract after rework without touching sibling tasks. " +
1102
+ "Completed tasks are structurally protected. Re-renders the task/slice PLAN projection and records replan history.",
1103
+ promptSnippet: "Replan one pending GSD task after rework",
1104
+ promptGuidelines: [
1105
+ "Use gsd_replan_task when a reopened pending task needs its title, description, files, verify, inputs, or expectedOutput updated for rework.",
1106
+ "The task must already exist and be pending; completed tasks are rejected.",
1107
+ "Set reworkBriefRef when a structured rework brief triggered the task replan.",
1108
+ ],
1109
+ parameters: Type.Object({
1110
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
1111
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
1112
+ taskId: Type.String({ description: "Task ID (e.g. T01)" }),
1113
+ title: Type.String({ description: "Updated task title" }),
1114
+ description: Type.String({ description: "Updated task description incorporating rework scope" }),
1115
+ estimate: Type.String({ description: "Updated task estimate string" }),
1116
+ files: Type.Array(Type.String(), { description: "Updated files likely touched" }),
1117
+ verify: Type.String({ description: "Updated verification command or block" }),
1118
+ inputs: Type.Array(Type.String(), { description: "Updated input files or references" }),
1119
+ expectedOutput: Type.Array(Type.String(), { description: "Updated files this task creates or overwrites" }),
1120
+ reworkBriefRef: Type.Optional(Type.String({ description: "Rework brief ID/reference that triggered this task replan" })),
1121
+ actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail" })),
1122
+ triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered" })),
1123
+ }),
1124
+ execute: replanTaskExecute,
1125
+ };
1126
+ registerWorkflowTool(pi, replanTaskTool);
1127
+ // ─── gsd_rework_brief_save ─────────────────────────────────────────────
1128
+ const reworkBriefSaveExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
1129
+ const { executeReworkBriefSave } = await loadWorkflowExecutors();
1130
+ return executeReworkBriefSave(params, resolveWorkflowToolBasePath(_ctx, params));
1131
+ };
1132
+ const reworkBriefSaveTool = {
1133
+ name: "gsd_rework_brief_save",
1134
+ label: "Save Rework Brief",
1135
+ description: "Persist a structured rework brief for a task. Blocking findings are enforced by gsd_task_complete until resolved with evidence.",
1136
+ promptSnippet: "Persist structured task rework findings",
1137
+ promptGuidelines: [
1138
+ "Use gsd_rework_brief_save when post-unit checks produce task rework findings.",
1139
+ "Use severity 'blocking' for findings that must be resolved before gsd_task_complete succeeds.",
1140
+ "Each finding needs a stable findingId, requiredFix, and verificationCommands.",
1141
+ ],
1142
+ parameters: Type.Object({
1143
+ briefId: Type.Optional(Type.String({ description: "Stable brief ID; omitted defaults to RB-<milestone>-<slice>-<task>" })),
1144
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
1145
+ sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
1146
+ taskId: Type.String({ description: "Task ID (e.g. T01)" }),
1147
+ findings: Type.Array(Type.Object({
1148
+ findingId: Type.String({ description: "Stable finding ID (e.g. F1)" }),
1149
+ severity: StringEnum(["blocking", "advisory"], { description: "Whether this finding blocks task completion" }),
1150
+ description: Type.String({ description: "What was found" }),
1151
+ requiredFix: Type.String({ description: "What must change" }),
1152
+ verificationCommands: Type.Array(Type.String(), { description: "Commands that must pass after the fix" }),
1153
+ status: Type.Optional(StringEnum(["pending", "resolved", "deferred-with-override"], { description: "Finding status; defaults to pending" })),
1154
+ evidence: Type.Optional(Type.String({ description: "Resolution evidence when already resolved" })),
1155
+ decisionRef: Type.Optional(Type.String({ description: "Decision reference for deferred-with-override" })),
1156
+ }), { description: "Structured rework findings for this task" }),
1157
+ }),
1158
+ execute: reworkBriefSaveExecute,
1159
+ };
1160
+ registerWorkflowTool(pi, reworkBriefSaveTool);
1087
1161
  // ─── gsd_reassess_roadmap (gsd_roadmap_reassess alias) ─────────────────
1088
1162
  const reassessRoadmapExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
1089
1163
  const { executeReassessRoadmap } = await loadWorkflowExecutors();