@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.4813ead6

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 (500) hide show
  1. package/dist/cli-web-branch.d.ts +2 -0
  2. package/dist/cli-web-branch.js +9 -2
  3. package/dist/help-text.js +5 -0
  4. package/dist/project-sessions.js +4 -2
  5. package/dist/resources/.managed-resources-content-hash +1 -1
  6. package/dist/resources/extensions/ask-user-questions.js +78 -23
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +101 -237
  8. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +224 -0
  9. package/dist/resources/extensions/github-sync/templates.js +3 -3
  10. package/dist/resources/extensions/gsd/artifact-projection.js +14 -0
  11. package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
  12. package/dist/resources/extensions/gsd/auto/loop.js +74 -56
  13. package/dist/resources/extensions/gsd/auto/orchestrator.js +763 -63
  14. package/dist/resources/extensions/gsd/auto/phases.js +28 -3
  15. package/dist/resources/extensions/gsd/auto/run-unit.js +2 -1
  16. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-dashboard.js +16 -4
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +6 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +8 -0
  20. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  21. package/dist/resources/extensions/gsd/auto-prompts.js +191 -9
  22. package/dist/resources/extensions/gsd/auto-recovery.js +48 -49
  23. package/dist/resources/extensions/gsd/auto-runtime-state.js +17 -0
  24. package/dist/resources/extensions/gsd/auto-start.js +12 -23
  25. package/dist/resources/extensions/gsd/auto-timers.js +16 -2
  26. package/dist/resources/extensions/gsd/auto-tool-tracking.js +37 -0
  27. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +33 -29
  28. package/dist/resources/extensions/gsd/auto-verification.js +7 -7
  29. package/dist/resources/extensions/gsd/auto-worktree.js +45 -36
  30. package/dist/resources/extensions/gsd/auto.js +73 -471
  31. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +28 -37
  32. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +11 -37
  33. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  34. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +103 -138
  35. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +63 -4
  36. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +21 -4
  37. package/dist/resources/extensions/gsd/codebase-generator.js +8 -4
  38. package/dist/resources/extensions/gsd/commands/handlers/auto.js +3 -0
  39. package/dist/resources/extensions/gsd/commands-handlers.js +20 -0
  40. package/dist/resources/extensions/gsd/commands-inspect.js +4 -8
  41. package/dist/resources/extensions/gsd/commands-maintenance.js +61 -41
  42. package/dist/resources/extensions/gsd/commands-ship.js +2 -2
  43. package/dist/resources/extensions/gsd/commands-verdict.js +12 -2
  44. package/dist/resources/extensions/gsd/db-workspace.js +103 -0
  45. package/dist/resources/extensions/gsd/debug-logger.js +10 -0
  46. package/dist/resources/extensions/gsd/delegation-policy.js +2 -10
  47. package/dist/resources/extensions/gsd/discussion-handoff.js +218 -0
  48. package/dist/resources/extensions/gsd/docs/preferences-reference.md +9 -0
  49. package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
  50. package/dist/resources/extensions/gsd/doctor.js +16 -9
  51. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  52. package/dist/resources/extensions/gsd/git-conflict-state.js +16 -1
  53. package/dist/resources/extensions/gsd/gsd-db.js +12 -0
  54. package/dist/resources/extensions/gsd/guided-flow.js +36 -470
  55. package/dist/resources/extensions/gsd/guided-unit-completion.js +225 -0
  56. package/dist/resources/extensions/gsd/markdown-renderer.js +33 -33
  57. package/dist/resources/extensions/gsd/mcp-filter.js +8 -1
  58. package/dist/resources/extensions/gsd/mcp-tool-name.js +26 -0
  59. package/dist/resources/extensions/gsd/md-importer.js +4 -3
  60. package/dist/resources/extensions/gsd/migrate/safety.js +2 -2
  61. package/dist/resources/extensions/gsd/migration-auto-check.js +3 -2
  62. package/dist/resources/extensions/gsd/milestone-closeout-proof.js +72 -0
  63. package/dist/resources/extensions/gsd/milestone-closeout.js +12 -4
  64. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +10 -0
  65. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +156 -0
  66. package/dist/resources/extensions/gsd/milestone-readiness.js +77 -0
  67. package/dist/resources/extensions/gsd/milestone-settlement.js +50 -0
  68. package/dist/resources/extensions/gsd/milestone-validation-evidence.js +73 -0
  69. package/dist/resources/extensions/gsd/milestone-validation-verdict.js +57 -0
  70. package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
  71. package/dist/resources/extensions/gsd/parallel-eligibility.js +3 -6
  72. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -2
  73. package/dist/resources/extensions/gsd/preferences-diagnostics.js +67 -0
  74. package/dist/resources/extensions/gsd/preferences.js +147 -29
  75. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -0
  76. package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
  77. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  78. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
  79. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
  80. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
  81. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
  82. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -0
  83. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  84. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -0
  85. package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  86. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  87. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
  88. package/dist/resources/extensions/gsd/provider-payload-policy.js +83 -0
  89. package/dist/resources/extensions/gsd/pull-request-process.js +13 -0
  90. package/dist/resources/extensions/gsd/quality-gate-closure.js +109 -0
  91. package/dist/resources/extensions/gsd/question-transport.js +86 -0
  92. package/dist/resources/extensions/gsd/roadmap-slices.js +8 -2
  93. package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
  94. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +3 -2
  95. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
  96. package/dist/resources/extensions/gsd/state.js +13 -5
  97. package/dist/resources/extensions/gsd/templates/plan.md +7 -0
  98. package/dist/resources/extensions/gsd/templates/project.md +1 -0
  99. package/dist/resources/extensions/gsd/templates/roadmap.md +1 -1
  100. package/dist/resources/extensions/gsd/templates/uat.md +5 -1
  101. package/dist/resources/extensions/gsd/tool-contract.js +52 -8
  102. package/dist/resources/extensions/gsd/tool-presentation-plan.js +15 -34
  103. package/dist/resources/extensions/gsd/tool-surface-snapshot.js +17 -0
  104. package/dist/resources/extensions/gsd/tools/plan-milestone.js +15 -143
  105. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +39 -0
  106. package/dist/resources/extensions/gsd/tools/validate-milestone.js +15 -78
  107. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
  108. package/dist/resources/extensions/gsd/uat-policy.js +16 -10
  109. package/dist/resources/extensions/gsd/uat-run.js +9 -14
  110. package/dist/resources/extensions/gsd/unit-context-composer.js +40 -20
  111. package/dist/resources/extensions/gsd/unit-runtime.js +3 -2
  112. package/dist/resources/extensions/gsd/unit-tool-contracts.js +2 -1
  113. package/dist/resources/extensions/gsd/user-input-boundary.js +65 -4
  114. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
  115. package/dist/resources/extensions/gsd/web-app-uat.js +80 -0
  116. package/dist/resources/extensions/gsd/workflow-mcp.js +15 -102
  117. package/dist/resources/extensions/gsd/workflow-reconcile.js +4 -3
  118. package/dist/resources/extensions/gsd/workflow-tool-surface.js +46 -0
  119. package/dist/resources/extensions/gsd/workspace-git-guard.js +2 -0
  120. package/dist/resources/extensions/gsd/worktree-state-projection.js +33 -4
  121. package/dist/resources/extensions/gsd/worktree-telemetry.js +12 -0
  122. package/dist/resources/extensions/shared/interview-ui.js +2 -2
  123. package/dist/resources/shared/claude-runtime-floor.js +182 -0
  124. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  125. package/dist/update-cmd.js +20 -0
  126. package/dist/web/standalone/.next/BUILD_ID +1 -1
  127. package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
  128. package/dist/web/standalone/.next/build-manifest.json +3 -3
  129. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  130. package/dist/web/standalone/.next/react-loadable-manifest.json +8 -8
  131. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  145. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  146. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  147. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  174. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  177. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  178. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  179. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  182. package/dist/web/standalone/.next/server/app/index.html +1 -1
  183. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  184. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  185. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  186. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  187. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  188. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  189. package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
  190. package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
  191. package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
  192. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  193. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  196. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  197. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  198. package/dist/web/standalone/.next/static/chunks/2659.b7b129ee6a769448.js +1 -0
  199. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +1 -0
  200. package/dist/web/standalone/.next/static/chunks/{3616.4113d484a994e411.js → 3616.3c60753b8ffcbd2e.js} +1 -1
  201. package/dist/web/standalone/.next/static/chunks/4283.e4873b058df143a1.js +2 -0
  202. package/dist/web/standalone/.next/static/chunks/5826.a46ecdd1cfe8dabc.js +1 -0
  203. package/dist/web/standalone/.next/static/chunks/796.cf859a427a2cb2ac.js +10 -0
  204. package/dist/web/standalone/.next/static/chunks/8785.2e5a118797fb2dd2.js +1 -0
  205. package/dist/web/standalone/.next/static/chunks/{webpack-dda80a1ef5587410.js → webpack-fbea77b5f9953368.js} +1 -1
  206. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  207. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  208. package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
  209. package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
  210. package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
  211. package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
  212. package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
  213. package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
  214. package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
  215. package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
  216. package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
  217. package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
  218. package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
  219. package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
  220. package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
  221. package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
  222. package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
  223. package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
  224. package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
  225. package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
  226. package/dist/web/standalone/node_modules/postcss/package.json +48 -48
  227. package/dist/web-mode.d.ts +2 -0
  228. package/dist/web-mode.js +20 -8
  229. package/package.json +17 -11
  230. package/packages/cloud-mcp-gateway/package.json +2 -2
  231. package/packages/contracts/package.json +1 -1
  232. package/packages/daemon/package.json +4 -4
  233. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +2 -0
  234. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
  235. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +14 -0
  236. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
  237. package/packages/gsd-agent-core/package.json +5 -5
  238. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  239. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -0
  240. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  241. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
  242. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  243. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  244. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +106 -40
  245. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  246. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.d.ts.map +1 -1
  247. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.js +6 -0
  248. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.js.map +1 -1
  249. package/packages/gsd-agent-modes/package.json +7 -7
  250. package/packages/mcp-server/dist/server.d.ts +10 -0
  251. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  252. package/packages/mcp-server/dist/server.js +8 -0
  253. package/packages/mcp-server/dist/server.js.map +1 -1
  254. package/packages/mcp-server/dist/workflow-tools.d.ts +41 -0
  255. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  256. package/packages/mcp-server/dist/workflow-tools.js +2 -1
  257. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  258. package/packages/mcp-server/package.json +3 -3
  259. package/packages/native/package.json +1 -1
  260. package/packages/pi-agent-core/package.json +1 -1
  261. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  262. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  263. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  264. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  265. package/packages/pi-ai/dist/models.generated.d.ts +8 -127
  266. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  267. package/packages/pi-ai/dist/models.generated.js +47 -166
  268. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  269. package/packages/pi-ai/package.json +1 -1
  270. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
  272. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  273. package/packages/pi-coding-agent/package.json +7 -7
  274. package/packages/pi-tui/dist/components/input.js +1 -1
  275. package/packages/pi-tui/dist/components/input.js.map +1 -1
  276. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  277. package/packages/pi-tui/dist/keys.js +39 -30
  278. package/packages/pi-tui/dist/keys.js.map +1 -1
  279. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  280. package/packages/pi-tui/dist/stdin-buffer.js +22 -0
  281. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  282. package/packages/pi-tui/package.json +2 -2
  283. package/packages/rpc-client/package.json +2 -2
  284. package/pkg/package.json +1 -1
  285. package/scripts/install/deps.js +10 -0
  286. package/scripts/link-workspace-packages.cjs +7 -40
  287. package/src/resources/extensions/ask-user-questions.ts +87 -24
  288. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +126 -289
  289. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +242 -2
  290. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +287 -0
  291. package/src/resources/extensions/github-sync/templates.ts +3 -3
  292. package/src/resources/extensions/github-sync/tests/templates.test.ts +2 -2
  293. package/src/resources/extensions/gsd/artifact-projection.ts +31 -0
  294. package/src/resources/extensions/gsd/auto/contracts.ts +40 -121
  295. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  296. package/src/resources/extensions/gsd/auto/loop.ts +83 -61
  297. package/src/resources/extensions/gsd/auto/orchestrator.ts +913 -64
  298. package/src/resources/extensions/gsd/auto/phases.ts +35 -3
  299. package/src/resources/extensions/gsd/auto/run-unit.ts +2 -1
  300. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  301. package/src/resources/extensions/gsd/auto-dashboard.ts +18 -4
  302. package/src/resources/extensions/gsd/auto-dispatch.ts +20 -7
  303. package/src/resources/extensions/gsd/auto-model-selection.ts +8 -0
  304. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -3
  305. package/src/resources/extensions/gsd/auto-prompts.ts +220 -9
  306. package/src/resources/extensions/gsd/auto-recovery.ts +50 -50
  307. package/src/resources/extensions/gsd/auto-runtime-state.ts +30 -0
  308. package/src/resources/extensions/gsd/auto-start.ts +17 -20
  309. package/src/resources/extensions/gsd/auto-timers.ts +16 -2
  310. package/src/resources/extensions/gsd/auto-tool-tracking.ts +40 -0
  311. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +42 -30
  312. package/src/resources/extensions/gsd/auto-verification.ts +7 -8
  313. package/src/resources/extensions/gsd/auto-worktree.ts +57 -42
  314. package/src/resources/extensions/gsd/auto.ts +96 -508
  315. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +29 -37
  316. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -37
  317. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  318. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +120 -151
  319. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +107 -3
  320. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +27 -5
  321. package/src/resources/extensions/gsd/codebase-generator.ts +9 -5
  322. package/src/resources/extensions/gsd/commands/handlers/auto.ts +3 -0
  323. package/src/resources/extensions/gsd/commands-handlers.ts +18 -0
  324. package/src/resources/extensions/gsd/commands-inspect.ts +7 -8
  325. package/src/resources/extensions/gsd/commands-maintenance.ts +74 -40
  326. package/src/resources/extensions/gsd/commands-ship.ts +2 -2
  327. package/src/resources/extensions/gsd/commands-verdict.ts +19 -2
  328. package/src/resources/extensions/gsd/db-workspace.ts +170 -0
  329. package/src/resources/extensions/gsd/debug-logger.ts +11 -0
  330. package/src/resources/extensions/gsd/delegation-policy.ts +3 -11
  331. package/src/resources/extensions/gsd/discussion-handoff.ts +276 -0
  332. package/src/resources/extensions/gsd/docs/preferences-reference.md +9 -0
  333. package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
  334. package/src/resources/extensions/gsd/doctor.ts +15 -5
  335. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  336. package/src/resources/extensions/gsd/git-conflict-state.ts +17 -1
  337. package/src/resources/extensions/gsd/gsd-db.ts +12 -0
  338. package/src/resources/extensions/gsd/guided-flow.ts +49 -560
  339. package/src/resources/extensions/gsd/guided-unit-completion.ts +275 -0
  340. package/src/resources/extensions/gsd/markdown-renderer.ts +40 -20
  341. package/src/resources/extensions/gsd/mcp-filter.ts +9 -1
  342. package/src/resources/extensions/gsd/mcp-tool-name.ts +35 -0
  343. package/src/resources/extensions/gsd/md-importer.ts +3 -3
  344. package/src/resources/extensions/gsd/migrate/safety.ts +2 -2
  345. package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
  346. package/src/resources/extensions/gsd/milestone-closeout-proof.ts +131 -0
  347. package/src/resources/extensions/gsd/milestone-closeout.ts +12 -4
  348. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +47 -0
  349. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +224 -0
  350. package/src/resources/extensions/gsd/milestone-readiness.ts +125 -0
  351. package/src/resources/extensions/gsd/milestone-settlement.ts +81 -0
  352. package/src/resources/extensions/gsd/milestone-validation-evidence.ts +95 -0
  353. package/src/resources/extensions/gsd/milestone-validation-verdict.ts +80 -0
  354. package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
  355. package/src/resources/extensions/gsd/parallel-eligibility.ts +4 -5
  356. package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -2
  357. package/src/resources/extensions/gsd/preferences-diagnostics.ts +98 -0
  358. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  359. package/src/resources/extensions/gsd/preferences.ts +173 -28
  360. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -0
  361. package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
  362. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  363. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
  364. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
  365. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
  366. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
  367. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -0
  368. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  369. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -0
  370. package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  371. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  372. package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
  373. package/src/resources/extensions/gsd/provider-payload-policy.ts +140 -0
  374. package/src/resources/extensions/gsd/pull-request-process.ts +41 -0
  375. package/src/resources/extensions/gsd/quality-gate-closure.ts +140 -0
  376. package/src/resources/extensions/gsd/question-transport.ts +138 -0
  377. package/src/resources/extensions/gsd/roadmap-slices.ts +8 -2
  378. package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
  379. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +6 -2
  380. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
  381. package/src/resources/extensions/gsd/state.ts +15 -5
  382. package/src/resources/extensions/gsd/templates/plan.md +7 -0
  383. package/src/resources/extensions/gsd/templates/project.md +1 -0
  384. package/src/resources/extensions/gsd/templates/roadmap.md +1 -1
  385. package/src/resources/extensions/gsd/templates/uat.md +5 -1
  386. package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
  387. package/src/resources/extensions/gsd/tests/ask-user-questions-render.test.ts +92 -0
  388. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +29 -1
  389. package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
  390. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +321 -5
  391. package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +23 -0
  392. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +18 -0
  393. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +709 -845
  394. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
  395. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +34 -0
  396. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +20 -0
  397. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  398. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +11 -0
  399. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +38 -1
  400. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
  401. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +34 -3
  402. package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +88 -0
  403. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +18 -0
  404. package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
  405. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +1 -0
  406. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +1 -5
  407. package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +1 -5
  408. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +48 -1
  409. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
  410. package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
  411. package/src/resources/extensions/gsd/tests/mcp-tool-name.test.ts +34 -0
  412. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +58 -0
  413. package/src/resources/extensions/gsd/tests/milestone-closeout-proof.test.ts +99 -0
  414. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +25 -0
  415. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +46 -0
  416. package/src/resources/extensions/gsd/tests/milestone-readiness.test.ts +65 -0
  417. package/src/resources/extensions/gsd/tests/milestone-validation-evidence.test.ts +41 -0
  418. package/src/resources/extensions/gsd/tests/milestone-validation-verdict.test.ts +55 -0
  419. package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
  420. package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
  421. package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
  422. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +45 -0
  423. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +6 -2
  424. package/src/resources/extensions/gsd/tests/planning-crossval.test.ts +45 -0
  425. package/src/resources/extensions/gsd/tests/preferences-diagnostics.test.ts +67 -0
  426. package/src/resources/extensions/gsd/tests/preferences.test.ts +183 -0
  427. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +75 -2
  428. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
  429. package/src/resources/extensions/gsd/tests/provider-payload-policy.test.ts +165 -0
  430. package/src/resources/extensions/gsd/tests/pull-request-process.test.ts +47 -0
  431. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +94 -0
  432. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
  433. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +40 -0
  434. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +25 -1
  435. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +80 -0
  436. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +101 -1
  437. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +27 -0
  438. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +21 -6
  439. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
  440. package/src/resources/extensions/gsd/tests/tool-availability-audit.test.ts +35 -0
  441. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +35 -42
  442. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +23 -0
  443. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +47 -0
  444. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +147 -0
  445. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +39 -0
  446. package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +150 -0
  447. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +126 -9
  448. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +15 -0
  449. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +21 -0
  450. package/src/resources/extensions/gsd/tests/worktree-projection-writers.test.ts +1 -1
  451. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
  452. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +22 -0
  453. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
  454. package/src/resources/extensions/gsd/tests/write-gate.test.ts +79 -0
  455. package/src/resources/extensions/gsd/tool-contract.ts +86 -8
  456. package/src/resources/extensions/gsd/tool-presentation-plan.ts +16 -33
  457. package/src/resources/extensions/gsd/tool-surface-snapshot.ts +47 -0
  458. package/src/resources/extensions/gsd/tools/plan-milestone.ts +19 -160
  459. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +43 -0
  460. package/src/resources/extensions/gsd/tools/validate-milestone.ts +25 -84
  461. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
  462. package/src/resources/extensions/gsd/uat-policy.ts +19 -10
  463. package/src/resources/extensions/gsd/uat-run.ts +10 -14
  464. package/src/resources/extensions/gsd/unit-context-composer.ts +85 -20
  465. package/src/resources/extensions/gsd/unit-runtime.ts +3 -2
  466. package/src/resources/extensions/gsd/unit-tool-contracts.ts +2 -1
  467. package/src/resources/extensions/gsd/user-input-boundary.ts +55 -5
  468. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
  469. package/src/resources/extensions/gsd/web-app-uat.ts +101 -0
  470. package/src/resources/extensions/gsd/workflow-mcp.ts +22 -110
  471. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
  472. package/src/resources/extensions/gsd/workflow-tool-surface.ts +73 -0
  473. package/src/resources/extensions/gsd/workspace-git-guard.ts +1 -0
  474. package/src/resources/extensions/gsd/worktree-lifecycle.ts +7 -16
  475. package/src/resources/extensions/gsd/worktree-state-projection.ts +55 -7
  476. package/src/resources/extensions/gsd/worktree-telemetry.ts +16 -0
  477. package/src/resources/extensions/shared/interview-ui.ts +15 -2
  478. package/src/resources/shared/claude-runtime-floor.ts +248 -0
  479. package/dist/web/standalone/.next/server/chunks/678.js +0 -2
  480. package/dist/web/standalone/.next/static/chunks/2659.feb6499ca863ebfc.js +0 -1
  481. package/dist/web/standalone/.next/static/chunks/2772.151789db0edea835.js +0 -1
  482. package/dist/web/standalone/.next/static/chunks/4283.10a065467b5340d8.js +0 -2
  483. package/dist/web/standalone/.next/static/chunks/5826.960dc4634cc9b0d3.js +0 -1
  484. package/dist/web/standalone/.next/static/chunks/796.46f811c0fac23aab.js +0 -10
  485. package/dist/web/standalone/.next/static/chunks/8785.d32f7a61f55c1600.js +0 -1
  486. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
  487. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
  488. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
  489. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
  490. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
  491. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
  492. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
  493. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
  494. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
  495. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
  496. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
  497. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
  498. package/scripts/ensure-workspace-builds.cjs +0 -129
  499. /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → tkLHUSzPA2kMmWz4DmGwI}/_buildManifest.js +0 -0
  500. /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → tkLHUSzPA2kMmWz4DmGwI}/_ssgManifest.js +0 -0
