gsd-pi 2.81.0 → 2.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +36 -24
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/loop.js +111 -8
  4. package/dist/resources/extensions/gsd/auto/phases.js +190 -97
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +66 -3
  6. package/dist/resources/extensions/gsd/auto/session.js +9 -0
  7. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +43 -0
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +182 -178
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +14 -11
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
  11. package/dist/resources/extensions/gsd/auto-recovery.js +6 -181
  12. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -0
  13. package/dist/resources/extensions/gsd/auto-start.js +20 -23
  14. package/dist/resources/extensions/gsd/auto-unit-closeout.js +33 -5
  15. package/dist/resources/extensions/gsd/auto-verification.js +12 -6
  16. package/dist/resources/extensions/gsd/auto-worktree.js +8 -0
  17. package/dist/resources/extensions/gsd/auto.js +265 -76
  18. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +13 -6
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -2
  20. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +4 -8
  21. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +4 -10
  22. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +9 -0
  23. package/dist/resources/extensions/gsd/git-service.js +2 -1
  24. package/dist/resources/extensions/gsd/gsd-db.js +7 -23
  25. package/dist/resources/extensions/gsd/health-widget-core.js +1 -1
  26. package/dist/resources/extensions/gsd/health-widget.js +4 -10
  27. package/dist/resources/extensions/gsd/markdown-renderer.js +0 -95
  28. package/dist/resources/extensions/gsd/native-git-bridge.js +14 -14
  29. package/dist/resources/extensions/gsd/notification-overlay.js +35 -40
  30. package/dist/resources/extensions/gsd/parallel-merge.js +53 -30
  31. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +25 -33
  32. package/dist/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  33. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  34. package/dist/resources/extensions/gsd/prompts/discuss.md +20 -2
  35. package/dist/resources/extensions/gsd/recovery-classification.js +15 -1
  36. package/dist/resources/extensions/gsd/session-lock.js +40 -0
  37. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +131 -0
  38. package/dist/resources/extensions/gsd/state-reconciliation/drift/merge-state.js +247 -0
  39. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +50 -0
  40. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +87 -0
  41. package/dist/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.js +50 -0
  42. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +124 -0
  43. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +32 -0
  44. package/dist/resources/extensions/gsd/state-reconciliation/errors.js +41 -0
  45. package/dist/resources/extensions/gsd/state-reconciliation/index.js +99 -0
  46. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +24 -0
  47. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +43 -0
  48. package/dist/resources/extensions/gsd/state-reconciliation/types.js +3 -0
  49. package/dist/resources/extensions/gsd/state-reconciliation.js +5 -26
  50. package/dist/resources/extensions/gsd/tui/render-kit.js +74 -0
  51. package/dist/resources/extensions/gsd/watch/header-renderer.js +92 -69
  52. package/dist/resources/extensions/gsd/watch/splash-palette.js +10 -0
  53. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  54. package/dist/resources/extensions/gsd/worktree-lifecycle.js +722 -316
  55. package/dist/resources/extensions/gsd/worktree-telemetry.js +3 -1
  56. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  59. package/dist/web/standalone/.next/build-manifest.json +2 -2
  60. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.html +1 -1
  77. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  84. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  86. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  87. package/dist/welcome-screen.d.ts +0 -7
  88. package/dist/welcome-screen.js +60 -69
  89. package/package.json +1 -1
  90. package/packages/daemon/package.json +2 -2
  91. package/packages/mcp-server/package.json +2 -2
  92. package/packages/native/package.json +1 -1
  93. package/packages/pi-agent-core/package.json +1 -1
  94. package/packages/pi-ai/package.json +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts +2 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.d.ts.map +1 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js +47 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/assistant-message-design.test.js.map +1 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +76 -9
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts +2 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.d.ts.map +1 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js +40 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/user-message-design.test.js.map +1 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +0 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +30 -29
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +10 -3
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +13 -13
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -3
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +58 -3
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +2 -2
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +12 -6
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -41
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +86 -82
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts +35 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js +152 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/transcript-design.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts +16 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.d.ts.map +1 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js +73 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tui-style-kit.js.map +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +12 -8
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +105 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +27 -26
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +9 -6
  152. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  153. package/packages/pi-coding-agent/package.json +1 -1
  154. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/assistant-message-design.test.ts +56 -0
  155. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +113 -9
  156. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/user-message-design.test.ts +48 -0
  157. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +10 -3
  158. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +43 -42
  159. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +14 -14
  160. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +64 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +13 -7
  162. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +15 -42
  163. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -104
  164. package/packages/pi-coding-agent/src/modes/interactive/components/transcript-design.ts +196 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/tui-style-kit.ts +94 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +14 -9
  167. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-highlight.test.ts +23 -0
  168. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +106 -1
  169. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +27 -26
  170. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +9 -6
  171. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  172. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +14 -1
  173. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  174. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
  175. package/packages/pi-tui/dist/overlay-layout.js +9 -6
  176. package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
  177. package/packages/pi-tui/package.json +1 -1
  178. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +20 -1
  179. package/packages/pi-tui/src/overlay-layout.ts +10 -7
  180. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  181. package/packages/rpc-client/package.json +1 -1
  182. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts +2 -0
  183. package/pkg/dist/modes/interactive/theme/theme-highlight.test.d.ts.map +1 -0
  184. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js +17 -0
  185. package/pkg/dist/modes/interactive/theme/theme-highlight.test.js.map +1 -0
  186. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  187. package/pkg/dist/modes/interactive/theme/theme.js +105 -1
  188. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  189. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  190. package/pkg/dist/modes/interactive/theme/themes.js +27 -26
  191. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  192. package/pkg/package.json +1 -1
  193. package/src/resources/extensions/gsd/auto/loop-deps.ts +9 -5
  194. package/src/resources/extensions/gsd/auto/loop.ts +113 -9
  195. package/src/resources/extensions/gsd/auto/phases.ts +144 -19
  196. package/src/resources/extensions/gsd/auto/run-unit.ts +69 -4
  197. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  198. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +82 -0
  199. package/src/resources/extensions/gsd/auto-dashboard.ts +230 -183
  200. package/src/resources/extensions/gsd/auto-dispatch.ts +15 -1
  201. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -1
  202. package/src/resources/extensions/gsd/auto-recovery.ts +7 -209
  203. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  204. package/src/resources/extensions/gsd/auto-start.ts +22 -22
  205. package/src/resources/extensions/gsd/auto-unit-closeout.ts +51 -0
  206. package/src/resources/extensions/gsd/auto-verification.ts +12 -6
  207. package/src/resources/extensions/gsd/auto-worktree.ts +8 -0
  208. package/src/resources/extensions/gsd/auto.ts +295 -75
  209. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -6
  210. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -2
  211. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +5 -8
  212. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +4 -10
  213. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +12 -0
  214. package/src/resources/extensions/gsd/git-service.ts +2 -0
  215. package/src/resources/extensions/gsd/gsd-db.ts +7 -23
  216. package/src/resources/extensions/gsd/health-widget-core.ts +1 -1
  217. package/src/resources/extensions/gsd/health-widget.ts +6 -10
  218. package/src/resources/extensions/gsd/journal.ts +2 -0
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -95
  220. package/src/resources/extensions/gsd/native-git-bridge.ts +14 -13
  221. package/src/resources/extensions/gsd/notification-overlay.ts +50 -46
  222. package/src/resources/extensions/gsd/parallel-merge.ts +61 -34
  223. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +33 -35
  224. package/src/resources/extensions/gsd/prompts/complete-slice.md +14 -12
  225. package/src/resources/extensions/gsd/prompts/discuss-headless.md +20 -2
  226. package/src/resources/extensions/gsd/prompts/discuss.md +20 -2
  227. package/src/resources/extensions/gsd/recovery-classification.ts +18 -1
  228. package/src/resources/extensions/gsd/session-lock.ts +41 -0
  229. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +172 -0
  230. package/src/resources/extensions/gsd/state-reconciliation/drift/merge-state.ts +337 -0
  231. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +69 -0
  232. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +109 -0
  233. package/src/resources/extensions/gsd/state-reconciliation/drift/sketch-flag.ts +68 -0
  234. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +185 -0
  235. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +46 -0
  236. package/src/resources/extensions/gsd/state-reconciliation/errors.ts +67 -0
  237. package/src/resources/extensions/gsd/state-reconciliation/index.ts +142 -0
  238. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +27 -0
  239. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +60 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/types.ts +83 -0
  241. package/src/resources/extensions/gsd/state-reconciliation.ts +21 -53
  242. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +1 -1
  243. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +654 -176
  245. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +291 -4
  246. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +16 -1
  247. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +18 -0
  248. package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +68 -0
  249. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +28 -1
  250. package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +20 -2
  251. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +44 -0
  252. package/src/resources/extensions/gsd/tests/header-renderer.test.ts +40 -0
  253. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +10 -0
  254. package/src/resources/extensions/gsd/tests/health-widget.test.ts +14 -4
  255. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +26 -0
  256. package/src/resources/extensions/gsd/tests/integration/integration-proof.test.ts +1 -1
  257. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +116 -24
  258. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -1
  259. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +1 -1
  260. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +46 -11
  261. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +78 -41
  262. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +44 -0
  263. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +12 -217
  264. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +38 -6
  265. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +2 -2
  266. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +24 -1
  268. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +7 -3
  269. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +6 -3
  270. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +24 -0
  271. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +65 -58
  272. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +952 -0
  273. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +4 -0
  274. package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +121 -1
  275. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +66 -0
  276. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +83 -0
  277. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +6 -0
  278. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +158 -58
  279. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +572 -118
  280. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +59 -2
  281. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +18 -0
  282. package/src/resources/extensions/gsd/tui/render-kit.ts +109 -0
  283. package/src/resources/extensions/gsd/watch/header-renderer.ts +121 -79
  284. package/src/resources/extensions/gsd/watch/splash-palette.ts +11 -0
  285. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  286. package/src/resources/extensions/gsd/worktree-lifecycle.ts +1151 -524
  287. package/src/resources/extensions/gsd/worktree-telemetry.ts +7 -2
  288. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +0 -1544
  289. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_buildManifest.js +0 -0
  290. /package/dist/web/standalone/.next/static/{drLMkgfHQ8lzS229_HWYR → S44UQTFCUdA44dkjfYt6S}/_ssgManifest.js +0 -0
