gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.0150ae9

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 (356) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/resources/extensions/mcp-client/index.js +2 -1
  42. package/dist/tool-bootstrap.js +59 -11
  43. package/dist/worktree-cli.js +7 -7
  44. package/package.json +1 -1
  45. package/packages/native/dist/native.d.ts +0 -2
  46. package/packages/native/dist/native.js +0 -2
  47. package/packages/native/src/native.ts +0 -3
  48. package/packages/pi-agent-core/dist/proxy.d.ts +1 -25
  49. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  50. package/packages/pi-agent-core/dist/proxy.js +1 -1
  51. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  52. package/packages/pi-agent-core/src/proxy.ts +1 -1
  53. package/packages/pi-ai/dist/api-registry.d.ts +0 -2
  54. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  55. package/packages/pi-ai/dist/api-registry.js +0 -10
  56. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  57. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  58. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/models.generated.js +735 -2588
  60. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  61. package/packages/pi-ai/dist/providers/anthropic.d.ts +0 -8
  62. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  63. package/packages/pi-ai/dist/providers/anthropic.js +1 -1
  64. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  65. package/packages/pi-ai/dist/providers/github-copilot-headers.d.ts +0 -1
  66. package/packages/pi-ai/dist/providers/github-copilot-headers.d.ts.map +1 -1
  67. package/packages/pi-ai/dist/providers/github-copilot-headers.js +1 -1
  68. package/packages/pi-ai/dist/providers/github-copilot-headers.js.map +1 -1
  69. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts +1 -43
  70. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/google-gemini-cli.js +2 -2
  72. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/google-shared.d.ts +0 -4
  74. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  75. package/packages/pi-ai/dist/providers/google-shared.js +1 -1
  76. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  77. package/packages/pi-ai/dist/providers/register-builtins.d.ts +0 -1
  78. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/providers/register-builtins.js +1 -1
  80. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  81. package/packages/pi-ai/dist/utils/event-stream.d.ts +0 -2
  82. package/packages/pi-ai/dist/utils/event-stream.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/utils/event-stream.js +0 -4
  84. package/packages/pi-ai/dist/utils/event-stream.js.map +1 -1
  85. package/packages/pi-ai/dist/utils/overflow.d.ts +0 -4
  86. package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  87. package/packages/pi-ai/dist/utils/overflow.js +0 -6
  88. package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
  89. package/packages/pi-ai/dist/utils/validation.d.ts +0 -8
  90. package/packages/pi-ai/dist/utils/validation.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/utils/validation.js +0 -14
  92. package/packages/pi-ai/dist/utils/validation.js.map +1 -1
  93. package/packages/pi-ai/src/api-registry.ts +0 -12
  94. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  95. package/packages/pi-ai/src/providers/anthropic.ts +1 -1
  96. package/packages/pi-ai/src/providers/github-copilot-headers.ts +1 -1
  97. package/packages/pi-ai/src/providers/google-gemini-cli.ts +2 -2
  98. package/packages/pi-ai/src/providers/google-shared.ts +1 -1
  99. package/packages/pi-ai/src/providers/register-builtins.ts +1 -1
  100. package/packages/pi-ai/src/utils/event-stream.ts +0 -5
  101. package/packages/pi-ai/src/utils/overflow.ts +1 -8
  102. package/packages/pi-ai/src/utils/validation.ts +0 -15
  103. package/packages/pi-coding-agent/dist/config.d.ts +0 -9
  104. package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/config.js +4 -8
  106. package/packages/pi-coding-agent/dist/config.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.d.ts +0 -4
  108. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.js +1 -1
  110. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +0 -5
  112. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/extensions/runner.js +0 -13
  114. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +0 -8
  116. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -2
  118. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +0 -17
  120. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -62
  122. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +0 -2
  124. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/config.js +0 -7
  126. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +0 -5
  128. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/lsp/edits.js +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +0 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +1 -6
  136. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/lsp/utils.js +1 -28
  138. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/messages.d.ts +0 -8
  140. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/messages.js +5 -5
  142. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +0 -3
  144. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -3
  146. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts +1 -26
  148. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -59
  150. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/prompt-templates.d.ts +0 -17
  152. package/packages/pi-coding-agent/dist/core/prompt-templates.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/prompt-templates.js +2 -2
  154. package/packages/pi-coding-agent/dist/core/prompt-templates.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -4
  156. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/session-manager.js +2 -4
  158. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +0 -12
  160. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/settings-manager.js +2 -2
  162. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/migrations.d.ts +0 -16
  164. package/packages/pi-coding-agent/dist/migrations.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/migrations.js +2 -2
  166. package/packages/pi-coding-agent/dist/migrations.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.d.ts +0 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.js +2 -2
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -24
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +50 -512
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts +71 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -0
  177. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +514 -0
  178. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -0
  179. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -4
  180. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -23
  182. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  185. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
  187. package/packages/pi-coding-agent/dist/utils/changelog.d.ts +0 -4
  188. package/packages/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/utils/changelog.js +1 -1
  190. package/packages/pi-coding-agent/dist/utils/changelog.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts +0 -1
  192. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  193. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +1 -1
  194. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  195. package/packages/pi-coding-agent/dist/utils/photon.d.ts +0 -19
  196. package/packages/pi-coding-agent/dist/utils/photon.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/utils/photon.js +1 -120
  198. package/packages/pi-coding-agent/dist/utils/photon.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/utils/tools-manager.d.ts +0 -1
  200. package/packages/pi-coding-agent/dist/utils/tools-manager.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/utils/tools-manager.js +1 -1
  202. package/packages/pi-coding-agent/dist/utils/tools-manager.js.map +1 -1
  203. package/packages/pi-coding-agent/package.json +1 -1
  204. package/packages/pi-coding-agent/src/config.ts +5 -10
  205. package/packages/pi-coding-agent/src/core/export-html/ansi-to-html.ts +1 -1
  206. package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -13
  207. package/packages/pi-coding-agent/src/core/keybindings.ts +2 -2
  208. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -73
  209. package/packages/pi-coding-agent/src/core/lsp/config.ts +0 -11
  210. package/packages/pi-coding-agent/src/core/lsp/edits.ts +1 -1
  211. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +1 -1
  212. package/packages/pi-coding-agent/src/core/lsp/utils.ts +1 -33
  213. package/packages/pi-coding-agent/src/core/messages.ts +5 -5
  214. package/packages/pi-coding-agent/src/core/model-registry.ts +0 -2
  215. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -77
  216. package/packages/pi-coding-agent/src/core/prompt-templates.ts +2 -2
  217. package/packages/pi-coding-agent/src/core/session-manager.ts +2 -4
  218. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  219. package/packages/pi-coding-agent/src/migrations.ts +2 -2
  220. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector-search.ts +2 -2
  221. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -561
  222. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +653 -0
  223. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -26
  224. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
  225. package/packages/pi-coding-agent/src/utils/changelog.ts +1 -1
  226. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +1 -1
  227. package/packages/pi-coding-agent/src/utils/photon.ts +0 -137
  228. package/packages/pi-coding-agent/src/utils/tools-manager.ts +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +0 -10
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +1 -1
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/overlay-layout.d.ts +55 -0
  234. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -0
  235. package/packages/pi-tui/dist/overlay-layout.js +288 -0
  236. package/packages/pi-tui/dist/overlay-layout.js.map +1 -0
  237. package/packages/pi-tui/dist/tui.d.ts +0 -22
  238. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  239. package/packages/pi-tui/dist/tui.js +6 -272
  240. package/packages/pi-tui/dist/tui.js.map +1 -1
  241. package/packages/pi-tui/dist/utils.d.ts +0 -7
  242. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  243. package/packages/pi-tui/dist/utils.js +0 -44
  244. package/packages/pi-tui/dist/utils.js.map +1 -1
  245. package/packages/pi-tui/src/components/editor.ts +1 -1
  246. package/packages/pi-tui/src/overlay-layout.ts +372 -0
  247. package/packages/pi-tui/src/tui.ts +11 -312
  248. package/packages/pi-tui/src/utils.ts +0 -43
  249. package/pkg/dist/core/export-html/ansi-to-html.d.ts +0 -4
  250. package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  251. package/pkg/dist/core/export-html/ansi-to-html.js +1 -1
  252. package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -1
  253. package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -4
  254. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  255. package/pkg/dist/modes/interactive/theme/theme.js +6 -23
  256. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  257. package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
  258. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  259. package/pkg/dist/modes/interactive/theme/themes.js +175 -0
  260. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
  261. package/pkg/package.json +1 -1
  262. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  263. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  264. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  265. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  266. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  267. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  268. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  269. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  270. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  271. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  272. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  273. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  274. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  275. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  276. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  277. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  278. package/src/resources/extensions/gsd/auto.ts +515 -1199
  279. package/src/resources/extensions/gsd/captures.ts +10 -4
  280. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  281. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  282. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  283. package/src/resources/extensions/gsd/git-service.ts +8 -1
  284. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  285. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  286. package/src/resources/extensions/gsd/index.ts +104 -263
  287. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  288. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  289. package/src/resources/extensions/gsd/quick.ts +121 -125
  290. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  291. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  292. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  293. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  294. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  295. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  296. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  297. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  298. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  299. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  300. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  301. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  302. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  303. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  304. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  305. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  306. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  307. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  308. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  309. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  310. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  311. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  312. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  313. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  314. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  315. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  316. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  317. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  318. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  319. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  320. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  321. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  322. package/src/resources/extensions/gsd/types.ts +90 -81
  323. package/src/resources/extensions/gsd/undo.ts +42 -46
  324. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  325. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  326. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  327. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  328. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  329. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  330. package/src/resources/extensions/gsd/worktree.ts +7 -44
  331. package/src/resources/extensions/mcp-client/index.ts +2 -1
  332. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  333. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  334. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  335. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  336. package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
  337. package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
  338. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
  339. package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
  340. package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
  341. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  342. package/pkg/dist/modes/interactive/theme/dark.json +0 -85
  343. package/pkg/dist/modes/interactive/theme/light.json +0 -84
  344. package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
  345. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  346. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  347. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  348. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  349. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  350. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  351. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  352. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  353. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  354. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  355. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  356. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -0,0 +1,705 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ WorktreeResolver,