@@ -1,8 +1,71 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: Auto Orchestration module implementation and ADR-015 invariant pipeline owner.
3
+ //
4
+ // Phase 2 of #442 collapsed the nine single-implementation adapter seams
5
+ // (DispatchAdapter, RecoveryAdapter, StateReconciliationAdapter,
6
+ // ToolContractAdapter, WorktreeAdapter, HealthAdapter, UokGateAdapter,
7
+ // RuntimePersistenceAdapter, NotificationAdapter) into this class. The
8
+ // orchestrator now constructs from the concrete extension context and calls
9
+ // the real collaborators (state-reconciliation, doctor-proactive,
10
+ // auto-dispatch, recovery-classification, tool-contract, worktree-safety,
11
+ // uok/gate-runner, journal, session-lock, ctx.ui.notify) directly.
3
12
 
4
- import type { AutoAdvanceResult, AutoOrchestrationModule, AutoOrchestratorDeps, AutoSessionContext, AutoStatus } from "./contracts.js";
13
+ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
14
+
15
+ import type { AutoAdvanceResult, AutoOrchestrationModule, AutoSessionContext, AutoStatus, AutoTerminalOutcome } from "./contracts.js";
16
+ import type { AutoSession, PendingOrchestrationDispatch } from "./session.js";
5
17
  import type { GSDState } from "../types.js";
