@opengsd/gsd-pi 1.2.0-dev.84c56d87 → 1.2.0-dev.9ad8ae33

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 (460) hide show
  1. package/dist/headless-events.js +7 -5
  2. package/dist/mcp-server.js +2 -1
  3. package/dist/resource-loader.d.ts +10 -5
  4. package/dist/resource-loader.js +121 -6
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/GSD-WORKFLOW.md +5 -4
  7. package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
  8. package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
  9. package/dist/resources/extensions/async-jobs/index.js +65 -0
  10. package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
  11. package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
  12. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
  13. package/dist/resources/extensions/bg-shell/overlay.js +9 -6
  14. package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
  15. package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
  16. package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
  17. package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
  18. package/dist/resources/extensions/gsd/auto/dispatch-history.js +105 -0
  19. package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
  20. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  21. package/dist/resources/extensions/gsd/auto/orchestrator.js +89 -54
  22. package/dist/resources/extensions/gsd/auto/phases.js +49 -6
  23. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  24. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
  26. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +30 -12
  28. package/dist/resources/extensions/gsd/auto-prompts.js +78 -19
  29. package/dist/resources/extensions/gsd/auto-start.js +35 -15
  30. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  31. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +5 -4
  32. package/dist/resources/extensions/gsd/auto-verification.js +23 -30
  33. package/dist/resources/extensions/gsd/auto-worktree.js +14 -1
  34. package/dist/resources/extensions/gsd/auto.js +37 -1
  35. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  36. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  37. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +23 -6
  38. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +145 -50
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +302 -80
  41. package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
  42. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  44. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  45. package/dist/resources/extensions/gsd/consent-question.js +353 -0
  46. package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
  47. package/dist/resources/extensions/gsd/constants.js +0 -2
  48. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  49. package/dist/resources/extensions/gsd/db/queries.js +26 -0
  50. package/dist/resources/extensions/gsd/db-writer.js +8 -17
  51. package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
  52. package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
  53. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  54. package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
  55. package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
  56. package/dist/resources/extensions/gsd/files.js +33 -19
  57. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  58. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  59. package/dist/resources/extensions/gsd/guidance.js +60 -0
  60. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  61. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
  62. package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
  63. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  64. package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
  65. package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
  66. package/dist/resources/extensions/gsd/preferences-models.js +2 -2
  67. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  68. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  72. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  73. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/run-uat.md +7 -5
  80. package/dist/resources/extensions/gsd/prompts/system.md +5 -2
  81. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  83. package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
  84. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  85. package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
  86. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  87. package/dist/resources/extensions/gsd/state.js +5 -0
  88. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  89. package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
  90. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  91. package/dist/resources/extensions/gsd/tools/complete-slice.js +22 -12
  92. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  93. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -0
  94. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  95. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  96. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  97. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  98. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  99. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  100. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  101. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  102. package/dist/resources/extensions/gsd/uat-policy.js +40 -15
  103. package/dist/resources/extensions/gsd/unit-context-composer.js +65 -0
  104. package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
  105. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  106. package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
  107. package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
  108. package/dist/resources/extensions/gsd/workflow-events.js +6 -18
  109. package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
  110. package/dist/resources/extensions/gsd/worktree-lifecycle.js +3 -2
  111. package/dist/resources/extensions/gsd/worktree-manager.js +7 -1
  112. package/dist/resources/extensions/gsd/worktree.js +8 -1
  113. package/dist/resources/extensions/shared/gsd-browser-cli.js +45 -3
  114. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  115. package/dist/resources/shared/package-manager-detection.js +1 -1
  116. package/dist/resources/shared/package.json +3 -0
  117. package/dist/resources/skills/create-skill/SKILL.md +3 -0
  118. package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
  119. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  120. package/dist/update-check.d.ts +2 -0
  121. package/dist/update-check.js +24 -1
  122. package/dist/update-cmd.js +20 -3
  123. package/dist/web/standalone/.next/BUILD_ID +1 -1
  124. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  125. package/dist/web/standalone/.next/build-manifest.json +3 -3
  126. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  127. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/index.html +1 -1
  147. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  154. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  155. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/static/chunks/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
  161. package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  163. package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
  164. package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
  165. package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
  166. package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
  167. package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
  168. package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
  169. package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
  170. package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
  171. package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
  172. package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
  173. package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
  174. package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
  175. package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
  176. package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
  177. package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
  178. package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
  179. package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
  180. package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
  181. package/dist/web/standalone/node_modules/postcss/package.json +48 -48
  182. package/package.json +2 -2
  183. package/packages/cloud-mcp-gateway/package.json +2 -2
  184. package/packages/contracts/dist/rpc.d.ts +1 -0
  185. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  186. package/packages/contracts/dist/rpc.js.map +1 -1
  187. package/packages/contracts/package.json +1 -1
  188. package/packages/daemon/package.json +4 -4
  189. package/packages/gsd-agent-core/package.json +5 -5
  190. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -0
  191. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  192. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +5 -0
  193. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  194. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  195. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
  196. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  197. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  198. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
  199. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  200. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  201. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
  202. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  203. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  204. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
  205. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  206. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  207. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
  208. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  209. package/packages/gsd-agent-modes/package.json +7 -7
  210. package/packages/mcp-server/dist/cli.js +10 -5
  211. package/packages/mcp-server/dist/cli.js.map +1 -1
  212. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  213. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  214. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  215. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  216. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  217. package/packages/mcp-server/dist/server.js +4 -0
  218. package/packages/mcp-server/dist/server.js.map +1 -1
  219. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  220. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  221. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  222. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  223. package/packages/mcp-server/package.json +5 -4
  224. package/packages/native/package.json +1 -1
  225. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
  226. package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  227. package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
  228. package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  229. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  230. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  231. package/packages/pi-agent-core/dist/index.js +3 -0
  232. package/packages/pi-agent-core/dist/index.js.map +1 -1
  233. package/packages/pi-agent-core/package.json +1 -1
  234. package/packages/pi-ai/README.md +1 -0
  235. package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
  236. package/packages/pi-ai/dist/image-models.generated.js +6 -6
  237. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  238. package/packages/pi-ai/dist/index.d.ts +2 -0
  239. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  240. package/packages/pi-ai/dist/index.js +2 -0
  241. package/packages/pi-ai/dist/index.js.map +1 -1
  242. package/packages/pi-ai/dist/models.generated.d.ts +419 -221
  243. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  244. package/packages/pi-ai/dist/models.generated.js +460 -261
  245. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  246. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  247. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  248. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  249. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  250. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  252. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  255. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  256. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  257. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  258. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  259. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  260. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  261. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  262. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  263. package/packages/pi-ai/package.json +3 -2
  264. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
  265. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
  267. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
  270. package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
  272. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
  274. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  276. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/index.js +1 -1
  278. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
  280. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
  282. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  283. package/packages/pi-coding-agent/package.json +7 -7
  284. package/packages/pi-tui/package.json +2 -2
  285. package/packages/rpc-client/package.json +2 -2
  286. package/pkg/package.json +1 -1
  287. package/src/resources/GSD-WORKFLOW.md +5 -4
  288. package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
  289. package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
  290. package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
  291. package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
  292. package/src/resources/extensions/async-jobs/index.ts +79 -0
  293. package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
  294. package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
  295. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
  296. package/src/resources/extensions/bg-shell/overlay.ts +9 -5
  297. package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
  298. package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
  299. package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
  300. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
  301. package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
  302. package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
  303. package/src/resources/extensions/gsd/auto/dispatch-history.ts +152 -0
  304. package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
  305. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  306. package/src/resources/extensions/gsd/auto/orchestrator.ts +98 -56
  307. package/src/resources/extensions/gsd/auto/phases.ts +65 -26
  308. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  309. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
  310. package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
  311. package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
  312. package/src/resources/extensions/gsd/auto-post-unit.ts +33 -12
  313. package/src/resources/extensions/gsd/auto-prompts.ts +115 -35
  314. package/src/resources/extensions/gsd/auto-start.ts +36 -18
  315. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  316. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +4 -4
  317. package/src/resources/extensions/gsd/auto-verification.ts +26 -28
  318. package/src/resources/extensions/gsd/auto-worktree.ts +14 -1
  319. package/src/resources/extensions/gsd/auto.ts +44 -1
  320. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  321. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  322. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +23 -6
  323. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  324. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +163 -55
  325. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +350 -86
  326. package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
  327. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  328. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  329. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  330. package/src/resources/extensions/gsd/consent-question.ts +431 -0
  331. package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
  332. package/src/resources/extensions/gsd/constants.ts +0 -3
  333. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  334. package/src/resources/extensions/gsd/db/queries.ts +37 -0
  335. package/src/resources/extensions/gsd/db-writer.ts +11 -19
  336. package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
  337. package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
  338. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  339. package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
  340. package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
  341. package/src/resources/extensions/gsd/files.ts +33 -12
  342. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  343. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  344. package/src/resources/extensions/gsd/guidance.ts +78 -0
  345. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  346. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
  347. package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
  348. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  349. package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
  350. package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
  351. package/src/resources/extensions/gsd/preferences-models.ts +2 -1
  352. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  353. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  354. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  355. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  356. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  357. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  358. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  359. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  360. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  361. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  362. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  363. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  364. package/src/resources/extensions/gsd/prompts/run-uat.md +7 -5
  365. package/src/resources/extensions/gsd/prompts/system.md +5 -2
  366. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  367. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  368. package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
  369. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  370. package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
  371. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  372. package/src/resources/extensions/gsd/state.ts +5 -0
  373. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
  374. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +198 -26
  375. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  376. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +236 -0
  377. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  378. package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
  379. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
  380. package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
  381. package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
  382. package/src/resources/extensions/gsd/tests/db-writer.test.ts +15 -4
  383. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
  384. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
  385. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
  386. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
  387. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  388. package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
  389. package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
  390. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
  391. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
  392. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  393. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
  394. package/src/resources/extensions/gsd/tests/guidance.test.ts +23 -0
  395. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  396. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
  397. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
  398. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  399. package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
  400. package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
  401. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
  402. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
  403. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  404. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  405. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
  406. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  407. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  408. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
  409. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +41 -0
  410. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  411. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  412. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +88 -0
  413. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +44 -0
  414. package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
  415. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  416. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
  417. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
  418. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  419. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +22 -0
  420. package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
  421. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
  422. package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -1
  423. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  424. package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
  425. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  426. package/src/resources/extensions/gsd/tools/complete-slice.ts +22 -12
  427. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  428. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -0
  429. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  430. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  431. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  432. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  433. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  434. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  435. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  436. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  437. package/src/resources/extensions/gsd/uat-policy.ts +60 -15
  438. package/src/resources/extensions/gsd/unit-context-composer.ts +99 -0
  439. package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
  440. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  441. package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
  442. package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
  443. package/src/resources/extensions/gsd/workflow-events.ts +12 -20
  444. package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
  445. package/src/resources/extensions/gsd/worktree-lifecycle.ts +3 -8
  446. package/src/resources/extensions/gsd/worktree-manager.ts +6 -1
  447. package/src/resources/extensions/gsd/worktree.ts +7 -1
  448. package/src/resources/extensions/shared/gsd-browser-cli.ts +54 -3
  449. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  450. package/src/resources/shared/package-manager-detection.ts +1 -1
  451. package/src/resources/shared/package.json +3 -0
  452. package/src/resources/skills/create-skill/SKILL.md +3 -0
  453. package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
  454. package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
  455. package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
  456. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
  457. package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
  458. package/src/resources/skills/gsd-browser/SKILL.md +0 -41
  459. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_buildManifest.js +0 -0
  460. /package/dist/web/standalone/.next/static/{AOpDeK_gJHU8OZjRo31gQ → FBNo5cT_chy7YNoAQsU3o}/_ssgManifest.js +0 -0
