@opengsd/gsd-pi 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (499) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/ask-user-questions.js +1 -1
  4. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  5. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  6. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  7. package/dist/resources/extensions/gsd/auto/orchestrator.js +20 -13
  8. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  9. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  10. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
  11. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
  13. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  14. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  15. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  16. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  17. package/dist/resources/extensions/gsd/auto-start.js +57 -33
  18. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  19. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  20. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  36. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  37. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  38. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  39. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  40. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  41. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  42. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  43. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  44. package/dist/resources/extensions/gsd/auto.js +106 -34
  45. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  46. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  47. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  48. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  49. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  50. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  51. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  52. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  53. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  54. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  55. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  56. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  57. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  59. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  60. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  61. package/dist/resources/extensions/gsd/forensics.js +1 -1
  62. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  63. package/dist/resources/extensions/gsd/git-service.js +45 -0
  64. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  65. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  66. package/dist/resources/extensions/gsd/journal.js +1 -1
  67. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -79
  68. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  69. package/dist/resources/extensions/gsd/metrics.js +2 -2
  70. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  71. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  72. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  73. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  74. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  75. package/dist/resources/extensions/gsd/paths.js +87 -52
  76. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  77. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  78. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  79. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  80. package/dist/resources/extensions/gsd/preferences-validation.js +148 -10
  81. package/dist/resources/extensions/gsd/preferences.js +20 -0
  82. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  83. package/dist/resources/extensions/gsd/quick.js +12 -3
  84. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  85. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  86. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  87. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  88. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  89. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  90. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  91. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  92. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  93. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  94. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  95. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  96. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  97. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  98. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  99. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  100. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  101. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  102. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  103. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  104. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  105. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  106. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  107. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  108. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  109. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  110. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  111. package/dist/resources/extensions/gsd/workspace.js +5 -14
  112. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  113. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  114. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  115. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  116. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  117. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  118. package/dist/web/standalone/.next/BUILD_ID +1 -1
  119. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  120. package/dist/web/standalone/.next/build-manifest.json +2 -2
  121. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  122. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  123. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  131. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/index.html +1 -1
  173. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  174. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  175. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  176. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  178. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  180. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  181. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  182. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  184. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  185. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  186. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  187. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  188. package/dist/worktree-cli-create.d.ts +10 -0
  189. package/dist/worktree-cli-create.js +11 -0
  190. package/dist/worktree-cli-format.d.ts +12 -0
  191. package/dist/worktree-cli-format.js +38 -0
  192. package/dist/worktree-cli-plan.d.ts +16 -0
  193. package/dist/worktree-cli-plan.js +19 -0
  194. package/dist/worktree-cli-session.d.ts +13 -0
  195. package/dist/worktree-cli-session.js +18 -0
  196. package/dist/worktree-cli-status.d.ts +35 -0
  197. package/dist/worktree-cli-status.js +54 -0
  198. package/dist/worktree-cli.d.ts +1 -16
  199. package/dist/worktree-cli.js +28 -122
  200. package/integrations/hermes/open_gsd_hermes/gsd_client.py +25 -36
  201. package/integrations/hermes/pyproject.toml +1 -1
  202. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  203. package/package.json +6 -6
  204. package/packages/cloud-mcp-gateway/package.json +2 -2
  205. package/packages/contracts/package.json +1 -1
  206. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  207. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  208. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  209. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  210. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  211. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  212. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  213. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  214. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  215. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  216. package/packages/daemon/dist/orchestrator.js +13 -303
  217. package/packages/daemon/dist/orchestrator.js.map +1 -1
  218. package/packages/daemon/dist/orchestrator.test.js +11 -1
  219. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  220. package/packages/daemon/package.json +4 -4
  221. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  222. package/packages/gsd-agent-core/package.json +5 -5
  223. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  224. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  225. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  226. package/packages/gsd-agent-modes/package.json +7 -7
  227. package/packages/mcp-server/package.json +4 -4
  228. package/packages/native/package.json +1 -1
  229. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  230. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  231. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  232. package/packages/pi-agent-core/package.json +1 -1
  233. package/packages/pi-ai/dist/index.d.ts +1 -0
  234. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  235. package/packages/pi-ai/dist/index.js +1 -0
  236. package/packages/pi-ai/dist/index.js.map +1 -1
  237. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  238. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  239. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  240. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  241. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  242. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  243. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  244. package/packages/pi-ai/dist/types.d.ts +8 -20
  245. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  246. package/packages/pi-ai/dist/types.js.map +1 -1
  247. package/packages/pi-ai/package.json +1 -1
  248. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  249. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  250. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  251. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  253. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  255. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  257. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  258. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  259. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  260. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  261. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  263. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  264. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  265. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/index.js +1 -0
  267. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  268. package/packages/pi-coding-agent/package.json +7 -7
  269. package/packages/pi-tui/package.json +2 -2
  270. package/packages/rpc-client/package.json +2 -2
  271. package/pkg/dist/core/export-html/template.js +53 -4
  272. package/pkg/package.json +1 -1
  273. package/src/resources/extensions/ask-user-questions.ts +1 -1
  274. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  275. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  276. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  277. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  278. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  279. package/src/resources/extensions/gsd/auto/orchestrator.ts +21 -14
  280. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  281. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  282. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
  283. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  284. package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
  285. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  286. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  287. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  288. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  289. package/src/resources/extensions/gsd/auto-start.ts +62 -31
  290. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  291. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  292. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  293. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  294. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  295. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  296. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  297. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  298. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  299. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  300. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  301. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  302. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  303. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  304. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  305. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  306. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  307. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  308. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  309. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  310. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  311. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  312. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  313. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  314. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  315. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  316. package/src/resources/extensions/gsd/auto.ts +114 -47
  317. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  318. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  319. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  320. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  321. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  322. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  323. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  324. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  325. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  326. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  327. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  328. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  329. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  330. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  331. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  332. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  333. package/src/resources/extensions/gsd/forensics.ts +1 -1
  334. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  335. package/src/resources/extensions/gsd/git-service.ts +53 -0
  336. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  337. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  338. package/src/resources/extensions/gsd/journal.ts +1 -1
  339. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -91
  340. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  341. package/src/resources/extensions/gsd/metrics.ts +2 -2
  342. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  343. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  344. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  345. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  346. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  347. package/src/resources/extensions/gsd/paths.ts +108 -51
  348. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  349. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  350. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  351. package/src/resources/extensions/gsd/preferences-types.ts +54 -1
  352. package/src/resources/extensions/gsd/preferences-validation.ts +146 -9
  353. package/src/resources/extensions/gsd/preferences.ts +20 -0
  354. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  355. package/src/resources/extensions/gsd/quick.ts +11 -3
  356. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  357. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  358. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  359. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  360. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  361. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  362. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  363. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  364. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  365. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  366. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  367. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  368. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  369. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  370. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  371. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  372. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  373. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  374. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  375. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  376. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  377. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  378. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  379. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  380. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  381. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  382. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  383. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  384. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  385. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  386. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  387. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  388. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  389. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  390. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  391. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  392. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  393. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  394. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  395. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  396. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  397. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +46 -1
  398. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  399. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  400. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  401. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  402. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  403. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  404. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  405. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  406. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  407. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  408. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  409. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  410. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  411. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  412. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  413. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  414. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  415. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  416. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  417. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  418. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  419. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  420. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  421. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  422. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  423. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  424. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  425. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  426. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  427. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  428. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  429. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  430. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  431. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  432. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  433. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  434. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  435. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  436. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  437. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  438. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  439. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  440. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  441. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  442. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  443. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  444. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  445. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  446. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  447. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  448. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  449. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  450. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  451. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  452. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  453. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  454. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  455. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  456. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  457. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  458. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  459. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  460. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  461. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  462. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  463. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  464. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  465. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  466. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  467. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  468. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  469. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  470. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  471. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  472. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  473. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  474. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  475. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  476. package/src/resources/extensions/gsd/types.ts +23 -4
  477. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  478. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  479. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  480. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  481. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  482. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  483. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  484. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  485. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  486. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  487. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  488. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  489. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  490. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  491. package/src/resources/extensions/gsd/workspace.ts +5 -21
  492. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  493. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  494. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  495. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  496. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  497. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  498. /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_buildManifest.js +0 -0
  499. /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_ssgManifest.js +0 -0