5
+ type WorktreeResolverDeps,
6
+ type NotifyCtx,
7
+ } from "../worktree-resolver.js";
8
+ import { AutoSession } from "../auto/session.js";
9
+
10
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
11
+
12
+ /** Track calls to mock deps for assertion. */
13
+ interface CallLog {
14
+ fn: string;
15
+ args: unknown[];
16
+ }
17
+
18
+ function makeSession(
19
+ overrides?: Partial<{ basePath: string; originalBasePath: string }>,
20
+ ): AutoSession {
21
+ const s = new AutoSession();
22
+ s.basePath = overrides?.basePath ?? "/project";
23
+ s.originalBasePath = overrides?.originalBasePath ?? "/project";
24
+ return s;
25
+ }
26
+
27
+ function makeDeps(
28
+ overrides?: Partial<WorktreeResolverDeps>,
29
+ ): WorktreeResolverDeps & { calls: CallLog[] } {
30
+ const calls: CallLog[] = [];
31
+
32
+ const deps: WorktreeResolverDeps & { calls: CallLog[] } = {
33
+ calls,
34
+ isInAutoWorktree: (basePath: string) => {
35
+ calls.push({ fn: "isInAutoWorktree", args: [basePath] });
36
+ return false;
37
+ },
38
+ shouldUseWorktreeIsolation: () => {
39
+ calls.push({ fn: "shouldUseWorktreeIsolation", args: [] });
40
+ return true;
41
+ },
42
+ getIsolationMode: () => {
43
+ calls.push({ fn: "getIsolationMode", args: [] });
44
+ return "worktree";
45
+ },
46
+ mergeMilestoneToMain: (
47
+ basePath: string,
48
+ milestoneId: string,
49
+ roadmapContent: string,
50
+ ) => {
51
+ calls.push({
52
+ fn: "mergeMilestoneToMain",
53
+ args: [basePath, milestoneId, roadmapContent],
54
+ });
55
+ return { pushed: false };
56
+ },
57
+ syncWorktreeStateBack: (
58
+ mainBasePath: string,
59
+ worktreePath: string,
60
+ milestoneId: string,
61
+ ) => {
62
+ calls.push({
63
+ fn: "syncWorktreeStateBack",
64
+ args: [mainBasePath, worktreePath, milestoneId],
65
+ });
66
+ return { synced: [] };
67
+ },
68
+ teardownAutoWorktree: (
69
+ basePath: string,
70
+ milestoneId: string,
71
+ opts?: { preserveBranch?: boolean },
72
+ ) => {
73
+ calls.push({
74
+ fn: "teardownAutoWorktree",
75
+ args: [basePath, milestoneId, opts],
76
+ });
77
+ },
78
+ createAutoWorktree: (basePath: string, milestoneId: string) => {
79
+ calls.push({ fn: "createAutoWorktree", args: [basePath, milestoneId] });
80
+ return `/project/.gsd/worktrees/${milestoneId}`;
81
+ },
82
+ enterAutoWorktree: (basePath: string, milestoneId: string) => {
83
+ calls.push({ fn: "enterAutoWorktree", args: [basePath, milestoneId] });
84
+ return `/project/.gsd/worktrees/${milestoneId}`;
85
+ },
86
+ getAutoWorktreePath: (basePath: string, milestoneId: string) => {
87
+ calls.push({ fn: "getAutoWorktreePath", args: [basePath, milestoneId] });
88
+ return null;
89
+ },
90
+ autoCommitCurrentBranch: (
91
+ basePath: string,
92
+ reason: string,
93
+ milestoneId: string,
94
+ ) => {
95
+ calls.push({
96
+ fn: "autoCommitCurrentBranch",
97
+ args: [basePath, reason, milestoneId],
98
+ });
99
+ },
100
+ getCurrentBranch: (basePath: string) => {
101
+ calls.push({ fn: "getCurrentBranch", args: [basePath] });
102
+ return "main";
103
+ },
104
+ autoWorktreeBranch: (milestoneId: string) => {
105
+ calls.push({ fn: "autoWorktreeBranch", args: [milestoneId] });
106
+ return `milestone/${milestoneId}`;
107
+ },
108
+ resolveMilestoneFile: (
109
+ basePath: string,
110
+ milestoneId: string,
111
+ fileType: string,
112
+ ) => {
113
+ calls.push({
114
+ fn: "resolveMilestoneFile",
115
+ args: [basePath, milestoneId, fileType],
116
+ });
117
+ return `/project/.gsd/milestones/${milestoneId}/${milestoneId}-ROADMAP.md`;
118
+ },
119
+ readFileSync: (path: string, _encoding: string) => {
120
+ calls.push({ fn: "readFileSync", args: [path] });
121
+ return "# Roadmap\n- [x] S01: Slice one\n";
122
+ },
123
+ GitServiceImpl: class MockGitServiceImpl {
124
+ basePath: string;
125
+ gitConfig: unknown;
126
+ constructor(basePath: string, gitConfig: unknown) {
127
+ calls.push({ fn: "GitServiceImpl", args: [basePath, gitConfig] });
128
+ this.basePath = basePath;
129
+ this.gitConfig = gitConfig;
130
+ }
131
+ } as unknown as WorktreeResolverDeps["GitServiceImpl"],
132
+ loadEffectiveGSDPreferences: () => {
133
+ calls.push({ fn: "loadEffectiveGSDPreferences", args: [] });
134
+ return { preferences: { git: {} } };
135
+ },
136
+ invalidateAllCaches: () => {
137
+ calls.push({ fn: "invalidateAllCaches", args: [] });
138
+ },
139
+ captureIntegrationBranch: (
140
+ basePath: string,
141
+ mid: string | undefined,
142
+ opts?: { commitDocs?: boolean },
143
+ ) => {
144
+ calls.push({
145
+ fn: "captureIntegrationBranch",
146
+ args: [basePath, mid, opts],
147
+ });
148
+ },
149
+ ...overrides,
150
+ };
151
+
152
+ // Re-apply overrides that add the call tracking
153
+ if (overrides) {
154
+ for (const [key, val] of Object.entries(overrides)) {
155
+ if (key !== "calls") {
156
+ (deps as unknown as Record<string, unknown>)[key] = val;
157
+ }
158
+ }
159
+ }
160
+
161
+ return deps;
162
+ }
163
+
164
+ function makeNotifyCtx(): NotifyCtx & {
165
+ messages: Array<{ msg: string; level?: string }>;
166
+ } {
167
+ const messages: Array<{ msg: string; level?: string }> = [];
168
+ return {
169
+ messages,
170
+ notify: (msg: string, level?: "info" | "warning" | "error" | "success") => {
171
+ messages.push({ msg, level });
172
+ },
173
+ };
174
+ }
175
+
176
+ function findCalls(calls: CallLog[], fn: string): CallLog[] {
177
+ return calls.filter((c) => c.fn === fn);
178
+ }
179
+
180
+ // ─── Getter Tests ────────────────────────────────────────────────────────────
181
+
182
+ test("workPath returns s.basePath", () => {
183
+ const s = makeSession({ basePath: "/project/.gsd/worktrees/M001" });
184
+ const resolver = new WorktreeResolver(s, makeDeps());
185
+ assert.equal(resolver.workPath, "/project/.gsd/worktrees/M001");
186
+ });
187
+
188
+ test("projectRoot returns originalBasePath when set", () => {
189
+ const s = makeSession({
190
+ basePath: "/project/.gsd/worktrees/M001",
191
+ originalBasePath: "/project",
192
+ });
193
+ const resolver = new WorktreeResolver(s, makeDeps());
194
+ assert.equal(resolver.projectRoot, "/project");
195
+ });
196
+
197
+ test("projectRoot falls back to basePath when originalBasePath is empty", () => {
198
+ const s = makeSession({ basePath: "/project", originalBasePath: "" });
199
+ const resolver = new WorktreeResolver(s, makeDeps());
200
+ assert.equal(resolver.projectRoot, "/project");
201
+ });
202
+
203
+ test("lockPath returns originalBasePath when set (same as lockBase)", () => {
204
+ const s = makeSession({
205
+ basePath: "/project/.gsd/worktrees/M001",
206
+ originalBasePath: "/project",
207
+ });
208
+ const resolver = new WorktreeResolver(s, makeDeps());
209
+ assert.equal(resolver.lockPath, "/project");
210
+ });
211
+
212
+ test("lockPath falls back to basePath when originalBasePath is empty", () => {
213
+ const s = makeSession({ basePath: "/project", originalBasePath: "" });
214
+ const resolver = new WorktreeResolver(s, makeDeps());
215
+ assert.equal(resolver.lockPath, "/project");
216
+ });
217
+
218
+ // ─── enterMilestone Tests ────────────────────────────────────────────────────
219
+
220
+ test("enterMilestone creates new worktree when none exists", () => {
221
+ const s = makeSession();
222
+ const deps = makeDeps({
223
+ getAutoWorktreePath: () => null,
224
+ });
225
+ const ctx = makeNotifyCtx();
226
+ const resolver = new WorktreeResolver(s, deps);
227
+
228
+ resolver.enterMilestone("M001", ctx);
229
+
230
+ assert.equal(s.basePath, "/project/.gsd/worktrees/M001");
231
+ assert.equal(findCalls(deps.calls, "createAutoWorktree").length, 1);
232
+ assert.equal(findCalls(deps.calls, "enterAutoWorktree").length, 0);
233
+ assert.equal(findCalls(deps.calls, "GitServiceImpl").length, 1);
234
+ assert.ok(
235
+ ctx.messages.some(
236
+ (m) => m.level === "info" && m.msg.includes("Entered worktree"),
237
+ ),
238
+ );
239
+ });
240
+
241
+ test("enterMilestone enters existing worktree instead of creating", () => {
242
+ const s = makeSession();
243
+ const deps = makeDeps({
244
+ getAutoWorktreePath: () => "/project/.gsd/worktrees/M001",
245
+ });
246
+ const ctx = makeNotifyCtx();
247
+ const resolver = new WorktreeResolver(s, deps);
248
+
249
+ resolver.enterMilestone("M001", ctx);
250
+
251
+ assert.equal(s.basePath, "/project/.gsd/worktrees/M001");
252
+ assert.equal(findCalls(deps.calls, "enterAutoWorktree").length, 1);
253
+ assert.equal(findCalls(deps.calls, "createAutoWorktree").length, 0);
254
+ });
255
+
256
+ test("enterMilestone is no-op when shouldUseWorktreeIsolation is false", () => {
257
+ const s = makeSession();
258
+ const deps = makeDeps({
259
+ shouldUseWorktreeIsolation: () => false,
260
+ });
261
+ const ctx = makeNotifyCtx();
262
+ const resolver = new WorktreeResolver(s, deps);
263
+
264
+ resolver.enterMilestone("M001", ctx);
265
+
266
+ assert.equal(s.basePath, "/project"); // unchanged
267
+ assert.equal(findCalls(deps.calls, "createAutoWorktree").length, 0);
268
+ assert.equal(findCalls(deps.calls, "enterAutoWorktree").length, 0);
269
+ });
270
+
271
+ test("enterMilestone does NOT update basePath on creation failure", () => {
272
+ const s = makeSession();
273
+ const deps = makeDeps({
274
+ getAutoWorktreePath: () => null,
275
+ createAutoWorktree: () => {
276
+ throw new Error("disk full");
277
+ },
278
+ });
279
+ const ctx = makeNotifyCtx();
280
+ const resolver = new WorktreeResolver(s, deps);
281
+
282
+ resolver.enterMilestone("M001", ctx);
283
+
284
+ assert.equal(s.basePath, "/project"); // unchanged — error recovery
285
+ assert.ok(
286
+ ctx.messages.some(
287
+ (m) => m.level === "warning" && m.msg.includes("disk full"),
288
+ ),
289
+ );
290
+ });
291
+
292
+ test("enterMilestone uses originalBasePath as base for worktree ops", () => {
293
+ const s = makeSession({
294
+ basePath: "/project/.gsd/worktrees/M001",
295
+ originalBasePath: "/project",
296
+ });
297
+ let createdFrom = "";
298
+ const deps = makeDeps({
299
+ getAutoWorktreePath: () => null,
300
+ createAutoWorktree: (basePath: string, _mid: string) => {
301
+ createdFrom = basePath;
302
+ return "/project/.gsd/worktrees/M002";
303
+ },
304
+ });
305
+ const ctx = makeNotifyCtx();
306
+ const resolver = new WorktreeResolver(s, deps);
307
+
308
+ resolver.enterMilestone("M002", ctx);
309
+
310
+ assert.equal(createdFrom, "/project"); // uses originalBasePath, not current basePath
311
+ });
312
+
313
+ // ─── exitMilestone Tests ─────────────────────────────────────────────────────
314
+
315
+ test("exitMilestone commits, tears down, and resets basePath", () => {
316
+ const s = makeSession({
317
+ basePath: "/project/.gsd/worktrees/M001",
318
+ originalBasePath: "/project",
319
+ });
320
+ const deps = makeDeps({
321
+ isInAutoWorktree: () => true,
322
+ });
323
+ const ctx = makeNotifyCtx();
324
+ const resolver = new WorktreeResolver(s, deps);
325
+
326
+ resolver.exitMilestone("M001", ctx);
327
+
328
+ assert.equal(s.basePath, "/project"); // reset to originalBasePath
329
+ assert.equal(findCalls(deps.calls, "autoCommitCurrentBranch").length, 1);
330
+ assert.equal(findCalls(deps.calls, "teardownAutoWorktree").length, 1);
331
+ assert.equal(findCalls(deps.calls, "GitServiceImpl").length, 1); // rebuilt
332
+ assert.equal(findCalls(deps.calls, "invalidateAllCaches").length, 1);
333
+ });
334
+
335
+ test("exitMilestone is no-op when not in worktree", () => {
336
+ const s = makeSession();
337
+ const deps = makeDeps({
338
+ isInAutoWorktree: () => false,
339
+ });
340
+ const ctx = makeNotifyCtx();
341
+ const resolver = new WorktreeResolver(s, deps);
342
+
343
+ resolver.exitMilestone("M001", ctx);
344
+
345
+ assert.equal(s.basePath, "/project"); // unchanged
346
+ assert.equal(findCalls(deps.calls, "autoCommitCurrentBranch").length, 0);
347
+ assert.equal(findCalls(deps.calls, "teardownAutoWorktree").length, 0);
348
+ });
349
+
350
+ test("exitMilestone passes preserveBranch option", () => {
351
+ const s = makeSession({
352
+ basePath: "/project/.gsd/worktrees/M001",
353
+ originalBasePath: "/project",
354
+ });
355
+ let preserveOpts: unknown = null;
356
+ const deps = makeDeps({
357
+ isInAutoWorktree: () => true,
358
+ teardownAutoWorktree: (
359
+ _basePath: string,
360
+ _mid: string,
361
+ opts?: { preserveBranch?: boolean },
362
+ ) => {
363
+ preserveOpts = opts;
364
+ },
365
+ });
366
+ const ctx = makeNotifyCtx();
367
+ const resolver = new WorktreeResolver(s, deps);
368
+
369
+ resolver.exitMilestone("M001", ctx, { preserveBranch: true });
370
+
371
+ assert.deepEqual(preserveOpts, { preserveBranch: true });
372
+ });
373
+
374
+ test("exitMilestone still resets basePath even if auto-commit fails", () => {
375
+ const s = makeSession({
376
+ basePath: "/project/.gsd/worktrees/M001",
377
+ originalBasePath: "/project",
378
+ });
379
+ const deps = makeDeps({
380
+ isInAutoWorktree: () => true,
381
+ autoCommitCurrentBranch: () => {
382
+ throw new Error("commit error");
383
+ },
384
+ });
385
+ const ctx = makeNotifyCtx();
386
+ const resolver = new WorktreeResolver(s, deps);
387
+
388
+ resolver.exitMilestone("M001", ctx);
389
+
390
+ // Should still complete: reset basePath, rebuild git service
391
+ assert.equal(s.basePath, "/project");
392
+ assert.equal(findCalls(deps.calls, "GitServiceImpl").length, 1);
393
+ });
394
+
395
+ // ─── mergeAndExit Tests (worktree mode) ──────────────────────────────────────
396
+
397
+ test("mergeAndExit in worktree mode reads roadmap and merges", () => {
398
+ const s = makeSession({
399
+ basePath: "/project/.gsd/worktrees/M001",
400
+ originalBasePath: "/project",
401
+ });
402
+ const deps = makeDeps({
403
+ isInAutoWorktree: () => true,
404
+ getIsolationMode: () => "worktree",
405
+ });
406
+ const ctx = makeNotifyCtx();
407
+ const resolver = new WorktreeResolver(s, deps);
408
+
409
+ resolver.mergeAndExit("M001", ctx);
410
+
411
+ assert.equal(findCalls(deps.calls, "syncWorktreeStateBack").length, 1);
412
+ assert.equal(findCalls(deps.calls, "resolveMilestoneFile").length, 1);
413
+ assert.equal(findCalls(deps.calls, "readFileSync").length, 1);
414
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 1);
415
+ assert.equal(s.basePath, "/project"); // restored
416
+ assert.ok(ctx.messages.some((m) => m.msg.includes("merged to main")));
417
+ });
418
+
419
+ test("mergeAndExit in worktree mode shows pushed status", () => {
420
+ const s = makeSession({
421
+ basePath: "/project/.gsd/worktrees/M001",
422
+ originalBasePath: "/project",
423
+ });
424
+ const deps = makeDeps({
425
+ isInAutoWorktree: () => true,
426
+ getIsolationMode: () => "worktree",
427
+ mergeMilestoneToMain: () => ({ pushed: true }),
428
+ });
429
+ const ctx = makeNotifyCtx();
430
+ const resolver = new WorktreeResolver(s, deps);
431
+
432
+ resolver.mergeAndExit("M001", ctx);
433
+
434
+ assert.ok(ctx.messages.some((m) => m.msg.includes("Pushed to remote")));
435
+ });
436
+
437
+ test("mergeAndExit falls back to teardown when roadmap is missing", () => {
438
+ const s = makeSession({
439
+ basePath: "/project/.gsd/worktrees/M001",
440
+ originalBasePath: "/project",
441
+ });
442
+ const deps = makeDeps({
443
+ isInAutoWorktree: () => true,
444
+ getIsolationMode: () => "worktree",
445
+ resolveMilestoneFile: () => null,
446
+ });
447
+ const ctx = makeNotifyCtx();
448
+ const resolver = new WorktreeResolver(s, deps);
449
+
450
+ resolver.mergeAndExit("M001", ctx);
451
+
452
+ assert.equal(findCalls(deps.calls, "teardownAutoWorktree").length, 1);
453
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
454
+ assert.equal(s.basePath, "/project"); // restored
455
+ assert.ok(ctx.messages.some((m) => m.msg.includes("no roadmap for merge")));
456
+ });
457
+
458
+ test("mergeAndExit in worktree mode restores to project root on merge failure", () => {
459
+ const s = makeSession({
460
+ basePath: "/project/.gsd/worktrees/M001",
461
+ originalBasePath: "/project",
462
+ });
463
+ const deps = makeDeps({
464
+ isInAutoWorktree: () => true,
465
+ getIsolationMode: () => "worktree",
466
+ mergeMilestoneToMain: () => {
467
+ throw new Error("conflict in main");
468
+ },
469
+ });
470
+ const ctx = makeNotifyCtx();
471
+ const resolver = new WorktreeResolver(s, deps);
472
+
473
+ resolver.mergeAndExit("M001", ctx);
474
+
475
+ assert.equal(s.basePath, "/project"); // error recovery — restored
476
+ assert.ok(
477
+ ctx.messages.some(
478
+ (m) => m.level === "warning" && m.msg.includes("conflict in main"),
479
+ ),
480
+ );
481
+ assert.equal(findCalls(deps.calls, "GitServiceImpl").length, 1); // rebuilt after recovery
482
+ });
483
+
484
+ // ─── mergeAndExit Tests (branch mode) ────────────────────────────────────────
485
+
486
+ test("mergeAndExit in branch mode merges when on milestone branch", () => {
487
+ const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
488
+ const deps = makeDeps({
489
+ isInAutoWorktree: () => false,
490
+ getIsolationMode: () => "branch",
491
+ getCurrentBranch: () => "milestone/M001",
492
+ autoWorktreeBranch: () => "milestone/M001",
493
+ });
494
+ const ctx = makeNotifyCtx();
495
+ const resolver = new WorktreeResolver(s, deps);
496
+
497
+ resolver.mergeAndExit("M001", ctx);
498
+
499
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 1);
500
+ assert.ok(ctx.messages.some((m) => m.msg.includes("branch mode")));
501
+ });
502
+
503
+ test("mergeAndExit in branch mode skips when not on milestone branch", () => {
504
+ const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
505
+ const deps = makeDeps({
506
+ isInAutoWorktree: () => false,
507
+ getIsolationMode: () => "branch",
508
+ getCurrentBranch: () => "main",
509
+ autoWorktreeBranch: () => "milestone/M001",
510
+ });
511
+ const ctx = makeNotifyCtx();
512
+ const resolver = new WorktreeResolver(s, deps);
513
+
514
+ resolver.mergeAndExit("M001", ctx);
515
+
516
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
517
+ assert.equal(ctx.messages.length, 0);
518
+ });
519
+
520
+ test("mergeAndExit in branch mode handles merge failure gracefully", () => {
521
+ const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
522
+ const deps = makeDeps({
523
+ isInAutoWorktree: () => false,
524
+ getIsolationMode: () => "branch",
525
+ getCurrentBranch: () => "milestone/M001",
526
+ autoWorktreeBranch: () => "milestone/M001",
527
+ mergeMilestoneToMain: () => {
528
+ throw new Error("branch merge conflict");
529
+ },
530
+ });
531
+ const ctx = makeNotifyCtx();
532
+ const resolver = new WorktreeResolver(s, deps);
533
+
534
+ resolver.mergeAndExit("M001", ctx);
535
+
536
+ assert.ok(
537
+ ctx.messages.some(
538
+ (m) => m.level === "warning" && m.msg.includes("branch merge conflict"),
539
+ ),
540
+ );
541
+ });
542
+
543
+ test("mergeAndExit in branch mode skips when no roadmap", () => {
544
+ const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
545
+ const deps = makeDeps({
546
+ isInAutoWorktree: () => false,
547
+ getIsolationMode: () => "branch",
548
+ getCurrentBranch: () => "milestone/M001",
549
+ autoWorktreeBranch: () => "milestone/M001",
550
+ resolveMilestoneFile: () => null,
551
+ });
552
+ const ctx = makeNotifyCtx();
553
+ const resolver = new WorktreeResolver(s, deps);
554
+
555
+ resolver.mergeAndExit("M001", ctx);
556
+
557
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
558
+ });
559
+
560
+ test("mergeAndExit in branch mode rebuilds GitService after merge", () => {
561
+ const s = makeSession({ basePath: "/project", originalBasePath: "/project" });
562
+ const deps = makeDeps({
563
+ isInAutoWorktree: () => false,
564
+ getIsolationMode: () => "branch",
565
+ getCurrentBranch: () => "milestone/M001",
566
+ autoWorktreeBranch: () => "milestone/M001",
567
+ });
568
+ const ctx = makeNotifyCtx();
569
+ const resolver = new WorktreeResolver(s, deps);
570
+
571
+ resolver.mergeAndExit("M001", ctx);
572
+
573
+ assert.equal(findCalls(deps.calls, "GitServiceImpl").length, 1);
574
+ });
575
+
576
+ // ─── mergeAndExit Tests (none mode) ──────────────────────────────────────────
577
+
578
+ test("mergeAndExit in none mode is a no-op", () => {
579
+ const s = makeSession();
580
+ const deps = makeDeps({
581
+ getIsolationMode: () => "none",
582
+ });
583
+ const ctx = makeNotifyCtx();
584
+ const resolver = new WorktreeResolver(s, deps);
585
+
586
+ resolver.mergeAndExit("M001", ctx);
587
+
588
+ assert.equal(findCalls(deps.calls, "mergeMilestoneToMain").length, 0);
589
+ assert.equal(findCalls(deps.calls, "teardownAutoWorktree").length, 0);
590
+ assert.equal(ctx.messages.length, 0);
591
+ });
592
+
593
+ // ─── mergeAndEnterNext Tests ─────────────────────────────────────────────────
594
+
595
+ test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
596
+ const s = makeSession({
597
+ basePath: "/project/.gsd/worktrees/M001",
598
+ originalBasePath: "/project",
599
+ });
600
+ const callOrder: string[] = [];
601
+ const deps = makeDeps({
602
+ isInAutoWorktree: () => true,
603
+ getIsolationMode: () => "worktree",
604
+ shouldUseWorktreeIsolation: () => true,
605
+ mergeMilestoneToMain: (
606
+ basePath: string,
607
+ milestoneId: string,
608
+ _roadmap: string,
609
+ ) => {
610
+ callOrder.push(`merge:${milestoneId}`);
611
+ return { pushed: false };
612
+ },
613
+ getAutoWorktreePath: () => null,
614
+ createAutoWorktree: (basePath: string, milestoneId: string) => {
615
+ callOrder.push(`create:${milestoneId}`);
616
+ return `/project/.gsd/worktrees/${milestoneId}`;
617
+ },
618
+ });
619
+ const ctx = makeNotifyCtx();
620
+ const resolver = new WorktreeResolver(s, deps);
621
+
622
+ resolver.mergeAndEnterNext("M001", "M002", ctx);
623
+
624
+ assert.deepEqual(callOrder, ["merge:M001", "create:M002"]);
625
+ assert.equal(s.basePath, "/project/.gsd/worktrees/M002");
626
+ });
627
+
628
+ test("mergeAndEnterNext enters next milestone even if merge fails", () => {
629
+ const s = makeSession({
630
+ basePath: "/project/.gsd/worktrees/M001",
631
+ originalBasePath: "/project",
632
+ });
633
+ const deps = makeDeps({
634
+ isInAutoWorktree: (basePath: string) => basePath.includes("worktrees"),
635
+ getIsolationMode: () => "worktree",
636
+ shouldUseWorktreeIsolation: () => true,
637
+ mergeMilestoneToMain: () => {
638
+ throw new Error("merge failed");
639
+ },
640
+ getAutoWorktreePath: () => null,
641
+ createAutoWorktree: (_basePath: string, milestoneId: string) => {
642
+ return `/project/.gsd/worktrees/${milestoneId}`;
643
+ },
644
+ });
645
+ const ctx = makeNotifyCtx();
646
+ const resolver = new WorktreeResolver(s, deps);
647
+
648
+ resolver.mergeAndEnterNext("M001", "M002", ctx);
649
+
650
+ // Merge failed but enter should still happen
651
+ assert.equal(s.basePath, "/project/.gsd/worktrees/M002");
652
+ assert.ok(
653
+ ctx.messages.some(
654
+ (m) => m.level === "warning" && m.msg.includes("merge failed"),
655
+ ),
656
+ );
657
+ assert.ok(
658
+ ctx.messages.some(
659
+ (m) => m.level === "info" && m.msg.includes("Entered worktree"),
660
+ ),
661
+ );
662
+ });
663
+
664
+ // ─── GitService Rebuild Atomicity ────────────────────────────────────────────
665
+
666
+ test("GitService is rebuilt with the NEW basePath after enterMilestone", () => {
667
+ const s = makeSession();
668
+ let gitServiceBasePath = "";
669
+ const deps = makeDeps({
670
+ getAutoWorktreePath: () => null,
671
+ GitServiceImpl: class {
672
+ constructor(basePath: string, _config: unknown) {
673
+ gitServiceBasePath = basePath;
674
+ }
675
+ } as unknown as WorktreeResolverDeps["GitServiceImpl"],
676
+ });
677
+ const ctx = makeNotifyCtx();
678
+ const resolver = new WorktreeResolver(s, deps);
679
+
680
+ resolver.enterMilestone("M001", ctx);
681
+
682
+ assert.equal(gitServiceBasePath, "/project/.gsd/worktrees/M001"); // new path, not old
683
+ });
684
+
685
+ test("GitService is rebuilt with originalBasePath after exitMilestone", () => {
686
+ const s = makeSession({
687
+ basePath: "/project/.gsd/worktrees/M001",
688
+ originalBasePath: "/project",
689
+ });
690
+ let gitServiceBasePath = "";
691
+ const deps = makeDeps({
692
+ isInAutoWorktree: () => true,
693
+ GitServiceImpl: class {
694
+ constructor(basePath: string, _config: unknown) {
695
+ gitServiceBasePath = basePath;
696
+ }
697
+ } as unknown as WorktreeResolverDeps["GitServiceImpl"],
698
+ });
699
+ const ctx = makeNotifyCtx();
700
+ const resolver = new WorktreeResolver(s, deps);
701
+
702
+ resolver.exitMilestone("M001", ctx);
703
+
704
+ assert.equal(gitServiceBasePath, "/project"); // project root, not worktree
705
+ });