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

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 (264) hide show
  1. package/dist/cli-model-override.d.ts +15 -0
  2. package/dist/cli-model-override.js +21 -0
  3. package/dist/cli.js +1 -18
  4. package/dist/loader.js +6 -4
  5. package/dist/register-agent-bundles.d.ts +11 -2
  6. package/dist/register-agent-bundles.js +18 -4
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/ask-user-questions.js +3 -2
  9. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +447 -215
  10. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
  11. package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
  12. package/dist/resources/extensions/gsd/auto/dispatch-history.js +21 -6
  13. package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
  14. package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
  15. package/dist/resources/extensions/gsd/auto/loop.js +4 -1
  16. package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
  17. package/dist/resources/extensions/gsd/auto/orchestrator.js +85 -15
  18. package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
  19. package/dist/resources/extensions/gsd/auto/phases.js +17 -2372
  20. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
  21. package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
  22. package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
  23. package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
  24. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  25. package/dist/resources/extensions/gsd/auto.js +15 -1
  26. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +37 -7
  27. package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
  28. package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
  29. package/dist/resources/extensions/gsd/db/queries.js +30 -0
  30. package/dist/resources/extensions/gsd/doctor-environment.js +256 -125
  31. package/dist/resources/extensions/gsd/guided-flow.js +88 -2
  32. package/dist/resources/extensions/gsd/health-widget.js +87 -28
  33. package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
  34. package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
  35. package/dist/resources/extensions/gsd/notifications.js +12 -7
  36. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -1
  38. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
  39. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  40. package/dist/resources/extensions/gsd/skill-activation.js +3 -6
  41. package/dist/resources/extensions/gsd/state.js +6 -2
  42. package/dist/resources/extensions/gsd/tool-surface-readiness.js +83 -31
  43. package/dist/resources/extensions/gsd/tools/complete-task.js +62 -0
  44. package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
  45. package/dist/resources/extensions/gsd/unit-registry.js +34 -4
  46. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
  47. package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
  48. package/dist/resources/extensions/gsd/worktree-safety.js +28 -26
  49. package/dist/resources/extensions/mcp-client/manager.js +6 -1
  50. package/dist/runtime-checks.d.ts +10 -0
  51. package/dist/runtime-checks.js +27 -0
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +2 -2
  86. package/packages/cloud-mcp-gateway/package.json +2 -2
  87. package/packages/contracts/package.json +1 -1
  88. package/packages/daemon/package.json +4 -4
  89. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  90. package/packages/gsd-agent-core/dist/sdk.js +6 -4
  91. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  92. package/packages/gsd-agent-core/package.json +5 -5
  93. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  94. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  95. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
  96. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  97. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +8 -0
  98. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  99. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +50 -6
  100. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
  106. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
  108. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
  112. package/packages/gsd-agent-modes/package.json +7 -7
  113. package/packages/mcp-server/README.md +12 -3
  114. package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
  115. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
  116. package/packages/mcp-server/dist/cli-runner.js +137 -0
  117. package/packages/mcp-server/dist/cli-runner.js.map +1 -0
  118. package/packages/mcp-server/dist/cli.js +2 -58
  119. package/packages/mcp-server/dist/cli.js.map +1 -1
  120. package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
  121. package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
  122. package/packages/mcp-server/dist/pid-registry.js +452 -0
  123. package/packages/mcp-server/dist/pid-registry.js.map +1 -0
  124. package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
  125. package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
  126. package/packages/mcp-server/dist/probe-mode.js +10 -0
  127. package/packages/mcp-server/dist/probe-mode.js.map +1 -0
  128. package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
  129. package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
  130. package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
  131. package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
  132. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/workflow-tools.js +62 -43
  134. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  135. package/packages/mcp-server/package.json +5 -5
  136. package/packages/native/package.json +1 -1
  137. package/packages/pi-agent-core/dist/agent-loop.js +43 -2
  138. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  139. package/packages/pi-agent-core/package.json +1 -1
  140. package/packages/pi-ai/package.json +1 -1
  141. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  142. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  144. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
  147. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  148. package/packages/pi-coding-agent/package.json +7 -7
  149. package/packages/pi-tui/dist/index.d.ts +1 -1
  150. package/packages/pi-tui/dist/index.d.ts.map +1 -1
  151. package/packages/pi-tui/dist/index.js +1 -1
  152. package/packages/pi-tui/dist/index.js.map +1 -1
  153. package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
  154. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  155. package/packages/pi-tui/dist/terminal-image.js +54 -2
  156. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  157. package/packages/pi-tui/dist/tui.d.ts +8 -0
  158. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  159. package/packages/pi-tui/dist/tui.js +63 -18
  160. package/packages/pi-tui/dist/tui.js.map +1 -1
  161. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/utils.js +110 -36
  163. package/packages/pi-tui/dist/utils.js.map +1 -1
  164. package/packages/pi-tui/package.json +2 -2
  165. package/packages/rpc-client/package.json +2 -2
  166. package/pkg/dist/theme/theme.d.ts.map +1 -1
  167. package/pkg/dist/theme/theme.js +45 -17
  168. package/pkg/dist/theme/theme.js.map +1 -1
  169. package/pkg/package.json +1 -1
  170. package/src/resources/extensions/ask-user-questions.ts +7 -2
  171. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +531 -226
  172. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +672 -7
  173. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
  174. package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
  175. package/src/resources/extensions/gsd/auto/dispatch-history.ts +22 -6
  176. package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
  177. package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
  178. package/src/resources/extensions/gsd/auto/loop.ts +4 -1
  179. package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
  180. package/src/resources/extensions/gsd/auto/orchestrator.ts +95 -15
  181. package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
  182. package/src/resources/extensions/gsd/auto/phases.ts +58 -3061
  183. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
  184. package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
  185. package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
  186. package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
  187. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  188. package/src/resources/extensions/gsd/auto.ts +20 -1
  189. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +56 -6
  190. package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
  191. package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
  192. package/src/resources/extensions/gsd/db/queries.ts +29 -0
  193. package/src/resources/extensions/gsd/doctor-environment.ts +267 -142
  194. package/src/resources/extensions/gsd/guided-flow.ts +128 -2
  195. package/src/resources/extensions/gsd/health-widget.ts +91 -27
  196. package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
  197. package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
  198. package/src/resources/extensions/gsd/notifications.ts +13 -6
  199. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
  201. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
  202. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
  203. package/src/resources/extensions/gsd/skill-activation.ts +3 -6
  204. package/src/resources/extensions/gsd/state.ts +7 -1
  205. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
  206. package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +206 -22
  208. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +76 -12
  209. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +77 -1
  211. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
  212. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
  213. package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
  214. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +2 -1
  215. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
  216. package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +55 -0
  217. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
  218. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
  219. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
  220. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +16 -0
  221. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +15 -0
  222. package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
  223. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +18 -0
  224. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
  225. package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
  226. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
  227. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
  230. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
  232. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
  233. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +5 -0
  234. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
  235. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +1 -1
  236. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
  237. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
  238. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
  239. package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
  240. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -1
  241. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
  242. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
  243. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
  244. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -2
  245. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +184 -10
  246. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
  248. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +65 -2
  249. package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
  250. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
  251. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
  252. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
  253. package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
  254. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +72 -0
  255. package/src/resources/extensions/gsd/tool-surface-readiness.ts +126 -19
  256. package/src/resources/extensions/gsd/tools/complete-task.ts +87 -0
  257. package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
  258. package/src/resources/extensions/gsd/unit-registry.ts +34 -4
  259. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
  260. package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
  261. package/src/resources/extensions/gsd/worktree-safety.ts +41 -39
  262. package/src/resources/extensions/mcp-client/manager.ts +7 -1
  263. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_buildManifest.js +0 -0
  264. /package/dist/web/standalone/.next/static/{FBNo5cT_chy7YNoAQsU3o → xyMkEaICFHJoa98VgJyzY}/_ssgManifest.js +0 -0