@@ -32,6 +32,18 @@ interface ManagedConnection {
32
32
  launch: GsdBrowserMcpLaunchConfig;
33
33
  }
34
34
 
35
+ type ConnectManagedGsdBrowser = (
36
+ launch: GsdBrowserMcpLaunchConfig,
37
+ signal?: AbortSignal,
38
+ ) => Promise<ManagedConnection>;
39
+
40
+ type CloseManagedGsdBrowserConnection = (connection: ManagedConnection) => Promise<void>;
41
+
42
+ interface PendingManagedConnection {
43
+ promise: Promise<ManagedConnection>;
44
+ abortController: AbortController;
45
+ }
46
+
35
47
  interface McpContentItem {
36
48
  type: string;
37
49
  text?: string;
@@ -69,8 +81,6 @@ interface ManagedBrowserToolSpec {
69
81
  compatibility?: { producesImages?: boolean };
70
82
  }
71
83
 
72
- const connections = new Map<string, ManagedConnection>();
73
- const pendingConnections = new Map<string, Promise<ManagedConnection>>();
74
84
  const DEFAULT_MAX_LINES = 2_000;
75
85
  const DEFAULT_MAX_BYTES = 50 * 1024;
76
86
  const MCP_CALL_TIMEOUT_MS = 60_000;
@@ -368,6 +378,120 @@ function buildConnectionKey(launch: GsdBrowserMcpLaunchConfig): string {
368
378
  });
