gsd-pi 2.81.0 → 2.82.0-dev.20138ae42

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 (740) hide show
  1. package/README.md +64 -33
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +10 -1
  4. package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
  5. package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
  6. package/dist/resources/extensions/claude-code-cli/partial-builder.js +2 -1
  7. package/dist/resources/extensions/cmux/index.js +5 -0
  8. package/dist/resources/extensions/gsd/auto/infra-errors.js +9 -3
  9. package/dist/resources/extensions/gsd/auto/loop.js +116 -13
  10. package/dist/resources/extensions/gsd/auto/orchestrator.js +124 -6
  11. package/dist/resources/extensions/gsd/auto/phases.js +220 -98
  12. package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
  13. package/dist/resources/extensions/gsd/auto/session.js +9 -0
  14. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
  15. package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +12 -0
  16. package/dist/resources/extensions/gsd/auto-dashboard.js +247 -178
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +27 -17
  18. package/dist/resources/extensions/gsd/auto-model-selection.js +2 -0
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +240 -128
  20. package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
  21. package/dist/resources/extensions/gsd/auto-recovery.js +34 -179
  22. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
  23. package/dist/resources/extensions/gsd/auto-start.js +105 -35
  24. package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
  25. package/dist/resources/extensions/gsd/auto-verification.js +38 -26
  26. package/dist/resources/extensions/gsd/auto-worktree.js +119 -1
  27. package/dist/resources/extensions/gsd/auto.js +400 -108
  28. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +38 -12
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +9 -8
  30. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -4
  31. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
  32. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +21 -9
  33. package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
  34. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -2
  35. package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
  36. package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
  37. package/dist/resources/extensions/gsd/commands/handlers/core.js +38 -1
  38. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
  39. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
  40. package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
  41. package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
  42. package/dist/resources/extensions/gsd/context-store.js +112 -0
  43. package/dist/resources/extensions/gsd/crash-recovery.js +31 -5
  44. package/dist/resources/extensions/gsd/db/unit-dispatches.js +3 -2
  45. package/dist/resources/extensions/gsd/db-writer.js +150 -84
  46. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -2
  47. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  48. package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
  49. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +28 -11
  50. package/dist/resources/extensions/gsd/doctor.js +2 -28
  51. package/dist/resources/extensions/gsd/export-html.js +27 -425
  52. package/dist/resources/extensions/gsd/git-service.js +41 -2
  53. package/dist/resources/extensions/gsd/gsd-db.js +8 -23
  54. package/dist/resources/extensions/gsd/guided-flow.js +93 -111
  55. package/dist/resources/extensions/gsd/guided-unit-context.js +23 -0
  56. package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
  57. package/dist/resources/extensions/gsd/health-widget.js +4 -10
  58. package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
  59. package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
  60. package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
  61. package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
  62. package/dist/resources/extensions/gsd/markdown-renderer.js +6 -96
  63. package/dist/resources/extensions/gsd/md-importer.js +1 -1
  64. package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
  65. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
  66. package/dist/resources/extensions/gsd/migrate/command.js +5 -0
  67. package/dist/resources/extensions/gsd/migrate/parsers.js +10 -0
  68. package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
  69. package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
  70. package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
  71. package/dist/resources/extensions/gsd/migration-auto-check.js +12 -17
  72. package/dist/resources/extensions/gsd/milestone-actions.js +11 -4
  73. package/dist/resources/extensions/gsd/native-git-bridge.js +62 -26
  74. package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
  75. package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
  76. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
  77. package/dist/resources/extensions/gsd/pending-auto-start.js +52 -0
  78. package/dist/resources/extensions/gsd/post-execution-checks.js +73 -2
  79. package/dist/resources/extensions/gsd/pre-execution-checks.js +28 -1
  80. package/dist/resources/extensions/gsd/prompt-loader.js +1 -1
  81. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  82. package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  83. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +28 -10
  84. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -11
  85. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  86. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  87. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -4
  88. package/dist/resources/extensions/gsd/prompts/queue.md +4 -4
  89. package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  90. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  91. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  92. package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
  93. package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
  94. package/dist/resources/extensions/gsd/session-lock.js +40 -0
  95. package/dist/resources/extensions/gsd/smart-entry-routing.js +36 -0
  96. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
  97. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +252 -0
  98. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +45 -0
  99. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +82 -0
  100. package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
  101. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
  102. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
  103. package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
  104. package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
  105. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
  106. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
  107. package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
  108. package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
  109. package/dist/resources/extensions/gsd/status-guards.js +4 -0
  110. package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
  111. package/dist/resources/extensions/gsd/templates/plan.md +8 -5
  112. package/dist/resources/extensions/gsd/templates/task-plan.md +4 -2
  113. package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -8
  114. package/dist/resources/extensions/gsd/tools/complete-slice.js +6 -8
  115. package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -1
  116. package/dist/resources/extensions/gsd/tools/plan-slice.js +89 -14
  117. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
  118. package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
  119. package/dist/resources/extensions/gsd/unit-context-manifest.js +32 -10
  120. package/dist/resources/extensions/gsd/validation.js +23 -1
  121. package/dist/resources/extensions/gsd/verification-gate.js +68 -7
  122. package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
  123. package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
  124. package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
  125. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  126. package/dist/resources/extensions/gsd/workflow-projections.js +6 -8
  127. package/dist/resources/extensions/gsd/worktree-lifecycle.js +769 -319
  128. package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
  129. package/dist/resources/extensions/shared/html-shell.js +388 -0
  130. package/dist/resources/extensions/subagent/index.js +448 -78
  131. package/dist/resources/extensions/subagent/launch.js +77 -0
  132. package/dist/resources/extensions/subagent/run-store.js +148 -0
  133. package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
  134. package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
  135. package/dist/resources/extensions/visual-brief/index.js +5 -0
  136. package/dist/resources/extensions/visual-brief/page-contract.js +124 -0
  137. package/dist/resources/extensions/visual-brief/prompts.js +140 -0
  138. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  139. package/dist/web/standalone/.next/BUILD_ID +1 -1
  140. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  141. package/dist/web/standalone/.next/build-manifest.json +4 -4
  142. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  143. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  144. package/dist/web/standalone/.next/required-server-files.json +3 -3
  145. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  146. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  148. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  155. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  156. package/dist/web/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  158. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  159. package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -7
  160. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  161. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  162. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -5
  163. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  164. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  166. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  173. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  185. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  205. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  206. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  207. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  209. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  210. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  211. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  213. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  215. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  217. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  218. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  219. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  220. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  221. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  222. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  223. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  224. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  225. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  226. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  227. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  228. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  229. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  230. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  231. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  232. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  233. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  234. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  235. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  236. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  237. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  238. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  239. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  240. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  241. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  242. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  243. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  244. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  245. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  246. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  247. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  248. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  249. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  250. package/dist/web/standalone/.next/server/app/index.html +1 -1
  251. package/dist/web/standalone/.next/server/app/index.rsc +5 -8
  252. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  253. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -8
  254. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  255. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -5
  256. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  257. package/dist/web/standalone/.next/server/app/page.js +2 -2
  258. package/dist/web/standalone/.next/server/app/page.js.nft.json +1 -1
  259. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  260. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  261. package/dist/web/standalone/.next/server/chunks/4266.js +2 -0
  262. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  263. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  264. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  265. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  266. package/dist/web/standalone/.next/server/middleware.js +2 -2
  267. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  268. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  269. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  270. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  271. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  272. package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
  273. package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
  274. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  275. package/dist/web/standalone/.next/static/chunks/app/layout-8c10ec293ae0f1d5.js +1 -0
  276. package/dist/web/standalone/.next/static/chunks/app/page-752f1e2ebdaa3e45.js +1 -0
  277. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  278. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  279. package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-9a4db269f9ed63ad.js} +1 -1
  280. package/dist/web/standalone/.next/static/css/746ee28c929d1880.css +1 -0
  281. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  282. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  283. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  284. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  285. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  286. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  287. package/dist/web/standalone/server.js +1 -1
  288. package/dist/welcome-screen.d.ts +0 -7
  289. package/dist/welcome-screen.js +60 -69
  290. package/package.json +6 -5
  291. package/packages/contracts/dist/rpc.test.js +7 -0
  292. package/packages/contracts/dist/rpc.test.js.map +1 -1
  293. package/packages/contracts/dist/workflow.d.ts +21 -0
  294. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  295. package/packages/contracts/dist/workflow.js +24 -0
  296. package/packages/contracts/dist/workflow.js.map +1 -1
  297. package/packages/contracts/src/rpc.test.ts +8 -0
  298. package/packages/contracts/src/workflow.ts +24 -0
  299. package/packages/daemon/package.json +2 -2
  300. package/packages/mcp-server/README.md +14 -3
  301. package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
  302. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  303. package/packages/mcp-server/dist/workflow-tools.js +80 -0
  304. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  305. package/packages/mcp-server/package.json +2 -2
  306. package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
  307. package/packages/mcp-server/src/workflow-tools.test.ts +23 -1
  308. package/packages/mcp-server/src/workflow-tools.ts +168 -0
  309. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  310. package/packages/native/package.json +1 -1
  311. package/packages/native/tsconfig.json +2 -1
  312. package/packages/native/tsconfig.tsbuildinfo +1 -1
  313. package/packages/pi-agent-core/package.json +1 -1
  314. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  315. package/packages/pi-ai/dist/index.d.ts +2 -2
  316. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  317. package/packages/pi-ai/dist/index.js +1 -1
  318. package/packages/pi-ai/dist/index.js.map +1 -1
  319. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  320. package/packages/pi-ai/dist/providers/openai-codex-responses.js +82 -1
  321. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  322. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts +2 -0
  323. package/packages/pi-ai/dist/providers/openai-codex-responses.test.d.ts.map +1 -0
  324. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +52 -0
  325. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -0
  326. package/packages/pi-ai/dist/providers/simple-options.d.ts +2 -4
  327. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  328. package/packages/pi-ai/dist/providers/simple-options.js +5 -6
  329. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  330. package/packages/pi-ai/dist/providers/simple-options.test.d.ts +2 -0
  331. package/packages/pi-ai/dist/providers/simple-options.test.d.ts.map +1 -0
  332. package/packages/pi-ai/dist/providers/simple-options.test.js +50 -0
  333. package/packages/pi-ai/dist/providers/simple-options.test.js.map +1 -0
  334. package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
  335. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  336. package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
  337. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  338. package/packages/pi-ai/package.json +1 -1
  339. package/packages/pi-ai/src/index.ts +7 -2
  340. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +63 -0
  341. package/packages/pi-ai/src/providers/openai-codex-responses.ts +91 -1
  342. package/packages/pi-ai/src/providers/simple-options.test.ts +60 -0
  343. package/packages/pi-ai/src/providers/simple-options.ts +5 -6
  344. package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
  345. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  346. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts +2 -0
  347. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.d.ts.map +1 -0
  348. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js +66 -0
  349. package/packages/pi-coding-agent/dist/core/agent-session-thinking-level.test.js.map +1 -0
  350. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  351. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  352. package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
  353. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  354. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
  355. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
  356. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
  357. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
  358. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
  359. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  360. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
  361. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
  362. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
  363. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
  364. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
  365. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
  366. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
  367. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
  369. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
  370. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  371. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
  372. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  373. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
  374. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  375. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
  376. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  377. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
  378. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  379. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
  380. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  381. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  382. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +33 -42
  383. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  384. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  385. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  386. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
  387. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  388. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
  389. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
  390. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
  391. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
  392. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
  393. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
  394. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
  395. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
  396. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
  397. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  398. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
  399. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  400. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  401. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  402. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  403. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  404. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  405. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
  406. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  407. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  408. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
  409. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  410. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
  411. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  412. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
  413. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
  414. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
  415. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
  416. package/packages/pi-coding-agent/package.json +1 -1
  417. package/packages/pi-coding-agent/src/core/agent-session-thinking-level.test.ts +79 -0
  418. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  419. package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
  420. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
  421. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
  422. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
  423. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
  424. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
  425. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
  426. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
  427. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
  428. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +33 -44
  429. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
  430. package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
  431. package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
  432. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
  433. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
  434. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
  435. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
  436. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
  437. package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
  438. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  439. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
  440. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  441. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  442. package/packages/pi-tui/dist/overlay-layout.js +9 -6
  443. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  444. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  445. package/packages/pi-tui/dist/tui.js +5 -0
  446. package/packages/pi-tui/dist/tui.js.map +1 -1
  447. package/packages/pi-tui/package.json +1 -1
  448. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
  449. package/packages/pi-tui/src/overlay-layout.ts +10 -7
  450. package/packages/pi-tui/src/tui.ts +6 -0
  451. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  452. package/packages/rpc-client/package.json +1 -1
  453. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  454. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  455. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  456. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  457. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  458. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  459. package/pkg/dist/modes/interactive/theme/theme.js +105 -1
  460. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  461. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  462. package/pkg/dist/modes/interactive/theme/themes.js +27 -26
  463. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  464. package/pkg/package.json +1 -1
  465. package/src/resources/GSD-WORKFLOW.md +10 -1
  466. package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
  467. package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
  468. package/src/resources/extensions/claude-code-cli/partial-builder.ts +2 -1
  469. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +19 -2
  470. package/src/resources/extensions/cmux/index.ts +6 -0
  471. package/src/resources/extensions/gsd/auto/contracts.ts +59 -16
  472. package/src/resources/extensions/gsd/auto/infra-errors.ts +9 -3
  473. package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
  474. package/src/resources/extensions/gsd/auto/loop.ts +121 -14
  475. package/src/resources/extensions/gsd/auto/orchestrator.ts +129 -6
  476. package/src/resources/extensions/gsd/auto/phases.ts +180 -20
  477. package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
  478. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  479. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
  480. package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +13 -0
  481. package/src/resources/extensions/gsd/auto-dashboard.ts +301 -183
  482. package/src/resources/extensions/gsd/auto-dispatch.ts +29 -7
  483. package/src/resources/extensions/gsd/auto-model-selection.ts +2 -1
  484. package/src/resources/extensions/gsd/auto-post-unit.ts +273 -140
  485. package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
  486. package/src/resources/extensions/gsd/auto-recovery.ts +36 -209
  487. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  488. package/src/resources/extensions/gsd/auto-start.ts +114 -31
  489. package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
  490. package/src/resources/extensions/gsd/auto-verification.ts +46 -38
  491. package/src/resources/extensions/gsd/auto-worktree.ts +127 -1
  492. package/src/resources/extensions/gsd/auto.ts +441 -107
  493. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +54 -12
  494. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +9 -8
  495. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +16 -4
  496. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
  497. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +19 -7
  498. package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
  499. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +19 -3
  500. package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
  501. package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
  502. package/src/resources/extensions/gsd/commands/handlers/core.ts +41 -1
  503. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
  504. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
  505. package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
  506. package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
  507. package/src/resources/extensions/gsd/context-store.ts +120 -1
  508. package/src/resources/extensions/gsd/crash-recovery.ts +30 -4
  509. package/src/resources/extensions/gsd/db/unit-dispatches.ts +4 -3
  510. package/src/resources/extensions/gsd/db-writer.ts +167 -84
  511. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -2
  512. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  513. package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
  514. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +25 -13
  515. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  516. package/src/resources/extensions/gsd/doctor.ts +2 -27
  517. package/src/resources/extensions/gsd/export-html.ts +27 -427
  518. package/src/resources/extensions/gsd/git-service.ts +47 -1
  519. package/src/resources/extensions/gsd/gsd-db.ts +10 -23
  520. package/src/resources/extensions/gsd/guided-flow.ts +126 -128
  521. package/src/resources/extensions/gsd/guided-unit-context.ts +30 -0
  522. package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
  523. package/src/resources/extensions/gsd/health-widget.ts +6 -10
  524. package/src/resources/extensions/gsd/journal.ts +2 -0
  525. package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
  526. package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
  527. package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
  528. package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
  529. package/src/resources/extensions/gsd/markdown-renderer.ts +10 -96
  530. package/src/resources/extensions/gsd/md-importer.ts +1 -1
  531. package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
  532. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
  533. package/src/resources/extensions/gsd/migrate/command.ts +5 -0
  534. package/src/resources/extensions/gsd/migrate/parsers.ts +11 -0
  535. package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
  536. package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
  537. package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
  538. package/src/resources/extensions/gsd/migration-auto-check.ts +15 -23
  539. package/src/resources/extensions/gsd/milestone-actions.ts +10 -4
  540. package/src/resources/extensions/gsd/native-git-bridge.ts +68 -25
  541. package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
  542. package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
  543. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
  544. package/src/resources/extensions/gsd/pending-auto-start.ts +79 -0
  545. package/src/resources/extensions/gsd/post-execution-checks.ts +87 -2
  546. package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -1
  547. package/src/resources/extensions/gsd/prompt-loader.ts +1 -1
  548. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  549. package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  550. package/src/resources/extensions/gsd/prompts/discuss-headless.md +28 -10
  551. package/src/resources/extensions/gsd/prompts/discuss.md +29 -11
  552. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  553. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  554. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -4
  555. package/src/resources/extensions/gsd/prompts/queue.md +4 -4
  556. package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  557. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  558. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  559. package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
  560. package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
  561. package/src/resources/extensions/gsd/session-lock.ts +41 -0
  562. package/src/resources/extensions/gsd/smart-entry-routing.ts +77 -0
  563. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
  564. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +344 -0
  565. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +66 -0
  566. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +101 -0
  567. package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
  568. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
  569. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
  570. package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
  571. package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
  572. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
  573. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
  574. package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
  575. package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
  576. package/src/resources/extensions/gsd/status-guards.ts +5 -0
  577. package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
  578. package/src/resources/extensions/gsd/templates/plan.md +8 -5
  579. package/src/resources/extensions/gsd/templates/task-plan.md +4 -2
  580. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
  581. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +170 -0
  582. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +116 -0
  583. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +783 -176
  584. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +487 -4
  585. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +292 -4
  586. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +53 -2
  587. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +12 -1
  588. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +15 -1
  589. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +20 -5
  590. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +19 -0
  591. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
  592. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +69 -1
  593. package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
  594. package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
  595. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
  596. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +11 -2
  597. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
  598. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +4 -1
  599. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +5 -9
  600. package/src/resources/extensions/gsd/tests/complete-task.test.ts +3 -1
  601. package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
  602. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -2
  603. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
  604. package/src/resources/extensions/gsd/tests/db-authority-regression.test.ts +208 -0
  605. package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
  606. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
  607. package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
  608. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +79 -4
  609. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +83 -0
  610. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  611. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
  612. package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +8 -0
  613. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
  614. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
  615. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +2 -0
  616. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +106 -0
  617. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +59 -11
  618. package/src/resources/extensions/gsd/tests/guided-flow.test.ts +21 -0
  619. package/src/resources/extensions/gsd/tests/guided-tool-contract.test.ts +65 -0
  620. package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
  621. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +17 -7
  622. package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
  623. package/src/resources/extensions/gsd/tests/hook-model-resolution.test.ts +5 -0
  624. package/src/resources/extensions/gsd/tests/infra-error.test.ts +2 -2
  625. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +9 -0
  626. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
  627. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +20 -0
  628. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +129 -1
  629. package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
  630. package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
  631. package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
  632. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
  633. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +6 -1
  634. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
  635. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
  636. package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
  637. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
  638. package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
  639. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
  640. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
  641. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
  642. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
  643. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +24 -1
  644. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
  645. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +26 -18
  646. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +63 -2
  647. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
  648. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
  649. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
  650. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +121 -1
  651. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
  652. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +55 -1
  653. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +29 -5
  654. package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
  655. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +26 -0
  656. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +2 -0
  657. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +225 -1
  658. package/src/resources/extensions/gsd/tests/plan-task.test.ts +17 -0
  659. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +81 -3
  660. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +86 -0
  661. package/src/resources/extensions/gsd/tests/post-unit-git-failure.test.ts +1 -1
  662. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +53 -0
  663. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
  664. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +32 -1
  665. package/src/resources/extensions/gsd/tests/prompt-loader.test.ts +23 -0
  666. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +20 -1
  667. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
  668. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +46 -2
  669. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
  670. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
  671. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
  672. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +34 -0
  673. package/src/resources/extensions/gsd/tests/smart-entry-routing.test.ts +113 -0
  674. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +53 -2
  675. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +71 -58
  676. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +1048 -0
  677. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +64 -1
  678. package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +7 -3
  679. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
  680. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
  681. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
  682. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +86 -7
  683. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +110 -1
  684. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
  685. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
  686. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +7 -1
  687. package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +21 -1
  688. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +1 -1
  689. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +39 -0
  690. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +211 -59
  691. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +597 -118
  692. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
  693. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +72 -0
  694. package/src/resources/extensions/gsd/tools/complete-milestone.ts +8 -10
  695. package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -8
  696. package/src/resources/extensions/gsd/tools/plan-milestone.ts +5 -1
  697. package/src/resources/extensions/gsd/tools/plan-slice.ts +98 -12
  698. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
  699. package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
  700. package/src/resources/extensions/gsd/types.ts +1 -1
  701. package/src/resources/extensions/gsd/unit-context-manifest.ts +47 -11
  702. package/src/resources/extensions/gsd/validation.ts +23 -1
  703. package/src/resources/extensions/gsd/verification-gate.ts +78 -6
  704. package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
  705. package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
  706. package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
  707. package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
  708. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  709. package/src/resources/extensions/gsd/workflow-projections.ts +6 -8
  710. package/src/resources/extensions/gsd/worktree-lifecycle.ts +1196 -518
  711. package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
  712. package/src/resources/extensions/shared/html-shell.ts +412 -0
  713. package/src/resources/extensions/subagent/index.ts +567 -103
  714. package/src/resources/extensions/subagent/launch.ts +131 -0
  715. package/src/resources/extensions/subagent/run-store.ts +218 -0
  716. package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
  717. package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
  718. package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
  719. package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
  720. package/src/resources/extensions/visual-brief/index.ts +8 -0
  721. package/src/resources/extensions/visual-brief/page-contract.ts +136 -0
  722. package/src/resources/extensions/visual-brief/prompts.ts +183 -0
  723. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +212 -0
  724. package/dist/web/standalone/.next/server/chunks/5822.js +0 -2
  725. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  726. package/dist/web/standalone/.next/static/chunks/app/layout-9ecfd95f343793f0.js +0 -1
  727. package/dist/web/standalone/.next/static/chunks/app/page-200592a7f3baf579.js +0 -1
  728. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  729. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  730. package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
  731. package/dist/web/standalone/.next/static/css/de70bee13400563f.css +0 -1
  732. package/dist/web/standalone/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  733. package/dist/web/standalone/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  734. package/dist/web/standalone/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  735. package/dist/web/standalone/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  736. package/dist/web/standalone/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  737. package/dist/web/standalone/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  738. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
  739. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → xRy4LqKSNKdT7y6ATYyEl}/_buildManifest.js +0 -0
  740. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → xRy4LqKSNKdT7y6ATYyEl}/_ssgManifest.js +0 -0