@@ -0,0 +1,347 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Auto-loop finalize phase — post-unit verification and UAT pause.
3
+ import { clearCurrentPhase } from "../../shared/gsd-phase-state.js";
4
+ import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
5
+ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
6
+ import { buildManualValidationGuidance } from "../worktree-manager.js";
7
+ import { relSliceFile } from "../paths.js";
8
+ import { detectRootWriteLeak, formatRootWriteLeakMessage, } from "../root-write-leak-guard.js";
9
+ import { drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
10
+ import { debugLog } from "../debug-logger.js";
11
+ import { buildPhaseHandoffOutcome, setAutoOutcomeWidget } from "../auto-dashboard.js";
12
+ import { applyVerificationRetryPolicy, rememberRetryDispatch, isIsolatedWorktreeSession, } from "./phase-helpers.js";
13
+ import { _runMilestoneMergeOnceWithStashRestore } from "./closeout.js";
14
+ import { MAX_FINALIZE_TIMEOUTS } from "./types.js";
15
+ export async function failClosedOnFinalizeTimeout(ic, iterData, loopState, stage, startedAt) {
16
+ const { ctx, pi, s, deps } = ic;
17
+ const now = Date.now();
18
+ const unitType = iterData.unitType;
19
+ const unitId = iterData.unitId;
20
+ const timeoutMs = stage === "pre" ? FINALIZE_PRE_TIMEOUT_MS : FINALIZE_POST_TIMEOUT_MS;
21
+ const progressKind = stage === "pre" ? "finalize-pre-timeout" : "finalize-post-timeout";
22
+ writeUnitRuntimeRecord(s.basePath, unitType, unitId, startedAt, {
23
+ phase: "finalize-timeout",
24
+ timeoutAt: now,
25
+ lastProgressAt: now,
26
+ lastProgressKind: progressKind,
27
+ });
28
+ deps.emitJournalEvent({
29
+ ts: new Date(now).toISOString(),
30
+ flowId: ic.flowId,
31
+ seq: ic.nextSeq(),
32
+ eventType: "unit-end",
33
+ data: {
34
+ unitType,
35
+ unitId,
36
+ status: "timed-out-finalize",
37
+ artifactVerified: false,
38
+ finalizeStage: stage,
39
+ },
40
+ });
41
+ loopState.consecutiveFinalizeTimeouts++;
42
+ debugLog("autoLoop", {
43
+ phase: progressKind,
44
+ iteration: ic.iteration,
45
+ unitType,
46
+ unitId,
47
+ consecutiveTimeouts: loopState.consecutiveFinalizeTimeouts,
48
+ });
49
+ ctx.ui.notify(`${stage === "pre" ? "postUnitPreVerification" : "postUnitPostVerification"} timed out after ${timeoutMs / 1000}s for ${unitType} ${unitId} (${loopState.consecutiveFinalizeTimeouts}/${MAX_FINALIZE_TIMEOUTS}) — pausing auto-mode for recovery.`, "warning");
50
+ await deps.pauseAuto(ctx, pi);
51
+ s.clearCurrentUnit();
52
+ clearCurrentPhase();
53
+ drainLogs();
54
+ return { action: "break", reason: progressKind };
55
+ }
56
+ /**
57
+ * Phase 5: Post-unit finalize — pre/post verification, UAT pause, step-wizard.
58
+ * Returns break/continue/next to control the outer loop.
59
+ */
60
+ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
61
+ const { ctx, pi, s, deps } = ic;
62
+ const { pauseAfterUatDispatch } = iterData;
63
+ debugLog("autoLoop", { phase: "finalize", iteration: ic.iteration });
64
+ // Clear unit timeout (unit completed)
65
+ deps.clearUnitTimeout();
66
+ // Post-unit context for pre/post verification
67
+ const postUnitCtx = {
68
+ s,
69
+ ctx,
70
+ pi,
71
+ buildSnapshotOpts: deps.buildSnapshotOpts,
72
+ lockBase: deps.lockBase,
73
+ stopAuto: deps.stopAuto,
74
+ pauseAuto: deps.pauseAuto,
75
+ updateProgressWidget: deps.updateProgressWidget,
76
+ };
77
+ // Pre-verification processing (commit, doctor, state rebuild, etc.)
78
+ // Timeout guard: if postUnitPreVerification hangs (e.g., safety harness
79
+ // deadlock, browser teardown hang, worktree sync stall), force-continue
80
+ // after timeout so the auto-loop is not permanently frozen (#3757).
81
+ //
82
+ // On timeout, null out s.currentUnit so the timed-out task's late async
83
+ // mutations are harmless — postUnitPreVerification guards all side effects
84
+ // behind `if (s.currentUnit)`. The next iteration sets a fresh currentUnit.
85
+ // Sidecar items use lightweight pre-verification opts
86
+ const preVerificationOpts = sidecarItem
87
+ ? sidecarItem.kind === "hook"
88
+ ? { skipSettleDelay: true, skipWorktreeSync: true, agentEndMessages: s.lastUnitAgentEndMessages ?? undefined }
89
+ : { skipSettleDelay: true, agentEndMessages: s.lastUnitAgentEndMessages ?? undefined }
90
+ : { agentEndMessages: s.lastUnitAgentEndMessages ?? undefined };
91
+ const preUnitSnapshot = s.currentUnit
92
+ ? { type: s.currentUnit.type, id: s.currentUnit.id, startedAt: s.currentUnit.startedAt }
93
+ : null;
94
+ const clearFinalizingUnit = () => {
95
+ if (preUnitSnapshot &&
96
+ s.currentUnit?.type === preUnitSnapshot.type &&
97
+ s.currentUnit?.id === preUnitSnapshot.id &&
98
+ s.currentUnit?.startedAt === preUnitSnapshot.startedAt) {
99
+ s.clearCurrentUnit();
100
+ }
101
+ s.rootWriteBaseline = null;
102
+ };
103
+ clearCurrentPhase();
104
+ const preResultGuard = await withTimeout(deps.postUnitPreVerification(postUnitCtx, preVerificationOpts), FINALIZE_PRE_TIMEOUT_MS, "postUnitPreVerification");
105
+ if (preResultGuard.timedOut) {
106
+ return failClosedOnFinalizeTimeout(ic, iterData, loopState, "pre", preUnitSnapshot?.startedAt ?? Date.now());
107
+ }
108
+ const preResult = preResultGuard.value;
109
+ if (preResult === "dispatched") {
110
+ const dispatchedReason = s.lastGitActionFailure
111
+ ? "git-closeout-failure"
112
+ : "pre-verification-dispatched";
113
+ debugLog("autoLoop", {
114
+ phase: "exit",
115
+ reason: dispatchedReason,
116
+ gitError: s.lastGitActionFailure ?? undefined,
117
+ });
118
+ clearFinalizingUnit();
119
+ return { action: "break", reason: dispatchedReason };
120
+ }
121
+ if (preResult === "retry") {
122
+ if (sidecarItem) {
123
+ // Sidecar artifact retries are skipped — just continue
124
+ debugLog("autoLoop", { phase: "sidecar-artifact-retry-skipped", iteration: ic.iteration });
125
+ }
126
+ else {
127
+ // s.pendingVerificationRetry was set by postUnitPreVerification.
128
+ // Emit a dedicated journal event so forensics can distinguish bounded
129
+ // verification retries from genuine stuck-loop dispatch repetitions (#4540).
130
+ const retryInfo = s.pendingVerificationRetry;
131
+ deps.emitJournalEvent({
132
+ ts: new Date().toISOString(),
133
+ flowId: ic.flowId,
134
+ seq: ic.nextSeq(),
135
+ eventType: "artifact-verification-retry",
136
+ data: {
137
+ unitType: preUnitSnapshot?.type,
138
+ unitId: retryInfo?.unitId,
139
+ attempt: retryInfo?.attempt,
140
+ },
141
+ });
142
+ const retryPolicyResult = await applyVerificationRetryPolicy(ic, preUnitSnapshot?.type, "artifact-verification-retry");
143
+ if (retryPolicyResult) {
144
+ clearFinalizingUnit();
145
+ return retryPolicyResult;
146
+ }
147
+ // Continue the loop — next iteration will inject the retry context into the prompt.
148
+ rememberRetryDispatch(s, preUnitSnapshot, iterData);
149
+ debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
150
+ clearFinalizingUnit();
151
+ return { action: "continue" };
152
+ }
153
+ }
154
+ if (pauseAfterUatDispatch) {
155
+ const pauseMid = iterData.mid;
156
+ const pauseSliceId = pauseMid && iterData.unitId.startsWith(`${pauseMid}/`)
157
+ ? iterData.unitId.slice(pauseMid.length + 1)
158
+ : undefined;
159
+ const guidance = pauseMid
160
+ ? buildManualValidationGuidance(s.basePath, pauseMid, {
161
+ uatPath: pauseSliceId
162
+ ? relSliceFile(s.basePath, pauseMid, pauseSliceId, "UAT")
163
+ : undefined,
164
+ })
165
+ : null;
166
+ const pauseMessage = guidance
167
+ ? `UAT requires human execution. Auto-mode will pause after this unit writes the result file.\n\n${guidance}`
168
+ : "UAT requires human execution. Auto-mode will pause after this unit writes the result file.";
169
+ ctx.ui.notify(pauseMessage, "info");
170
+ await deps.pauseAuto(ctx, pi);
171
+ debugLog("autoLoop", { phase: "exit", reason: "uat-pause" });
172
+ clearFinalizingUnit();
173
+ return { action: "break", reason: "uat-pause" };
174
+ }
175
+ // Verification gate
176
+ // Hook sidecar items skip verification entirely.
177
+ // Non-hook sidecar items run verification but skip retries (just continue).
178
+ const skipVerification = sidecarItem?.kind === "hook";
179
+ if (!skipVerification) {
180
+ const verificationResult = await deps.runPostUnitVerification({ s, ctx, pi }, deps.pauseAuto);
181
+ if (verificationResult === "pause") {
182
+ debugLog("autoLoop", { phase: "exit", reason: "verification-pause" });
183
+ clearFinalizingUnit();
184
+ return { action: "break", reason: "verification-pause" };
185
+ }
186
+ if (verificationResult === "retry") {
187
+ if (sidecarItem) {
188
+ // Sidecar verification retries are skipped — just continue
189
+ debugLog("autoLoop", { phase: "sidecar-verification-retry-skipped", iteration: ic.iteration });
190
+ }
191
+ else {
192
+ // s.pendingVerificationRetry was set by runPostUnitVerification.
193
+ const retryPolicyResult = await applyVerificationRetryPolicy(ic, iterData.unitType, "verification-retry");
194
+ if (retryPolicyResult) {
195
+ clearFinalizingUnit();
196
+ return retryPolicyResult;
197
+ }
198
+ // Continue the loop — next iteration will inject the retry context into the prompt.
199
+ rememberRetryDispatch(s, preUnitSnapshot, iterData);
200
+ debugLog("autoLoop", { phase: "verification-retry", iteration: ic.iteration });
201
+ clearFinalizingUnit();
202
+ return { action: "continue" };
203
+ }
204
+ }
205
+ }
206
+ // Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
207
+ // Timeout guard: if postUnitPostVerification hangs (e.g., module import
208
+ // deadlock, SQLite transaction hang), force-continue after timeout so the
209
+ // auto-loop is not permanently frozen (#2344).
210
+ const postResultGuard = await withTimeout(deps.postUnitPostVerification(postUnitCtx), FINALIZE_POST_TIMEOUT_MS, "postUnitPostVerification");
211
+ if (postResultGuard.timedOut) {
212
+ return failClosedOnFinalizeTimeout(ic, iterData, loopState, "post", preUnitSnapshot?.startedAt ?? Date.now());
213
+ }
214
+ const postResult = postResultGuard.value;
215
+ if (postResult === "retry") {
216
+ if (sidecarItem) {
217
+ debugLog("autoLoop", { phase: "sidecar-pre-execution-retry-skipped", iteration: ic.iteration });
218
+ }
219
+ else {
220
+ const retryInfo = s.pendingVerificationRetry;
221
+ deps.emitJournalEvent({
222
+ ts: new Date().toISOString(),
223
+ flowId: ic.flowId,
224
+ seq: ic.nextSeq(),
225
+ eventType: "pre-execution-retry",
226
+ data: {
227
+ unitType: preUnitSnapshot?.type,
228
+ unitId: retryInfo?.unitId,
229
+ attempt: retryInfo?.attempt,
230
+ },
231
+ });
232
+ const retryPolicyResult = await applyVerificationRetryPolicy(ic, preUnitSnapshot?.type, "pre-execution-retry");
233
+ if (retryPolicyResult) {
234
+ clearFinalizingUnit();
235
+ return retryPolicyResult;
236
+ }
237
+ rememberRetryDispatch(s, preUnitSnapshot, iterData);
238
+ debugLog("autoLoop", {
239
+ phase: "pre-execution-retry",
240
+ iteration: ic.iteration,
241
+ unitType: preUnitSnapshot?.type,
242
+ unitId: retryInfo?.unitId,
243
+ attempt: retryInfo?.attempt,
244
+ });
245
+ clearFinalizingUnit();
246
+ return { action: "continue" };
247
+ }
248
+ }
249
+ if (postResult === "stopped") {
250
+ debugLog("autoLoop", {
251
+ phase: "exit",
252
+ reason: "post-verification-stopped",
253
+ });
254
+ clearFinalizingUnit();
255
+ return { action: "break", reason: "post-verification-stopped" };
256
+ }
257
+ if (postResult === "step-wizard") {
258
+ // Step mode — exit the loop (caller handles wizard)
259
+ debugLog("autoLoop", { phase: "exit", reason: "step-wizard" });
260
+ clearFinalizingUnit();
261
+ return { action: "break", reason: "step-wizard" };
262
+ }
263
+ if (preUnitSnapshot && isIsolatedWorktreeSession(s)) {
264
+ const leak = detectRootWriteLeak({
265
+ rootPath: s.originalBasePath,
266
+ worktreePath: s.basePath,
267
+ unitType: preUnitSnapshot.type,
268
+ unitId: preUnitSnapshot.id,
269
+ before: s.rootWriteBaseline,
270
+ });
271
+ s.rootWriteBaseline = null;
272
+ if (leak) {
273
+ const message = formatRootWriteLeakMessage(leak);
274
+ debugLog("autoLoop", {
275
+ phase: "root-write-leak",
276
+ unitType: preUnitSnapshot.type,
277
+ unitId: preUnitSnapshot.id,
278
+ rootPath: leak.rootPath,
279
+ worktreePath: leak.worktreePath,
280
+ files: leak.files.map((file) => ({ path: file.path, status: file.status })),
281
+ });
282
+ ctx.ui.notify(message, "error");
283
+ await deps.stopAuto(ctx, pi, "Root-write leak during isolated auto-mode", {
284
+ preserveCompletedMilestoneBranch: true,
285
+ });
286
+ clearFinalizingUnit();
287
+ return { action: "break", reason: "root-write-leak" };
288
+ }
289
+ }
290
+ else {
291
+ s.rootWriteBaseline = null;
292
+ }
293
+ if (preUnitSnapshot?.type === "complete-milestone" && s.currentMilestoneId) {
294
+ const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId, {
295
+ preserveCloseoutTranscript: true,
296
+ });
297
+ if (stop) {
298
+ clearFinalizingUnit();
299
+ return stop;
300
+ }
301
+ }
302
+ // Both pre and post verification completed without timeout — reset counter
303
+ loopState.consecutiveFinalizeTimeouts = 0;
304
+ if (preUnitSnapshot) {
305
+ writeUnitRuntimeRecord(s.basePath, preUnitSnapshot.type, preUnitSnapshot.id, preUnitSnapshot.startedAt, {
306
+ phase: "finalized",
307
+ lastProgressAt: Date.now(),
308
+ lastProgressKind: "finalize-success",
309
+ });
310
+ if (!preUnitSnapshot.type.startsWith("hook/") &&
311
+ preUnitSnapshot.type !== "custom-step" &&
312
+ preUnitSnapshot.type !== "complete-milestone") {
313
+ setAutoOutcomeWidget(ctx, {
314
+ ...buildPhaseHandoffOutcome({
315
+ unitType: preUnitSnapshot.type,
316
+ unitId: preUnitSnapshot.id,
317
+ agentEndMessages: s.lastUnitAgentEndMessages,
318
+ }),
319
+ startedAt: s.autoStartTime,
320
+ });
321
+ }
322
+ }
323
+ clearFinalizingUnit();
324
+ // Surface accumulated workflow-logger issues for this unit to the user.
325
+ // Warnings/errors logged during the unit are buffered in the logger and
326
+ // drained here so the user sees a single consolidated post-unit alert.
327
+ if (hasAnyIssues()) {
328
+ const { logs } = drainAndSummarize();
329
+ if (logs.length > 0) {
330
+ const severity = logs.some((e) => e.severity === "error") ? "error" : "warning";
331
+ ctx.ui.notify(formatForNotification(logs), severity);
332
+ }
333
+ }
334
+ if (preUnitSnapshot?.type === "complete-milestone" && s.currentMilestoneId) {
335
+ // cleanupAfterLoopExit skips gsd-progress when preserveCompletionSurface is true, so clear stale controls here.
336
+ ctx.ui.setStatus?.("gsd-step", undefined);
337
+ ctx.ui.setWidget?.("gsd-progress", undefined);
338
+ await deps.stopAuto(ctx, pi, `Milestone ${s.currentMilestoneId} complete`, {
339
+ completionWidget: {
340
+ milestoneId: s.currentMilestoneId,
341
+ milestoneTitle: iterData.midTitle,
342
+ },
343
+ });
344
+ return { action: "break", reason: "milestone-complete" };
345
+ }
346
+ return { action: "next", data: undefined };
347
+ }
@@ -13,7 +13,9 @@ import { mkdirSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { MAX_LOOP_ITERATIONS, } from "./types.js";
15
15
  import { _clearCurrentResolve } from "./resolve.js";
16
- import { runGuards, runFinalize } from "./phases.js";
16
+ import { runGuards } from "./phases.js";
17
+ import { runFinalize } from "./finalize.js";
18
+ import { resetSessionTimeoutState } from "./unit-phase.js";
17
19
  import { STUCK_WINDOW_SIZE } from "./dispatch-history.js";
18
20
  import { debugLog } from "../debug-logger.js";
19
21
  import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
@@ -274,6 +276,7 @@ function closeOutCrashedUnit(s, iterData, err) {
274
276
  */
275
277
  export async function autoLoop(ctx, pi, s, deps, options) {
276
278
  debugLog("autoLoop", { phase: "enter" });
279
+ resetSessionTimeoutState();
277
280
  let iteration = 0;
278
281
  const dispatchContract = options?.dispatchContract ?? "legacy-direct";
279
282
  const unitDispatchDeps = createExecutionGraphUnitDispatchDeps();
@@ -0,0 +1,56 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Recover missing milestone lease state for resumed isolated workers.
3
+ import { debugLog } from "../debug-logger.js";
4
+ import { claimMilestoneLease } from "../db/milestone-leases.js";
5
+ export function hasHeldMilestoneLease(session, milestoneId) {
6
+ return (Boolean(milestoneId) &&
7
+ session.currentMilestoneId === milestoneId &&
8
+ typeof session.milestoneLeaseToken === "number");
9
+ }
10
+ export function reclaimMissingMilestoneLease(session, milestoneId, isolationMode, phase) {
11
+ if (isolationMode === "none")
12
+ return;
13
+ if (!session.workerId || !milestoneId)
14
+ return;
15
+ if (hasHeldMilestoneLease(session, milestoneId))
16
+ return;
17
+ // Note: we intentionally do NOT bail just because the session already holds a
18
+ // lease for a *different* milestone. When dispatch advances to a new
19
+ // milestone the session's stale `currentMilestoneId`/token are for the prior
20
+ // one, and the active milestone's lease must still be claimed — otherwise
21
+ // worktree safety sees `held: false` and fails dispatch (#760). Claiming is
22
+ // safe: claimMilestoneLease refuses to steal a lease another worker holds.
23
+ try {
24
+ const claim = claimMilestoneLease(session.workerId, milestoneId);
25
+ if (claim.ok) {
26
+ session.currentMilestoneId = milestoneId;
27
+ session.milestoneLeaseToken = claim.token;
28
+ debugLog("worktreeSafety", {
29
+ phase: "lease-reclaimed",
30
+ source: phase,
31
+ milestoneId,
32
+ workerId: session.workerId,
33
+ token: claim.token,
34
+ });
35
+ }
36
+ else {
37
+ debugLog("worktreeSafety", {
38
+ phase: "lease-reclaim-blocked",
39
+ source: phase,
40
+ milestoneId,
41
+ workerId: session.workerId,
42
+ holderWorkerId: claim.byWorker,
43
+ expiresAt: claim.expiresAt,
44
+ });
45
+ }
46
+ }
47
+ catch (err) {
48
+ debugLog("worktreeSafety", {
49
+ phase: "lease-reclaim-failed",
50
+ source: phase,
51
+ milestoneId,
52
+ workerId: session.workerId,
53
+ error: err instanceof Error ? err.message : String(err),
54
+ });
55
+ }
56
+ }
@@ -47,6 +47,7 @@ import { buildDispatchKey, createDispatchHistory, STUCK_WINDOW_SIZE, } from "./d
47
47
  import { existsSync, readFileSync } from "node:fs";
48
48
  import { join } from "node:path";
49
49
  import { evaluateAllCompleteSettlement } from "../milestone-settlement.js";
50
+ import { hasHeldMilestoneLease, reclaimMissingMilestoneLease } from "./milestone-lease-reclaim.js";
50
51
  function now() {
51
52
  return Date.now();
52
53
  }
@@ -127,6 +128,7 @@ export async function decideOrchestratorDispatch(ctx, pi, dispatchBasePath, sess
127
128
  }
128
129
  if (active && activeSession && shouldAdoptActiveMilestone(state, activeSession, activeDispatchBasePath)) {
129
130
  activeSession.currentMilestoneId = active.id;
131
+ activeSession.milestoneLeaseToken = null;
130
132
  }
131
133
  const dispatchMid = active?.id ?? activeSession?.currentMilestoneId ?? "";
132
134
  const dispatchMidTitle = active?.title ?? "";
@@ -476,6 +478,29 @@ export class AutoOrchestrator {
476
478
  },
477
479
  };
478
480
  }
481
+ async mergePendingCompleteMilestone(milestoneId) {
482
+ const result = this.buildLifecycle().exitMilestone(milestoneId, { merge: true }, this.ctx.ui);
483
+ if (!result.ok) {
484
+ const detail = result.cause instanceof Error
485
+ ? result.cause.message
486
+ : result.reason;
487
+ return {
488
+ ok: false,
489
+ reason: `Milestone ${milestoneId} is complete, but the system-owned merge failed: ${detail}`,
490
+ };
491
+ }
492
+ this.s.milestoneMergedInPhases = true;
493
+ this.s.milestoneSettlement = { ok: true, reason: "settled" };
494
+ try {
495
+ const projectRoot = this.s.originalBasePath || this.s.canonicalProjectRoot || this.runtimeBasePath;
496
+ const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
497
+ await rebuildMarkdownProjectionsFromDb(projectRoot);
498
+ }
499
+ catch (err) {
500
+ logWarning("engine", `markdown projection rebuild after settlement merge failed: ${err instanceof Error ? err.message : String(err)}`);
501
+ }
502
+ return { ok: true };
503
+ }
479
504
  clearPendingDispatch() {
480
505
  this.s.pendingOrchestrationDispatch = null;
481
506
  }
@@ -536,9 +561,6 @@ export class AutoOrchestrator {
536
561
  reason: `No Unit manifest is registered for ${unitType}`,
537
562
  };
538
563
  }