369
379
  }
370
380
 
381
+ function combineAbortSignals(
382
+ poolSignal: AbortSignal,
383
+ callerSignal?: AbortSignal,
384
+ ): AbortSignal {
385
+ if (!callerSignal) return poolSignal;
386
+ if (typeof AbortSignal.any === "function") {
387
+ return AbortSignal.any([poolSignal, callerSignal]);
388
+ }
389
+
390
+ const controller = new AbortController();
391
+ const abort = (): void => {
392
+ if (!controller.signal.aborted) controller.abort();
393
+ };
394
+ if (poolSignal.aborted || callerSignal.aborted) {
395
+ abort();
396
+ } else {
397
+ poolSignal.addEventListener("abort", abort, { once: true });
398
+ callerSignal.addEventListener("abort", abort, { once: true });
399
+ }
400
+ return controller.signal;
401
+ }
402
+
403
+ export class ManagedGsdBrowserConnectionPool {
404
+ private readonly connections = new Map<string, ManagedConnection>();
405
+ private readonly pendingConnections = new Map<string, PendingManagedConnection>();
406
+ private readonly connect: ConnectManagedGsdBrowser;
407
+ private readonly closeConnection: CloseManagedGsdBrowserConnection;
408
+ private generation = 0;
409
+ private closing = false;
410
+
411
+ constructor(
412
+ connect: ConnectManagedGsdBrowser,
413
+ closeConnection: CloseManagedGsdBrowserConnection,
414
+ ) {
415
+ this.connect = connect;
416
+ this.closeConnection = closeConnection;
417
+ }
418
+
419
+ get activeCount(): number {
420
+ return this.connections.size;
421
+ }
422
+
423
+ get pendingCount(): number {
424
+ return this.pendingConnections.size;
425
+ }
426
+
427
+ getOrConnect(
428
+ launch: GsdBrowserMcpLaunchConfig,
429
+ signal?: AbortSignal,
430
+ ): Promise<ManagedConnection> {
431
+ if (this.closing) {
432
+ return Promise.reject(new Error("gsd-browser connection pool is closing"));
433
+ }
434
+
435
+ const key = buildConnectionKey(launch);
436
+ const existing = this.connections.get(key);
437
+ if (existing) return Promise.resolve(existing);
438
+
439
+ const pending = this.pendingConnections.get(key);
440
+ if (pending) return pending.promise;
441
+
442
+ const generation = this.generation;
443
+ const abortController = new AbortController();
444
+ const connectionSignal = combineAbortSignals(abortController.signal, signal);
445
+ const connectionPromise = this.connect(launch, connectionSignal)
446
+ .then(async (connection) => {
447
+ if (this.closing || this.generation !== generation) {
448
+ await this.closeConnection(connection);
449
+ throw new Error("gsd-browser connection closed during startup");
450
+ }
451
+ this.connections.set(key, connection);
452
+ return connection;
453
+ })
454
+ .finally(() => {
455
+ if (this.pendingConnections.get(key)?.promise === connectionPromise) {
456
+ this.pendingConnections.delete(key);
457
+ }
458
+ });
459
+
460
+ this.pendingConnections.set(key, { promise: connectionPromise, abortController });
461
+ return connectionPromise;
462
+ }
463
+
464
+ async closeAll(): Promise<void> {
465
+ this.closing = true;
466
+ this.generation += 1;
467
+
468
+ const activeConnections = Array.from(this.connections.values());
469
+ const pendingConnections = Array.from(this.pendingConnections.values());
470
+ this.connections.clear();
471
+ this.pendingConnections.clear();
472
+ for (const pending of pendingConnections) {
473
+ pending.abortController.abort();
474
+ }
475
+
476
+ const closingActive = activeConnections.map((connection) => this.closeConnection(connection));
477
+ const closingPending = pendingConnections.map(async (pending) => {
478
+ try {
479
+ const connection = await pending.promise;
480
+ await this.closeConnection(connection);
481
+ } catch {
482
+ // Failed or invalidated connection attempts have already cleaned up.
483
+ }
484
+ });
485
+
486
+ try {
487
+ await Promise.allSettled([...closingActive, ...closingPending]);
488
+ } finally {
489
+ this.closing = false;
490
+ this.generation += 1;
491
+ }
492
+ }
493
+ }
494
+
371
495
  async function connectManagedGsdBrowser(
372
496
  launch: GsdBrowserMcpLaunchConfig,
373
497
  signal?: AbortSignal,
@@ -399,6 +523,24 @@ async function connectManagedGsdBrowser(
399
523
  }
400
524
  }