@@ -17,12 +17,13 @@
17
17
  * a circular reference. Both classes share the body until the Resolver retires.
18
18
  */
19
19
 
20
- import { existsSync, unlinkSync } from "node:fs";
20
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
21
21
  import { randomUUID } from "node:crypto";
22
22
  import { join } from "node:path";
23
23
 
24
24
  import type { AutoSession } from "./auto/session.js";
25
25
  import { debugLog } from "./debug-logger.js";
26
+ import { logWarning } from "./workflow-logger.js";
26
27
  import { emitJournalEvent } from "./journal.js";
27
28
  import { emitWorktreeCreated, emitWorktreeMerged } from "./worktree-telemetry.js";
28
29
  import {
@@ -35,14 +36,53 @@ import {
35
36
  releaseMilestoneLease,
36
37
  } from "./db/milestone-leases.js";
37
38
  import { MergeConflictError } from "./git-service.js";
39
+ import type { GitPreferences } from "./git-service.js";
38
40
  import {
39
41
  getCollapseCadence,
40
42
  getMilestoneResquash,
41
43
  resquashMilestoneOnMain,
42
44
  } from "./slice-cadence.js";
43
- import { loadEffectiveGSDPreferences } from "./preferences.js";
45
+ // ADR-016 phase 2 / C3 (#5626): cache + preferences + path helpers inlined
46
+ // as direct imports. They are leaf-level functions that do not vary across
47
+ // callers — production wiring previously injected them via deps; the seam
48
+ // added type churn without enabling test variation.
49
+ import { loadEffectiveGSDPreferences, getIsolationMode } from "./preferences.js";
50
+ import { invalidateAllCaches } from "./cache.js";
51
+ import { resolveMilestoneFile } from "./paths.js";
44
52
  import type { WorktreeStateProjection } from "./worktree-state-projection.js";
45
53
  import { createWorkspace, scopeMilestone } from "./workspace.js";
54
+ // ADR-016 phase 2 / C1 (#5624): file-system + git-CLI leaf primitives
55
+ // inlined as direct imports rather than injected through `WorktreeLifecycleDeps`.
56
+ // These four symbols (`readFileSync` from node:fs, `getCurrentBranch` and
57
+ // `autoCommitCurrentBranch` from `./worktree.js`, `nativeCheckoutBranch` from
58
+ // `./native-git-bridge.js`) are leaf-level primitives — no environment varies
59
+ // across callers — so the dependency-injection seam they used to inhabit was
60
+ // adding type churn without enabling any test variation.
61
+ import {
62
+ autoCommitCurrentBranch,
63
+ getCurrentBranch,
64
+ } from "./worktree.js";
65
+ import { nativeCheckoutBranch } from "./native-git-bridge.js";
66
+ // ADR-016 phase 2 / C2 (#5625): worktree-manager helpers inlined from
67
+ // `./auto-worktree.js`. These seven functions are not real seams — Lifecycle
68
+ // is the only Module that calls them, and they live alongside the Module's
69
+ // other primitives in `auto-worktree.ts`.
70
+ import {
71
+ autoWorktreeBranch,
72
+ createAutoWorktree,
73
+ enterAutoWorktree,
74
+ enterBranchModeForMilestone,
75
+ getAutoWorktreePath,
76
+ isInAutoWorktree,
77
+ teardownAutoWorktree,
78
+ } from "./auto-worktree.js";
79
+
80
+ const recentWorktreeMergeFailures = new Map<string, number>();
81
+ const MERGE_FAILURE_DEDUPE_MS = 60_000;
82
+
83
+ export function resetRecentWorktreeMergeFailuresForTest(): void {
84
+ recentWorktreeMergeFailures.clear();
85
+ }
46
86
 
47
87
  // ─── Types ───────────────────────────────────────────────────────────────
48
88
 
@@ -64,19 +104,16 @@ export interface NotifyCtx {
64
104
  * recursion retires; shrinking it now would force a parallel migration.
65
105
  */
66
106
  export interface WorktreeLifecycleDeps {
67
- // ── Entry / branch-mode setup ────────────────────────────────────────
68
- enterAutoWorktree: (basePath: string, milestoneId: string) => string;
69
- createAutoWorktree: (basePath: string, milestoneId: string) => string;
70
- enterBranchModeForMilestone: (basePath: string, milestoneId: string) => void;
71
- getAutoWorktreePath: (basePath: string, milestoneId: string) => string | null;
72
- getIsolationMode: (basePath?: string) => "worktree" | "branch" | "none";
73
-
74
- // ── Cache + git service rebuild ──────────────────────────────────────
75
- invalidateAllCaches: () => void;
76
- GitServiceImpl: new (basePath: string, gitConfig: unknown) => unknown;
77
- loadEffectiveGSDPreferences: () =>
78
- | { preferences?: { git?: Record<string, unknown> } }
79
- | undefined;
107
+ // ── Git service factory (ADR-016 phase 2 / C4) ───────────────────────
108
+ /**
109
+ * Build a fresh `GitService` instance bound to `basePath`.
110
+ *
111
+ * Hides the constructor shape (new GitServiceImpl(basePath, gitConfig))
112
+ * and the gitConfig load from Lifecycle. The factory takes only a
113
+ * `basePath` and is responsible for loading any config it needs.
114
+ * Tests substitute fakes by passing a function that returns a stub.
115
+ */
116
+ gitServiceFactory: (basePath: string) => AutoSession["gitService"];
80
117
 
81
118
  // ── State Projection Module (ADR-016 one-way edge) ───────────────────
82
119
  /**
@@ -85,44 +122,60 @@ export interface WorktreeLifecycleDeps {
85
122
  */
86
123
  worktreeProjection: WorktreeStateProjection;
87
124
 
88
- // ── Exit / merge / teardown ──────────────────────────────────────────
89
- isInAutoWorktree: (basePath: string) => boolean;
90
- autoCommitCurrentBranch: (
91
- basePath: string,
92
- reason: string,
93
- milestoneId: string,
94
- ) => void;
95
- autoWorktreeBranch: (milestoneId: string) => string;
96
- teardownAutoWorktree: (
97
- basePath: string,
98
- milestoneId: string,
99
- opts?: { preserveBranch?: boolean },
100
- ) => void;
101
- mergeMilestoneToMain: (
102
- basePath: string,
103
- milestoneId: string,
104
- roadmapContent: string,
105
- ) => { pushed: boolean; codeFilesChanged: boolean };
106
- getCurrentBranch: (basePath: string) => string;
125
+ // ── Merge primitive ──────────────────────────────────────────────────
107
126
  /**
108
- * Force-checkout the named branch in `basePath`. Required by the branch-mode
109
- * merge path when HEAD has been moved off the milestone branch — silently
110
- * skipping the merge would strand the milestone's commits.
127
+ * Inner squash-merge primitive (`auto-worktree.ts:mergeMilestoneToMain`).
128
+ *
129
+ * **Module-internal seam do not construct your own.** Only the wiring
130
+ * factory `auto.ts:buildWorktreeLifecycleDeps()` is permitted to populate
131
+ * this field. The primitive is `@internal`; production callers reach the
132
+ * merge body through `WorktreeLifecycle.exitMilestone({ merge: true })`,
133
+ * never by calling this dep directly.
111
134
  */
112
- checkoutBranch: (basePath: string, branch: string) => void;
113
- resolveMilestoneFile: (
135
+ mergeMilestoneToMain: (
114
136
  basePath: string,
115
137
  milestoneId: string,
116
- fileType: string,
117
- ) => string | null;
118
- /**
119
- * Roadmap file reader. Injected so unit tests can substitute fixture
120
- * content without touching the filesystem; production wiring passes
121
- * `node:fs.readFileSync`.
122
- */
123
- readFileSync: (path: string, encoding: string) => string;
138
+ roadmapContent: string,
139
+ ) => {
140
+ pushed: boolean;
141
+ codeFilesChanged: boolean;
142
+ commitMessage?: string;
143
+ };
144
+
145
+ // ADR-016 phase 2 / C1 + C2 + C3 + C4 inlined the following fields as
146
+ // direct imports — leaf primitives that did not vary across callers:
147
+ // C1 (#5624): readFileSync, getCurrentBranch, checkoutBranch,
148
+ // autoCommitCurrentBranch
149
+ // C2 (#5625): enterAutoWorktree, createAutoWorktree,
150
+ // enterBranchModeForMilestone, getAutoWorktreePath,
151
+ // teardownAutoWorktree, isInAutoWorktree, autoWorktreeBranch
152
+ // C3 (#5626): invalidateAllCaches, loadEffectiveGSDPreferences,
153
+ // getIsolationMode, resolveMilestoneFile
154
+ // C4 (#5627): GitServiceImpl constructor → gitServiceFactory above
155
+ //
156
+ // ADR-016 phase 3 (#5693) deleted the @deprecated optional fields that
157
+ // remained on this Interface for legacy test fixtures. Tests that need to
158
+ // substitute primitive implementations cast their deps to
159
+ // `WorktreeLifecycleTestOverrides` (exported below) — the test seam now
160
+ // lives outside the public Interface.
161
+ //
162
+ // Final dep bag: 3 fields. The ADR's envisioned shape was ≤6.
124
163
  }
125
164
 
165
+ /**
166
+ * Test-only override shim. Production callers do not use this type — it
167
+ * exists so legacy test fixtures can substitute the primitive implementations
168
+ * that were inlined into Lifecycle in ADR-016 phase 2 (C1-C4). Pass an object
169
+ * typed `WorktreeLifecycleDeps & WorktreeLifecycleTestOverrides` to the
170
+ * `WorktreeLifecycle` constructor; Lifecycle reads the overrides through the
171
+ * structural-typing escape hatch in `primitiveOverrides()`.
172
+ *
173
+ * The fields here intentionally duplicate the C1-C4-inlined primitive
174
+ * signatures. Adding new fields is fine when a test needs to vary a primitive
175
+ * that has no other seam.
176
+ */
177
+ export type WorktreeLifecycleTestOverrides = WorktreeLifecyclePrimitiveOverrides;
178
+
126
179
  /**
127
180
  * Internal sentinel — thrown by `_mergeBranchMode` when it has already
128
181
  * emitted a user-visible error. The outer `mergeAndExit` catches the type
@@ -166,6 +219,49 @@ export type ExitResult =
166
219
  | { ok: true; merged: boolean; codeFilesChanged: boolean }
167
220
  | { ok: false; reason: "merge-conflict" | "teardown-failed"; cause?: unknown };
168
221
 
222
+ /**
223
+ * Session-less merge entry context. Per ADR-016 phase 2 / A1 (#5616), the
224
+ * merge body is structurally session-less — it reads project root, worktree
225
+ * path, and milestoneId. Single-loop callers (`_mergeAndExit`) build a
226
+ * MergeContext from `this.s`. Parallel callers (`parallel-merge.ts`) build
227
+ * one directly without an `AutoSession`.
228
+ */
229
+ export interface MergeContext {
230
+ /** Project root — merge target (where `git merge --squash` lands). */
231
+ originalBasePath: string;
232
+ /**
233
+ * Current worktree path or project root when in branch mode. Used as the
234
+ * cwd anchor for `mergeMilestoneToMain` and the source for
235
+ * `Projection.finalizeProjectionForMerge`.
236
+ */
237
+ worktreeBasePath: string;
238
+ milestoneId: string;
239
+ /**
240
+ * When true, `mergeMilestoneStandalone` returns `{ merged: false,
241
+ * mode: "skipped" }` immediately (mirrors the single-loop guard). Default
242
+ * `false` for parallel callers, which never run with degraded isolation.
243
+ */
244
+ isolationDegraded?: boolean;
245
+ notify: NotifyCtx["notify"];
246
+ }
247
+
248
+ /**
249
+ * Result of `mergeMilestoneStandalone`. `mode` lets callers decide which
250
+ * session-bound side effects to run (worktree-mode → `restoreToProjectRoot`,
251
+ * branch-mode → `rebuildGitService`, skipped → none).
252
+ */
253
+ export interface MergeStandaloneResult {
254
+ merged: boolean;
255
+ mode: "worktree" | "branch" | "skipped";
256
+ codeFilesChanged: boolean;
257
+ pushed: boolean;
258
+ /**
259
+ * Commit message produced by the squash merge, if available. Forwarded
260
+ * from `mergeMilestoneToMain`. Only populated when `merged === true`.
261
+ */
262
+ commitMessage?: string;
263
+ }
264
+
169
265
  // ─── Validation ──────────────────────────────────────────────────────────
170
266
 
171
267
  function isValidMilestoneId(milestoneId: string): boolean {
@@ -178,6 +274,206 @@ function invalidMilestoneIdError(milestoneId: string): Error {
178
274
  );
179
275
  }
180
276
 
277
+ type WorktreeLifecyclePrimitiveOverrides = {
278
+ readFileSync?: (path: string, encoding: BufferEncoding) => string;
279
+ getCurrentBranch?: (basePath: string) => string;
280
+ checkoutBranch?: (basePath: string, branch: string) => void;
281
+ autoCommitCurrentBranch?: (
282
+ basePath: string,
283
+ unitType: string,
284
+ unitId: string,
285
+ taskContext?: unknown,
286
+ ) => string | null;
287
+ getAutoWorktreePath?: (
288
+ basePath: string,
289
+ milestoneId: string,
290
+ ) => string | null;
291
+ // ADR-016 phase 2 / C2-inlined worktree-manager primitives. Tests still
292
+ // stub these via the structural-typing escape hatch on `WorktreeLifecycleDeps`,
293
+ // so the call sites below check for an override first and fall back to the
294
+ // imported direct primitive.
295
+ isInAutoWorktree?: (basePath: string) => boolean;
296
+ autoWorktreeBranch?: (milestoneId: string) => string;
297
+ teardownAutoWorktree?: (
298
+ basePath: string,
299
+ milestoneId: string,
300
+ opts?: { preserveBranch?: boolean },
301
+ ) => void;
302
+ createAutoWorktree?: (basePath: string, milestoneId: string) => string;
303
+ enterAutoWorktree?: (basePath: string, milestoneId: string) => string;
304
+ enterBranchModeForMilestone?: (basePath: string, milestoneId: string) => void;
305
+ // ADR-016 phase 2 / C3-inlined cache + preferences + path helpers.
306
+ getIsolationMode?: (basePath?: string) => "worktree" | "branch" | "none";
307
+ invalidateAllCaches?: () => void;
308
+ resolveMilestoneFile?: (
309
+ basePath: string,
310
+ milestoneId: string,
311
+ fileType: string,
312
+ ) => string | null;
313
+ loadEffectiveGSDPreferences?: (basePath?: string) =>
314
+ | { preferences?: { git?: Record<string, unknown> } }
315
+ | null
316
+ | undefined;
317
+ };
318
+
319
+ function primitiveOverrides(
320
+ deps: WorktreeLifecycleDeps,
321
+ ): WorktreeLifecyclePrimitiveOverrides {
322
+ return deps as WorktreeLifecycleDeps & WorktreeLifecyclePrimitiveOverrides;
323
+ }
324
+
325
+ function readLifecycleFile(
326
+ deps: WorktreeLifecycleDeps,
327
+ path: string,
328
+ ): string {
329
+ return primitiveOverrides(deps).readFileSync?.(path, "utf-8") ??
330
+ readFileSync(path, "utf-8");
331
+ }
332
+
333
+ function currentLifecycleBranch(
334
+ deps: WorktreeLifecycleDeps,
335
+ basePath: string,
336
+ ): string {
337
+ return primitiveOverrides(deps).getCurrentBranch?.(basePath) ??
338
+ getCurrentBranch(basePath);
339
+ }
340
+
341
+ function checkoutLifecycleBranch(
342
+ deps: WorktreeLifecycleDeps,
343
+ basePath: string,
344
+ branch: string,
345
+ ): void {
346
+ const checkoutBranch = primitiveOverrides(deps).checkoutBranch;
347
+ if (checkoutBranch) {
348
+ checkoutBranch(basePath, branch);
349
+ return;
350
+ }
351
+ nativeCheckoutBranch(basePath, branch);
352
+ }
353
+
354
+ function autoCommitLifecycleBranch(
355
+ deps: WorktreeLifecycleDeps,
356
+ basePath: string,
357
+ unitType: string,
358
+ unitId: string,
359
+ ): string | null {
360
+ return primitiveOverrides(deps).autoCommitCurrentBranch?.(
361
+ basePath,
362
+ unitType,
363
+ unitId,
364
+ ) ?? autoCommitCurrentBranch(basePath, unitType, unitId);
365
+ }
366
+
367
+ // ADR-016 phase 2 / C2-inlined worktree-manager primitives — helpers that
368
+ // honour the structural-typing override pattern so legacy test fixtures keep
369
+ // working without rewriting them onto real-git fixtures.
370
+ function lifecycleIsInAutoWorktree(
371
+ deps: WorktreeLifecycleDeps,
372
+ basePath: string,
373
+ ): boolean {
374
+ return primitiveOverrides(deps).isInAutoWorktree?.(basePath) ??
375
+ isInAutoWorktree(basePath);
376
+ }
377
+
378
+ function lifecycleAutoWorktreeBranch(
379
+ deps: WorktreeLifecycleDeps,
380
+ milestoneId: string,
381
+ ): string {
382
+ return primitiveOverrides(deps).autoWorktreeBranch?.(milestoneId) ??
383
+ autoWorktreeBranch(milestoneId);
384
+ }
385
+
386
+ function lifecycleTeardownAutoWorktree(
387
+ deps: WorktreeLifecycleDeps,
388
+ basePath: string,
389
+ milestoneId: string,
390
+ opts?: { preserveBranch?: boolean },
391
+ ): void {
392
+ const override = primitiveOverrides(deps).teardownAutoWorktree;
393
+ if (override) {
394
+ override(basePath, milestoneId, opts);
395
+ return;
396
+ }
397
+ teardownAutoWorktree(basePath, milestoneId, opts);
398
+ }
399
+
400
+ function lifecycleCreateAutoWorktree(
401
+ deps: WorktreeLifecycleDeps,
402
+ basePath: string,
403
+ milestoneId: string,
404
+ ): string {
405
+ return primitiveOverrides(deps).createAutoWorktree?.(basePath, milestoneId) ??
406
+ createAutoWorktree(basePath, milestoneId);
407
+ }
408
+
409
+ function lifecycleEnterAutoWorktree(
410
+ deps: WorktreeLifecycleDeps,
411
+ basePath: string,
412
+ milestoneId: string,
413
+ ): string {
414
+ return primitiveOverrides(deps).enterAutoWorktree?.(basePath, milestoneId) ??
415
+ enterAutoWorktree(basePath, milestoneId);
416
+ }
417
+
418
+ function lifecycleEnterBranchMode(
419
+ deps: WorktreeLifecycleDeps,
420
+ basePath: string,
421
+ milestoneId: string,
422
+ ): void {
423
+ const override = primitiveOverrides(deps).enterBranchModeForMilestone;
424
+ if (override) {
425
+ override(basePath, milestoneId);
426
+ return;
427
+ }
428
+ enterBranchModeForMilestone(basePath, milestoneId);
429
+ }
430
+
431
+ // ADR-016 phase 2 / C3-inlined cache + preferences + path helpers.
432
+ function lifecycleGetIsolationMode(
433
+ deps: WorktreeLifecycleDeps,
434
+ basePath?: string,
435
+ ): "worktree" | "branch" | "none" {
436
+ return primitiveOverrides(deps).getIsolationMode?.(basePath) ??
437
+ getIsolationMode(basePath);
438
+ }
439
+
440
+ function lifecycleInvalidateAllCaches(deps: WorktreeLifecycleDeps): void {
441
+ const override = primitiveOverrides(deps).invalidateAllCaches;
442
+ if (override) {
443
+ override();
444
+ return;
445
+ }
446
+ invalidateAllCaches();
447
+ }
448
+
449
+ function lifecycleResolveMilestoneFile(
450
+ deps: WorktreeLifecycleDeps,
451
+ basePath: string,
452
+ milestoneId: string,
453
+ fileType: string,
454
+ ): string | null {
455
+ return primitiveOverrides(deps).resolveMilestoneFile?.(
456
+ basePath,
457
+ milestoneId,
458
+ fileType,
459
+ ) ?? resolveMilestoneFile(basePath, milestoneId, fileType);
460
+ }
461
+
462
+ function lifecycleLoadPreferences(
463
+ deps: WorktreeLifecycleDeps,
464
+ basePath?: string,
465
+ ):
466
+ | { preferences?: { git?: Record<string, unknown> } }
467
+ | null
468
+ | undefined {
469
+ const override = primitiveOverrides(deps).loadEffectiveGSDPreferences;
470
+ if (override) return override(basePath);
471
+ return loadEffectiveGSDPreferences(basePath) as
472
+ | { preferences?: { git?: Record<string, unknown> } }
473
+ | null
474
+ | undefined;
475
+ }
476
+
181
477
  /**
182
478
  * Throwing variant used by the merge/exit paths that surface failures via
183
479
  * the typed `ExitResult` (callers wrap the throw → cause). The enter path
@@ -237,7 +533,7 @@ export function _enterMilestoneCore(
237
533
 
238
534
  // Phase B: claim a milestone lease before any worktree mutation. Two
239
535
  // workers cannot enter the same milestone concurrently. Best-effort:
240
- // skip if no worker registered (single-worker fallback) or DB
536
+ // warn if no worker registered (single-worker fallback) or skip if DB
241
537
  // unavailable; reuse existing lease if we already hold it on this
242
538
  // milestone (re-entry within the same session).
243
539
  if (s.workerId) {
@@ -330,13 +626,18 @@ export function _enterMilestoneCore(
330
626
  });
331
627
  }
332
628
  }
629
+ } else {
630
+ logWarning(
631
+ "worktree",
632
+ `enterMilestone(${milestoneId}) ran before auto worker registration; milestone lease was not claimed.`,
633
+ );
333
634
  }
334
635
 
335
636
  // Resolve the project root for worktree operations via shared helper.
336
637
  // Handles the case where originalBasePath is falsy and basePath is itself
337
638
  // a worktree path — prevents double-nested worktree paths (#3729).
338
639
  const basePath = resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
339
- const mode = deps.getIsolationMode(basePath);
640
+ const mode = getIsolationMode(basePath);
340
641
 
341
642
  if (mode === "none") {
342
643
  debugLog("WorktreeLifecycle", {
@@ -380,12 +681,12 @@ export function _enterMilestoneCore(
380
681
  // ── Branch mode: create/checkout milestone branch, stay in project root ──
381
682
  if (mode === "branch") {
382
683
  try {
383
- deps.enterBranchModeForMilestone(basePath, milestoneId);
684
+ lifecycleEnterBranchMode(deps, basePath, milestoneId);
384
685
  // basePath does not change — no worktree, no chdir.
385
686
  // Rebuild GitService so the new HEAD is reflected, then flush any
386
687
  // path-keyed caches that may have been populated before the checkout.
387
688
  rebuildGitService(s, deps);
388
- deps.invalidateAllCaches();
689
+ invalidateAllCaches();
389
690
  debugLog("WorktreeLifecycle", {
390
691
  action: "enterMilestone",
391
692
  milestoneId,
@@ -421,18 +722,22 @@ export function _enterMilestoneCore(
421
722
 
422
723
  // ── Worktree mode ────────────────────────────────────────────────────────
423
724
  try {
424
- const existingPath = deps.getAutoWorktreePath(basePath, milestoneId);
725
+ const existingPath =
726
+ (primitiveOverrides(deps).getAutoWorktreePath ?? getAutoWorktreePath)(
727
+ basePath,
728
+ milestoneId,
729
+ );
425
730
  let wtPath: string;
426
731
 
427
732
  if (existingPath) {
428
- wtPath = deps.enterAutoWorktree(basePath, milestoneId);
733
+ wtPath = lifecycleEnterAutoWorktree(deps, basePath, milestoneId);
429
734
  } else {
430
- wtPath = deps.createAutoWorktree(basePath, milestoneId);
735
+ wtPath = lifecycleCreateAutoWorktree(deps, basePath, milestoneId);
431
736
  }
432
737
 
433
738
  s.basePath = wtPath;
434
739
  rebuildGitService(s, deps);
435
- deps.invalidateAllCaches();
740
+ invalidateAllCaches();
436
741
 
437
742
  // Per ADR-016: Lifecycle calls Projection on entry, before any Unit
438
743
  // dispatches. Build a temporary scope from the new basePath; callers may
@@ -511,113 +816,618 @@ export function _enterMilestoneCore(
511
816
  }
512
817
  }
513
818
 
819
+ /**
820
+ * Resolve the basePath to adopt on resume from a paused session.
821
+ *
822
+ * Returns `persistedWorktreePath` when the path is non-null and exists on
823
+ * disk; otherwise falls back to `base`. Used by
824
+ * `WorktreeLifecycle.resumeFromPausedSession` (#5621). Exported as a pure
825
+ * function so unit tests can exercise the path-resolution logic without
826
+ * constructing a `WorktreeLifecycle` instance.
827
+ *
828
+ * The optional `pathExists` parameter exists only for tests that need to
829
+ * substitute a stub for `existsSync`.
830
+ */
831
+ export function resolvePausedResumeBasePath(
832
+ base: string,
833
+ persistedWorktreePath: string | null | undefined,
834
+ pathExists: (p: string) => boolean = existsSync,
835
+ ): string {
836
+ return persistedWorktreePath && pathExists(persistedWorktreePath)
837
+ ? persistedWorktreePath
838
+ : base;
839
+ }
840
+
514
841
  function rebuildGitService(
515
842
  s: AutoSession,
516
843
  deps: WorktreeLifecycleDeps,
517
844
  ): void {
518
- const gitConfig =
519
- deps.loadEffectiveGSDPreferences()?.preferences?.git ?? {};
520
- s.gitService = new deps.GitServiceImpl(
521
- s.basePath,
522
- gitConfig,
523
- ) as AutoSession["gitService"];
845
+ // ADR-016 phase 2 / C4 (#5627): the gitConfig load and constructor
846
+ // construction live behind `gitServiceFactory`. Lifecycle no longer
847
+ // sees the constructor shape, the gitConfig type, or the unknown→
848
+ // GitService cast.
849
+ s.gitService = deps.gitServiceFactory(s.basePath);
524
850
  }
525
851
 
526
- // ─── Module class ────────────────────────────────────────────────────────
852
+ function emitWorktreeMergeFailedOnce(
853
+ basePath: string,
854
+ milestoneId: string,
855
+ err: unknown,
856
+ ): void {
857
+ const msg = err instanceof Error ? err.message : String(err);
858
+ const errorCategory = err instanceof Error ? err.name : "Error";
859
+ const now = Date.now();
860
+ const key = `${basePath}\0${milestoneId}\0${errorCategory}`;
861
+ const previous = recentWorktreeMergeFailures.get(key);
862
+ if (previous && now - previous < MERGE_FAILURE_DEDUPE_MS) return;
863
+ for (const [candidate, ts] of recentWorktreeMergeFailures) {
864
+ if (now - ts >= MERGE_FAILURE_DEDUPE_MS) {
865
+ recentWorktreeMergeFailures.delete(candidate);
866
+ }
867
+ }
868
+ emitJournalEvent(basePath, {
869
+ ts: new Date().toISOString(),
870
+ flowId: randomUUID(),
871
+ seq: 0,
872
+ eventType: "worktree-merge-failed",
873
+ data: { milestoneId, error: msg },
874
+ });
875
+ recentWorktreeMergeFailures.set(key, now);
876
+ }
877
+
878
+ // ─── Session-less merge entry (ADR-016 phase 2 / A1) ─────────────────────
527
879
 
528
880
  /**
529
- * Worktree Lifecycle module instance.
881
+ * Worktree-mode merge body. Session-less — operates on a `MergeContext`.
530
882
  *
531
- * Constructed once per auto-mode session. Holds the session reference so
532
- * verbs can mutate `s.basePath` and related coordination state directly
533
- * without round-tripping through callers.
883
+ * On error: emits the "worktree-merge-failed" journal event, notifies the
884
+ * user, cleans up stale `SQUASH_MSG` / `MERGE_HEAD` / `MERGE_MSG` files
885
+ * (#1389), and chdirs back to project root before rethrowing. Session-side
886
+ * cleanup (`restoreToProjectRoot`, `gitService` rebuild) is the caller's
887
+ * responsibility.
534
888
  */
535
- export class WorktreeLifecycle {
536
- private readonly s: AutoSession;
537
- private readonly deps: WorktreeLifecycleDeps;
538
-
539
- constructor(s: AutoSession, deps: WorktreeLifecycleDeps) {
540
- this.s = s;
541
- this.deps = deps;
889
+ function _mergeWorktreeModeImpl(
890
+ deps: WorktreeLifecycleDeps,
891
+ mctx: MergeContext,
892
+ ): MergeStandaloneResult {
893
+ const { originalBasePath, worktreeBasePath, milestoneId, notify } = mctx;
894
+ if (!originalBasePath) {
895
+ debugLog("WorktreeLifecycle", {
896
+ action: "mergeAndExit",
897
+ milestoneId,
898
+ mode: "worktree",
899
+ skipped: true,
900
+ reason: "missing-original-base",
901
+ });
902
+ return {
903
+ merged: false,
904
+ mode: "worktree",
905
+ codeFilesChanged: false,
906
+ pushed: false,
907
+ };
542
908
  }
543
909
 
544
- /**
545
- * Enter or create the auto-worktree for `milestoneId`. Idempotent if
546
- * already in this milestone (lease refreshed; basePath unchanged).
547
- *
548
- * Returns a typed `EnterResult` describing the outcome. Callers may
549
- * ignore the result if they read `s.basePath` directly afterwards
550
- * (legacy behaviour); new callers should branch on the result.
551
- */
552
- enterMilestone(milestoneId: string, ctx: NotifyCtx): EnterResult {
553
- return _enterMilestoneCore(this.s, this.deps, milestoneId, ctx);
554
- }
910
+ try {
911
+ // ADR-016: final projection before teardown. Replaces the legacy
912
+ // syncWorktreeStateBack(originalBase, basePath, milestoneId) call.
913
+ const finalScope = scopeMilestone(
914
+ createWorkspace(worktreeBasePath),
915
+ milestoneId,
916
+ );
917
+ const { synced } = deps.worktreeProjection.finalizeProjectionForMerge(
918
+ finalScope,
919
+ );
920
+ if (synced.length > 0) {
921
+ debugLog("WorktreeLifecycle", {
922
+ action: "mergeAndExit",
923
+ milestoneId,
924
+ phase: "reverse-sync",
925
+ synced: synced.length,
926
+ });
927
+ }
555
928
 
556
- /**
557
- * Exit the current worktree. With `opts.merge === true`, runs the full
558
- * merge-and-teardown path (worktree-mode or branch-mode auto-detected).
559
- * With `opts.merge === false`, runs auto-commit and teardown without
560
- * merging to main.
561
- *
562
- * Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
563
- * `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
564
- * giving callers a typed branch for the expected failure path.
565
- * Unexpected failures (filesystem, git permissions, etc.) are wrapped
566
- * as `{ ok: false, reason: "teardown-failed", cause }` so callers always
567
- * receive a discriminated union — no exceptions for any expected outcome.
568
- */
569
- exitMilestone(
570
- milestoneId: string,
571
- opts: { merge: boolean; preserveBranch?: boolean },
572
- ctx: NotifyCtx,
573
- ): ExitResult {
574
- if (opts.merge) {
575
- try {
576
- const merged = this._mergeAndExit(milestoneId, ctx);
577
- return { ok: true, merged, codeFilesChanged: false };
578
- } catch (err) {
579
- if (err instanceof MergeConflictError) {
580
- return { ok: false, reason: "merge-conflict", cause: err };
581
- }
582
- return { ok: false, reason: "teardown-failed", cause: err };
929
+ // Resolve roadmap — try project root first, then worktree path as
930
+ // fallback. The worktree may hold the only copy when state-back
931
+ // projection silently dropped it or .gsd/ is not symlinked. Without
932
+ // the fallback, a missing roadmap triggers bare teardown which
933
+ // deletes the branch and orphans all milestone commits (#1573).
934
+ let roadmapPath = resolveMilestoneFile(
935
+ originalBasePath,
936
+ milestoneId,
937
+ "ROADMAP",
938
+ );
939
+ if (
940
+ !roadmapPath &&
941
+ !isSamePathPhysical(worktreeBasePath, originalBasePath)
942
+ ) {
943
+ roadmapPath = resolveMilestoneFile(
944
+ worktreeBasePath,
945
+ milestoneId,
946
+ "ROADMAP",
947
+ );
948
+ if (roadmapPath) {
949
+ debugLog("WorktreeLifecycle", {
950
+ action: "mergeAndExit",
951
+ milestoneId,
952
+ phase: "roadmap-fallback",
953
+ note: "resolved from worktree path",
954
+ });
583
955
  }
584
956
  }
585
- try {
586
- this._exitWithoutMerge(milestoneId, ctx, {
587
- preserveBranch: opts.preserveBranch,
957
+
958
+ if (!roadmapPath) {
959
+ // No roadmap at either location — teardown but PRESERVE the branch
960
+ // so commits are not orphaned (#1573).
961
+ lifecycleTeardownAutoWorktree(deps, originalBasePath, milestoneId, {
962
+ preserveBranch: true,
588
963
  });
589
- return { ok: true, merged: false, codeFilesChanged: false };
590
- } catch (err) {
591
- return { ok: false, reason: "teardown-failed", cause: err };
964
+ notify(
965
+ `Exited worktree for ${milestoneId} (no roadmap found — branch preserved for manual merge).`,
966
+ "warning",
967
+ );
968
+ return {
969
+ merged: false,
970
+ mode: "worktree",
971
+ codeFilesChanged: false,
972
+ pushed: false,
973
+ };
592
974
  }
593
- }
594
975
 
595
- /**
596
- * Milestone transition: merge the current milestone, then enter the next
597
- * one. Pattern used when the loop detects that the active milestone has
598
- * changed (current completed, next is now active). Caller is responsible
599
- * for re-deriving state between the merge and the enter.
600
- */
601
- mergeAndEnterNext(
602
- currentMilestoneId: string,
603
- nextMilestoneId: string,
604
- ctx: NotifyCtx,
605
- ): void {
606
- debugLog("WorktreeLifecycle", {
607
- action: "mergeAndEnterNext",
608
- currentMilestoneId,
609
- nextMilestoneId,
610
- });
611
- let merged = false;
612
- let mergeThrew = false;
976
+ const roadmapContent = readLifecycleFile(deps, roadmapPath);
977
+ const mergeResult = deps.mergeMilestoneToMain(
978
+ originalBasePath,
979
+ milestoneId,
980
+ roadmapContent,
981
+ );
982
+
983
+ // #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
984
+ // cleanup internally (step 12), but it can silently fail on Windows
985
+ // or when the worktree directory is locked. Perform a secondary
986
+ // teardown here to ensure the worktree is properly cleaned up.
987
+ // Idempotent — if already removed, teardownAutoWorktree no-ops.
613
988
  try {
614
- merged = this._mergeAndExit(currentMilestoneId, ctx);
615
- } catch (err) {
616
- if (err instanceof UserNotifiedError) throw err;
617
- mergeThrew = true;
618
- // _mergeAndExit emits a warning and restores state on failure during
619
- // merge/cleanup. If it throws before recovery runs (e.g. validation,
620
- // emitJournalEvent), basePath isn't restored — re-throw so we don't
989
+ lifecycleTeardownAutoWorktree(deps, originalBasePath, milestoneId);
990
+ } catch {
991
+ // Best-effort primary cleanup in mergeMilestoneToMain may have
992
+ // already removed the worktree.
993
+ }
994
+
995
+ if (mergeResult.codeFilesChanged) {
996
+ notify(
997
+ `Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
998
+ "info",
999
+ );
1000
+ } else {
1001
+ // #1906 — milestone produced only .gsd/ metadata. Surface
1002
+ // clearly so the user knows the milestone is not truly complete.
1003
+ notify(
1004
+ `WARNING: Milestone ${milestoneId} merged to main but contained NO code changes — only .gsd/ metadata files. ` +
1005
+ `The milestone summary may describe planned work that was never implemented. ` +
1006
+ `Review the milestone output and re-run if code is missing.`,
1007
+ "warning",
1008
+ );
1009
+ }
1010
+
1011
+ return {
1012
+ merged: true,
1013
+ mode: "worktree",
1014
+ codeFilesChanged: mergeResult.codeFilesChanged,
1015
+ pushed: mergeResult.pushed,
1016
+ commitMessage: mergeResult.commitMessage,
1017
+ };
1018
+ } catch (err) {
1019
+ const msg = err instanceof Error ? err.message : String(err);
1020
+ debugLog("WorktreeLifecycle", {
1021
+ action: "mergeAndExit",
1022
+ milestoneId,
1023
+ result: "error",
1024
+ error: msg,
1025
+ fallback: "chdir-to-project-root",
1026
+ });
1027
+ emitWorktreeMergeFailedOnce(originalBasePath || worktreeBasePath, milestoneId, err);
1028
+ // Surface a clear, actionable error. Worktree and milestone branch
1029
+ // are intentionally preserved — nothing has been deleted. User can
1030
+ // retry /gsd dispatch complete-milestone or merge manually once the
1031
+ // underlying issue is fixed (#1668, #1891).
1032
+ notify(
1033
+ `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/gsd dispatch complete-milestone\` or merge manually.`,
1034
+ "warning",
1035
+ );
1036
+
1037
+ // Clean up stale merge state left by failed squash-merge (#1389)
1038
+ try {
1039
+ const gitDir = join(originalBasePath || worktreeBasePath, ".git");
1040
+ for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
1041
+ const p = join(gitDir, f);
1042
+ if (existsSync(p)) unlinkSync(p);
1043
+ }
1044
+ } catch {
1045
+ /* best-effort */
1046
+ }
1047
+
1048
+ // Error recovery: chdir back to project root only when no real worktree
1049
+ // path is available. Session-side cleanup (restoreToProjectRoot,
1050
+ // gitService rebuild) is the caller's responsibility.
1051
+ if (originalBasePath && !worktreeBasePath) {
1052
+ try {
1053
+ process.chdir(originalBasePath);
1054
+ } catch {
1055
+ /* best-effort */
1056
+ }
1057
+ }
1058
+
1059
+ // Re-throw: MergeConflictError stops the auto loop (#2330);
1060
+ // non-conflict errors must also propagate so broken states are
1061
+ // diagnosable (#4380).
1062
+ throw err;
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Branch-mode merge body. Session-less.
1068
+ *
1069
+ * Session-side `gitService` rebuild after HEAD changes is the caller's
1070
+ * responsibility. The branch-mode `UserNotifiedError` sentinel still flows
1071
+ * through unchanged so the outer caller can suppress duplicate toasts.
1072
+ */
1073
+ function _mergeBranchModeImpl(
1074
+ deps: WorktreeLifecycleDeps,
1075
+ mctx: MergeContext,
1076
+ ): MergeStandaloneResult {
1077
+ const { worktreeBasePath, milestoneId, notify } = mctx;
1078
+ try {
1079
+ const currentBranch = currentLifecycleBranch(deps, worktreeBasePath);
1080
+ const milestoneBranch = lifecycleAutoWorktreeBranch(deps, milestoneId);
1081
+
1082
+ if (currentBranch !== milestoneBranch) {
1083
+ // #5538-followup: previous behaviour was to silently `return false`
1084
+ // when HEAD wasn't on the milestone branch — that let the loop
1085
+ // advance with the milestone's commits stranded on the branch.
1086
+ // Attempt recovery by force-checking-out the milestone branch; if
1087
+ // that fails, throw so the caller pauses auto-mode and the user
1088
+ // sees the failure instead of a silent merge skip.
1089
+ debugLog("WorktreeLifecycle", {
1090
+ action: "mergeAndExit",
1091
+ milestoneId,
1092
+ mode: "branch",
1093
+ recovery: "checkout-milestone-branch",
1094
+ currentBranch,
1095
+ milestoneBranch,
1096
+ });
1097
+ try {
1098
+ checkoutLifecycleBranch(deps, worktreeBasePath, milestoneBranch);
1099
+ } catch (checkoutErr) {
1100
+ const checkoutMsg =
1101
+ checkoutErr instanceof Error
1102
+ ? checkoutErr.message
1103
+ : String(checkoutErr);
1104
+ notify(
1105
+ `Cannot merge milestone ${milestoneId}: working tree is on ${currentBranch} and checkout to ${milestoneBranch} failed (${checkoutMsg}). Resolve manually and run /gsd auto to resume.`,
1106
+ "error",
1107
+ );
1108
+ throw new UserNotifiedError(checkoutMsg, checkoutErr);
1109
+ }
1110
+
1111
+ const reverify = currentLifecycleBranch(deps, worktreeBasePath);
1112
+ if (reverify !== milestoneBranch) {
1113
+ const reverifyMsg = `branch checkout to ${milestoneBranch} reported success but current branch is ${reverify}`;
1114
+ notify(
1115
+ `Cannot merge milestone ${milestoneId}: ${reverifyMsg}. Resolve manually and run /gsd auto to resume.`,
1116
+ "error",
1117
+ );
1118
+ throw new UserNotifiedError(reverifyMsg);
1119
+ }
1120
+ }
1121
+
1122
+ const roadmapPath = resolveMilestoneFile(
1123
+ worktreeBasePath,
1124
+ milestoneId,
1125
+ "ROADMAP",
1126
+ );
1127
+ if (!roadmapPath) {
1128
+ debugLog("WorktreeLifecycle", {
1129
+ action: "mergeAndExit",
1130
+ milestoneId,
1131
+ mode: "branch",
1132
+ skipped: true,
1133
+ reason: "no-roadmap",
1134
+ });
1135
+ return {
1136
+ merged: false,
1137
+ mode: "branch",
1138
+ codeFilesChanged: false,
1139
+ pushed: false,
1140
+ };
1141
+ }
1142
+
1143
+ const roadmapContent = readLifecycleFile(deps, roadmapPath);
1144
+ const mergeResult = deps.mergeMilestoneToMain(
1145
+ worktreeBasePath,
1146
+ milestoneId,
1147
+ roadmapContent,
1148
+ );
1149
+
1150
+ if (mergeResult.codeFilesChanged) {
1151
+ notify(
1152
+ `Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
1153
+ "info",
1154
+ );
1155
+ } else {
1156
+ notify(
1157
+ `WARNING: Milestone ${milestoneId} merged (branch mode) but contained NO code changes — only .gsd/ metadata. ` +
1158
+ `Review the milestone output and re-run if code is missing.`,
1159
+ "warning",
1160
+ );
1161
+ }
1162
+ debugLog("WorktreeLifecycle", {
1163
+ action: "mergeAndExit",
1164
+ milestoneId,
1165
+ mode: "branch",
1166
+ result: "success",
1167
+ });
1168
+ return {
1169
+ merged: true,
1170
+ mode: "branch",
1171
+ codeFilesChanged: mergeResult.codeFilesChanged,
1172
+ pushed: mergeResult.pushed,
1173
+ commitMessage: mergeResult.commitMessage,
1174
+ };
1175
+ } catch (err) {
1176
+ const msg = err instanceof Error ? err.message : String(err);
1177
+ debugLog("WorktreeLifecycle", {
1178
+ action: "mergeAndExit",
1179
+ milestoneId,
1180
+ mode: "branch",
1181
+ result: "error",
1182
+ error: msg,
1183
+ });
1184
+ if (!(err instanceof UserNotifiedError)) {
1185
+ notify(`Milestone merge failed (branch mode): ${msg}`, "warning");
1186
+ }
1187
+ // Re-throw all errors so callers can apply their own recovery (#4380).
1188
+ throw err;
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * Session-less merge entry (ADR-016 phase 2 / A1, issue #5618).
1194
+ *
1195
+ * Runs the worktree-mode or branch-mode merge body without touching session
1196
+ * state. Used directly by `parallel-merge.ts` and indirectly (via
1197
+ * `_mergeAndExit`) by the single-loop path. Caller is responsible for any
1198
+ * session-side cleanup based on the returned `mode`.
1199
+ *
1200
+ * **CWD anchor**: anchors `process.cwd()` at `originalBasePath` before
1201
+ * non-worktree merge paths to mirror the single-loop guard against ENOENT
1202
+ * after teardown (de73fb43d). Worktree-mode merge paths keep the real
1203
+ * worktree as cwd because `mergeMilestoneToMain()` infers source worktree
1204
+ * state from `process.cwd()`. Best-effort; silent on failure.
1205
+ *
1206
+ * **Failure handling**: `MergeConflictError` and other unrecoverable errors
1207
+ * propagate to the caller. The caller is responsible for any state restore
1208
+ * (single-loop callers re-`chdir` and `restoreToProjectRoot`; parallel
1209
+ * callers surface to the user as a `MergeResult` with `success: false`).
1210
+ */
1211
+ export function mergeMilestoneStandalone(
1212
+ deps: WorktreeLifecycleDeps,
1213
+ mctx: MergeContext,
1214
+ ): MergeStandaloneResult {
1215
+ const { originalBasePath, worktreeBasePath, milestoneId, notify } = mctx;
1216
+ validateMilestoneId(milestoneId);
1217
+
1218
+ if (mctx.isolationDegraded) {
1219
+ if (originalBasePath) {
1220
+ try {
1221
+ process.chdir(originalBasePath);
1222
+ } catch (err) {
1223
+ debugLog("WorktreeLifecycle", {
1224
+ action: "mergeAndExit",
1225
+ phase: "pre-merge-chdir-failed",
1226
+ milestoneId,
1227
+ originalBasePath,
1228
+ error: err instanceof Error ? err.message : String(err),
1229
+ });
1230
+ }
1231
+ }
1232
+ debugLog("WorktreeLifecycle", {
1233
+ action: "mergeAndExit",
1234
+ milestoneId,
1235
+ skipped: true,
1236
+ reason: "isolation-degraded",
1237
+ });
1238
+ notify(
1239
+ `Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
1240
+ "info",
1241
+ );
1242
+ return {
1243
+ merged: false,
1244
+ mode: "skipped",
1245
+ codeFilesChanged: false,
1246
+ pushed: false,
1247
+ };
1248
+ }
1249
+
1250
+ const mode = getIsolationMode(originalBasePath || worktreeBasePath);
1251
+ debugLog("WorktreeLifecycle", {
1252
+ action: "mergeAndExit",
1253
+ milestoneId,
1254
+ mode,
1255
+ basePath: worktreeBasePath,
1256
+ });
1257
+ emitJournalEvent(originalBasePath || worktreeBasePath, {
1258
+ ts: new Date().toISOString(),
1259
+ flowId: randomUUID(),
1260
+ seq: 0,
1261
+ eventType: "worktree-merge-start",
1262
+ data: { milestoneId, mode },
1263
+ });
1264
+
1265
+ // #2625: If we are physically inside an auto-worktree, we MUST merge
1266
+ // regardless of the current isolation config. This prevents data loss
1267
+ // when the default isolation mode changes between versions.
1268
+ const inWorktree =
1269
+ lifecycleIsInAutoWorktree(deps, worktreeBasePath) && Boolean(originalBasePath);
1270
+
1271
+ if (mode === "none" && !inWorktree) {
1272
+ debugLog("WorktreeLifecycle", {
1273
+ action: "mergeAndExit",
1274
+ milestoneId,
1275
+ skipped: true,
1276
+ reason: "mode-none",
1277
+ });
1278
+ // Anchor cwd at project root before the early return so subsequent
1279
+ // process.cwd() calls after the skip don't ENOENT if we were inside a
1280
+ // worktree directory that gets torn down later. Best-effort.
1281
+ if (originalBasePath) {
1282
+ try {
1283
+ process.chdir(originalBasePath);
1284
+ } catch {
1285
+ /* best-effort */
1286
+ }
1287
+ }
1288
+ return {
1289
+ merged: false,
1290
+ mode: "skipped",
1291
+ codeFilesChanged: false,
1292
+ pushed: false,
1293
+ };
1294
+ }
1295
+
1296
+ // Set cwd to the correct anchor before dispatching to mode implementations.
1297
+ // Worktree mode / in-worktree override must run from the live worktree so
1298
+ // mergeMilestoneToMain can find worktree-local state; branch mode runs from
1299
+ // the original project root. Best-effort for synthetic test paths.
1300
+ const targetCwd = mode === "worktree" || inWorktree
1301
+ ? worktreeBasePath
1302
+ : originalBasePath;
1303
+ if (targetCwd) {
1304
+ try {
1305
+ process.chdir(targetCwd);
1306
+ } catch (err) {
1307
+ debugLog("WorktreeLifecycle", {
1308
+ action: "mergeAndExit",
1309
+ phase: "pre-merge-chdir-failed",
1310
+ milestoneId,
1311
+ targetCwd,
1312
+ error: err instanceof Error ? err.message : String(err),
1313
+ });
1314
+ }
1315
+ }
1316
+
1317
+ if (mode === "worktree" || inWorktree) {
1318
+ return _mergeWorktreeModeImpl(deps, mctx);
1319
+ }
1320
+ if (mode === "branch") {
1321
+ return _mergeBranchModeImpl(deps, mctx);
1322
+ }
1323
+ // Defensive fallback — should not reach here given the mode-none guard above.
1324
+ return {
1325
+ merged: false,
1326
+ mode: "skipped",
1327
+ codeFilesChanged: false,
1328
+ pushed: false,
1329
+ };
1330
+ }
1331
+
1332
+ // ─── Module class ────────────────────────────────────────────────────────
1333
+
1334
+ /**
1335
+ * Worktree Lifecycle module instance.
1336
+ *
1337
+ * Constructed once per auto-mode session. Holds the session reference so
1338
+ * verbs can mutate `s.basePath` and related coordination state directly
1339
+ * without round-tripping through callers.
1340
+ */
1341
+ export class WorktreeLifecycle {
1342
+ private readonly s: AutoSession;
1343
+ private readonly deps: WorktreeLifecycleDeps;
1344
+
1345
+ constructor(s: AutoSession, deps: WorktreeLifecycleDeps) {
1346
+ this.s = s;
1347
+ this.deps = deps;
1348
+ }
1349
+
1350
+ /**
1351
+ * Enter or create the auto-worktree for `milestoneId`. Idempotent if
1352
+ * already in this milestone (lease refreshed; basePath unchanged).
1353
+ *
1354
+ * Returns a typed `EnterResult` describing the outcome. Callers may
1355
+ * ignore the result if they read `s.basePath` directly afterwards
1356
+ * (legacy behaviour); new callers should branch on the result.
1357
+ */
1358
+ enterMilestone(milestoneId: string, ctx: NotifyCtx): EnterResult {
1359
+ return _enterMilestoneCore(this.s, this.deps, milestoneId, ctx);
1360
+ }
1361
+
1362
+ /**
1363
+ * Exit the current worktree. With `opts.merge === true`, runs the full
1364
+ * merge-and-teardown path (worktree-mode or branch-mode auto-detected).
1365
+ * With `opts.merge === false`, runs auto-commit and teardown without
1366
+ * merging to main.
1367
+ *
1368
+ * Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
1369
+ * `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
1370
+ * giving callers a typed branch for the expected failure path.
1371
+ * Unexpected failures (filesystem, git permissions, etc.) are wrapped
1372
+ * as `{ ok: false, reason: "teardown-failed", cause }` so callers always
1373
+ * receive a discriminated union — no exceptions for any expected outcome.
1374
+ */
1375
+ exitMilestone(
1376
+ milestoneId: string,
1377
+ opts: { merge: boolean; preserveBranch?: boolean },
1378
+ ctx: NotifyCtx,
1379
+ ): ExitResult {
1380
+ if (opts.merge) {
1381
+ try {
1382
+ const merged = this._mergeAndExit(milestoneId, ctx);
1383
+ return {
1384
+ ok: true,
1385
+ merged: merged.merged,
1386
+ codeFilesChanged: merged.codeFilesChanged,
1387
+ };
1388
+ } catch (err) {
1389
+ if (err instanceof MergeConflictError) {
1390
+ return { ok: false, reason: "merge-conflict", cause: err };
1391
+ }
1392
+ return { ok: false, reason: "teardown-failed", cause: err };
1393
+ }
1394
+ }
1395
+ try {
1396
+ this._exitWithoutMerge(milestoneId, ctx, {
1397
+ preserveBranch: opts.preserveBranch,
1398
+ });
1399
+ return { ok: true, merged: false, codeFilesChanged: false };
1400
+ } catch (err) {
1401
+ return { ok: false, reason: "teardown-failed", cause: err };
1402
+ }
1403
+ }
1404
+
1405
+ /**
1406
+ * Milestone transition: merge the current milestone, then enter the next
1407
+ * one. Pattern used when the loop detects that the active milestone has
1408
+ * changed (current completed, next is now active). Caller is responsible
1409
+ * for re-deriving state between the merge and the enter.
1410
+ */
1411
+ mergeAndEnterNext(
1412
+ currentMilestoneId: string,
1413
+ nextMilestoneId: string,
1414
+ ctx: NotifyCtx,
1415
+ ): void {
1416
+ debugLog("WorktreeLifecycle", {
1417
+ action: "mergeAndEnterNext",
1418
+ currentMilestoneId,
1419
+ nextMilestoneId,
1420
+ });
1421
+ let merged = false;
1422
+ let mergeThrew = false;
1423
+ try {
1424
+ merged = this._mergeAndExit(currentMilestoneId, ctx).merged;
1425
+ } catch (err) {
1426
+ if (err instanceof UserNotifiedError) throw err;
1427
+ mergeThrew = true;
1428
+ // _mergeAndExit emits a warning and restores state on failure during
1429
+ // merge/cleanup. If it throws before recovery runs (e.g. validation,
1430
+ // emitJournalEvent), basePath isn't restored — re-throw so we don't
621
1431
  // enter the next milestone with the current one unmerged.
622
1432
  const projectRoot = resolveWorktreeProjectRoot(
623
1433
  this.s.basePath,
@@ -654,7 +1464,7 @@ export class WorktreeLifecycle {
654
1464
  opts: { preserveBranch?: boolean },
655
1465
  ): void {
656
1466
  validateMilestoneId(milestoneId);
657
- if (!this.deps.isInAutoWorktree(this.s.basePath)) {
1467
+ if (!lifecycleIsInAutoWorktree(this.deps, this.s.basePath)) {
658
1468
  debugLog("WorktreeLifecycle", {
659
1469
  action: "exitMilestone",
660
1470
  milestoneId,
@@ -671,7 +1481,7 @@ export class WorktreeLifecycle {
671
1481
  });
672
1482
 
673
1483
  try {
674
- this.deps.autoCommitCurrentBranch(this.s.basePath, "stop", milestoneId);
1484
+ autoCommitLifecycleBranch(this.deps, this.s.basePath, "stop", milestoneId);
675
1485
  } catch (err) {
676
1486
  debugLog("WorktreeLifecycle", {
677
1487
  action: "exitMilestone",
@@ -680,7 +1490,7 @@ export class WorktreeLifecycle {
680
1490
  error: err instanceof Error ? err.message : String(err),
681
1491
  });
682
1492
  ctx.notify(
683
- `Auto-commit before exiting ${milestoneId} failed: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps.autoWorktreeBranch(milestoneId)} is preserved for recovery.`,
1493
+ `Auto-commit before exiting ${milestoneId} failed: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
684
1494
  "warning",
685
1495
  );
686
1496
  }
@@ -697,7 +1507,7 @@ export class WorktreeLifecycle {
697
1507
  error: err instanceof Error ? err.message : String(err),
698
1508
  });
699
1509
  ctx.notify(
700
- `Could not leave milestone worktree before cleanup: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps.autoWorktreeBranch(milestoneId)} is preserved for recovery.`,
1510
+ `Could not leave milestone worktree before cleanup: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
701
1511
  "warning",
702
1512
  );
703
1513
  }
@@ -705,7 +1515,7 @@ export class WorktreeLifecycle {
705
1515
 
706
1516
  let teardownFailed = false;
707
1517
  try {
708
- this.deps.teardownAutoWorktree(this.s.originalBasePath, milestoneId, {
1518
+ lifecycleTeardownAutoWorktree(this.deps, this.s.originalBasePath, milestoneId, {
709
1519
  preserveBranch: opts.preserveBranch ?? false,
710
1520
  });
711
1521
  } catch (err) {
@@ -717,7 +1527,7 @@ export class WorktreeLifecycle {
717
1527
  error: err instanceof Error ? err.message : String(err),
718
1528
  });
719
1529
  ctx.notify(
720
- `Worktree cleanup failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Branch ${this.deps.autoWorktreeBranch(milestoneId)} is preserved for recovery.`,
1530
+ `Worktree cleanup failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Branch ${lifecycleAutoWorktreeBranch(this.deps, milestoneId)} is preserved for recovery.`,
721
1531
  "warning",
722
1532
  );
723
1533
  }
@@ -742,103 +1552,60 @@ export class WorktreeLifecycle {
742
1552
  /**
743
1553
  * Merge the completed milestone branch back to main and exit the worktree.
744
1554
  *
745
- * - **worktree mode**: reads the roadmap, runs squash merge, projects
746
- * final state back via Projection.finalizeProjectionForMerge, tears
747
- * down the worktree, restores `s.basePath`. Falls back to bare
748
- * teardown (preserving the branch) if no roadmap exists.
749
- * - **branch mode**: validates HEAD is on the milestone branch (recovers
750
- * via checkout if not), merges, rebuilds GitService.
751
- * - **none**: no-op unless physically inside an auto-worktree (#2625).
1555
+ * Session-bound wrapper around `mergeMilestoneStandalone`. Builds a
1556
+ * `MergeContext` from `this.s`, layers session-side bookkeeping on top of
1557
+ * the result:
752
1558
  *
753
- * Returns true when an actual squash-merge ran. Throws MergeConflictError
754
- * (and other non-recoverable errors) for callers to handle.
1559
+ * - resquash-on-merge using `s.milestoneStartShas`
1560
+ * - merge-completion telemetry (duration)
1561
+ * - mode-specific session restore: worktree-mode → `restoreToProjectRoot`,
1562
+ * branch-mode → `gitService` rebuild
1563
+ *
1564
+ * Returns the session-less merge result. Errors propagate after
1565
+ * `restoreToProjectRoot()` runs so callers always receive a consistent
1566
+ * session.
755
1567
  */
756
- private _mergeAndExit(milestoneId: string, ctx: NotifyCtx): boolean {
757
- validateMilestoneId(milestoneId);
758
-
759
- // Anchor cwd at the project root before any merge work. Some merge
760
- // paths (mergeMilestoneToMain, slice-cadence) chdir explicitly; others
761
- // (branch-mode, isolation-degraded skip) do not. If the worktree dir
762
- // is later torn down while cwd still points into it, every subsequent
763
- // process.cwd() throws ENOENT — which after de73fb43d surfaces as a
764
- // session-failed cancel and (in headless mode) terminates the whole
765
- // gsd process. Best-effort: silent on failure so synthetic test paths
766
- // still pass.
767
- if (this.s.originalBasePath) {
768
- try {
769
- process.chdir(this.s.originalBasePath);
770
- } catch (err) {
771
- debugLog("WorktreeLifecycle", {
772
- action: "mergeAndExit",
773
- phase: "pre-merge-chdir-failed",
774
- milestoneId,
775
- originalBasePath: this.s.originalBasePath,
776
- error: err instanceof Error ? err.message : String(err),
777
- });
778
- }
779
- }
780
-
1568
+ private _mergeAndExit(
1569
+ milestoneId: string,
1570
+ ctx: NotifyCtx,
1571
+ ): MergeStandaloneResult {
781
1572
  // #4764 — telemetry: record start timestamp so we can emit merge duration.
782
1573
  const mergeStartedAt = new Date().toISOString();
783
1574
  const mergeStartMs = Date.now();
784
1575
 
785
- if (this.s.isolationDegraded) {
786
- debugLog("WorktreeLifecycle", {
787
- action: "mergeAndExit",
788
- milestoneId,
789
- skipped: true,
790
- reason: "isolation-degraded",
791
- });
792
- ctx.notify(
793
- `Skipping worktree merge for ${milestoneId} — isolation was degraded (worktree creation failed earlier). Work is on the current branch.`,
794
- "info",
795
- );
796
- return false;
797
- }
798
-
799
- const mode = this.deps.getIsolationMode(
800
- this.s.originalBasePath || this.s.basePath,
801
- );
802
- debugLog("WorktreeLifecycle", {
803
- action: "mergeAndExit",
804
- milestoneId,
805
- mode,
806
- basePath: this.s.basePath,
807
- });
808
- emitJournalEvent(this.s.originalBasePath || this.s.basePath, {
809
- ts: new Date().toISOString(),
810
- flowId: randomUUID(),
811
- seq: 0,
812
- eventType: "worktree-merge-start",
813
- data: { milestoneId, mode },
814
- });
815
-
816
- // #2625: If we are physically inside an auto-worktree, we MUST merge
817
- // regardless of the current isolation config. This prevents data loss
818
- // when the default isolation mode changes between versions.
819
- const inWorktree =
820
- this.deps.isInAutoWorktree(this.s.basePath) && this.s.originalBasePath;
821
-
822
- if (mode === "none" && !inWorktree) {
823
- debugLog("WorktreeLifecycle", {
824
- action: "mergeAndExit",
1576
+ let result: MergeStandaloneResult;
1577
+ try {
1578
+ result = mergeMilestoneStandalone(this.deps, {
1579
+ originalBasePath: this.s.originalBasePath,
1580
+ worktreeBasePath: this.s.basePath,
825
1581
  milestoneId,
826
- skipped: true,
827
- reason: "mode-none",
1582
+ isolationDegraded: this.s.isolationDegraded,
1583
+ notify: ctx.notify,
828
1584
  });
829
- return false;
830
- }
831
-
832
- let actuallyMerged = false;
833
- if (mode === "worktree" || inWorktree) {
834
- actuallyMerged = this._mergeWorktreeMode(milestoneId, ctx);
835
- } else if (mode === "branch") {
836
- actuallyMerged = this._mergeBranchMode(milestoneId, ctx);
1585
+ } catch (err) {
1586
+ // Standalone has already done its session-less cleanup
1587
+ // (chdir, SQUASH_MSG cleanup, journal event). Layer session-side
1588
+ // restore on top so callers get a consistent session.
1589
+ this.restoreToProjectRoot();
1590
+ throw err;
837
1591
  }
838
1592
 
839
- if (!actuallyMerged) {
1593
+ if (!result.merged) {
1594
+ // Skip / no-roadmap / mode-none paths. milestoneStartShas housekeeping
1595
+ // is unconditional; mode-specific session restore happens for
1596
+ // worktree-mode (preserve-branch path tore down the worktree, so
1597
+ // basePath must restore) and not for branch-mode (no basePath change).
840
1598
  this.s.milestoneStartShas.delete(milestoneId);
841
- return false;
1599
+ if (result.mode === "worktree") {
1600
+ this.restoreToProjectRoot();
1601
+ debugLog("WorktreeLifecycle", {
1602
+ action: "mergeAndExit",
1603
+ milestoneId,
1604
+ result: "done",
1605
+ basePath: this.s.basePath,
1606
+ });
1607
+ }
1608
+ return result;
842
1609
  }
843
1610
 
844
1611
  // #4765 — when collapse_cadence=slice AND milestone_resquash=true, the
@@ -848,19 +1615,20 @@ export class WorktreeLifecycle {
848
1615
  try {
849
1616
  const startSha = this.s.milestoneStartShas.get(milestoneId);
850
1617
  if (startSha) {
851
- const prefs = loadEffectiveGSDPreferences(
1618
+ const prefs = lifecycleLoadPreferences(
1619
+ this.deps,
852
1620
  this.s.originalBasePath || this.s.basePath,
853
1621
  )?.preferences;
854
1622
  if (
855
1623
  getCollapseCadence(prefs) === "slice" &&
856
1624
  getMilestoneResquash(prefs)
857
1625
  ) {
858
- const result = resquashMilestoneOnMain(
1626
+ const resquashResult = resquashMilestoneOnMain(
859
1627
  this.s.originalBasePath || this.s.basePath,
860
1628
  milestoneId,
861
1629
  startSha,
862
1630
  );
863
- if (result.resquashed) {
1631
+ if (resquashResult.resquashed) {
864
1632
  ctx.notify(
865
1633
  `slice-cadence: re-squashed slice commits for ${milestoneId} into a single milestone commit.`,
866
1634
  "info",
@@ -900,291 +1668,29 @@ export class WorktreeLifecycle {
900
1668
  : String(telemetryErr),
901
1669
  });
902
1670
  }
903
- return true;
904
- }
905
-
906
- /** Worktree-mode merge body. Returns true when an actual squash-merge ran. */
907
- private _mergeWorktreeMode(milestoneId: string, ctx: NotifyCtx): boolean {
908
- const originalBase = this.s.originalBasePath;
909
- if (!originalBase) {
910
- debugLog("WorktreeLifecycle", {
911
- action: "mergeAndExit",
912
- milestoneId,
913
- mode: "worktree",
914
- skipped: true,
915
- reason: "missing-original-base",
916
- });
917
- return false;
918
- }
919
1671
 
920
- let merged = false;
921
- try {
922
- // ADR-016: final projection before teardown. Replaces the legacy
923
- // syncWorktreeStateBack(originalBase, basePath, milestoneId) call.
924
- const finalScope = scopeMilestone(
925
- createWorkspace(this.s.basePath),
926
- milestoneId,
927
- );
928
- const { synced } =
929
- this.deps.worktreeProjection.finalizeProjectionForMerge(finalScope);
930
- if (synced.length > 0) {
931
- debugLog("WorktreeLifecycle", {
932
- action: "mergeAndExit",
933
- milestoneId,
934
- phase: "reverse-sync",
935
- synced: synced.length,
936
- });
937
- }
938
-
939
- // Resolve roadmap — try project root first, then worktree path as
940
- // fallback. The worktree may hold the only copy when state-back
941
- // projection silently dropped it or .gsd/ is not symlinked. Without
942
- // the fallback, a missing roadmap triggers bare teardown which
943
- // deletes the branch and orphans all milestone commits (#1573).
944
- let roadmapPath = this.deps.resolveMilestoneFile(
945
- originalBase,
946
- milestoneId,
947
- "ROADMAP",
948
- );
949
- if (
950
- !roadmapPath &&
951
- !isSamePathPhysical(this.s.basePath, originalBase)
952
- ) {
953
- roadmapPath = this.deps.resolveMilestoneFile(
954
- this.s.basePath,
955
- milestoneId,
956
- "ROADMAP",
957
- );
958
- if (roadmapPath) {
959
- debugLog("WorktreeLifecycle", {
960
- action: "mergeAndExit",
961
- milestoneId,
962
- phase: "roadmap-fallback",
963
- note: "resolved from worktree path",
964
- });
965
- }
966
- }
967
-
968
- if (roadmapPath) {
969
- const roadmapContent = this.deps.readFileSync(roadmapPath, "utf-8");
970
- const mergeResult = this.deps.mergeMilestoneToMain(
971
- originalBase,
972
- milestoneId,
973
- roadmapContent,
974
- );
975
- merged = true;
976
-
977
- // #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
978
- // cleanup internally (step 12), but it can silently fail on Windows
979
- // or when the worktree directory is locked. Perform a secondary
980
- // teardown here to ensure the worktree is properly cleaned up.
981
- // Idempotent — if already removed, teardownAutoWorktree no-ops.
982
- try {
983
- this.deps.teardownAutoWorktree(originalBase, milestoneId);
984
- } catch {
985
- // Best-effort — primary cleanup in mergeMilestoneToMain may have
986
- // already removed the worktree.
987
- }
988
-
989
- if (mergeResult.codeFilesChanged) {
990
- ctx.notify(
991
- `Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
992
- "info",
993
- );
994
- } else {
995
- // #1906 — milestone produced only .gsd/ metadata. Surface
996
- // clearly so the user knows the milestone is not truly complete.
997
- ctx.notify(
998
- `WARNING: Milestone ${milestoneId} merged to main but contained NO code changes — only .gsd/ metadata files. ` +
999
- `The milestone summary may describe planned work that was never implemented. ` +
1000
- `Review the milestone output and re-run if code is missing.`,
1001
- "warning",
1002
- );
1003
- }
1004
- } else {
1005
- // No roadmap at either location — teardown but PRESERVE the branch
1006
- // so commits are not orphaned (#1573).
1007
- this.deps.teardownAutoWorktree(originalBase, milestoneId, {
1008
- preserveBranch: true,
1009
- });
1010
- ctx.notify(
1011
- `Exited worktree for ${milestoneId} (no roadmap found — branch preserved for manual merge).`,
1012
- "warning",
1013
- );
1014
- }
1015
- } catch (err) {
1016
- const msg = err instanceof Error ? err.message : String(err);
1672
+ // Mode-specific session restore.
1673
+ if (result.mode === "worktree") {
1674
+ this.restoreToProjectRoot();
1017
1675
  debugLog("WorktreeLifecycle", {
1018
1676
  action: "mergeAndExit",
1019
1677
  milestoneId,
1020
- result: "error",
1021
- error: msg,
1022
- fallback: "chdir-to-project-root",
1023
- });
1024
- emitJournalEvent(this.s.originalBasePath || this.s.basePath, {
1025
- ts: new Date().toISOString(),
1026
- flowId: randomUUID(),
1027
- seq: 0,
1028
- eventType: "worktree-merge-failed",
1029
- data: { milestoneId, error: msg },
1678
+ result: "done",
1679
+ basePath: this.s.basePath,
1030
1680
  });
1031
- // Surface a clear, actionable error. Worktree and milestone branch
1032
- // are intentionally preserved — nothing has been deleted. User can
1033
- // retry /gsd dispatch complete-milestone or merge manually once the
1034
- // underlying issue is fixed (#1668, #1891).
1035
- ctx.notify(
1036
- `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/gsd dispatch complete-milestone\` or merge manually.`,
1037
- "warning",
1038
- );
1039
-
1040
- // Clean up stale merge state left by failed squash-merge (#1389)
1041
- try {
1042
- const gitDir = join(originalBase || this.s.basePath, ".git");
1043
- for (const f of ["SQUASH_MSG", "MERGE_HEAD", "MERGE_MSG"]) {
1044
- const p = join(gitDir, f);
1045
- if (existsSync(p)) unlinkSync(p);
1046
- }
1047
- } catch {
1048
- /* best-effort */
1049
- }
1050
-
1051
- // Error recovery: always restore to project root
1052
- if (originalBase) {
1053
- try {
1054
- process.chdir(originalBase);
1055
- } catch {
1056
- /* best-effort */
1057
- }
1058
- }
1059
-
1060
- // Restore state before re-throwing so callers always get a
1061
- // consistent session (#4380).
1062
- this.restoreToProjectRoot();
1063
- // Re-throw: MergeConflictError stops the auto loop (#2330);
1064
- // non-conflict errors must also propagate so broken states are
1065
- // diagnosable (#4380).
1066
- throw err;
1067
- }
1068
-
1069
- // Always restore basePath and rebuild — whether merge succeeded or failed
1070
- this.restoreToProjectRoot();
1071
- debugLog("WorktreeLifecycle", {
1072
- action: "mergeAndExit",
1073
- milestoneId,
1074
- result: "done",
1075
- basePath: this.s.basePath,
1076
- });
1077
- return merged;
1078
- }
1079
-
1080
- /** Branch-mode merge body. Returns true when a merge actually ran. */
1081
- private _mergeBranchMode(milestoneId: string, ctx: NotifyCtx): boolean {
1082
- try {
1083
- const currentBranch = this.deps.getCurrentBranch(this.s.basePath);
1084
- const milestoneBranch = this.deps.autoWorktreeBranch(milestoneId);
1085
-
1086
- if (currentBranch !== milestoneBranch) {
1087
- // #5538-followup: previous behaviour was to silently `return false`
1088
- // when HEAD wasn't on the milestone branch — that let the loop
1089
- // advance with the milestone's commits stranded on the branch.
1090
- // Attempt recovery by force-checking-out the milestone branch; if
1091
- // that fails, throw so the caller pauses auto-mode and the user
1092
- // sees the failure instead of a silent merge skip.
1093
- debugLog("WorktreeLifecycle", {
1094
- action: "mergeAndExit",
1095
- milestoneId,
1096
- mode: "branch",
1097
- recovery: "checkout-milestone-branch",
1098
- currentBranch,
1099
- milestoneBranch,
1100
- });
1101
- try {
1102
- this.deps.checkoutBranch(this.s.basePath, milestoneBranch);
1103
- } catch (checkoutErr) {
1104
- const checkoutMsg =
1105
- checkoutErr instanceof Error
1106
- ? checkoutErr.message
1107
- : String(checkoutErr);
1108
- ctx.notify(
1109
- `Cannot merge milestone ${milestoneId}: working tree is on ${currentBranch} and checkout to ${milestoneBranch} failed (${checkoutMsg}). Resolve manually and run /gsd auto to resume.`,
1110
- "error",
1111
- );
1112
- throw new UserNotifiedError(checkoutMsg, checkoutErr);
1113
- }
1114
-
1115
- const reverify = this.deps.getCurrentBranch(this.s.basePath);
1116
- if (reverify !== milestoneBranch) {
1117
- const reverifyMsg = `branch checkout to ${milestoneBranch} reported success but current branch is ${reverify}`;
1118
- ctx.notify(
1119
- `Cannot merge milestone ${milestoneId}: ${reverifyMsg}. Resolve manually and run /gsd auto to resume.`,
1120
- "error",
1121
- );
1122
- throw new UserNotifiedError(reverifyMsg);
1123
- }
1124
- }
1125
-
1126
- const roadmapPath = this.deps.resolveMilestoneFile(
1127
- this.s.basePath,
1128
- milestoneId,
1129
- "ROADMAP",
1130
- );
1131
- if (!roadmapPath) {
1132
- debugLog("WorktreeLifecycle", {
1133
- action: "mergeAndExit",
1134
- milestoneId,
1135
- mode: "branch",
1136
- skipped: true,
1137
- reason: "no-roadmap",
1138
- });
1139
- return false;
1140
- }
1141
-
1142
- const roadmapContent = this.deps.readFileSync(roadmapPath, "utf-8");
1143
- const mergeResult = this.deps.mergeMilestoneToMain(
1144
- this.s.basePath,
1145
- milestoneId,
1146
- roadmapContent,
1147
- );
1148
-
1681
+ } else if (result.mode === "branch") {
1149
1682
  // Rebuild GitService after merge (branch HEAD changed)
1150
1683
  rebuildGitService(this.s, this.deps);
1151
-
1152
- if (mergeResult.codeFilesChanged) {
1153
- ctx.notify(
1154
- `Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
1155
- "info",
1156
- );
1157
- } else {
1158
- ctx.notify(
1159
- `WARNING: Milestone ${milestoneId} merged (branch mode) but contained NO code changes — only .gsd/ metadata. ` +
1160
- `Review the milestone output and re-run if code is missing.`,
1161
- "warning",
1162
- );
1163
- }
1164
- debugLog("WorktreeLifecycle", {
1165
- action: "mergeAndExit",
1166
- milestoneId,
1167
- mode: "branch",
1168
- result: "success",
1169
- });
1170
- return true;
1171
- } catch (err) {
1172
- const msg = err instanceof Error ? err.message : String(err);
1173
- debugLog("WorktreeLifecycle", {
1174
- action: "mergeAndExit",
1175
- milestoneId,
1176
- mode: "branch",
1177
- result: "error",
1178
- error: msg,
1179
- });
1180
- if (!(err instanceof UserNotifiedError)) {
1181
- ctx.notify(`Milestone merge failed (branch mode): ${msg}`, "warning");
1182
- }
1183
- // Re-throw all errors so callers can apply their own recovery (#4380).
1184
- throw err;
1185
1684
  }
1685
+ return result;
1186
1686
  }
1187
1687
 
1688
+ // ── Removed: _mergeWorktreeMode / _mergeBranchMode bodies ────────────
1689
+ // The merge bodies moved to file-scope `_mergeWorktreeModeImpl` and
1690
+ // `_mergeBranchModeImpl`, callable from the session-less
1691
+ // `mergeMilestoneStandalone` entry. The previous private methods are
1692
+ // gone; `_mergeAndExit` above is the only session-bound caller.
1693
+
1188
1694
  /**
1189
1695
  * Fall back to branch-mode for `milestoneId` after a failed worktree
1190
1696
  * creation, marking the session's isolation as degraded.
@@ -1210,9 +1716,9 @@ export class WorktreeLifecycle {
1210
1716
  this.s.originalBasePath,
1211
1717
  );
1212
1718
  try {
1213
- this.deps.enterBranchModeForMilestone(basePath, milestoneId);
1719
+ lifecycleEnterBranchMode(this.deps, basePath, milestoneId);
1214
1720
  rebuildGitService(this.s, this.deps);
1215
- this.deps.invalidateAllCaches();
1721
+ invalidateAllCaches();
1216
1722
  this.s.isolationDegraded = true;
1217
1723
  ctx.notify(
1218
1724
  `Switched to branch milestone/${milestoneId} (isolation degraded).`,
@@ -1229,18 +1735,190 @@ export class WorktreeLifecycle {
1229
1735
  }
1230
1736
 
1231
1737
  /**
1232
- * Restore `s.basePath` to `s.originalBasePath` and rebuild `s.gitService`.
1233
- * No-op when `originalBasePath` is empty (fresh sessions).
1738
+ * Restore `s.basePath` to `s.originalBasePath`, chdir process cwd, and
1739
+ * rebuild `s.gitService`. No-op when `originalBasePath` is empty (fresh
1740
+ * sessions).
1234
1741
  *
1235
1742
  * Used by error/cleanup paths that need the session to behave as if the
1236
1743
  * worktree was never entered. Does NOT teardown the worktree directory —
1237
1744
  * callers that need teardown go through `exitMilestone({ merge: false })`.
1745
+ *
1746
+ * ADR-016 phase 3 (#5693): chdir lives inside the verb so callers do not
1747
+ * pair `restoreToProjectRoot()` with a redundant `process.chdir`. The
1748
+ * chdir runs BEFORE the throwable work (`rebuildGitService`, cache
1749
+ * invalidation) so that cleanup-path cwd is restored even if the
1750
+ * downstream rebuild throws. The chdir itself is best-effort; failure is
1751
+ * logged via debugLog and swallowed.
1238
1752
  */
1239
1753
  restoreToProjectRoot(): void {
1240
1754
  if (!this.s.originalBasePath) return;
1241
1755
  this.s.basePath = this.s.originalBasePath;
1756
+ try {
1757
+ process.chdir(this.s.basePath);
1758
+ } catch (err) {
1759
+ debugLog("WorktreeLifecycle", {
1760
+ action: "restoreToProjectRoot",
1761
+ result: "chdir-failed",
1762
+ basePath: this.s.basePath,
1763
+ error: err instanceof Error ? err.message : String(err),
1764
+ });
1765
+ }
1242
1766
  rebuildGitService(this.s, this.deps);
1243
- this.deps.invalidateAllCaches();
1767
+ invalidateAllCaches();
1768
+ }
1769
+
1770
+ /**
1771
+ * Adopt a session root (ADR-016 phase 2 / B2, issue #5620).
1772
+ *
1773
+ * Sole owner of `s.basePath` mutation for bootstrap-class transitions:
1774
+ * initial session start, paused-resume entry (before persisted-state
1775
+ * consultation), and hook-trigger session activation. Defensive about
1776
+ * `s.originalBasePath`:
1777
+ *
1778
+ * - When `originalBase` is explicit: overwrite.
1779
+ * - Otherwise, set `s.originalBasePath` only if it is currently empty —
1780
+ * resume paths that already restored `s.originalBasePath` from paused
1781
+ * metadata keep their value.
1782
+ *
1783
+ * Does NOT chdir; callers that need cwd alignment with the new basePath
1784
+ * are responsible for it. Does NOT rebuild `s.gitService` — callers that
1785
+ * mutate `s.basePath` to a non-project-root path (e.g. a worktree on a
1786
+ * subsequent milestone enter) go through `enterMilestone`, which handles
1787
+ * the rebuild.
1788
+ */
1789
+ adoptSessionRoot(base: string, originalBase?: string): void {
1790
+ this.s.basePath = base;
1791
+ if (originalBase !== undefined) {
1792
+ this.s.originalBasePath = originalBase;
1793
+ } else if (!this.s.originalBasePath) {
1794
+ this.s.originalBasePath = base;
1795
+ }
1796
+ }
1797
+
1798
+ /**
1799
+ * Resume from a paused session (ADR-016 phase 2 / B3, issue #5621).
1800
+ *
1801
+ * Adopts `persistedWorktreePath` as `s.basePath` when the path is
1802
+ * non-null and exists on disk; otherwise falls back to `base`. Mirrors
1803
+ * the resume guard at `auto.ts:2164` — a stale or removed worktree
1804
+ * directory must not strand the resumed session in an invalid root.
1805
+ *
1806
+ * Folds in the body of the legacy `_resolvePausedResumeBasePathForTest`
1807
+ * helper (see `resolvePausedResumeBasePath` below). After this verb
1808
+ * lands the helper is deleted from `auto.ts` per the slice-7 closure
1809
+ * decision to retire `_*ForTest` suffixes from production paths.
1810
+ *
1811
+ * Like `adoptSessionRoot`, this is a pure session-state mutation — no
1812
+ * chdir, no git service rebuild, no cache invalidation.
1813
+ */
1814
+ resumeFromPausedSession(
1815
+ base: string,
1816
+ persistedWorktreePath: string | null,
1817
+ ): void {
1818
+ this.s.basePath = resolvePausedResumeBasePath(base, persistedWorktreePath);
1819
+ }
1820
+
1821
+ /**
1822
+ * Adopt an orphan worktree for a bootstrap-time merge (ADR-016 phase 2 / B4,
1823
+ * issue #5622).
1824
+ *
1825
+ * Owns the swap-run-revert protocol that bootstrap previously open-coded:
1826
+ *
1827
+ * 1. Snapshot prior `s.basePath` and `s.originalBasePath`.
1828
+ * 2. Resolve `getAutoWorktreePath(base, milestoneId) ?? base` before
1829
+ * mutating session state, then set `s.originalBasePath = base` and
1830
+ * `s.basePath` to the resolved path.
1831
+ * 3. Invoke the caller-supplied `run` callback under the swap.
1832
+ * 4. On `!result.merged`: revert to `base` and `chdir(base)` so the
1833
+ * caller can return early without leaving the session in a half-
1834
+ * swapped state.
1835
+ * 5. On `result.merged && !s.active`: revert to the snapshotted prior
1836
+ * paths (the orphan merge succeeded but bootstrap chose not to keep
1837
+ * the session active).
1838
+ * 6. On `result.merged && s.active`: leave the swap in place — the
1839
+ * loop will continue from the worktree path.
1840
+ *
1841
+ * The callback shape forces every caller through the same revert
1842
+ * protocol; an open-coded swap that forgets to revert on failure was the
1843
+ * original bug pattern this verb is designed to prevent.
1844
+ */
1845
+ adoptOrphanWorktree<T extends { merged: boolean }>(
1846
+ milestoneId: string,
1847
+ base: string,
1848
+ run: () => T,
1849
+ ): T {
1850
+ validateMilestoneId(milestoneId);
1851
+
1852
+ const priorBasePath = this.s.basePath;
1853
+ const priorOriginalBasePath = this.s.originalBasePath;
1854
+ const restorePriorPaths = (phase: string): void => {
1855
+ this.s.basePath = priorBasePath || base;
1856
+ this.s.originalBasePath = priorOriginalBasePath || base;
1857
+ try {
1858
+ process.chdir(this.s.originalBasePath || base);
1859
+ } catch (err) {
1860
+ debugLog("WorktreeLifecycle", {
1861
+ action: "adoptOrphanWorktree",
1862
+ phase,
1863
+ base: this.s.originalBasePath || base,
1864
+ error: err instanceof Error ? err.message : String(err),
1865
+ });
1866
+ }
1867
+ };
1868
+
1869
+ let adoptedBasePath: string;
1870
+ try {
1871
+ const wtPathFn =
1872
+ primitiveOverrides(this.deps).getAutoWorktreePath ?? getAutoWorktreePath;
1873
+ adoptedBasePath = wtPathFn(base, milestoneId) ?? base;
1874
+ } catch (err) {
1875
+ restorePriorPaths("rollback-resolve-worktree-failed");
1876
+ throw err;
1877
+ }
1878
+
1879
+ // Swap into the orphan worktree.
1880
+ this.s.originalBasePath = base;
1881
+ this.s.basePath = adoptedBasePath;
1882
+
1883
+ let result: T;
1884
+ try {
1885
+ result = run();
1886
+ } catch (err) {
1887
+ restorePriorPaths("rollback-run-failed");
1888
+ throw err;
1889
+ }
1890
+
1891
+ if (!result.merged) {
1892
+ // Failed orphan merge — revert to project root so the caller can
1893
+ // safely return early without leaving the session in an invalid
1894
+ // basePath. Mirror the chdir that bootstrap performed inline.
1895
+ this.s.basePath = base;
1896
+ this.s.originalBasePath = base;
1897
+ try {
1898
+ process.chdir(base);
1899
+ } catch (err) {
1900
+ debugLog("WorktreeLifecycle", {
1901
+ action: "adoptOrphanWorktree",
1902
+ phase: "revert-chdir-failed",
1903
+ base,
1904
+ error: err instanceof Error ? err.message : String(err),
1905
+ });
1906
+ }
1907
+ return result;
1908
+ }
1909
+
1910
+ if (!this.s.active) {
1911
+ // Merge succeeded but the session was not (re)activated — restore
1912
+ // the snapshotted paths so the calling context resumes where it
1913
+ // was, with the orphan branch now merged on main.
1914
+ this.s.basePath = priorBasePath || base;
1915
+ this.s.originalBasePath = priorOriginalBasePath || base;
1916
+ }
1917
+ // else: merged && active — leave the swap; the loop continues from
1918
+ // the worktree path. Subsequent milestone enters mutate `s.basePath`
1919
+ // through their own Lifecycle verbs.
1920
+
1921
+ return result;
1244
1922
  }
1245
1923
 
1246
1924
  /** True if `milestoneId` is the session's currently-active milestone. */