18
+ import type { MinimalModelRegistry } from "../context-budget.js";
19
+
20
+ type BlockedAdvanceResult = Extract<AutoAdvanceResult, { kind: "blocked" }>;
21
+
22
+ import { debugCount, debugTime } from "../debug-logger.js";
23
+ import { reconcileBeforeDispatch } from "../state-reconciliation.js";
24
+ import { resolveDispatch } from "../auto-dispatch.js";
25
+ import { classifyFailure } from "../recovery-classification.js";
26
+ import { verifyExpectedArtifact, refreshRecoveryDbForArtifact } from "../auto-recovery.js";
27
+ import { invalidateAllCaches } from "../cache.js";
28
+ import { compileUnitToolContract } from "../tool-contract.js";
29
+ import { createWorktreeSafetyModule } from "../worktree-safety.js";
30
+ import { repairAutoWorktreeSafetyFailure } from "../auto-worktree-repair.js";
31
+ import { resolveManifest } from "../unit-context-manifest.js";
32
+ import {
33
+ preDispatchHealthGate,
34
+ recordHealthSnapshot,
35
+ } from "../doctor-proactive.js";
36
+ import { checkResourcesStale, autoWorktreeBranch, mergeMilestoneToMain } from "../auto-worktree.js";
37
+ import { getSessionLockStatus } from "../session-lock.js";
38
+ import { resolveUokFlags } from "../uok/flags.js";
39
+ import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
40
+ import { loadEffectiveGSDPreferences, getIsolationMode } from "../preferences.js";
41
+ import {
42
+ detectWorktreeName,
43
+ getMainBranch,
44
+ resolveProjectRoot,
45
+ resolveWorktreeProjectRoot,
46
+ } from "../worktree.js";
47
+ import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
48
+ import { GitServiceImpl } from "../git-service.js";
49
+ import { WorktreeStateProjection } from "../worktree-state-projection.js";
50
+ import { WorktreeLifecycle } from "../worktree-lifecycle.js";
51
+ import { createMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
52
+ import { createWorkspace, scopeMilestone } from "../workspace.js";
53
+ import { supportsStructuredQuestions } from "../workflow-mcp.js";
54
+ import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
55
+ import { deriveState } from "../state.js";
56
+ import { parseUnitId } from "../unit-id.js";
57
+ import { isClosedStatus } from "../status-guards.js";
58
+ import {
59
+ isDbAvailable,
60
+ getSlice,
61
+ getTask,
62
+ } from "../gsd-db.js";
63
+ import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
64
+ import { getErrorMessage } from "../error-utils.js";
65
+ import { logWarning } from "../workflow-logger.js";
66
+ import { existsSync, readFileSync } from "node:fs";
67
+ import { join } from "node:path";
68
+ import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
6
69
 
7
70
  function now(): number {
8
71
  return Date.now();
@@ -18,11 +81,238 @@ function now(): number {
18
81
  */
19
82
  export const STUCK_WINDOW_SIZE = 6;
20
83
 
21
- function noRemainingUnitsReason(stateSnapshot: GSDState): string {
84
+ function noRemainingUnitsOutcome(stateSnapshot: GSDState): AutoTerminalOutcome {
22
85
  if (stateSnapshot.phase === "complete") {
23
- return "all milestones complete";
86
+ return {
87
+ code: "all-complete",
88
+ displayReason: "All milestones complete",
89
+ allMilestonesComplete: true,
90
+ };
91
+ }
92
+ return {
93
+ code: "no-remaining-units",
94
+ displayReason: "No remaining units",
95
+ allMilestonesComplete: false,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Concrete construction context for the Auto Orchestrator.
101
+ *
102
+ * Phase 2 of #442 replaced the nine adapter interfaces with this bundle of the
103
+ * real values the wiring factory used to close over: the extension context and
104
+ * API, the dispatch/runtime base paths, and the shared {@link AutoSession}
105
+ * singleton.
106
+ */
107
+ export interface OrchestratorContext {
108
+ ctx: ExtensionContext;
109
+ pi: ExtensionAPI;
110
+ dispatchBasePath: string;
111
+ runtimeBasePath: string;
112
+ session: AutoSession;
113
+ }
114
+
115
+ /** Result type of a single dispatch decision. */
116
+ export type DispatchDecision =
117
+ | { kind: "blocked"; reason: string; action: "pause" | "stop" }
118
+ | { kind: "skipped"; reason: string }
119
+ | { unitType: string; unitId: string; reason: string; preconditions: string[] }
120
+ | null;
121
+
122
+ /** Inputs to a dispatch decision. Caller-supplied fields override ctx-derived ones. */
123
+ export interface DispatchDecisionInput {
124
+ stateSnapshot: GSDState;
125
+ /** Optional live session context, forwarded to dispatch rules that need session-derived state. */
126
+ session?: AutoSession;
127
+ /** Mirrors `DispatchContext.structuredQuestionsAvailable` — "true"/"false" string per the dispatch contract. */
128
+ structuredQuestionsAvailable?: "true" | "false";
129
+ /** Session model context window in tokens, forwarded to the budget engine. */
130
+ sessionContextWindow?: number;
131
+ /** Session model provider, used for provider-specific effective context windows. */
132
+ sessionProvider?: string;
133
+ /** Model registry for executor-model lookups inside the budget engine. */
134
+ modelRegistry?: MinimalModelRegistry;
135
+ }
136
+
137
+ function getAlreadyClosedDispatchReason(unitType: string, unitId: string): string | null {
138
+ if (!isDbAvailable()) return null;
139
+ refreshWorkflowDatabaseFromDisk();
140
+ const { milestone, slice, task } = parseUnitId(unitId);
141
+ if (unitType === "execute-task" && milestone && slice && task) {
142
+ const row = getTask(milestone, slice, task);
143
+ return row && isClosedStatus(row.status)
144
+ ? `execute-task ${unitId} is already ${row.status}`
145
+ : null;
146
+ }
147
+ if (unitType === "complete-slice" && milestone && slice) {
148
+ const row = getSlice(milestone, slice);
149
+ return row && isClosedStatus(row.status)
150
+ ? `complete-slice ${unitId} is already ${row.status}`
151
+ : null;
152
+ }
153
+ return null;
154
+ }
155
+
156
+ function shouldAdoptActiveMilestone(
157
+ state: GSDState,
158
+ activeSession: AutoSession | undefined,
159
+ activeDispatchBasePath: string,
160
+ ): boolean {
161
+ const activeMilestoneId = state.activeMilestone?.id;
162
+ const currentMilestoneId = activeSession?.currentMilestoneId;
163
+ if (!activeSession || !activeMilestoneId || !currentMilestoneId || activeMilestoneId === currentMilestoneId) {
164
+ return false;
24
165
  }
25
- return "no remaining units";
166
+
167
+ const scopedWorktreeMilestone =
168
+ (activeSession.basePath ? detectWorktreeName(activeSession.basePath) : null) ??
169
+ detectWorktreeName(activeDispatchBasePath);
170
+ if (scopedWorktreeMilestone && scopedWorktreeMilestone !== activeMilestoneId) {
171
+ return false;
172
+ }
173
+
174
+ const currentMilestone = state.registry.find((milestone) => milestone.id === currentMilestoneId);
175
+ return !!currentMilestone && isClosedStatus(currentMilestone.status);
176
+ }
177
+
178
+ /**
179
+ * Pure dispatch-decision function — formerly `createWiredDispatchAdapter`'s
180
+ * `decideNextUnit`. Folded out of the closure so the orchestrator can call it
181
+ * directly and tests can drive the exact dispatch decision logic against real
182
+ * fixtures without re-introducing an adapter seam.
183
+ *
184
+ * Derives session-derived dispatch inputs the same way phases.ts:runDispatch
185
+ * does (#5789): prefers caller-supplied values when present so test harnesses
186
+ * and alternative wirings can inject deterministic snapshots; otherwise pulls
187
+ * from the captured pi/ctx references.
188
+ */
189
+ export async function decideOrchestratorDispatch(
190
+ ctx: ExtensionContext,
191
+ pi: ExtensionAPI,
192
+ dispatchBasePath: string,
193
+ session: AutoSession | undefined,
194
+ input: DispatchDecisionInput,
195
+ ): Promise<DispatchDecision> {
196
+ const state = input.stateSnapshot;
197
+ const active = state.activeMilestone;
198
+ if (!active) return null;
199
+
200
+ const activeSession = input.session ?? session;
201
+ const activeDispatchBasePath = activeSession?.basePath || dispatchBasePath;
202
+ if (activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
203
+ activeSession.currentMilestoneId = active.id;
204
+ }
205
+ const prefs = loadEffectiveGSDPreferences(activeDispatchBasePath)?.preferences;
206
+
207
+ // Derive session-derived dispatch inputs the same way phases.ts:runDispatch does
208
+ // (#5789). Prefer caller-supplied values when present so test harnesses and
209
+ // alternative wirings can inject deterministic snapshots; otherwise pull from
210
+ // the captured pi/ctx references.
211
+ const sessionProvider = input.sessionProvider ?? ctx.model?.provider;
212
+ const sessionContextWindow = input.sessionContextWindow ?? ctx.model?.contextWindow;
213
+ const modelRegistry = input.modelRegistry ?? (ctx.modelRegistry as MinimalModelRegistry | undefined);
214
+ const authMode =
215
+ sessionProvider && typeof ctx.modelRegistry?.getProviderAuthMode === "function"
216
+ ? ctx.modelRegistry.getProviderAuthMode(sessionProvider)
217
+ : undefined;
218
+ // Use baseline snapshot — same reason as phases.ts:runDispatch: the live
219
+ // active set may be narrowed by the prior unit before selectAndApplyModel
220
+ // restores it, causing false transport-preflight failures (#477 follow-up).
221
+ const activeTools = getToolBaselineSnapshot(pi);
222
+ const registeredTools = getRegisteredToolSnapshot(pi);
223
+ // Mirrors runDispatch: deep-planning keeps approval gates in plain chat
224
+ // because structured questions can be cancelled outside the chat turn on
225
+ // some transports.
226
+ const structuredQuestionsAvailable =
227
+ input.structuredQuestionsAvailable ??
228
+ (prefs?.planning_depth === "deep"
229
+ ? "false"
230
+ : supportsStructuredQuestions(activeTools, {
231
+ authMode,
232
+ baseUrl: ctx.model?.baseUrl,
233
+ })
234
+ ? "true"
235
+ : "false");
236
+
237
+ const pendingRetry = session?.pendingVerificationRetryDispatch;
238
+ if (session && pendingRetry) {
239
+ session.pendingVerificationRetryDispatch = null;
240
+ const alreadyClosedReason = getAlreadyClosedDispatchReason(
241
+ pendingRetry.unitType,
242
+ pendingRetry.unitId,
243
+ );
244
+ if (alreadyClosedReason) {
245
+ session.pendingOrchestrationDispatch = null;
246
+ session.pendingVerificationRetry = null;
247
+ return { kind: "skipped", reason: alreadyClosedReason };
248
+ }
249
+ session.pendingOrchestrationDispatch = pendingRetry;
250
+ return {
251
+ unitType: pendingRetry.unitType,
252
+ unitId: pendingRetry.unitId,
253
+ reason: "verification-retry",
254
+ preconditions: [],
255
+ };
256
+ }
257
+
258
+ const action = await resolveDispatch({
259
+ basePath: activeDispatchBasePath,
260
+ mid: active.id,
261
+ midTitle: active.title,
262
+ state,
263
+ prefs,
264
+ session: activeSession,
265
+ structuredQuestionsAvailable,
266
+ sessionContextWindow,
267
+ sessionProvider,
268
+ modelRegistry,
269
+ activeTools,
270
+ registeredTools,
271
+ sessionAuthMode: authMode,
272
+ sessionBaseUrl: ctx.model?.baseUrl,
273
+ });
274
+
275
+ if (action.action === "stop") {
276
+ if (session) session.pendingOrchestrationDispatch = null;
277
+ return {
278
+ kind: "blocked",
279
+ reason: action.reason,
280
+ action: action.level === "warning" ? "pause" : "stop",
281
+ };
282
+ }
283
+ if (action.action !== "dispatch") {
284
+ if (session) session.pendingOrchestrationDispatch = null;
285
+ return {
286
+ kind: "skipped",
287
+ reason: action.matchedRule ?? "dispatch-skip",
288
+ };
289
+ }
290
+ const alreadyClosedReason = getAlreadyClosedDispatchReason(action.unitType, action.unitId);
291
+ if (alreadyClosedReason) {
292
+ if (session) {
293
+ session.pendingOrchestrationDispatch = null;
294
+ session.pendingVerificationRetry = null;
295
+ }
296
+ return { kind: "skipped", reason: alreadyClosedReason };
297
+ }
298
+ if (session) {
299
+ const pending: PendingOrchestrationDispatch = {
300
+ unitType: action.unitType,
301
+ unitId: action.unitId,
302
+ prompt: action.prompt,
303
+ pauseAfterUatDispatch: action.pauseAfterDispatch ?? false,
304
+ state,
305
+ mid: active.id,
306
+ midTitle: active.title,
307
+ };
308
+ session.pendingOrchestrationDispatch = pending;
309
+ }
310
+ return {
311
+ unitType: action.unitType,
312
+ unitId: action.unitId,
313
+ reason: action.matchedRule ?? "dispatch",
314
+ preconditions: [],
315
+ };
26
316
  }
27
317
 
28
318
  export class AutoOrchestrator implements AutoOrchestrationModule {
@@ -30,33 +320,489 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
30
320
  phase: "idle",
31
321
  transitionCount: 0,
32
322
  };
33
- private readonly deps: AutoOrchestratorDeps;
323
+ private readonly ctx: ExtensionContext;
324
+ private readonly pi: ExtensionAPI;
325
+ private readonly dispatchBasePath: string;
326
+ private readonly runtimeBasePath: string;
327
+ private readonly s: AutoSession;
328
+ private readonly flowId: string;
329
+ private seq = 0;
34
330
  private lastAdvanceKey: string | null = null;
35
331
  private lastFinalizedUnitKey: string | null = null;
36
332
  private dispatchKeyWindow: string[] = [];
333
+ // #442: the unit key we last attempted graduated stuck-recovery for. Bounds
334
+ // recovery to one attempt per stuck episode per run (reset on start/resume/
335
+ // stop), mirroring the legacy Level-1-then-Level-2 escalation in phases.ts.
336
+ private lastStuckRecoveryKey: string | null = null;
337
+
338
+ public constructor(context: OrchestratorContext) {
339
+ this.ctx = context.ctx;
340
+ this.pi = context.pi;
341
+ this.dispatchBasePath = context.dispatchBasePath;
342
+ this.runtimeBasePath = context.runtimeBasePath;
343
+ this.s = context.session;
344
+ this.flowId = `auto-orchestrator-${Date.now()}`;
345
+ }
37
346
 
38
- public constructor(deps: AutoOrchestratorDeps) {
39
- this.deps = deps;
347
+ // ── Live base-path resolution (was the wiring factory's getLiveDispatchBasePath) ──
348
+
349
+ private getLiveDispatchBasePath(): string {
350
+ return resolveLiveOrchestratorBasePath({
351
+ capturedBasePath: this.dispatchBasePath,
352
+ runtimeBasePath: this.runtimeBasePath,
353
+ sessionBasePath: this.s.basePath,
354
+ originalBasePath: this.s.originalBasePath,
355
+ });
356
+ }
357
+
358
+ // ── RuntimePersistenceAdapter (folded) ───────────────────────────────────
359
+
360
+ private ensureLockOwnership(): void {
361
+ const status = getSessionLockStatus(this.runtimeBasePath);
362
+ if (!status.valid || status.failureReason === "pid-mismatch") {
363
+ throw new Error("session lock held by another process");
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Map an orchestrator lifecycle event name to its journal eventType and emit
369
+ * it. The name→eventType ternary is preserved byte-for-byte from the legacy
370
+ * wired RuntimePersistenceAdapter.journalTransition.
371
+ */
372
+ private journalTransition(event: {
373
+ name: string;
374
+ reason?: string;
375
+ unitType?: string;
376
+ unitId?: string;
377
+ }): void {
378
+ const eventType = event.name === "start"
379
+ ? "orchestrator-iteration-start"
380
+ : event.name === "resume"
381
+ ? "orchestrator-iteration-start"
382
+ : event.name === "advance"
383
+ ? "orchestrator-dispatch-match"
384
+ : event.name === "advance-blocked"
385
+ ? "orchestrator-guard-block"
386
+ : event.name === "advance-stopped"
387
+ ? "orchestrator-dispatch-stop"
388
+ : event.name === "advance-error"
389
+ ? "orchestrator-iteration-end"
390
+ : event.name === "advance-paused" || event.name === "advance-retry"
391
+ ? "orchestrator-guard-block"
392
+ : event.name === "stop"
393
+ ? "orchestrator-terminal"
394
+ : "orchestrator-iteration-end";
395
+
396
+ _emitJournalEvent(this.runtimeBasePath, {
397
+ ts: new Date().toISOString(),
398
+ flowId: this.flowId,
399
+ seq: ++this.seq,
400
+ eventType,
401
+ data: {
402
+ source: "auto-orchestrator",
403
+ name: event.name,
404
+ reason: event.reason,
405
+ unitType: event.unitType,
406
+ unitId: event.unitId,
407
+ },
408
+ });
409
+ }
410
+
411
+ // ── NotificationAdapter (folded) ─────────────────────────────────────────
412
+
413
+ private notifyLifecycle(event: { name: string; detail?: string }): void {
414
+ if (event.name === "error") {
415
+ this.ctx.ui.notify(event.detail ?? "auto orchestration error", "error");
416
+ }
417
+ }
418
+
419
+ // ── HealthAdapter (folded) ───────────────────────────────────────────────
420
+
421
+ private checkResourcesStale(): string | null {
422
+ return checkResourcesStale(this.s.resourceVersionOnStart);
423
+ }
424
+
425
+ private async preAdvanceGate(): Promise<
426
+ | { kind: "pass"; fixesApplied?: readonly string[] }
427
+ | { kind: "fail"; reason: string; action?: "pause" | "stop" }
428
+ | { kind: "threw"; error: unknown }
429
+ > {
430
+ try {
431
+ const gate = await preDispatchHealthGate(this.getLiveDispatchBasePath());
432
+ if (gate.proceed) {
433
+ return {
434
+ kind: "pass",
435
+ fixesApplied: gate.fixesApplied,
436
+ };
437
+ }
438
+ return {
439
+ kind: "fail",
440
+ reason: gate.reason ?? "Pre-dispatch health check failed — run /gsd doctor for details.",
441
+ action: gate.severity ?? "pause",
442
+ };
443
+ } catch (error) {
444
+ return { kind: "threw", error };
445
+ }
446
+ }
447
+
448
+ private postAdvanceRecord(result: AutoAdvanceResult): void {
449
+ if (result.kind === "error") {
450
+ recordHealthSnapshot(1, 0, 0, [{
451
+ code: "orchestration-error",
452
+ message: result.reason ?? "orchestration error",
453
+ severity: "error",
454
+ unitId: "orchestration",
455
+ }], [], "orchestration");
456
+ } else if (result.kind === "blocked") {
457
+ recordHealthSnapshot(0, 1, 0, [{
458
+ code: "orchestration-blocked",
459
+ message: result.reason ?? "orchestration blocked",
460
+ severity: "warning",
461
+ unitId: "orchestration",
462
+ }], [], "orchestration");
463
+ }
464
+ }
465
+
466
+ // ── UokGateAdapter (folded) ──────────────────────────────────────────────
467
+
468
+ private async emitUokGate(input: {
469
+ gateId: string;
470
+ gateType: "policy" | "execution";
471
+ outcome: "pass" | "fail" | "manual-attention";
472
+ failureClass: "none" | "policy" | "manual-attention";
473
+ rationale: string;
474
+ findings?: string;
475
+ milestoneId?: string;
476
+ }): Promise<void> {
477
+ const activeBasePath = this.getLiveDispatchBasePath();
478
+ const prefs = loadEffectiveGSDPreferences(activeBasePath)?.preferences;
479
+ const uokFlags = resolveUokFlags(prefs);
480
+ if (!uokFlags.gates) return;
481
+ const milestoneId = input.milestoneId ?? this.s.currentMilestoneId ?? undefined;
482
+ try {
483
+ const { UokGateRunner } = await import("../uok/gate-runner.js");
484
+ const runner = new UokGateRunner();
485
+ runner.register({
486
+ id: input.gateId,
487
+ type: input.gateType,
488
+ execute: async () => ({
489
+ outcome: input.outcome,
490
+ failureClass: input.failureClass,
491
+ rationale: input.rationale,
492
+ findings: input.findings ?? "",
493
+ }),
494
+ });
495
+ await runner.run(input.gateId, {
496
+ basePath: activeBasePath,
497
+ traceId: `pre-dispatch:${this.flowId}`,
498
+ turnId: `orch-${this.seq}`,
499
+ milestoneId,
500
+ unitType: "pre-dispatch",
501
+ unitId: `orch-${this.seq}`,
502
+ });
503
+ } catch (err) {
504
+ logWarning("engine", `uok gate emit failed: ${getErrorMessage(err)}`, {
505
+ file: "orchestrator.ts",
506
+ gateId: input.gateId,
507
+ gateType: input.gateType,
508
+ ...(milestoneId ? { milestoneId } : {}),
509
+ });
510
+ }
511
+ }
512
+
513
+ // ── StateReconciliationAdapter (folded) ──────────────────────────────────
514
+
515
+ private async reconcileBeforeDispatch(): Promise<
516
+ { ok: true; reason: string; stateSnapshot?: GSDState }
517
+ | { ok: false; reason: string; stateSnapshot?: GSDState }
518
+ > {
519
+ const activeBasePath = this.getLiveDispatchBasePath();
520
+ const result = await reconcileBeforeDispatch(activeBasePath);
521
+ // Failure-path summaries written by gsd_summary_save create
522
+ // artifact-db-status-divergence blockers for tasks that are still
523
+ // pending (gsd_task_complete never ran). These tasks can still be
524
+ // dispatched and the drift self-heals once they complete successfully.
525
+ const hardBlockers = result.blockers.filter(
526
+ (b) =>
527
+ !b.includes("has SUMMARY artifact while DB status is") &&
528
+ !b.includes("has SUMMARY on disk while DB status is") &&
529
+ !b.includes("has task SUMMARY artifacts but no DB tasks"),
530
+ );
531
+ if (hardBlockers.length > 0) {
532
+ return {
533
+ ok: false,
534
+ reason: hardBlockers[0],
535
+ stateSnapshot: result.stateSnapshot,
536
+ };
537
+ }
538
+ const repairedKinds = result.repaired.map((d) => d.kind);
539
+ return {
540
+ ok: true,
541
+ reason:
542
+ repairedKinds.length > 0
543
+ ? `repaired: ${repairedKinds.join(", ")}`
544
+ : "clean",
545
+ stateSnapshot: result.stateSnapshot,
546
+ };
547
+ }
548
+
549
+ // ── DispatchAdapter (folded) ─────────────────────────────────────────────
550
+
551
+ private decideNextUnit(input: DispatchDecisionInput): Promise<DispatchDecision> {
552
+ return decideOrchestratorDispatch(this.ctx, this.pi, this.dispatchBasePath, this.s, input);
553
+ }
554
+
555
+ private evaluateNoRemainingUnitsSettlement(stateSnapshot: GSDState): BlockedAdvanceResult | null {
556
+ const settlement = evaluateAllCompleteSettlement({
557
+ milestoneId: this.s.currentMilestoneId ?? stateSnapshot.activeMilestone?.id,
558
+ statePhase: stateSnapshot.phase,
559
+ basePath: this.s.basePath || this.getLiveDispatchBasePath(),
560
+ originalBasePath: this.s.originalBasePath || this.runtimeBasePath,
561
+ milestoneMerged: this.s.milestoneMergedInPhases,
562
+ });
563
+ this.s.milestoneSettlement = settlement;
564
+ if (settlement.ok) return null;
565
+ return {
566
+ kind: "blocked",
567
+ reason: settlement.message,
568
+ action: settlement.action,
569
+ stateSnapshot,
570
+ terminalOutcome: {
571
+ code: "settlement-blocked",
572
+ displayReason: settlement.message,
573
+ nextAction: settlement.nextAction,
574
+ milestoneId: settlement.milestoneId,
575
+ allMilestonesComplete: false,
576
+ },
577
+ };
578
+ }
579
+
580
+ private clearPendingDispatch(): void {
581
+ this.s.pendingOrchestrationDispatch = null;
582
+ }
583
+
584
+ private findPriorSliceCompletionBlocker(unitType: string, unitId: string): string | null {
585
+ const guardBasePath = resolveWorktreeProjectRoot(
586
+ this.getLiveDispatchBasePath(),
587
+ this.s.originalBasePath,
588
+ );
589
+ let mainBranch = "main";
590
+ try {
591
+ mainBranch = getMainBranch(guardBasePath);
592
+ } catch (err) {
593
+ // Preserve legacy dispatch behavior: fall back to main when branch
594
+ // discovery fails, then let the guard make the progression decision.
595
+ logWarning(
596
+ "engine",
597
+ `branch discovery failed, falling back to main: ${getErrorMessage(err)}`,
598
+ { file: "orchestrator.ts" },
599
+ );
600
+ }
601
+ return getPriorSliceCompletionBlocker(guardBasePath, mainBranch, unitType, unitId);
602
+ }
603
+
604
+ // ── ToolContractAdapter (folded) ─────────────────────────────────────────
605
+
606
+ private compileUnitToolContract(unitType: string): { ok: true; reason: string } | { ok: false; reason: string } {
607
+ const result = compileUnitToolContract(unitType);
608
+ if (!result.ok) return { ok: false, reason: result.detail };
609
+ return { ok: true, reason: result.contract.validationRules.join(", ") };
610
+ }
611
+
612
+ // ── WorktreeAdapter (folded) ─────────────────────────────────────────────
613
+
614
+ private getEffectiveUnitIsolationMode(basePath: string): ReturnType<typeof getIsolationMode> {
615
+ const configuredMode = getIsolationMode(basePath);
616
+ return configuredMode === "worktree" && this.s.isolationDegraded ? "branch" : configuredMode;
617
+ }
618
+
619
+ private buildLifecycle(): WorktreeLifecycle {
620
+ return new WorktreeLifecycle(this.s, {
621
+ gitServiceFactory: (basePath: string) => {
622
+ const gitConfig = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
623
+ return new GitServiceImpl(basePath, gitConfig);
624
+ },
625
+ worktreeProjection: new WorktreeStateProjection(),
626
+ mergeMilestoneToMain: createMilestoneMergeTransaction(mergeMilestoneToMain),
627
+ });
628
+ }
629
+
630
+ private rebuildScope(rawPath: string, milestoneId: string | null): void {
631
+ if (!milestoneId) {
632
+ this.s.scope = null;
633
+ return;
634
+ }
635
+ try {
636
+ const workspace = createWorkspace(rawPath);
637
+ this.s.scope = scopeMilestone(workspace, milestoneId);
638
+ } catch {
639
+ // Non-fatal — scope is additive. Existing readers still use basePath.
640
+ this.s.scope = null;
641
+ }
642
+ }
643
+
644
+ private async prepareWorktreeForUnit(
645
+ unitType: string,
646
+ unitId: string,
647
+ ): Promise<{ ok: true; reason: string } | { ok: false; reason: string }> {
648
+ const isolationMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);
649
+ const manifest = resolveManifest(unitType);
650
+ if (!manifest) {
651
+ return {
652
+ ok: false,
653
+ reason: `No Unit manifest is registered for ${unitType}`,
654
+ };
655
+ }
656
+ if (isolationMode !== "worktree") {
657
+ return { ok: true, reason: "not-required" };
658
+ }
659
+ const writeScope =
660
+ manifest.tools.mode === "all" || manifest.tools.mode === "docs"
661
+ ? "source-writing"
662
+ : "planning-only";
663
+ const safety = createWorktreeSafetyModule();
664
+ const activeBasePath = this.getLiveDispatchBasePath();
665
+ const snapshot = await deriveState(activeBasePath);
666
+ const milestoneId = snapshot.activeMilestone?.id ?? null;
667
+ const expectedBranch = milestoneId ? autoWorktreeBranch(milestoneId) : null;
668
+ let result = safety.validateUnitRoot({
669
+ unitType,
670
+ unitId,
671
+ writeScope,
672
+ projectRoot: this.runtimeBasePath,
673
+ unitRoot: activeBasePath,
674
+ milestoneId,
675
+ isolationMode,
676
+ expectedBranch,
677
+ });
678
+ if (!result.ok) {
679
+ const repaired = await repairAutoWorktreeSafetyFailure({
680
+ safetyResult: result,
681
+ projectRoot: this.runtimeBasePath,
682
+ activeRoot: activeBasePath,
683
+ milestoneId,
684
+ enterMilestone: async (id) => {
685
+ this.buildLifecycle().adoptSessionRoot(this.runtimeBasePath, this.s.originalBasePath || this.runtimeBasePath);
686
+ const enterResult = this.buildLifecycle().enterMilestone(id, {
687
+ notify: this.ctx.ui.notify.bind(this.ctx.ui),
688
+ });
689
+ if (!enterResult.ok) return { ok: false, reason: enterResult.reason };
690
+ this.rebuildScope(this.s.basePath, this.s.currentMilestoneId);
691
+ return { ok: true };
692
+ },
693
+ revalidate: () => safety.validateUnitRoot({
694
+ unitType,
695
+ unitId,
696
+ writeScope,
697
+ projectRoot: this.runtimeBasePath,
698
+ unitRoot: this.getLiveDispatchBasePath(),
699
+ milestoneId,
700
+ isolationMode: this.getEffectiveUnitIsolationMode(this.runtimeBasePath),
701
+ expectedBranch,
702
+ }),
703
+ });
704
+ result = repaired.result;
705
+ if (result.ok) {
706
+ return { ok: true, reason: repaired.repaired ? `repaired-${result.kind}` : result.kind };
707
+ }
708
+ const repairDetail = repaired.repairReason
709
+ ? ` (repair skipped: ${repaired.repairReason})`
710
+ : "";
711
+ return { ok: false, reason: `${result.kind}: ${result.reason}${repairDetail}` };
712
+ }
713
+ return { ok: true, reason: result.kind };
714
+ }
715
+
716
+ // ── RecoveryAdapter (folded) ─────────────────────────────────────────────
717
+
718
+ private classifyAndRecover(input: {
719
+ error: unknown;
720
+ unitType?: string;
721
+ unitId?: string;
722
+ }): { action: "retry" | "escalate" | "stop"; reason: string } {
723
+ const recovery = classifyFailure(input);
724
+ return { action: recovery.action, reason: recovery.reason };
725
+ }
726
+
727
+ // ── Lifecycle verbs ──────────────────────────────────────────────────────
728
+
729
+ /**
730
+ * #442: graduated stuck recovery, ported from the legacy
731
+ * auto/phases.ts:runDispatch path that Phase 3 retires. The ring-buffer
732
+ * hard-stops (stuck-loop saturation and finalized-repeat) would otherwise
733
+ * KILL a unit that actually completed on disk but whose DB row is still
734
+ * stale. Before hard-stopping, verify the expected artifact exists; if so,
735
+ * refresh the DB from it, invalidate caches and reset the dispatch ring so
736
+ * the next advance picks the correct next unit. Bounded to one attempt per
737
+ * stuck key per episode (reset on lifecycle + genuine finalize) to avoid an
738
+ * unbounded recover→re-saturate→recover loop — mirrors the legacy
739
+ * Level-1-recover-then-Level-2-hard-stop escalation.
740
+ *
741
+ * Returns true when recovery succeeded; the caller should re-loop (return a
742
+ * skipped result) instead of stopping.
743
+ */
744
+ private tryStuckArtifactRecovery(unitType: string, unitId: string): boolean {
745
+ const key = `${unitType}:${unitId}`;
746
+ if (this.lastStuckRecoveryKey === key) return false; // already tried this episode
747
+ const basePath = this.getLiveDispatchBasePath();
748
+ if (!verifyExpectedArtifact(unitType, unitId, basePath)) return false;
749
+ const refreshed = refreshRecoveryDbForArtifact(unitType, unitId, basePath);
750
+ // Fatal failures cannot be recovered — hard-stop. Non-fatal (e.g. plan-slice
751
+ // DB refresh hiccup) still fall through: invalidating caches and resetting
752
+ // the ring gives the next advance a clean slate to pick up the correct state,
753
+ // mirroring the legacy Level-1 "continue" escalation path.
754
+ if (!refreshed.ok && refreshed.fatal) return false;
755
+ this.lastStuckRecoveryKey = key;
756
+ invalidateAllCaches();
757
+ this.dispatchKeyWindow = [];
758
+ this.lastAdvanceKey = null;
759
+ this.lastFinalizedUnitKey = null;
760
+ return true;
761
+ }
762
+
763
+ private stuckRecovered(
764
+ decision: { unitType: string; unitId: string },
765
+ stateSnapshot: GSDState,
766
+ ): AutoAdvanceResult {
767
+ const recovered: AutoAdvanceResult = {
768
+ kind: "skipped",
769
+ reason: `stuck-recovery: ${decision.unitType} ${decision.unitId} artifact found on disk; DB refreshed`,
770
+ stateSnapshot,
771
+ };
772
+ this.status.phase = "running";
773
+ this.status.activeUnit = undefined;
774
+ this.bumpTransition();
775
+ this.journalTransition({
776
+ name: "advance-skipped",
777
+ reason: recovered.reason,
778
+ unitType: decision.unitType,
779
+ unitId: decision.unitId,
780
+ });
781
+ this.postAdvanceRecord(recovered);
782
+ return recovered;
40
783
  }
41
784
 
42
785
  public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
43
786
  this.lastAdvanceKey = null;
44
787
  this.lastFinalizedUnitKey = null;
45
788
  this.dispatchKeyWindow = [];
789
+ this.lastStuckRecoveryKey = null;
46
790
  this.status.phase = "running";
47
791
  this.bumpTransition();
48
- await this.deps.runtime.journalTransition({ name: "start" });
49
- await this.deps.notifications.notifyLifecycle({ name: "start" });
792
+ this.journalTransition({ name: "start" });
793
+ this.notifyLifecycle({ name: "start" });
50
794
  return { kind: "started" };
51
795
  }
52
796
 
53
797
  public async advance(): Promise<AutoAdvanceResult> {
798
+ debugCount("dispatches");
799
+ const stopAdvanceTimer = debugTime("orchestrator-advance");
54
800
  try {
55
- await this.deps.runtime.ensureLockOwnership();
801
+ this.ensureLockOwnership();
56
802
 
57
- const staleMsg = this.deps.health.checkResourcesStale();
803
+ const staleMsg = this.checkResourcesStale();
58
804
  if (staleMsg) {
59
- await this.deps.uokGate.emit({
805
+ await this.emitUokGate({
60
806
  gateId: "resource-version-guard",
61
807
  gateType: "policy",
62
808
  outcome: "fail",
@@ -65,11 +811,11 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
65
811
  findings: staleMsg,
66
812
  });
67
813
  const blocked: AutoAdvanceResult = { kind: "blocked", reason: staleMsg, action: "pause" };
68
- await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
69
- await this.deps.health.postAdvanceRecord(blocked);
814
+ this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
815
+ this.postAdvanceRecord(blocked);
70
816
  return blocked;
71
817
  }
72
- await this.deps.uokGate.emit({
818
+ await this.emitUokGate({
73
819
  gateId: "resource-version-guard",
74
820
  gateType: "policy",
75
821
  outcome: "pass",
@@ -77,9 +823,9 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
77
823
  rationale: "resource version guard passed",
78
824
  });
79
825
 
80
- const gate = await this.deps.health.preAdvanceGate();
826
+ const gate = await this.preAdvanceGate();
81
827
  if (gate.kind === "fail") {
82
- await this.deps.uokGate.emit({
828
+ await this.emitUokGate({
83
829
  gateId: "pre-dispatch-health-gate",
84
830
  gateType: "execution",
85
831
  outcome: "manual-attention",
@@ -92,12 +838,12 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
92
838
  reason: gate.reason,
93
839
  action: gate.action ?? "pause",
94
840
  };
95
- await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
96
- await this.deps.health.postAdvanceRecord(blocked);
841
+ this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
842
+ this.postAdvanceRecord(blocked);
97
843
  return blocked;
98
844
  }
99
845
  if (gate.kind === "threw") {
100
- await this.deps.uokGate.emit({
846
+ await this.emitUokGate({
101
847
  gateId: "pre-dispatch-health-gate",
102
848
  gateType: "execution",
103
849
  outcome: "manual-attention",
@@ -107,7 +853,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
107
853
  });
108
854
  // intentional fall-through: matches runPreDispatch behaviour
109
855
  } else {
110
- await this.deps.uokGate.emit({
856
+ await this.emitUokGate({
111
857
  gateId: "pre-dispatch-health-gate",
112
858
  gateType: "execution",
113
859
  outcome: "pass",
@@ -117,7 +863,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
117
863
  });
118
864
  }
119
865
 
120
- const reconciliation = await this.deps.stateReconciliation.reconcileBeforeDispatch();
866
+ const reconciliation = await this.reconcileBeforeDispatch();
121
867
  if (!reconciliation.ok || !reconciliation.stateSnapshot) {
122
868
  const blocked: AutoAdvanceResult = {
123
869
  kind: "blocked",
@@ -125,25 +871,38 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
125
871
  action: "pause",
126
872
  stateSnapshot: reconciliation.stateSnapshot,
127
873
  };
128
- await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
129
- await this.deps.health.postAdvanceRecord(blocked);
874
+ this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
875
+ this.postAdvanceRecord(blocked);
130
876
  return blocked;
131
877
  }
132
878
 
133
- const decision = await this.deps.dispatch.decideNextUnit({ stateSnapshot: reconciliation.stateSnapshot });
879
+ const decision = await this.decideNextUnit({ stateSnapshot: reconciliation.stateSnapshot });
134
880
  if (!decision) {
881
+ const settlementBlock = this.evaluateNoRemainingUnitsSettlement(reconciliation.stateSnapshot);
882
+ if (settlementBlock) {
883
+ this.status.phase = "paused";
884
+ this.status.activeUnit = undefined;
885
+ this.lastAdvanceKey = null;
886
+ this.dispatchKeyWindow = [];
887
+ this.bumpTransition();
888
+ this.journalTransition({ name: "advance-blocked", reason: settlementBlock.reason });
889
+ this.postAdvanceRecord(settlementBlock);
890
+ return settlementBlock;
891
+ }
892
+ const terminalOutcome = noRemainingUnitsOutcome(reconciliation.stateSnapshot);
135
893
  const stopped: AutoAdvanceResult = {
136
894
  kind: "stopped",
137
- reason: noRemainingUnitsReason(reconciliation.stateSnapshot),
895
+ reason: terminalOutcome.displayReason,
138
896
  stateSnapshot: reconciliation.stateSnapshot,
897
+ terminalOutcome,
139
898
  };
140
899
  this.status.phase = "stopped";
141
900
  this.status.activeUnit = undefined;
142
901
  this.lastAdvanceKey = null;
143
902
  this.dispatchKeyWindow = [];
144
903
  this.bumpTransition();
145
- await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
146
- await this.deps.health.postAdvanceRecord(stopped);
904
+ this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
905
+ this.postAdvanceRecord(stopped);
147
906
  return stopped;
148
907
  }
149
908
  if ("kind" in decision && decision.kind === "skipped") {
@@ -155,8 +914,8 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
155
914
  this.status.phase = "running";
156
915
  this.status.activeUnit = undefined;
157
916
  this.bumpTransition();
158
- await this.deps.runtime.journalTransition({ name: "advance-skipped", reason: skipped.reason });
159
- await this.deps.health.postAdvanceRecord(skipped);
917
+ this.journalTransition({ name: "advance-skipped", reason: skipped.reason });
918
+ this.postAdvanceRecord(skipped);
160
919
  return skipped;
161
920
  }
162
921
  if (!("unitType" in decision)) {
@@ -166,8 +925,27 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
166
925
  action: decision.action,
167
926
  stateSnapshot: reconciliation.stateSnapshot,
168
927
  };
169
- await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
170
- await this.deps.health.postAdvanceRecord(blocked);
928
+ this.journalTransition({ name: "advance-blocked", reason: blocked.reason });
929
+ this.postAdvanceRecord(blocked);
930
+ return blocked;
931
+ }
932
+
933
+ const priorSliceBlocker = this.findPriorSliceCompletionBlocker(decision.unitType, decision.unitId);
934
+ if (priorSliceBlocker) {
935
+ this.clearPendingDispatch();
936
+ const blocked: AutoAdvanceResult = {
937
+ kind: "blocked",
938
+ reason: priorSliceBlocker,
939
+ action: "stop",
940
+ stateSnapshot: reconciliation.stateSnapshot,
941
+ };
942
+ this.journalTransition({
943
+ name: "advance-blocked",
944
+ reason: blocked.reason,
945
+ unitType: decision.unitType,
946
+ unitId: decision.unitId,
947
+ });
948
+ this.postAdvanceRecord(blocked);
171
949
  return blocked;
172
950
  }
173
951
 
@@ -184,19 +962,27 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
184
962
 
185
963
  const matchingCount = this.dispatchKeyWindow.filter((k) => k === nextKey).length;
186
964
  if (this.lastFinalizedUnitKey === nextKey) {
965
+ // #442: the unit re-dispatched immediately after finalizing may have
966
+ // actually completed on disk with a stale DB. Verify + recover before
967
+ // hard-stopping (legacy graduated stuck-recovery parity).
968
+ if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
969
+ this.clearPendingDispatch();
970
+ return this.stuckRecovered(decision, reconciliation.stateSnapshot);
971
+ }
972
+ this.clearPendingDispatch();
187
973
  const blocked: AutoAdvanceResult = {
188
974
  kind: "blocked",
189
975
  reason: `state did not advance after finalized ${decision.unitType} ${decision.unitId}`,
190
976
  action: "stop",
191
977
  stateSnapshot: reconciliation.stateSnapshot,
192
978
  };
193
- await this.deps.runtime.journalTransition({
979
+ this.journalTransition({
194
980
  name: "advance-blocked",
195
981
  reason: blocked.reason,
196
982
  unitType: decision.unitType,
197
983
  unitId: decision.unitId,
198
984
  });
199
- await this.deps.health.postAdvanceRecord(blocked);
985
+ this.postAdvanceRecord(blocked);
200
986
  return blocked;
201
987
  }
202
988
 
@@ -207,14 +993,15 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
207
993
  // checks coexist: idempotency for the common immediate-repeat case,
208
994
  // stuck-loop for the saturated-window case.
209
995
  if (this.lastAdvanceKey === nextKey && matchingCount < STUCK_WINDOW_SIZE) {
996
+ this.clearPendingDispatch();
210
997
  const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active", action: "pause" };
211
- await this.deps.runtime.journalTransition({
998
+ this.journalTransition({
212
999
  name: "advance-blocked",
213
1000
  reason: blocked.reason,
214
1001
  unitType: decision.unitType,
215
1002
  unitId: decision.unitId,
216
1003
  });
217
- await this.deps.health.postAdvanceRecord(blocked);
1004
+ this.postAdvanceRecord(blocked);
218
1005
  return blocked;
219
1006
  }
220
1007
 
@@ -223,54 +1010,64 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
223
1010
  // picking the same unit across the whole window and must hard-stop with
224
1011
  // a diagnosable reason.
225
1012
  if (matchingCount >= STUCK_WINDOW_SIZE) {
1013
+ // #442: before declaring a stuck loop, verify the unit didn't actually
1014
+ // complete on disk (stale DB) and recover if so — legacy graduated
1015
+ // stuck-recovery parity. Otherwise hard-stop with a diagnosable reason.
1016
+ if (this.tryStuckArtifactRecovery(decision.unitType, decision.unitId)) {
1017
+ this.clearPendingDispatch();
1018
+ return this.stuckRecovered(decision, reconciliation.stateSnapshot);
1019
+ }
1020
+ this.clearPendingDispatch();
226
1021
  const blocked: AutoAdvanceResult = {
227
1022
  kind: "blocked",
228
1023
  reason: `stuck-loop: ${nextKey} picked ${matchingCount} times`,
229
1024
  action: "stop",
230
1025
  };
231
- await this.deps.runtime.journalTransition({
1026
+ this.journalTransition({
232
1027
  name: "advance-blocked",
233
1028
  reason: blocked.reason,
234
1029
  unitType: decision.unitType,
235
1030
  unitId: decision.unitId,
236
1031
  });
237
- await this.deps.health.postAdvanceRecord(blocked);
1032
+ this.postAdvanceRecord(blocked);
238
1033
  return blocked;
239
1034
  }
240
1035
 
241
- const contract = await this.deps.toolContract.compileUnitToolContract(decision.unitType, decision.unitId);
1036
+ const contract = this.compileUnitToolContract(decision.unitType);
242
1037
  if (!contract.ok) {
1038
+ this.clearPendingDispatch();
243
1039
  const blocked: AutoAdvanceResult = {
244
1040
  kind: "blocked",
245
1041
  reason: contract.reason,
246
1042
  action: "pause",
247
1043
  stateSnapshot: reconciliation.stateSnapshot,
248
1044
  };
249
- await this.deps.runtime.journalTransition({
1045
+ this.journalTransition({
250
1046
  name: "advance-blocked",
251
1047
  reason: blocked.reason,
252
1048
  unitType: decision.unitType,
253
1049
  unitId: decision.unitId,
254
1050
  });
255
- await this.deps.health.postAdvanceRecord(blocked);
1051
+ this.postAdvanceRecord(blocked);
256
1052
  return blocked;
257
1053
  }
258
1054
 
259
- const worktree = await this.deps.worktree.prepareForUnit(decision.unitType, decision.unitId);
1055
+ const worktree = await this.prepareWorktreeForUnit(decision.unitType, decision.unitId);
260
1056
  if (!worktree.ok) {
1057
+ this.clearPendingDispatch();
261
1058
  const blocked: AutoAdvanceResult = {
262
1059
  kind: "blocked",
263
1060
  reason: worktree.reason,
264
1061
  action: "pause",
265
1062
  stateSnapshot: reconciliation.stateSnapshot,
266
1063
  };
267
- await this.deps.runtime.journalTransition({
1064
+ this.journalTransition({
268
1065
  name: "advance-blocked",
269
1066
  reason: blocked.reason,
270
1067
  unitType: decision.unitType,
271
1068
  unitId: decision.unitId,
272
1069
  });
273
- await this.deps.health.postAdvanceRecord(blocked);
1070
+ this.postAdvanceRecord(blocked);
274
1071
  return blocked;
275
1072
  }
276
1073
 
@@ -279,23 +1076,23 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
279
1076
  this.lastAdvanceKey = nextKey;
280
1077
  this.bumpTransition();
281
1078
 
282
- await this.deps.runtime.journalTransition({
1079
+ this.journalTransition({
283
1080
  name: "advance",
284
1081
  reason: decision.reason,
285
1082
  unitType: decision.unitType,
286
1083
  unitId: decision.unitId,
287
1084
  });
288
- await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
1085
+ // syncAfterUnit was a no-op in the wired WorktreeAdapter.
289
1086
 
290
1087
  const advanced: AutoAdvanceResult = {
291
1088
  kind: "advanced",
292
1089
  unit: { unitType: decision.unitType, unitId: decision.unitId },
293
1090
  stateSnapshot: reconciliation.stateSnapshot,
294
1091
  };
295
- await this.deps.health.postAdvanceRecord(advanced);
1092
+ this.postAdvanceRecord(advanced);
296
1093
  return advanced;
297
1094
  } catch (error) {
298
- const recovery = await this.deps.recovery.classifyAndRecover({
1095
+ const recovery = this.classifyAndRecover({
299
1096
  error,
300
1097
  unitType: this.status.activeUnit?.unitType,
301
1098
  unitId: this.status.activeUnit?.unitId,
@@ -327,28 +1124,32 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
327
1124
  : result.kind === "stopped"
328
1125
  ? "advance-stopped"
329
1126
  : "advance-error";
330
- await this.deps.runtime.journalTransition({ name: journalName, reason: recovery.reason });
1127
+ this.journalTransition({ name: journalName, reason: recovery.reason });
331
1128
 
332
1129
  if (result.kind === "paused") {
333
- await this.deps.notifications.notifyLifecycle({ name: "pause", detail: recovery.reason });
1130
+ this.notifyLifecycle({ name: "pause", detail: recovery.reason });
334
1131
  } else if (result.kind === "stopped") {
335
- await this.deps.notifications.notifyLifecycle({ name: "stopped", detail: recovery.reason });
1132
+ this.notifyLifecycle({ name: "stopped", detail: recovery.reason });
336
1133
  } else if (result.kind === "error") {
337
- await this.deps.notifications.notifyLifecycle({ name: "error", detail: recovery.reason });
1134
+ this.notifyLifecycle({ name: "error", detail: recovery.reason });
338
1135
  }
339
- await this.deps.health.postAdvanceRecord(result);
1136
+ this.postAdvanceRecord(result);
340
1137
  return result;
1138
+ } finally {
1139
+ stopAdvanceTimer();
341
1140
  }
342
1141
  }
343
1142
 
344
1143
  public async resume(): Promise<AutoAdvanceResult> {
345
1144
  this.lastAdvanceKey = null;
346
1145
  this.lastFinalizedUnitKey = null;
347
- this.dispatchKeyWindow = [];
1146
+ // Preserve dispatchKeyWindow across resume so stuck-loop detection
1147
+ // accumulates across pause/resume cycles rather than resetting each time.
1148
+ this.lastStuckRecoveryKey = null;
348
1149
  this.status.phase = "running";
349
1150
  this.bumpTransition();
350
- await this.deps.runtime.journalTransition({ name: "resume" });
351
- await this.deps.notifications.notifyLifecycle({ name: "resume" });
1151
+ this.journalTransition({ name: "resume" });
1152
+ this.notifyLifecycle({ name: "resume" });
352
1153
  return { kind: "resumed" };
353
1154
  }
354
1155
 
@@ -356,15 +1157,20 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
356
1157
  if (this.status.phase === "stopped") {
357
1158
  return { kind: "stopped", reason };
358
1159
  }
359
- await this.deps.worktree.cleanupOnStop(reason);
1160
+ // cleanupOnStop was a no-op in the wired WorktreeAdapter.
360
1161
  this.status.phase = "stopped";
361
1162
  this.status.activeUnit = undefined;
362
1163
  this.lastAdvanceKey = null;
363
1164
  this.lastFinalizedUnitKey = null;
364
- this.dispatchKeyWindow = [];
1165
+ // Preserve dispatchKeyWindow on pause so stuck-loop detection accumulates
1166
+ // across pause/resume cycles. Only clear on a hard stop.
1167
+ if (reason !== "pause") {
1168
+ this.dispatchKeyWindow = [];
1169
+ }
1170
+ this.lastStuckRecoveryKey = null;
365
1171
  this.bumpTransition();
366
- await this.deps.runtime.journalTransition({ name: "stop", reason });
367
- await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
1172
+ this.journalTransition({ name: "stop", reason });
1173
+ this.notifyLifecycle({ name: "stop", detail: reason });
368
1174
  return { kind: "stopped", reason };
369
1175
  }
370
1176
 
@@ -382,8 +1188,10 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
382
1188
  this.status.activeUnit = undefined;
383
1189
  this.lastAdvanceKey = null;
384
1190
  this.lastFinalizedUnitKey = unitKey;
1191
+ // Genuine progress — re-enable graduated stuck recovery for future episodes.
1192
+ this.lastStuckRecoveryKey = null;
385
1193
  this.bumpTransition();
386
- await this.deps.runtime.journalTransition({
1194
+ this.journalTransition({
387
1195
  name: "unit-finalized",
388
1196
  unitType: unit.unitType,
389
1197
  unitId: unit.unitId,
@@ -403,7 +1211,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
403
1211
  this.lastAdvanceKey = null;
404
1212
  this.lastFinalizedUnitKey = null;
405
1213
  this.bumpTransition();
406
- await this.deps.runtime.journalTransition({
1214
+ this.journalTransition({
407
1215
  name: "unit-retry",
408
1216
  reason: "finalize-retry",
409
1217
  unitType: unit.unitType,
@@ -417,6 +1225,47 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
417
1225
  }
418
1226
  }
419
1227
 
420
- export function createAutoOrchestrator(deps: AutoOrchestratorDeps): AutoOrchestrationModule {
421
- return new AutoOrchestrator(deps);
1228
+ function isUsableLiveOrchestratorBasePath(basePath: string): boolean {
1229
+ if (!basePath || !existsSync(basePath)) return false;
1230
+ if (!detectWorktreeName(basePath)) return true;
1231
+
1232
+ try {
1233
+ return readFileSync(join(basePath, ".git"), "utf8").trim().startsWith("gitdir: ");
1234
+ } catch {
1235
+ return false;
1236
+ }
1237
+ }
1238
+
1239
+ /**
1240
+ * Resolve the base path the live orchestrator should dispatch from, falling
1241
+ * back to the project root when the captured worktree path has been removed
1242
+ * (e.g. after milestone-merge cleanup). Exported for the closeout-regression
1243
+ * tests and reused by the orchestrator's getLiveDispatchBasePath.
1244
+ */
1245
+ export function resolveLiveOrchestratorBasePath(input: {
1246
+ capturedBasePath: string;
1247
+ runtimeBasePath: string;
1248
+ sessionBasePath?: string | null;
1249
+ originalBasePath?: string | null;
1250
+ }): string {
1251
+ const primary = input.sessionBasePath || input.capturedBasePath;
1252
+ if (isUsableLiveOrchestratorBasePath(primary)) return primary;
1253
+
1254
+ const fallbacks = [
1255
+ input.originalBasePath,
1256
+ input.runtimeBasePath,
1257
+ resolveProjectRoot(input.capturedBasePath),
1258
+ ];
1259
+
1260
+ for (const candidate of fallbacks) {
1261
+ if (candidate && isUsableLiveOrchestratorBasePath(candidate)) {
1262
+ return candidate;
1263
+ }
1264
+ }
1265
+
1266
+ return input.runtimeBasePath || input.capturedBasePath;
1267
+ }
1268
+
1269
+ export function createAutoOrchestrator(context: OrchestratorContext): AutoOrchestrationModule {
1270
+ return new AutoOrchestrator(context);
422
1271
  }