@@ -124,6 +124,8 @@ test("buildMinimalAutoGsdToolSet includes closeout tool for complete-slice", ()
124
124
  "gsd_milestone_status",
125
125
  "gsd_checkpoint_db",
126
126
  "gsd_task_complete",
127
+ "gsd_task_reopen",
128
+ "gsd_replan_slice",
127
129
  "gsd_slice_complete",
128
130
  "gsd_complete_slice",
129
131
  "memory_query",
@@ -131,6 +133,8 @@ test("buildMinimalAutoGsdToolSet includes closeout tool for complete-slice", ()
131
133
  ], "complete-slice");
132
134
 
133
135
  assert.ok(result.includes("gsd_slice_complete"));
136
+ assert.ok(result.includes("gsd_task_reopen"));
137
+ assert.ok(result.includes("gsd_replan_slice"));
134
138
  assert.ok(result.includes("subagent"));
135
139
  assert.ok(result.includes("capture_thought"));
136
140
  assert.ok(!result.includes("gsd_task_complete"));
@@ -10,8 +10,9 @@ import assert from "node:assert/strict";
10
10
  import { mkdirSync, rmSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
+ import { visibleWidth } from "@gsd/pi-tui";
13
14
 
14
- import { updateProgressWidget } from "../auto-dashboard.ts";
15
+ import { setCompletionProgressWidget, updateProgressWidget } from "../auto-dashboard.ts";
15
16
  import type { GSDState } from "../types.ts";
16
17
 
17
18
  interface CapturedSetHeader {
@@ -46,6 +47,15 @@ const baseAccessors = {
46
47
  getCurrentDispatchedModelId: () => null,
47
48
  };
48
49
 
50
+ function assertLinesFit(lines: string[], width: number): void {
51
+ for (const line of lines) {
52
+ assert.ok(
53
+ visibleWidth(line) <= width,
54
+ `line exceeds width ${width}: ${visibleWidth(line)} "${line}"`,
55
+ );
56
+ }
57
+ }
58
+
49
59
  // ── Header lifecycle ────────────────────────────────────────────────────
50
60
 
51
61
  test("updateProgressWidget installs an EMPTY-rendering header (not undefined) — addresses codex P1 finding that setHeader(undefined) restores the built-in logo+instructions header", (t) => {
@@ -208,3 +218,113 @@ test("auto-dashboard widget render output omits Ctrl+N guidance when isStepMode
208
218
 
209
219
  if (component.dispose) component.dispose();
210
220
  });
221
+
222
+ test("auto-dashboard widget render output fits common terminal widths", (t) => {
223
+ const dir = makeTempDir("width-safe");
224
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
225
+ t.after(() => cleanup(dir));
226
+
227
+ let widgetFactory: ((tui: unknown, theme: unknown) => any) | undefined;
228
+
229
+ updateProgressWidget(
230
+ {
231
+ hasUI: true,
232
+ ui: {
233
+ setWidget(_key: string, factory: any) { widgetFactory = factory; },
234
+ setHeader() {},
235
+ setStatus() {},
236
+ },
237
+ } as any,
238
+ "execute-task",
239
+ "M001/S01/T01",
240
+ baseState,
241
+ { ...baseAccessors, getBasePath: () => dir },
242
+ );
243
+
244
+ assert.ok(widgetFactory);
245
+
246
+ const component = widgetFactory!(
247
+ { requestRender() {} },
248
+ {
249
+ fg: (_color: string, text: string) => text,
250
+ bold: (text: string) => text,
251
+ },
252
+ );
253
+ t.after(() => component.dispose?.());
254
+
255
+ for (const width of [40, 80, 120]) {
256
+ assertLinesFit(component.render(width), width);
257
+ component.invalidate();
258
+ }
259
+
260
+ if (component.dispose) component.dispose();
261
+ });
262
+
263
+ test("completion dashboard keeps final milestone roll-up in the progress widget", (t) => {
264
+ const dir = makeTempDir("completion-widget");
265
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
266
+ t.after(() => cleanup(dir));
267
+
268
+ let widgetFactory: ((tui: unknown, theme: unknown) => any) | undefined;
269
+
270
+ setCompletionProgressWidget(
271
+ {
272
+ hasUI: true,
273
+ ui: {
274
+ setWidget(_key: string, factory: any) { widgetFactory = factory; },
275
+ setHeader() {},
276
+ setStatus() {},
277
+ },
278
+ } as any,
279
+ {
280
+ milestoneId: "M003",
281
+ milestoneTitle: "Budget tracking",
282
+ oneLiner: "Added milestone budget warning output and provider roll-up details.",
283
+ successCriteriaResults: "Budget warnings appear at the end of milestone completion.",
284
+ requirementOutcomes: "Users can see what shipped without opening a fresh session.",
285
+ keyFiles: ["src/resources/extensions/gsd/auto-dashboard.ts", "src/resources/extensions/gsd/auto.ts"],
286
+ keyDecisions: ["Keep completion closeout in the same TUI surface."],
287
+ followUps: "None.",
288
+ reason: "Milestone M003 complete",
289
+ startedAt: Date.now() - 90_000,
290
+ totalCost: 21.29,
291
+ totalTokens: 1_000_000,
292
+ unitCount: 8,
293
+ cacheHitRate: 100,
294
+ contextPercent: 0.9,
295
+ contextWindow: 1_000_000,
296
+ completedSlices: 3,
297
+ totalSlices: 3,
298
+ basePath: dir,
299
+ },
300
+ );
301
+
302
+ assert.ok(widgetFactory, "completion widget factory must be installed");
303
+
304
+ const fakeTui = { requestRender() {} };
305
+ const fakeTheme = {
306
+ fg: (_color: string, text: string) => text,
307
+ bold: (text: string) => text,
308
+ };
309
+ const component = widgetFactory!(fakeTui, fakeTheme);
310
+ const output = component.render(140).join("\n");
311
+
312
+ assert.match(output, /Milestone M003 roll-up/);
313
+ assert.match(output, /Budget tracking/);
314
+ assert.match(output, /Outcome/);
315
+ assert.match(output, /Added milestone budget warning output/);
316
+ assert.match(output, /What changed/);
317
+ assert.match(output, /Budget warnings appear/);
318
+ assert.match(output, /Users can see what shipped/);
319
+ assert.match(output, /Keep completion closeout/);
320
+ assert.match(output, /Verification/);
321
+ assert.match(output, /Files: src\/resources\/extensions\/gsd\/auto-dashboard\.ts/);
322
+ assert.match(output, /Run totals 3\/3 slices/);
323
+ assert.match(output, /100% cache hit/);
324
+ assert.match(output, /\$21\.29/);
325
+ assert.match(output, /1\.0M tokens/);
326
+ assert.match(output, /8 units/);
327
+ assert.doesNotMatch(output, /COMPLETE-MILESTONE/);
328
+
329
+ if (component.dispose) component.dispose();
330
+ });
@@ -0,0 +1,66 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Unit tests for shared GSD TUI render helpers.
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { visibleWidth } from "@gsd/pi-tui";
8
+ import {
9
+ padRightVisible,
10
+ renderFrame,
11
+ renderKeyHints,
12
+ renderProgressBar,
13
+ rightAlign,
14
+ safeLine,
15
+ wrapVisibleText,
16
+ type ThemeLike,
17
+ } from "../tui/render-kit.ts";
18
+
19
+ const theme: ThemeLike = {
20
+ fg: (_color: string, text: string) => text,
21
+ bold: (text: string) => text,
22
+ };
23
+
24
+ function assertWidth(lines: string[], width: number): void {
25
+ for (const line of lines) {
26
+ assert.ok(
27
+ visibleWidth(line) <= width,
28
+ `line exceeds width ${width}: ${visibleWidth(line)} "${line}"`,
29
+ );
30
+ }
31
+ }
32
+
33
+ describe("tui render kit", () => {
34
+ test("safeLine clamps visible width", () => {
35
+ assert.equal(visibleWidth(safeLine("abcdef", 4)), 4);
36
+ assert.equal(safeLine("abcdef", 0), "");
37
+ });
38
+
39
+ test("padRightVisible fills exact visible width", () => {
40
+ const line = padRightVisible("abc", 8);
41
+ assert.equal(visibleWidth(line), 8);
42
+ });
43
+
44
+ test("rightAlign keeps output within width", () => {
45
+ for (const width of [10, 40, 80]) {
46
+ assertWidth([rightAlign("left side with overflow", "right side", width)], width);
47
+ }
48
+ });
49
+
50
+ test("wrapVisibleText clamps long words and ansi-aware content", () => {
51
+ const lines = wrapVisibleText("https://example.com/" + "a".repeat(120), 24);
52
+ assert.ok(lines.length > 0);
53
+ assertWidth(lines, 24);
54
+ });
55
+
56
+ test("renderFrame keeps borders and rows within width", () => {
57
+ for (const width of [3, 40, 80]) {
58
+ assertWidth(renderFrame(theme, ["row", "long ".repeat(40)], width), width);
59
+ }
60
+ });
61
+
62
+ test("renderKeyHints and renderProgressBar fit caller budgets", () => {
63
+ assert.ok(visibleWidth(renderKeyHints(theme, ["↑↓ scroll", "esc close"], 12)) <= 12);
64
+ assert.equal(visibleWidth(renderProgressBar(theme, 2, 4, 16)), 16);
65
+ });
66
+ });
@@ -0,0 +1,83 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Unit tests for auto-mode verification retry backoff decisions.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import {
8
+ decideVerificationRetry,
9
+ hashVerificationFailureContext,
10
+ verificationRetryDelayMs,
11
+ verificationRetryKey,
12
+ VERIFICATION_RETRY_MAX_DELAY_MS,
13
+ } from "../auto/verification-retry-policy.ts";
14
+
15
+ test("verificationRetryDelayMs uses exponential backoff with cap", () => {
16
+ assert.deepEqual(verificationRetryDelayMs(1, () => 0.5), {
17
+ delayMs: 2_000,
18
+ baseDelayMs: 2_000,
19
+ });
20
+ assert.deepEqual(verificationRetryDelayMs(2, () => 0.5), {
21
+ delayMs: 4_000,
22
+ baseDelayMs: 4_000,
23
+ });
24
+ assert.deepEqual(verificationRetryDelayMs(3, () => 0.5), {
25
+ delayMs: 8_000,
26
+ baseDelayMs: 8_000,
27
+ });
28
+ assert.deepEqual(verificationRetryDelayMs(99, () => 0.5), {
29
+ delayMs: VERIFICATION_RETRY_MAX_DELAY_MS,
30
+ baseDelayMs: VERIFICATION_RETRY_MAX_DELAY_MS,
31
+ });
32
+ });
33
+
34
+ test("decideVerificationRetry pauses on duplicate failure context", () => {
35
+ const failureContext = "lint failed\n";
36
+ const failureHash = hashVerificationFailureContext(failureContext);
37
+ const decision = decideVerificationRetry({
38
+ unitType: "execute-task",
39
+ retryInfo: {
40
+ unitId: "M001/S01/T01",
41
+ failureContext,
42
+ attempt: 2,
43
+ },
44
+ previousFailureHash: failureHash,
45
+ random: () => 0.5,
46
+ });
47
+
48
+ assert.deepEqual(decision, {
49
+ action: "pause",
50
+ reason: "duplicate-failure-context",
51
+ key: verificationRetryKey("execute-task", "M001/S01/T01"),
52
+ failureHash,
53
+ });
54
+ });
55
+
56
+ test("decideVerificationRetry delays changed failure context", () => {
57
+ const decision = decideVerificationRetry({
58
+ unitType: "execute-task",
59
+ retryInfo: {
60
+ unitId: "M001/S01/T01",
61
+ failureContext: "test failed differently",
62
+ attempt: 2,
63
+ },
64
+ previousFailureHash: hashVerificationFailureContext("test failed"),
65
+ random: () => 0.5,
66
+ });
67
+
68
+ assert.equal(decision.action, "delay");
69
+ assert.equal(decision.key, verificationRetryKey("execute-task", "M001/S01/T01"));
70
+ assert.equal(decision.delayMs, 4_000);
71
+ assert.equal(decision.baseDelayMs, 4_000);
72
+ });
73
+
74
+ test("decideVerificationRetry pauses when retry context is missing", () => {
75
+ assert.deepEqual(
76
+ decideVerificationRetry({
77
+ unitType: "execute-task",
78
+ retryInfo: null,
79
+ previousFailureHash: undefined,
80
+ }),
81
+ { action: "pause", reason: "missing-retry-context" },
82
+ );
83
+ });
@@ -35,6 +35,12 @@ test("auto execute-task requires canonical task completion tool", () => {
35
35
  assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("execute-task"), ["gsd_task_complete"]);
36
36
  });
37
37
 
38
+ test("complete-slice requires closeout and execution handoff tools", () => {
39
+ const expected = ["gsd_slice_complete", "gsd_task_reopen", "gsd_replan_slice"];
40
+ assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("complete-slice"), expected);
41
+ assert.deepEqual(getRequiredWorkflowToolsForAutoUnit("complete-slice"), expected);
42
+ });
43
+
38
44
  test("deep project setup units declare required workflow MCP tools", () => {
39
45
  assert.deepEqual(getRequiredWorkflowToolsForGuidedUnit("discuss-project"), [
40
46
  "ask_user_questions",
@@ -1,20 +1,57 @@
1
1
  import { describe, test, beforeEach, afterEach } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdtempSync, rmSync, readFileSync, readdirSync } from "node:fs";
3
+ import { mkdirSync, mkdtempSync, rmSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
+ import { execFileSync } from "node:child_process";
7
+
8
+ /**
9
+ * Initialize the temp dir as a real git repo with a `.gsd/preferences.md`
10
+ * declaring the requested isolation mode. Required after ADR-016 phase 2 /
11
+ * C1+C2+C3 inlined the worktree-manager + cache + preferences primitives —
12
+ * tests can no longer stub them via deps.
13
+ */
14
+ function initGitRepoIn(base: string, isolation: "worktree" | "branch" | "none"): void {
15
+ const git = (args: string[]): void => {
16
+ execFileSync("git", args, { cwd: base, stdio: "pipe" });
17
+ };
18
+ git(["init", "-b", "main"]);
19
+ git(["config", "user.email", "test@test.com"]);
20
+ git(["config", "user.name", "Test"]);
21
+ writeFileSync(join(base, "README.md"), "# test\n");
22
+ writeFileSync(join(base, ".gitignore"), ".gsd/worktrees/\n");
23
+ mkdirSync(join(base, ".gsd"), { recursive: true });
24
+ writeFileSync(
25
+ join(base, ".gsd", "preferences.md"),
26
+ `## Git\n- isolation: ${isolation}\n`,
27
+ );
28
+ git(["add", "."]);
29
+ git(["commit", "-m", "init"]);
30
+ }
6
31
  import {
7
32
  WorktreeLifecycle,
8
33
  type WorktreeLifecycleDeps,
9
34
  type NotifyCtx,
10
35
  } from "../worktree-lifecycle.js";
11
36
  import { WorktreeStateProjection } from "../worktree-state-projection.js";
37
+ import { type TaskCommitContext } from "../worktree.js";
12
38
 
13
- // Test-local: LegacyTestDeps had three fields Lifecycle does not need
14
- // (shouldUseWorktreeIsolation, syncWorktreeStateBack, captureIntegrationBranch).
15
- // Permit them in test fixtures so existing override patterns keep working —
16
- // Lifecycle ignores the extras via structural typing.
39
+ // ADR-016 phase 2 / C-track retired all worktree-manager + cache + prefs
40
+ // fields from `WorktreeLifecycleDeps`. Tests still pass them as overrides
41
+ // via the structural-typing escape hatch listed here as optional so
42
+ // fixtures can stub or omit them.
17
43
  type LegacyTestDeps = WorktreeLifecycleDeps & {
44
+ enterAutoWorktree?: (basePath: string, milestoneId: string) => string;
45
+ createAutoWorktree?: (basePath: string, milestoneId: string) => string;
46
+ enterBranchModeForMilestone?: (basePath: string, milestoneId: string) => void;
47
+ getAutoWorktreePath?: (basePath: string, milestoneId: string) => string | null;
48
+ isInAutoWorktree?: (basePath: string) => boolean;
49
+ autoWorktreeBranch?: (milestoneId: string) => string;
50
+ teardownAutoWorktree?: (
51
+ basePath: string,
52
+ milestoneId: string,
53
+ opts?: { preserveBranch?: boolean },
54
+ ) => void;
18
55
  shouldUseWorktreeIsolation?: () => boolean;
19
56
  syncWorktreeStateBack?: (
20
57
  mainBasePath: string,
@@ -22,6 +59,27 @@ type LegacyTestDeps = WorktreeLifecycleDeps & {
22
59
  milestoneId: string,
23
60
  ) => { synced: string[] };
24
61
  captureIntegrationBranch?: (basePath: string, mid: string | undefined) => void;
62
+ autoCommitCurrentBranch?: (
63
+ basePath: string,
64
+ unitType: string,
65
+ unitId: string,
66
+ taskContext?: TaskCommitContext,
67
+ ) => string | null;
68
+ getCurrentBranch?: (basePath: string) => string;
69
+ checkoutBranch?: (basePath: string, branch: string) => void;
70
+ readFileSync?: (path: string, encoding: BufferEncoding) => string;
71
+ getIsolationMode?: (basePath?: string) => "worktree" | "branch" | "none";
72
+ resolveMilestoneFile?: (
73
+ basePath: string,
74
+ milestoneId: string,
75
+ fileType: string,
76
+ ) => string | null;
77
+ GitServiceImpl?: new (basePath: string, gitConfig: unknown) => unknown;
78
+ loadEffectiveGSDPreferences?: () =>
79
+ | { preferences?: { git?: Record<string, unknown> } }
80
+ | null
81
+ | undefined;
82
+ invalidateAllCaches?: () => void;
25
83
  };
26
84
  import { AutoSession } from "../auto/session.js";
27
85
  import type { JournalEntry } from "../journal.js";
@@ -40,33 +98,19 @@ function makeSession(
40
98
  function makeDeps(
41
99
  overrides?: Partial<LegacyTestDeps>,
42
100
  ): LegacyTestDeps {
101
+ // ADR-016 phase 2 / C-track retired the worktree-manager + cache + prefs
102
+ // primitives from `WorktreeLifecycleDeps`. Tests in this file drive
103
+ // Lifecycle against real git fixtures (initGitRepoIn) — do NOT stub the
104
+ // C-track primitives here, or the override pattern will pre-empt the
105
+ // real `getAutoWorktreePath` / `createAutoWorktree` / etc. and the
106
+ // success/existing/failure branches won't fire as expected.
43
107
  const deps: LegacyTestDeps = {
44
- isInAutoWorktree: () => false,
45
- shouldUseWorktreeIsolation: () => true,
46
- getIsolationMode: () => "worktree",
47
108
  mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
48
- syncWorktreeStateBack: () => ({ synced: [] }),
49
- teardownAutoWorktree: () => {},
50
- createAutoWorktree: (_basePath: string, milestoneId: string) =>
51
- `/project/.gsd/worktrees/${milestoneId}`,
52
- enterAutoWorktree: (_basePath: string, milestoneId: string) =>
53
- `/project/.gsd/worktrees/${milestoneId}`,
54
- getAutoWorktreePath: () => null,
55
- autoCommitCurrentBranch: () => {},
56
- getCurrentBranch: () => "main",
57
- checkoutBranch: () => {},
58
- autoWorktreeBranch: (milestoneId: string) => `milestone/${milestoneId}`,
59
- resolveMilestoneFile: (_basePath: string, milestoneId: string) =>
60
- `/project/.gsd/milestones/${milestoneId}/${milestoneId}-ROADMAP.md`,
61
- readFileSync: () => "# Roadmap\n- [x] S01: Slice one\n",
62
- GitServiceImpl: class {
63
- constructor() {}
64
- } as unknown as LegacyTestDeps["GitServiceImpl"],
65
- loadEffectiveGSDPreferences: () => ({ preferences: { git: {} } }),
66
- invalidateAllCaches: () => {},
67
- captureIntegrationBranch: () => {},
68
- enterBranchModeForMilestone: () => {},
69
109
  worktreeProjection: new WorktreeStateProjection(),
110
+ // ADR-016 phase 2 / C4 (#5627): GitServiceImpl constructor → factory.
111
+ gitServiceFactory: () => ({}) as unknown as ReturnType<
112
+ WorktreeLifecycleDeps["gitServiceFactory"]
113
+ >,
70
114
  ...overrides,
71
115
  };
72
116
  return deps;
@@ -104,7 +148,9 @@ describe("worktree journal events", () => {
104
148
  const originalCwd = process.cwd();
105
149
 
106
150
  beforeEach(() => {
107
- tmp = mkdtempSync(join(tmpdir(), "wt-journal-"));
151
+ // realpathSync to match what `auto-worktree.ts` returns from
152
+ // `resolveWorktreeProjectRoot` (macOS resolves `/var` → `/private/var`).
153
+ tmp = realpathSync(mkdtempSync(join(tmpdir(), "wt-journal-")));
108
154
  });
109
155
  afterEach(() => {
110
156
  // Restore cwd before cleanup — on Windows, rmSync fails with EPERM
@@ -114,9 +160,17 @@ describe("worktree journal events", () => {
114
160
  });
115
161
 
116
162
  test("enterMilestone emits worktree-enter on success (new worktree)", () => {
163
+ initGitRepoIn(tmp, "worktree");
117
164
  const s = makeSession({ basePath: tmp, originalBasePath: tmp });
118
- const deps = makeDeps({ getAutoWorktreePath: () => null });
119
- new WorktreeLifecycle(s, deps).enterMilestone("M001", makeNotifyCtx());
165
+ const result = new WorktreeLifecycle(s, makeDeps()).enterMilestone(
166
+ "M001",
167
+ makeNotifyCtx(),
168
+ );
169
+ assert.equal(
170
+ result.ok,
171
+ true,
172
+ `enterMilestone failed: ${JSON.stringify(result)}`,
173
+ );
120
174
 
121
175
  const entries = readJournalEntries(tmp);
122
176
  const enter = entries.find(e => e.eventType === "worktree-enter");
@@ -127,11 +181,19 @@ describe("worktree journal events", () => {
127
181
  });
128
182
 
129
183
  test("enterMilestone emits worktree-enter with created=false for existing worktree", () => {
184
+ // Pre-create the worktree on disk so the second enter goes through the
185
+ // existing-worktree branch in `_enterMilestoneCore`.
186
+ initGitRepoIn(tmp, "worktree");
187
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: tmp, stdio: "pipe" });
188
+ execFileSync("git", ["checkout", "main"], { cwd: tmp, stdio: "pipe" });
189
+ execFileSync(
190
+ "git",
191
+ ["worktree", "add", join(tmp, ".gsd", "worktrees", "M001"), "milestone/M001"],
192
+ { cwd: tmp, stdio: "pipe" },
193
+ );
194
+
130
195
  const s = makeSession({ basePath: tmp, originalBasePath: tmp });
131
- const deps = makeDeps({
132
- getAutoWorktreePath: () => "/project/.gsd/worktrees/M001",
133
- });
134
- new WorktreeLifecycle(s, deps).enterMilestone("M001", makeNotifyCtx());
196
+ new WorktreeLifecycle(s, makeDeps()).enterMilestone("M001", makeNotifyCtx());
135
197
 
136
198
  const entries = readJournalEntries(tmp);
137
199
  const enter = entries.find(e => e.eventType === "worktree-enter");
@@ -140,9 +202,9 @@ describe("worktree journal events", () => {
140
202
  });
141
203
 
142
204
  test("enterMilestone emits worktree-skip when isolation disabled", () => {
205
+ initGitRepoIn(tmp, "none");
143
206
  const s = makeSession({ basePath: tmp, originalBasePath: tmp });
144
- const deps = makeDeps({ shouldUseWorktreeIsolation: () => false, getIsolationMode: () => "none" });
145
- new WorktreeLifecycle(s, deps).enterMilestone("M001", makeNotifyCtx());
207
+ new WorktreeLifecycle(s, makeDeps()).enterMilestone("M001", makeNotifyCtx());
146
208
 
147
209
  const entries = readJournalEntries(tmp);
148
210
  const skip = entries.find(e => e.eventType === "worktree-skip");
@@ -152,30 +214,31 @@ describe("worktree journal events", () => {
152
214
  });
153
215
 
154
216
  test("enterMilestone emits worktree-create-failed on error", () => {
217
+ // Real fixture with isolation:worktree, then delete .git to force the
218
+ // real createAutoWorktree to throw.
219
+ initGitRepoIn(tmp, "worktree");
220
+ rmSync(join(tmp, ".git"), { recursive: true, force: true });
155
221
  const s = makeSession({ basePath: tmp, originalBasePath: tmp });
156
- const deps = makeDeps({
157
- getAutoWorktreePath: () => null,
158
- createAutoWorktree: () => { throw new Error("disk full"); },
159
- });
160
- new WorktreeLifecycle(s, deps).enterMilestone("M001", makeNotifyCtx());
222
+ new WorktreeLifecycle(s, makeDeps()).enterMilestone("M001", makeNotifyCtx());
161
223
 
162
224
  const entries = readJournalEntries(tmp);
163
225
  const failed = entries.find(e => e.eventType === "worktree-create-failed");
164
226
  assert.ok(failed, "worktree-create-failed event should be emitted");
165
227
  assert.equal(failed!.data?.milestoneId, "M001");
166
- assert.equal(failed!.data?.error, "disk full");
228
+ assert.ok(failed!.data?.error, "error message should be present");
167
229
  assert.equal(failed!.data?.fallback, "project-root");
168
230
  });
169
231
 
170
232
  test("mergeAndExit emits worktree-merge-start", () => {
171
- const s = makeSession({
172
- basePath: join(tmp, "worktree"),
173
- originalBasePath: tmp,
174
- });
175
- const deps = makeDeps({
176
- isInAutoWorktree: () => true,
177
- getIsolationMode: () => "worktree",
178
- });
233
+ initGitRepoIn(tmp, "worktree");
234
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: tmp, stdio: "pipe" });
235
+ execFileSync("git", ["checkout", "main"], { cwd: tmp, stdio: "pipe" });
236
+ const wt = join(tmp, ".gsd", "worktrees", "M001");
237
+ execFileSync("git", ["worktree", "add", wt, "milestone/M001"], { cwd: tmp, stdio: "pipe" });
238
+
239
+ const s = makeSession({ basePath: wt, originalBasePath: tmp });
240
+ const deps = makeDeps();
241
+ process.chdir(wt);
179
242
  new WorktreeLifecycle(s, deps).exitMilestone(
180
243
  "M001",
181
244
  { merge: true },
@@ -189,14 +252,51 @@ describe("worktree journal events", () => {
189
252
  assert.equal(start!.data?.mode, "worktree");
190
253
  });
191
254
 
192
- test("mergeAndExit emits worktree-merge-failed on error", () => {
193
- const s = makeSession({
194
- basePath: join(tmp, "worktree"),
195
- originalBasePath: tmp,
255
+ test("exitMilestone propagates codeFilesChanged from merge result", () => {
256
+ initGitRepoIn(tmp, "worktree");
257
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: tmp, stdio: "pipe" });
258
+ execFileSync("git", ["checkout", "main"], { cwd: tmp, stdio: "pipe" });
259
+ const wt = join(tmp, ".gsd", "worktrees", "M001");
260
+ execFileSync("git", ["worktree", "add", wt, "milestone/M001"], { cwd: tmp, stdio: "pipe" });
261
+ mkdirSync(join(tmp, ".gsd", "milestones", "M001"), { recursive: true });
262
+ writeFileSync(
263
+ join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
264
+ "# M001\n- [x] S01: Slice one\n",
265
+ );
266
+
267
+ const s = makeSession({ basePath: wt, originalBasePath: tmp });
268
+ const deps = makeDeps({
269
+ mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
270
+ });
271
+ process.chdir(wt);
272
+
273
+ const result = new WorktreeLifecycle(s, deps).exitMilestone(
274
+ "M001",
275
+ { merge: true },
276
+ makeNotifyCtx(),
277
+ );
278
+
279
+ assert.deepEqual(result, {
280
+ ok: true,
281
+ merged: true,
282
+ codeFilesChanged: true,
196
283
  });
284
+ });
285
+
286
+ test("mergeAndExit emits worktree-merge-failed on error", () => {
287
+ initGitRepoIn(tmp, "worktree");
288
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: tmp, stdio: "pipe" });
289
+ execFileSync("git", ["checkout", "main"], { cwd: tmp, stdio: "pipe" });
290
+ const wt = join(tmp, ".gsd", "worktrees", "M001");
291
+ execFileSync("git", ["worktree", "add", wt, "milestone/M001"], { cwd: tmp, stdio: "pipe" });
292
+ mkdirSync(join(tmp, ".gsd", "milestones", "M001"), { recursive: true });
293
+ writeFileSync(
294
+ join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
295
+ "# M001\n- [x] S01: Slice one\n",
296
+ );
297
+
298
+ const s = makeSession({ basePath: wt, originalBasePath: tmp });
197
299
  const deps = makeDeps({
198
- isInAutoWorktree: () => true,
199
- getIsolationMode: () => "worktree",
200
300
  mergeMilestoneToMain: () => { throw new Error("conflict in main"); },
201
301
  });
202
302
  // Since #4380, mergeAndExit re-throws all errors after emitting the journal