539
- if (isolationMode !== "worktree") {
540
- return { ok: true, reason: "not-required" };
541
- }
542
564
  const writeScope = manifest.tools.mode === "all" || manifest.tools.mode === "docs"
543
565
  ? "source-writing"
544
566
  : "planning-only";
@@ -546,7 +568,21 @@ export class AutoOrchestrator {
546
568
  const activeBasePath = this.getLiveDispatchBasePath();
547
569
  const snapshot = await deriveState(activeBasePath);
548
570
  const milestoneId = snapshot.activeMilestone?.id ?? null;
549
- const expectedBranch = milestoneId ? autoWorktreeBranch(milestoneId) : null;
571
+ const buildExpectedBranch = (mode) => mode !== "none" && milestoneId ? autoWorktreeBranch(milestoneId) : null;
572
+ // The milestone lease coordinates concurrent workers on an isolated
573
+ // milestone worktree/branch. `none` mode has no per-milestone isolation
574
+ // and does not reliably claim a lease, so requiring one there would
575
+ // falsely fail dispatch; enforce it only in isolated modes.
576
+ const buildLease = (mode) => milestoneId && this.s.workerId
577
+ ? {
578
+ required: writeScope === "source-writing" && mode !== "none",
579
+ held: hasHeldMilestoneLease(this.s, milestoneId),
580
+ owner: this.s.workerId,
581
+ }
582
+ : undefined;
583
+ if (writeScope === "source-writing") {
584
+ reclaimMissingMilestoneLease(this.s, milestoneId, isolationMode, "orchestrator");
585
+ }
550
586
  let result = safety.validateUnitRoot({
551
587
  unitType,
552
588
  unitId,
@@ -555,7 +591,8 @@ export class AutoOrchestrator {
555
591
  unitRoot: activeBasePath,
556
592
  milestoneId,
557
593
  isolationMode,
558
- expectedBranch,
594
+ expectedBranch: buildExpectedBranch(isolationMode),
595
+ lease: buildLease(isolationMode),
559
596
  });
560
597
  if (!result.ok) {
561
598
  const repaired = await repairAutoWorktreeSafetyFailure({
@@ -573,16 +610,20 @@ export class AutoOrchestrator {
573
610
  this.rebuildScope(this.s.basePath, this.s.currentMilestoneId);
574
611
  return { ok: true };
575
612
  },
576
- revalidate: () => safety.validateUnitRoot({
577
- unitType,
578
- unitId,
579
- writeScope,
580
- projectRoot: this.runtimeBasePath,
581
- unitRoot: this.getLiveDispatchBasePath(),
582
- milestoneId,
583
- isolationMode: this.getEffectiveUnitIsolationMode(this.runtimeBasePath),
584
- expectedBranch,
585
- }),
613
+ revalidate: () => {
614
+ const revalidatedMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);
615
+ return safety.validateUnitRoot({
616
+ unitType,
617
+ unitId,
618
+ writeScope,
619
+ projectRoot: this.runtimeBasePath,
620
+ unitRoot: this.getLiveDispatchBasePath(),
621
+ milestoneId,
622
+ isolationMode: revalidatedMode,
623
+ expectedBranch: buildExpectedBranch(revalidatedMode),
624
+ lease: buildLease(revalidatedMode),
625
+ });
626
+ },
586
627
  });
587
628
  result = repaired.result;
588
629
  if (result.ok) {
@@ -775,6 +816,35 @@ export class AutoOrchestrator {
775
816
  if (!decision) {
776
817
  const settlementBlock = this.evaluateNoRemainingUnitsSettlement(reconciliation.stateSnapshot);
777
818
  if (settlementBlock) {
819
+ const settlement = this.s.milestoneSettlement;
820
+ if (settlement && !settlement.ok && settlement.reason === "merge-pending") {
821
+ const merged = await this.mergePendingCompleteMilestone(settlement.milestoneId);
822
+ if (merged.ok) {
823
+ const terminalOutcome = noRemainingUnitsOutcome(reconciliation.stateSnapshot);
824
+ const stopped = {
825
+ kind: "stopped",
826
+ reason: terminalOutcome.displayReason,
827
+ stateSnapshot: reconciliation.stateSnapshot,
828
+ terminalOutcome,
829
+ };
830
+ this.status.phase = "stopped";
831
+ this.status.activeUnit = undefined;
832
+ this.lastAdvanceKey = null;
833
+ this.dispatchHistory.clearOnRecovery();
834
+ this.bumpTransition();
835
+ this.journalTransition({ name: "advance-stopped", reason: stopped.reason });
836
+ this.postAdvanceRecord(stopped);
837
+ return stopped;
838
+ }
839
+ settlementBlock.reason = merged.reason;
840
+ settlementBlock.terminalOutcome = {
841
+ code: "settlement-blocked",
842
+ displayReason: merged.reason,
843
+ nextAction: `Fix the merge failure, then retry \`/gsd dispatch complete-milestone ${settlement.milestoneId}\`.`,
844
+ milestoneId: settlement.milestoneId,
845
+ allMilestonesComplete: false,
846
+ };
847
+ }
778
848
  this.status.phase = "paused";
779
849
  this.status.activeUnit = undefined;
780
850
  this.lastAdvanceKey = null;