@@ -22,7 +22,7 @@ type BlockedAdvanceResult = Extract<AutoAdvanceResult, { kind: "blocked" }>;
22
22
  import { debugCount, debugLog, debugTime } from "../debug-logger.js";
23
23
  import { reconcileBeforeDispatch } from "../state-reconciliation.js";
24
24
  import { isLegalEdge, IllegalPhaseTransitionError } from "../state-transition-matrix.js";
25
- import { resolveDispatch } from "../auto-dispatch.js";
25
+ import { hasPendingDeepStage, resolveDispatch } from "../auto-dispatch.js";
26
26
  import { classifyFailure } from "../recovery-classification.js";
27
27
  import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
28
28
  import { invalidateAllCaches } from "../cache.js";
@@ -64,6 +64,13 @@ import {
64
64
  import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
65
65
  import { getErrorMessage } from "../error-utils.js";
66
66
  import { logWarning } from "../workflow-logger.js";
67
+ import { normalizeRealPath } from "../paths.js";
68
+ import {
69
+ buildDispatchKey,
70
+ createDispatchHistory,
71
+ STUCK_WINDOW_SIZE,
72
+ type DispatchHistory,
73
+ } from "./dispatch-history.js";
67
74
  import { existsSync, readFileSync } from "node:fs";
68
75
  import { join } from "node:path";
69
76
  import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
@@ -72,16 +79,6 @@ function now(): number {
72
79
  return Date.now();
73
80
  }
74
81
 
75
- /**
76
- * Size of the dispatch-decision ring buffer used by the Auto Orchestration
77
- * module's stuck-loop detector. When the same `${unitType}:${unitId}` key
78
- * fills the window, advance() blocks with `action: "stop"`.
79
- *
80
- * Mirrors the legacy `STUCK_WINDOW_SIZE` in auto/phases.ts so behaviour is
81
- * preserved across the eventual cutover (issue #5791).
82
- */
83
- export const STUCK_WINDOW_SIZE = 6;
84
-
85
82
  function noRemainingUnitsOutcome(stateSnapshot: GSDState): AutoTerminalOutcome {
86
83
  if (stateSnapshot.phase === "complete") {
87
84
  return {
@@ -196,14 +193,25 @@ export async function decideOrchestratorDispatch(
196
193
  ): Promise<DispatchDecision> {
197
194
  const state = input.stateSnapshot;
198
195
  const active = state.activeMilestone;
199
- if (!active) return null;
200
-
201
196
  const activeSession = input.session ?? session;
202
197
  const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
203
- if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
198
+ const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
199
+ if (!active) {
200
+ if (state.phase !== "pre-planning") return null;
201
+ if (!hasPendingDeepStage(prefs, activeDispatchBasePath)) {
202
+ return {
203
+ kind: "blocked",
204
+ reason: state.nextAction || "No active milestone. Run /gsd unpark <id> or create a new milestone.",
205
+ action: "stop",
206
+ };
207
+ }
208
+ }
209
+
210
+ if (active && activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
204
211
  activeSession.currentMilestoneId = active.id;
205
212
  }
206
- const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
213
+ const dispatchMid = active?.id ?? activeSession?.currentMilestoneId ?? "";
214
+ const dispatchMidTitle = active?.title ?? "";
207
215
 
208
216
  // Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
209
217
  // (#5789). Prefer caller-supplied values when present so test harnesses and
@@ -235,8 +243,15 @@ export async function decideOrchestratorDispatch(
235
243
  ? "true"
236
244
  : "false");
237
245
 
246
+ // Only replay a milestone-scoped verification retry when a milestone is
247
+ // active. Pre-PR (#712 fix), `!active` returned null before reaching this
248
+ // block, so the retry was preserved for a future tick. The new
249
+ // pre-planning + deep-pending fall-through must keep that contract:
250
+ // otherwise a stale execute-task / complete-slice / complete-milestone
251
+ // retry whose target milestone has since been parked would preempt
252
+ // project-level deep rules like `discuss-project`.
238
253
  const pendingRetry = session?.pendingVerificationRetryDispatch;
239
- if (session && pendingRetry) {
254
+ if (session && pendingRetry && active) {
240
255
  session.pendingVerificationRetryDispatch = null;
241
256
  const alreadyClosedReason = getAlreadyClosedDispatchReason(
242
257
  pendingRetry.unitType,
@@ -258,8 +273,8 @@ export async function decideOrchestratorDispatch(
258
273
 
259
274
  const action = await resolveDispatch({
260
275
  basePath: activeDispatchBasePath,
261
- mid: active.id,
262
- midTitle: active.title,
276
+ mid: dispatchMid,
277
+ midTitle: dispatchMidTitle,
263
278
  state,
264
279
  prefs,
265
280
  session: activeSession,
@@ -303,8 +318,8 @@ export async function decideOrchestratorDispatch(
303
318
  prompt: action.prompt,
304
319
  pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
305
320
  state,
306
- mid: active.id,
307
- midTitle: active.title,
321
+ mid: dispatchMid,
322
+ midTitle: dispatchMidTitle,
308
323
  };
309
324
  session.pendingOrchestrationDispatch = pending;
310
325
  }
@@ -330,7 +345,9 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
330
345
  private seq = 0;
331
346
  private lastAdvanceKey: string | null = null;
332
347
  private lastFinalizedUnitKey: string | null = null;
333
- private dispatchKeyWindow: string[] = [];
348
+ // Dispatch History module (#482): the dispatch-decision window with
349
+ // cross-session DB rehydration and full detect-stuck rules.
350
+ private readonly dispatchHistory: DispatchHistory;
334
351
  // ADR-030 Phase Transition Invariant: the prior advance's reconciled Phase,
335
352
  // the "from" endpoint of the edge check. In-memory; reset on start/resume/stop
336
353
  // so the first advance of a session has no edge to assert.
@@ -347,6 +364,16 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
347
364
  this.runtimeBasePath = context.runtimeBasePath;
348
365
  this.s = context.session;
349
366
  this.flowId = `auto-orchestrator-${Date.now()}`;
367
+ this.dispatchHistory = createDispatchHistory({
368
+ windowSize: STUCK_WINDOW_SIZE,
369
+ // Same stable scope the auto-loop uses for stuck-state persistence so
370
+ // rehydration reads the rows the dispatch ledger wrote for this project.
371
+ resolveScopeId: () =>
372
+ normalizeRealPath(
373
+ this.s.scope?.workspace.projectRoot ??
374
+ (this.s.originalBasePath || this.s.basePath || this.runtimeBasePath),
375
+ ) || null,
376
+ });
350
377
  }
351
378
 
352
379
  // ── Live base-path resolution (was the wiring factory's getLiveDispatchBasePath) ──
@@ -765,7 +792,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
765
792
  * skipped result) instead of stopping.
766
793
  */
767
794
  private tryStuckArtifactRecovery(unitType: string, unitId: string): boolean {
768
- const key = `${unitType}:${unitId}`;
795
+ const key = buildDispatchKey(unitType, unitId);
769
796
  if (this.lastStuckRecoveryKey === key) return false; // already tried this episode
770
797
  const basePath = this.getLiveDispatchBasePath();
771
798
  if (!verifyExpectedArtifact(unitType, unitId, basePath)) return false;
@@ -777,7 +804,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
777
804
  if (!refreshed.ok && refreshed.fatal) return false;
778
805
  this.lastStuckRecoveryKey = key;
779
806
  invalidateAllCaches();
780
- this.dispatchKeyWindow = [];
807
+ this.dispatchHistory.clearOnRecovery();
781
808
  this.lastAdvanceKey = null;
782
809
  this.lastFinalizedUnitKey = null;
783
810
  return true;
@@ -808,7 +835,12 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
808
835
  public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
809
836
  this.lastAdvanceKey = null;
810
837
  this.lastFinalizedUnitKey = null;
811
- this.dispatchKeyWindow = [];
838
+ // #482: the DB dispatch ledger is the source of truth across sessions.
839
+ // Discard any in-memory window and rebuild it from the ledger so a unit
840
+ // that was re-dispatched in previous sessions is detected as stuck here
841
+ // instead of silently re-dispatching forever.
842
+ this.dispatchHistory.clearOnRecovery();
843
+ this.dispatchHistory.rehydrate();
812
844
  this.lastStuckRecoveryKey = null;
813
845
  this.lastDerivedPhase = null;
814
846
  this.status.phase = "running";
@@ -913,7 +945,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
913
945
  this.status.phase = "paused";
914
946
  this.status.activeUnit = undefined;
915
947
  this.lastAdvanceKey = null;
916
- this.dispatchKeyWindow = [];
948
+ this.dispatchHistory.clearOnRecovery();
917
949
  this.bumpTransition();
918
950
  this.journalTransition({ name: "advance-blocked", reason: settlementBlock.reason });
919
951
  this.postAdvanceRecord(settlementBlock);
@@ -929,7 +961,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
929
961
  this.status.phase = "stopped";
930
962
  this.status.activeUnit = undefined;
931
963
  this.lastAdvanceKey = null;
932
- this.dispatchKeyWindow = [];
964
+ this.dispatchHistory.clearOnRecovery();
933
965
  this.bumpTransition();
934
966
  this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
935
967
  this.postAdvanceRecord(stopped);
@@ -979,18 +1011,13 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
979
1011
  return blocked;
980
1012
  }
981
1013
 
982
- const nextKey = `${decision.unitType}:${decision.unitId}`;
983
-
984
- // Record every dispatch decision in the ring buffer before pre-flight
1014
+ // Record every dispatch decision in the history window before pre-flight
985
1015
  // checks so the stuck-loop detector observes the full decision history
986
1016
  // (including decisions that idempotency would otherwise short-circuit).
987
- // The ring is capped at STUCK_WINDOW_SIZE and evicts oldest-first.
988
- this.dispatchKeyWindow.push(nextKey);
989
- if (this.dispatchKeyWindow.length > STUCK_WINDOW_SIZE) {
990
- this.dispatchKeyWindow.shift();
991
- }
1017
+ // The window is capped at STUCK_WINDOW_SIZE and evicts oldest-first.
1018
+ const nextKey = this.dispatchHistory.recordDispatch(decision.unitType, decision.unitId);
992
1019
 
993
- const matchingCount = this.dispatchKeyWindow.filter((k) => k === nextKey).length;
1020
+ const matchingCount = this.dispatchHistory.countMatching(nextKey);
994
1021
  if (this.lastFinalizedUnitKey === nextKey) {
995
1022
  // #442: the unit re-dispatched immediately after finalizing may have
996
1023
  // actually completed on disk with a stale DB. Verify + recover before
@@ -1023,23 +1050,32 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1023
1050
  // checks coexist: idempotency for the common immediate-repeat case,
1024
1051
  // stuck-loop for the saturated-window case.
1025
1052
  if (this.lastAdvanceKey === nextKey && matchingCount < STUCK_WINDOW_SIZE) {
1053
+ // Unit already active — benign no-op. Return skipped so the loop re-polls
1054
+ // without cancelling the in-flight unit (blocked+pause would force-cancel it).
1026
1055
  this.clearPendingDispatch();
1027
- const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active", action: "pause" };
1056
+ const skipped: AutoAdvanceResult = { kind: "skipped", reason: "idempotent advance: unit already active" };
1028
1057
  this.journalTransition({
1029
- name: "advance-blocked",
1030
- reason: blocked.reason,
1058
+ name: "advance-skipped",
1059
+ reason: skipped.reason,
1031
1060
  unitType: decision.unitType,
1032
1061
  unitId: decision.unitId,
1033
1062
  });
1034
- this.postAdvanceRecord(blocked);
1035
- return blocked;
1063
+ this.postAdvanceRecord(skipped);
1064
+ return skipped;
1036
1065
  }
1037
1066
 
1038
- // Stuck-loop detection: when the ring is saturated with copies of
1039
- // `nextKey` (count >= STUCK_WINDOW_SIZE), the orchestrator has been
1040
- // picking the same unit across the whole window and must hard-stop with
1041
- // a diagnosable reason.
1042
- if (matchingCount >= STUCK_WINDOW_SIZE) {
1067
+ // Stuck-loop detection: when the window is saturated with copies of
1068
+ // `nextKey` (count >= STUCK_WINDOW_SIZE), consult the Dispatch History
1069
+ // module's full detect-stuck rule set for the verdict instead of the old
1070
+ // bare saturation count. This keeps the saturation threshold (the window
1071
+ // deliberately records benign idempotent repeats, so earlier-firing
1072
+ // rules would false-positive on pause/resume re-advances) while gaining
1073
+ // retry-budget suppression and diagnosable rule reasons. A saturated
1074
+ // window with no verdict means the dispatch ledger says we are inside
1075
+ // the unit's retry-backoff budget — let the retry proceed.
1076
+ const stuckVerdict =
1077
+ matchingCount >= STUCK_WINDOW_SIZE ? this.dispatchHistory.detectStuck() : null;
1078
+ if (stuckVerdict) {
1043
1079
  // #442: before declaring a stuck loop, verify the unit didn't actually
1044
1080
  // complete on disk (stale DB) and recover if so — legacy graduated
1045
1081
  // stuck-recovery parity. Otherwise hard-stop with a diagnosable reason.
@@ -1050,7 +1086,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1050
1086
  this.clearPendingDispatch();
1051
1087
  const blocked: AutoAdvanceResult = {
1052
1088
  kind: "blocked",
1053
- reason: `stuck-loop: ${nextKey} picked ${matchingCount} times`,
1089
+ reason: `stuck-loop: ${stuckVerdict.reason}`,
1054
1090
  action: "stop",
1055
1091
  };
1056
1092
  this.journalTransition({
@@ -1144,7 +1180,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1144
1180
  if (result.kind === "stopped") {
1145
1181
  this.lastAdvanceKey = null;
1146
1182
  this.lastFinalizedUnitKey = null;
1147
- this.dispatchKeyWindow = [];
1183
+ this.dispatchHistory.clearOnRecovery();
1148
1184
  this.status.activeUnit = undefined;
1149
1185
  }
1150
1186
  this.bumpTransition();
@@ -1173,8 +1209,14 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1173
1209
  public async resume(): Promise<AutoAdvanceResult> {
1174
1210
  this.lastAdvanceKey = null;
1175
1211
  this.lastFinalizedUnitKey = null;
1176
- // Preserve dispatchKeyWindow across resume so stuck-loop detection
1177
- // accumulates across pause/resume cycles rather than resetting each time.
1212
+ // Preserve the dispatch-history window across an in-process resume so
1213
+ // stuck-loop detection accumulates across pause/resume cycles rather than
1214
+ // resetting each time (#572 regression). When the window is empty (fresh
1215
+ // orchestrator resuming a prior session), rehydrate it from the DB
1216
+ // dispatch ledger so cross-session re-dispatch loops are detected (#482).
1217
+ if (this.dispatchHistory.getRecentWindow().length === 0) {
1218
+ this.dispatchHistory.rehydrate();
1219
+ }
1178
1220
  this.lastStuckRecoveryKey = null;
1179
1221
  // ADR-030: drop the prior "from" — the first advance after resume has no
1180
1222
  // edge to assert (avoids a false illegal-edge across the pause boundary).
@@ -1196,10 +1238,10 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1196
1238
  this.lastAdvanceKey = null;
1197
1239
  this.lastFinalizedUnitKey = null;
1198
1240
  this.lastDerivedPhase = null;
1199
- // Preserve dispatchKeyWindow on pause so stuck-loop detection accumulates
1200
- // across pause/resume cycles. Only clear on a hard stop.
1241
+ // Preserve the dispatch-history window on pause so stuck-loop detection
1242
+ // accumulates across pause/resume cycles. Only clear on a hard stop.
1201
1243
  if (reason !== "pause") {
1202
- this.dispatchKeyWindow = [];
1244
+ this.dispatchHistory.clearOnRecovery();
1203
1245
  }
1204
1246
  this.lastStuckRecoveryKey = null;
1205
1247
  this.bumpTransition();
@@ -1213,9 +1255,9 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1213
1255
  }
1214
1256
 
1215
1257
  public async completeActiveUnit(unit: { unitType: string; unitId: string }): Promise<void> {
1216
- const unitKey = `${unit.unitType}:${unit.unitId}`;
1258
+ const unitKey = buildDispatchKey(unit.unitType, unit.unitId);
1217
1259
  const activeUnitKey = this.status.activeUnit
1218
- ? `${this.status.activeUnit.unitType}:${this.status.activeUnit.unitId}`
1260
+ ? buildDispatchKey(this.status.activeUnit.unitType, this.status.activeUnit.unitId)
1219
1261
  : null;
1220
1262
  if (activeUnitKey !== unitKey) return;
1221
1263
 
@@ -1233,9 +1275,9 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1233
1275
  }
1234
1276
 
1235
1277
  public async retryActiveUnit(unit: { unitType: string; unitId: string }): Promise<void> {
1236
- const unitKey = `${unit.unitType}:${unit.unitId}`;
1278
+ const unitKey = buildDispatchKey(unit.unitType, unit.unitId);
1237
1279
  const activeUnitKey = this.status.activeUnit
1238
- ? `${this.status.activeUnit.unitType}:${this.status.activeUnit.unitId}`
1280
+ ? buildDispatchKey(this.status.activeUnit.unitType, this.status.activeUnit.unitId)
1239
1281
  : null;
1240
1282
  if (activeUnitKey !== unitKey && this.lastFinalizedUnitKey !== unitKey) return;
1241
1283
 
@@ -19,9 +19,9 @@ import {
19
19
  type PostUnitContext,
20
20
  type PreVerificationOpts,
21
21
  } from "../auto-post-unit.js";
22
- import { lastAssistantText } from "../user-input-boundary.js";
23
- import { resolveEffectiveUnitIsolationMode } from "../preferences.js";
24
- import type { Phase } from "../types.js";
22
+ import { lastAssistantText } from "../consent-question.js";
23
+ import { resolveEffectiveUnitIsolationMode, getIsolationMode } from "../preferences.js";
24
+ import type { GSDState, Phase } from "../types.js";
25
25
  import {
26
26
  MAX_RECOVERY_CHARS,
27
27
  BUDGET_THRESHOLDS,
@@ -33,6 +33,7 @@ import {
33
33
  type IterationData,
34
34
  } from "./types.js";
35
35
  import { detectStuck } from "./detect-stuck.js";
36
+ import { STUCK_WINDOW_SIZE } from "./dispatch-history.js";
36
37
  import { runUnit } from "./run-unit.js";
37
38
  import { debugLog } from "../debug-logger.js";
38
39
  import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
@@ -61,9 +62,10 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
61
62
  import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
62
63
  import { getEligibleSlices } from "../slice-parallel-eligibility.js";
63
64
  import { isSliceParallelActive, startSliceParallel } from "../slice-parallel-orchestrator.js";
64
- import { isDbAvailable, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
65
+ import { isDbAvailable, getMilestone, getMilestoneSlices, getSlice, getTask } from "../gsd-db.js";
65
66
  import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
66
67
  import { isClosedStatus } from "../status-guards.js";
68
+ import { findUnmergedCompletedMilestones } from "../unmerged-milestone-guard.js";
67
69
  import { setRuntimeKv } from "../db/runtime-kv.js";
68
70
  import { getLatestForUnit } from "../db/unit-dispatches.js";
69
71
  import { reconcileBeforeSpawn } from "../state-reconciliation.js";
@@ -81,11 +83,8 @@ import { parseUnitId } from "../unit-id.js";
81
83
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
82
84
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
83
85
  import { getContextPauseAction } from "../auto-budget.js";
84
- import {
85
- getWorkflowTransportSupportError,
86
- getRequiredWorkflowToolsForAutoUnit,
87
- supportsStructuredQuestions,
88
- } from "../workflow-mcp.js";
86
+ import { supportsStructuredQuestions } from "../workflow-mcp.js";
87
+ import { getUnitWorkflowDispatchReadinessError } from "../tool-contract.js";
89
88
  import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
90
89
  import {
91
90
  applyThinkingLevelForModel,
@@ -107,7 +106,6 @@ import {
107
106
  } from "../root-write-leak-guard.js";
108
107
  import { classifyError, isTransient } from "../error-classifier.js";
109
108
 
110
- export const STUCK_WINDOW_SIZE = 6;
111
109
  const STUCK_RECOVERY_ATTEMPTS_KEY = "stuck_recovery_attempts";
112
110
  const ZERO_TOOL_PROVIDER_ERROR_PREFIX_RE =
113
111
  /^(?:api error(?::|$|\s*\()|provider error(?::|$|\s*\()|request failed\b|(?:http\s*)?(?:429|500|502|503)\b|\b(?:econnreset|etimedout|econnrefused|epipe)\b|socket hang up\b|fetch failed\b|(?:network|connection|server) error(?::|$)|connection (?:reset|refused)(?::|$|\s+by\b)|dns\b.*(?:fail|error|timeout)|unexpected eof\b|stream idle timeout\b|partial response received\b|stream_exhausted\b|terminated(?::|$)|(?:connection|stream|request)\b.{0,40}\bterminated\b|other side closed\b|rate.?limit(?:ed| exceeded| reached| error)|too many requests\b|you(?:'ve| have) (?:hit|reached) your (?:\w+ )?limit\b|.*\b(?:usage|session|weekly|daily|monthly|quota) limit\b|limit\b.{0,40}\bresets?\b|out of extra usage\b|service.?unavailable\b|internal(?: server)? error(?::|$)|internal(?:[_-]server)?[_-]error\b|server[_-]error\b|(?:provider|server|api|model|codex|claude|openai|anthropic|gemini)\b.{0,80}\boverloaded\b|overloaded\b.{0,80}\b(?:provider|server|api|model)\b|context (?:window|length) exceed|context window exceed)/i;
@@ -838,6 +836,39 @@ async function failClosedOnFinalizeTimeout(
838
836
  return { action: "break", reason: progressKind };
839
837
  }
840
838
 
839
+ export async function shouldSkipTerminalMilestoneCloseout(
840
+ s: AutoSession,
841
+ state: Pick<GSDState, "phase" | "lastCompletedMilestone" | "activeMilestone">,
842
+ mid?: string | null,
843
+ ): Promise<{ skip: boolean; milestoneId?: string }> {
844
+ const closeoutMilestoneId = mid ?? s.currentMilestoneId ?? state.lastCompletedMilestone?.id;
845
+ if (s.completionStopInProgress) {
846
+ return { skip: true, milestoneId: closeoutMilestoneId };
847
+ }
848
+ if (!closeoutMilestoneId) {
849
+ return { skip: false };
850
+ }
851
+ if (isDbAvailable()) refreshWorkflowDatabaseFromDisk();
852
+ const closeoutBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
853
+ let closeoutMergePending = false;
854
+ if (getIsolationMode(closeoutBasePath) !== "none") {
855
+ try {
856
+ const blockers = await findUnmergedCompletedMilestones(closeoutBasePath);
857
+ closeoutMergePending = blockers.some((blocker) => blocker.milestoneId === closeoutMilestoneId);
858
+ } catch {
859
+ // Fail open: without git/DB inspection we cannot safely treat closeout as done.
860
+ closeoutMergePending = true;
861
+ }
862
+ }
863
+ const milestoneAlreadyClosedOut = isDbAvailable()
864
+ && isClosedStatus(getMilestone(closeoutMilestoneId)?.status ?? "")
865
+ && !closeoutMergePending;
866
+ if (milestoneAlreadyClosedOut) {
867
+ return { skip: true, milestoneId: closeoutMilestoneId };
868
+ }
869
+ return { skip: false, milestoneId: closeoutMilestoneId };
870
+ }
871
+
841
872
  // ─── runPreDispatch ───────────────────────────────────────────────────────────
842
873
 
843
874
  /**
@@ -1279,6 +1310,14 @@ export async function runPreDispatch(
1279
1310
 
1280
1311
  // ── Terminal conditions ──────────────────────────────────────────────
1281
1312
 
1313
+ if (state.phase === "complete") {
1314
+ const closeoutSkip = await shouldSkipTerminalMilestoneCloseout(s, state, mid);
1315
+ if (closeoutSkip.skip) {
1316
+ debugLog("autoLoop", { phase: "complete", reason: "milestone-already-closed", milestoneId: closeoutSkip.milestoneId });
1317
+ return { action: "break", reason: "milestone-complete" };
1318
+ }
1319
+ }
1320
+
1282
1321
  if (!mid) {
1283
1322
  if (s.currentUnit) {
1284
1323
  await deps.closeoutUnit(
@@ -2324,22 +2363,19 @@ export async function runUnitPhase(
2324
2363
  ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
2325
2364
  : null;
2326
2365
 
2327
- const compatibilityError = getWorkflowTransportSupportError(
2328
- s.currentUnitModel?.provider ?? ctx.model?.provider,
2329
- getRequiredWorkflowToolsForAutoUnit(unitType),
2330
- {
2331
- projectRoot: s.basePath,
2332
- surface: "auto-mode",
2333
- unitType,
2334
- authMode: s.currentUnitModel?.provider
2335
- ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
2336
- : ctx.model?.provider
2337
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
2338
- : undefined,
2339
- baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
2340
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
2341
- },
2342
- );
2366
+ const compatibilityError = getUnitWorkflowDispatchReadinessError({
2367
+ provider: s.currentUnitModel?.provider ?? ctx.model?.provider,
2368
+ projectRoot: s.basePath,
2369
+ surface: "auto-mode",
2370
+ unitType,
2371
+ authMode: s.currentUnitModel?.provider
2372
+ ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
2373
+ : ctx.model?.provider
2374
+ ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
2375
+ : undefined,
2376
+ baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
2377
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
2378
+ });
2343
2379
  const workflowMcpPrepModel = s.currentUnitModel;
2344
2380
  if (compatibilityError) {
2345
2381
  s.currentUnitRouting = prevUnitRouting;
@@ -2409,6 +2445,9 @@ export async function runUnitPhase(
2409
2445
  causedBy: "unit-start",
2410
2446
  });
2411
2447
  s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
2448
+ if (nextDispatchCount <= 1) {
2449
+ s.toolUnavailableRetries = 0;
2450
+ }
2412
2451
  const unitStartSeq = ic.nextSeq();
2413
2452
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
2414
2453
  deps.captureAvailableSkills();
@@ -212,6 +212,8 @@ export class AutoSession {
212
212
  /** Set when a GSD tool execution ends with isError due to malformed/truncated
213
213
  * JSON arguments. Checked by postUnitPreVerification to break retry loops. */
214
214
  lastToolInvocationError: string | null = null;
215
+ /** Consecutive tool-unavailable retries for the current unit (MCP startup race). */
216
+ toolUnavailableRetries = 0;
215
217
  /** Agent-end messages from the just-finished unit, consumed during finalize. */
216
218
  lastUnitAgentEndMessages: unknown[] | null = null;
217
219
  /** Set when turn-level git action fails during closeout. */
@@ -406,6 +408,7 @@ export class AutoSession {
406
408
  this.lastPreExecFailure = null;
407
409
  this.preExecRetryCount.clear();
408
410
  this.lastToolInvocationError = null;
411
+ this.toolUnavailableRetries = 0;
409
412
  this.lastUnitAgentEndMessages = null;
410
413
  this.lastGitActionFailure = null;
411
414
  this.lastGitActionStatus = null;
@@ -10,10 +10,9 @@ import type {
10
10
 
11
11
  import { deriveState } from "./state.js";
12
12
  import { loadFile } from "./files.js";
13
- import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
14
- import { parseRoadmap } from "./parsers-legacy.js";
13
+ import { isDbAvailable, getClosedSliceIds } from "./gsd-db.js";
15
14
  import {
16
- resolveMilestoneFile, resolveSliceFile, relSliceFile,
15
+ resolveSliceFile, relSliceFile,
17
16
  } from "./paths.js";
18
17
  import {
19
18
  buildResearchSlicePrompt,
@@ -32,10 +31,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
32
31
  import type { MinimalModelRegistry } from "./context-budget.js";
33
32
  import { pauseAuto } from "./auto.js";
34
33
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
35
- import {
36
- getWorkflowTransportSupportError,
37
- getRequiredWorkflowToolsForAutoUnit,
38
- } from "./workflow-mcp.js";
34
+ import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
39
35
 
40
36
  export async function dispatchDirectPhase(
41
37
  ctx: ExtensionCommandContext,
@@ -182,21 +178,9 @@ export async function dispatchDirectPhase(
182
178
 
183
179
  case "reassess":
184
180
  case "reassess-roadmap": {
185
- // DB primary pathget completed slices, fall back to file parsing when DB has no data
186
- let completedSliceIds: string[] = [];
187
- if (isDbAvailable()) {
188
- completedSliceIds = getMilestoneSlices(mid).filter(s => s.status === "complete").map(s => s.id);
189
- }
190
- if (completedSliceIds.length === 0) {
191
- // File-based fallback: parse roadmap checkboxes
192
- const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
193
- if (roadmapPath) {
194
- const roadmapContent = await loadFile(roadmapPath);
195
- if (roadmapContent) {
196
- completedSliceIds = parseRoadmap(roadmapContent).slices.filter(s => s.done).map(s => s.id);
197
- }
198
- }
199
- }
181
+ // DB-authoritative read (ADR-017)markdown projections are never
182
+ // consulted for dispatch decisions. No DB rows means no completed slices.
183
+ const completedSliceIds = isDbAvailable() ? getClosedSliceIds(mid) : [];
200
184
  if (completedSliceIds.length === 0) {
201
185
  ctx.ui.notify("Cannot dispatch reassess-roadmap: no completed slices.", "warning");
202
186
  return;
@@ -222,20 +206,9 @@ export async function dispatchDirectPhase(
222
206
  // incomplete) slice. After slice completion, state.activeSlice advances
223
207
  // to the next incomplete slice, so we find the last done slice from the
224
208
  // roadmap instead (#1693).
225
- let uatCompletedSliceIds: string[] = [];
226
- if (isDbAvailable()) {
227
- uatCompletedSliceIds = getMilestoneSlices(mid).filter(s => s.status === "complete").map(s => s.id);
228
- }
229
- if (uatCompletedSliceIds.length === 0) {
230
- // File-based fallback: parse roadmap checkboxes
231
- const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
232
- if (roadmapPath) {
233
- const roadmapContent = await loadFile(roadmapPath);
234
- if (roadmapContent) {
235
- uatCompletedSliceIds = parseRoadmap(roadmapContent).slices.filter(s => s.done).map(s => s.id);
236
- }
237
- }
238
- }
209
+ // DB-authoritative read (ADR-017) — no markdown fallback for dispatch
210
+ // decisions.
211
+ const uatCompletedSliceIds = isDbAvailable() ? getClosedSliceIds(mid) : [];
239
212
  if (uatCompletedSliceIds.length === 0) {
240
213
  ctx.ui.notify("Cannot dispatch run-uat: no completed slices.", "warning");
241
214
  return;
@@ -280,18 +253,15 @@ export async function dispatchDirectPhase(
280
253
  return;
281
254
  }
282
255
 
283
- const compatibilityError = getWorkflowTransportSupportError(
284
- ctx.model?.provider,
285
- getRequiredWorkflowToolsForAutoUnit(unitType),
286
- {
287
- projectRoot,
288
- surface: "direct phase dispatch",
289
- unitType,
290
- authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
291
- baseUrl: ctx.model?.baseUrl,
292
- activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
293
- },
294
- );
256
+ const compatibilityError = getUnitWorkflowDispatchReadinessError({
257
+ provider: ctx.model?.provider,
258
+ projectRoot,
259
+ surface: "direct phase dispatch",
260
+ unitType,
261
+ authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
262
+ baseUrl: ctx.model?.baseUrl,
263
+ activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
264
+ });
295
265
  if (compatibilityError) {
296
266
  ctx.ui.notify(compatibilityError, "error");
297
267
  return;