401
525
 
526
+ async function closeManagedGsdBrowserConnection(connection: ManagedConnection): Promise<void> {
527
+ try {
528
+ await connection.client.close();
529
+ } catch {
530
+ // Best-effort cleanup.
531
+ }
532
+ try {
533
+ await connection.transport.close();
534
+ } catch {
535
+ // Best-effort cleanup.
536
+ }
537
+ }
538
+
539
+ const connectionPool = new ManagedGsdBrowserConnectionPool(
540
+ connectManagedGsdBrowser,
541
+ closeManagedGsdBrowserConnection,
542
+ );
543
+
402
544
  async function getOrConnectManagedGsdBrowser(
403
545
  ctx?: ExtensionContext,
404
546
  signal?: AbortSignal,
@@ -406,22 +548,7 @@ async function getOrConnectManagedGsdBrowser(
406
548
  const launch = resolveGsdBrowserMcpLaunchConfig(resolveProjectRoot(ctx), process.env, {
407
549
  sessionSuffix: resolveManagedSessionSuffix(ctx),
408
550
  });
409
- const key = buildConnectionKey(launch);
410
- const existing = connections.get(key);
411
- if (existing) return existing;
412
-
413
- const pending = pendingConnections.get(key);
414
- if (pending) return pending;
415
-
416
- const connectionPromise = connectManagedGsdBrowser(launch, signal);
417
- pendingConnections.set(key, connectionPromise);
418
- try {
419
- const connection = await connectionPromise;
420
- connections.set(key, connection);
421
- return connection;
422
- } finally {
423
- pendingConnections.delete(key);
424
- }
551
+ return connectionPool.getOrConnect(launch, signal);
425
552
  }
426
553
 
427
554
  function isUnknownMcpToolError(error: unknown): boolean {
@@ -743,21 +870,7 @@ export function registerManagedGsdBrowserTools(pi: ExtensionAPI): void {
743
870
  }
744
871
 
745
872
  export async function closeManagedGsdBrowser(): Promise<void> {
746
- const closing = Array.from(connections.entries()).map(async ([key, connection]) => {
747
- try {
748
- await connection.client.close();
749
- } catch {
750
- // Best-effort cleanup.
751
- }
752
- try {
753
- await connection.transport.close();
754
- } catch {
755
- // Best-effort cleanup.
756
- }
757
- connections.delete(key);
758
- });
759
- await Promise.allSettled(closing);
760
- pendingConnections.clear();
873
+ await connectionPool.closeAll();
761
874
  }
762
875
 
763
876
  export async function _resetManagedGsdBrowserForTest(): Promise<void> {
@@ -55,6 +55,31 @@ describe("resolveGsdBrowserMcpLaunchConfig identity flags", () => {
55
55
  assert.equal(args[1], "mcp");
56
56
  });
57
57
 
58
+ it("appends GSD_BROWSER_MCP_EXTRA_ARGS after the identity flags (string form)", () => {
59
+ const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example-project", {
60
+ GSD_BROWSER_MCP_EXTRA_ARGS: "--stealth --browser-path /usr/bin/chromium",
61
+ });
62
+
63
+ // Managed flags stay intact and the extra flags trail them.
64
+ assert.ok(args.indexOf("--identity-project") >= 0);
65
+ assert.deepEqual(args.slice(-3), ["--stealth", "--browser-path", "/usr/bin/chromium"]);
66
+ assert.ok(args.indexOf("--stealth") > args.indexOf("--identity-project"));
67
+ });
68
+
69
+ it("accepts GSD_BROWSER_MCP_EXTRA_ARGS as a JSON array", () => {
70
+ const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example-project", {
71
+ GSD_BROWSER_MCP_EXTRA_ARGS: '["--stealth"]',
72
+ });
73
+ assert.equal(args[args.length - 1], "--stealth");
74
+ });
75
+
76
+ it("forwards GSD_BROWSER_MCP_EXTRA_ARGS to the daemon-start invocation", () => {
77
+ const env = { GSD_BROWSER_MCP_EXTRA_ARGS: "--stealth" };
78
+ const daemon = resolveGsdBrowserDaemonStartInvocation("/tmp/example-project", env);
79
+ assert.equal(daemon.args[daemon.args.length - 1], "--stealth");
80
+ assert.ok(daemon.args.indexOf("daemon") >= 0 && daemon.args.indexOf("start") >= 0);
81
+ });
82
+
58
83
  it("uses a path-safe identity-project identifier", () => {
59
84
  const { args } = resolveGsdBrowserMcpLaunchConfig("/tmp/example/project", {});
60
85
  const projectId = args[args.indexOf("--identity-project") + 1];
@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
4
4
  const {
5
5
  MANAGED_BROWSER_TOOL_SPECS,
6
6
  MANAGED_GSD_BROWSER_TOOL_NAMES,
7
+ ManagedGsdBrowserConnectionPool,
7
8
  findMissingContractCoverage,
8
9
  normalizeManagedArgs,
9
10
  registerManagedGsdBrowserTools,
@@ -29,6 +30,17 @@ const GSD_BROWSER_SERVED_TOOLS = [
29
30
  "browser_act",
30
31
  ];
31
32
 
33
+ function makeLaunchConfig() {
34
+ return {
35
+ command: "gsd-browser",
36
+ args: ["mcp"],
37
+ cwd: "/tmp/example-project",
38
+ projectRoot: "/tmp/example-project",
39
+ serverName: "gsd-browser",
40
+ sessionName: "test-session",
41
+ };
42
+ }
43
+
32
44
  describe("registerManagedGsdBrowserTools", () => {
33
45
  it("registers the curated Pi browser contract", () => {
34
46
  const tools = [];
@@ -72,6 +84,87 @@ describe("findMissingContractCoverage", () => {
72
84
  });
73
85
  });
74
86
 
87
+ describe("ManagedGsdBrowserConnectionPool", () => {
88
+ it("closes a pending connection that resolves after the pool is closed", async () => {
89
+ const closed = [];
90
+ let resolveConnect;
91
+ const pool = new ManagedGsdBrowserConnectionPool(async () => {
92
+ return new Promise((resolve) => {
93
+ resolveConnect = resolve;
94
+ });
95
+ }, async (connection) => {
96
+ closed.push(connection.id);
97
+ });
98
+
99
+ const launch = makeLaunchConfig();
100
+
101
+ const pending = pool.getOrConnect(launch);
102
+ const closedPool = pool.closeAll();
103
+ resolveConnect({ id: "late-connection" });
104
+
105
+ await assert.rejects(
106
+ pending,
107
+ /closed during startup/,
108
+ "pending callers must not receive a reusable connection after close",
109
+ );
110
+ await closedPool;
111
+
112
+ assert.deepEqual(closed, ["late-connection"]);
113
+ assert.equal(pool.activeCount, 0);
114
+ assert.equal(pool.pendingCount, 0);
115
+ });
116
+
117
+ it("aborts pending connection attempts when the pool is closed", async () => {
118
+ let receivedSignal;
119
+ const pool = new ManagedGsdBrowserConnectionPool(async (_launch, signal) => {
120
+ receivedSignal = signal;
121
+ return new Promise((_resolve, reject) => {
122
+ signal.addEventListener("abort", () => reject(new Error("connect aborted")), { once: true });
123
+ });
124
+ }, async () => {});
125
+
126
+ const pending = pool.getOrConnect(makeLaunchConfig());
127
+ const closedPool = pool.closeAll();
128
+
129
+ await assert.rejects(pending, /connect aborted/);
130
+ await closedPool;
131
+
132
+ assert.equal(receivedSignal?.aborted, true);
133
+ assert.equal(pool.activeCount, 0);
134
+ assert.equal(pool.pendingCount, 0);
135
+ });
136
+
137
+ it("rejects new connection attempts started while the pool is closing", async () => {
138
+ let resolveConnect;
139
+ let connectCount = 0;
140
+ const pool = new ManagedGsdBrowserConnectionPool(async () => {
141
+ connectCount += 1;
142
+ return new Promise((resolve) => {
143
+ resolveConnect = resolve;
144
+ });
145
+ }, async () => {});
146
+
147
+ const pending = pool.getOrConnect(makeLaunchConfig());
148
+ const closedPool = pool.closeAll();
149
+
150
+ // A caller that arrives during the in-flight closeAll must not open a new
151
+ // connection that could survive shutdown.
152
+ await assert.rejects(
153
+ pool.getOrConnect(makeLaunchConfig()),
154
+ /closing/,
155
+ "getOrConnect must reject while the pool is closing",
156
+ );
157
+
158
+ resolveConnect({ id: "pending-connection" });
159
+ await assert.rejects(pending, /closed during startup/);
160
+ await closedPool;
161
+
162
+ assert.equal(connectCount, 1, "no second connection is started during shutdown");
163
+ assert.equal(pool.activeCount, 0);
164
+ assert.equal(pool.pendingCount, 0);
165
+ });
166
+ });
167
+
75
168
  describe("contract tool translations", () => {
76
169
  it("translates browser_click into a single-step batch call", () => {
77
170
  const calls = MANAGED_BROWSER_TOOL_SPECS.browser_click.translate.build({ selector: "#save" });
@@ -138,7 +138,7 @@ export function saveActivityLog(
138
138
  state.nextSeq += 1;
139
139
  state.lastSnapshotKeyByUnit.set(unitKey, key);
140
140
 
141
- if (isUnifiedAuditEnabled()) {
141
+ if (isUnifiedAuditEnabled(basePath)) {
142
142
  emitUokAuditEvent(
143
143
  basePath,
144
144
  buildAuditEnvelope({
@@ -3,8 +3,10 @@
3
3
 
4
4
  import { importExtensionModule, type ExtensionAPI, type ExtensionContext } from "@gsd/pi-coding-agent";
5
5
 
6
+ import { execFileSync } from "node:child_process";
7
+ import { createHash } from "node:crypto";
6
8
  import { join, basename } from "node:path";
7
- import { existsSync, cpSync } from "node:fs";
9
+ import { existsSync, cpSync, readFileSync } from "node:fs";
8
10
  import type { AutoSession } from "./session.js";
9
11
  import type { LoopDeps } from "./loop-deps.js";
10
12
  import type { GSDState } from "../types.js";
@@ -16,7 +18,7 @@ import { getIsolationMode } from "../preferences.js";
16
18
  import { isDbAvailable, getMilestone } from "../gsd-db.js";
17
19
  import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
18
20
  import { isClosedStatus } from "../status-guards.js";
19
- import { gsdRoot } from "../paths.js";
21
+ import { gsdRoot, PLANNING_ARTIFACT_SUFFIXES } from "../paths.js";
20
22
  import { atomicWriteSync } from "../atomic-write.js";
21
23
  import { logWarning, logError } from "../workflow-logger.js";
22
24
  import { debugLog } from "../debug-logger.js";
@@ -98,7 +100,15 @@ export async function _runMilestoneMergeWithStashRestore(
98
100
  ): Promise<{ action: "break"; reason: string } | null> {
99
101
  const { ctx, pi, s, deps } = ic;
100
102
 
101
- const projectRoot = s.originalBasePath || s.basePath;
103
+ // Resolve the project root once, before the merge can flip session path
104
+ // pointers (worktree teardown mutates s.basePath / s.scope). The guarded
105
+ // merge's preflight/postflight stash restore, the pre-merge ignored-markdown
106
+ // snapshot, the dirty-projection check, and the rebuild must all target this
107
+ // same tree; otherwise `.gsd` markdown restored on one tree can be silently
108
+ // overwritten by a rebuild on another. canonicalProjectRoot is the
109
+ // authoritative `.gsd`/DB writer path (see AutoSession.canonicalProjectRoot).
110
+ const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
111
+ const ignoredProjectionSnapshot = snapshotIgnoredGsdMarkdownProjections(projectRoot);
102
112
  const mergeResult = deps.lifecycle.exitMilestone(
103
113
  milestoneId,
104
114
  {
@@ -113,12 +123,12 @@ export async function _runMilestoneMergeWithStashRestore(
113
123
  );
114
124
 
115
125
  if (mergeResult.ok) {
116
- await markMilestoneMergedAndRebuild(s);
126
+ await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
117
127
  return null;
118
128
  }
119
129
 
120
130
  if (mergeResult.reason === "postflight-stash-restore-failed") {
121
- await markMilestoneMergedAndRebuild(s);
131
+ await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
122
132
  }
123
133
 
124
134
  if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
@@ -180,10 +190,23 @@ export async function _runMilestoneMergeWithStashRestore(
180
190
  return null;
181
191
  }
182
192
 
183
- async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
193
+ type GsdMarkdownSnapshot = Map<string, string>;
194
+
195
+ async function markMilestoneMergedAndRebuild(
196
+ s: AutoSession,
197
+ rebuildBasePath: string,
198
+ ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
199
+ ): Promise<void> {
184
200
  s.milestoneMergedInPhases = true;
201
+ if (hasDirtyGsdMarkdownProjections(rebuildBasePath, ignoredProjectionSnapshot)) {
202
+ logWarning(
203
+ "engine",
204
+ "skipping markdown projection rebuild after milestone merge because restored .gsd edits are dirty",
205
+ );
206
+ return;
207
+ }
208
+
185
209
  try {
186
- const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
187
210
  const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
188
211
  await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
189
212
  } catch (err) {
@@ -194,6 +217,90 @@ async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
194
217
  }
195
218
  }
196
219
 
220
+ function hasDirtyGsdMarkdownProjections(
221
+ basePath: string,
222
+ ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
223
+ ): boolean {
224
+ try {
225
+ const output = execFileSync(
226
+ "git",
227
+ ["status", "--porcelain", "-z", "--untracked-files=all", "--", ".gsd"],
228
+ { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
229
+ );
230
+ const entries = output.split("\0").filter(Boolean);
231
+ for (let i = 0; i < entries.length; i++) {
232
+ const entry = entries[i];
233
+ if (entry.length <= 3) continue;
234
+ const status = entry.slice(0, 2);
235
+ const path = entry.slice(3);
236
+ if (isGeneratedGsdMarkdownProjection(path)) return true;
237
+ if ((status === "R " || status === "C ") && i + 1 < entries.length) {
238
+ const dest = entries[++i];
239
+ if (isGeneratedGsdMarkdownProjection(dest)) return true;
240
+ }
241
+ }
242
+ } catch (err) {
243
+ void err;
244
+ // Fall through to ignored-file snapshot comparison below.
245
+ }
246
+ return ignoredGsdMarkdownProjectionSnapshotChanged(basePath, ignoredProjectionSnapshot);
247
+ }
248
+
249
+ function isGeneratedGsdMarkdownProjection(path: string): boolean {
250
+ const normalized = path.replace(/\\/g, "/");
251
+ if (normalized === ".gsd/DECISIONS.md") return true;
252
+ if (!normalized.startsWith(".gsd/milestones/") && !normalized.startsWith(".gsd/phases/")) {
253
+ return false;
254
+ }
255
+ return isGeneratedPlanningArtifactFileName(basename(normalized));
256
+ }
257
+
258
+ function isGeneratedPlanningArtifactFileName(fileName: string): boolean {
259
+ if (!fileName.endsWith(".md")) return false;
260
+ const stem = fileName.slice(0, -".md".length).toUpperCase();
261
+ for (const suffix of PLANNING_ARTIFACT_SUFFIXES) {
262
+ if (!stem.endsWith(`-${suffix}`)) continue;
263
+ const idPrefix = stem.slice(0, -suffix.length - 1);
264
+ return /^(?:[MST]\d+|\d{2,})(?:-[A-Z0-9]+)*$/.test(idPrefix);
265
+ }
266
+ return false;
267
+ }
268
+
269
+ function snapshotIgnoredGsdMarkdownProjections(basePath: string): GsdMarkdownSnapshot | null {
270
+ try {
271
+ const output = execFileSync(
272
+ "git",
273
+ ["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", ".gsd"],
274
+ { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
275
+ );
276
+ const snapshot: GsdMarkdownSnapshot = new Map();
277
+ for (const path of output.split("\0")) {
278
+ if (!isGeneratedGsdMarkdownProjection(path)) continue;
279
+ snapshot.set(path, hashFile(join(basePath, path)));
280
+ }
281
+ return snapshot;
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ function hashFile(path: string): string {
288
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
289
+ }
290
+
291
+ function ignoredGsdMarkdownProjectionSnapshotChanged(
292
+ basePath: string,
293
+ before: GsdMarkdownSnapshot | null,
294
+ ): boolean {
295
+ if (!before) return true;
296
+ const after = snapshotIgnoredGsdMarkdownProjections(basePath);
297
+ if (!after || after.size !== before.size) return true;
298
+ for (const [path, hash] of before) {
299
+ if (after.get(path) !== hash) return true;
300
+ }
301
+ return false;
302
+ }
303
+
197
304
  export async function _runMilestoneMergeOnceWithStashRestore(
198
305
  ic: IterationContext,
199
306
  milestoneId: string,
@@ -34,7 +34,8 @@ import {
34
34
  preDispatchHealthGate,
35
35
  recordHealthSnapshot,
36
36
  } from "../doctor-proactive.js";
37
- import { checkResourcesStale, autoWorktreeBranch, mergeMilestoneToMain } from "../auto-worktree.js";
37
+ import { autoWorktreeBranch } from "../auto-worktree-branch-lifecycle.js";
38
+ import { checkResourcesStale } from "../auto-worktree-resource-version.js";
38
39
  import { getSessionLockStatus } from "../session-lock.js";
39
40
  import { resolveUokFlags } from "../uok/flags.js";
40
41
  import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
@@ -49,7 +50,7 @@ import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
49
50
  import { GitServiceImpl } from "../git-service.js";
50
51
  import { WorktreeStateProjection } from "../worktree-state-projection.js";
51
52
  import { WorktreeLifecycle } from "../worktree-lifecycle.js";
52
- import { createMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
53
+ import { createDefaultMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
53
54
  import { createWorkspace, scopeMilestone } from "../workspace.js";
54
55
  import { supportsStructuredQuestions } from "../workflow-mcp.js";
55
56
  import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
@@ -731,7 +732,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
731
732
  return new GitServiceImpl(basePath, gitConfig);
732
733
  },
733
734
  worktreeProjection: new WorktreeStateProjection(),
734
- mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
735
+ mergeMilestone: createDefaultMilestoneMergeTransaction(),
735
736
  });
736
737
  }
737
738
 
@@ -1195,17 +1196,23 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
1195
1196
  return skipped;
1196
1197
  }
1197
1198
 
1198
- // Stuck-loop detection: when the window is saturated with copies of
1199
- // `nextKey` (count >= STUCK_WINDOW_SIZE), consult the Dispatch History
1200
- // module's full detect-stuck rule set for the verdict instead of the old
1201
- // bare saturation count. This keeps the saturation threshold (the window
1202
- // deliberately records benign idempotent repeats, so earlier-firing
1203
- // rules would false-positive on pause/resume re-advances) while gaining
1204
- // retry-budget suppression and diagnosable rule reasons. A saturated
1205
- // window with no verdict means the dispatch ledger says we are inside
1206
- // the unit's retry-backoff budget let the retry proceed.
1207
- const stuckVerdict =
1208
- matchingCount >= STUCK_WINDOW_SIZE ? this.dispatchHistory.detectStuck() : null;
1199
+ // Stuck-loop detection: consult the Dispatch History module's full
1200
+ // detect-stuck rule set once the window is *full* — not only when a
1201
+ // single key saturates it. Gating on a single key's saturation count
1202
+ // (matchingCount >= STUCK_WINDOW_SIZE) let two-unit oscillations slip
1203
+ // through forever: an execute-task complete-slice loop (a slice gate
1204
+ // that keeps reopening the same task, #1225) fills the window with two
1205
+ // alternating keys, so neither key ever reaches the saturation count and
1206
+ // detect-stuck's oscillation/repeat rules were never even invoked. Firing
1207
+ // on window saturation instead covers both shapes. Consecutive same-key
1208
+ // re-advances (the benign pause/resume churn the old count gate guarded
1209
+ // against) are already short-circuited above by the idempotency skip, and
1210
+ // legitimate retry backoff is still suppressed inside detect-stuck via the
1211
+ // dispatch ledger — a saturated window with no verdict means we are inside
1212
+ // the unit's retry-backoff budget, so let the retry proceed.
1213
+ const windowSaturated =
1214
+ this.dispatchHistory.getRecentWindow().length >= STUCK_WINDOW_SIZE;
1215
+ const stuckVerdict = windowSaturated ? this.dispatchHistory.detectStuck() : null;
1209
1216
  if (stuckVerdict) {
1210
1217
  // #442: before declaring a stuck loop, verify the unit didn't actually
1211
1218
  // complete on disk (stale DB) and recover if so — legacy graduated
@@ -440,7 +440,8 @@ export async function runPreDispatch(
440
440
  if (stop) return stop;
441
441
  }
442
442
 
443
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
443
+ // PR creation (auto_pr) is handled by the publication handoff in
444
+ // the milestone merge transaction (#2302).
444
445
 
445
446
  deps.invalidateAllCaches();
446
447
 
@@ -551,7 +552,8 @@ export async function runPreDispatch(
551
552
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
552
553
  const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
553
554
  if (stop) return stop;
554
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
555
+ // PR creation (auto_pr) is handled by the publication handoff in
556
+ // the milestone merge transaction (#2302).
555
557
  }
556
558
  const unmappedActive = countUnmappedActiveRequirements();
557
559
  const completionStopReason = formatCompletePhaseNextAction(unmappedActive);
@@ -654,7 +656,8 @@ export async function runPreDispatch(
654
656
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
655
657
  const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
656
658
  if (stop) return stop;
657
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
659
+ // PR creation (auto_pr) is handled by the publication handoff in
660
+ // the milestone merge transaction (#2302).
658
661
  }
659
662
  deps.sendDesktopNotification(
660
663
  "GSD",
@@ -29,7 +29,7 @@ import type { MinimalModelRegistry } from "../context-budget.js";
29
29
  import { parseUnitId } from "../unit-id.js";
30
30
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
31
31
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
32
- import { getUnitWorkflowDispatchReadinessError } from "../tool-contract.js";
32
+ import { getUnitWorkflowDispatchReadinessErrorForModel } from "../tool-contract.js";
33
33
  import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
34
34
  import {
35
35
  applyThinkingLevelForModel,
@@ -343,17 +343,13 @@ export async function runUnitPhase(
343
343
  ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
344
344
  : null;
345
345
 
346
- const compatibilityError = getUnitWorkflowDispatchReadinessError({
347
- provider: s.currentUnitModel?.provider ?? ctx.model?.provider,
346
+ const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
347
+ model: s.currentUnitModel as any,
348
+ fallbackModel: ctx.model,
349
+ getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
348
350
  projectRoot: s.basePath,
349
351
  surface: "auto-mode",
350
352
  unitType,
351
- authMode: s.currentUnitModel?.provider
352
- ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
353
- : ctx.model?.provider
354
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
355
- : undefined,
356
- baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
357
353
  activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
358
354
  });
359
355
  const workflowMcpPrepModel = s.currentUnitModel;