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
@@ -2,12 +2,14 @@
2
2
  // File Purpose: Behavior tests for auto-loop cleanup after paused provider exits.
3
3
  import { test } from "node:test";
4
4
  import assert from "node:assert/strict";
5
- import { mkdtempSync, rmSync } from "node:fs";
5
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
8
 
9
- import { cleanupAfterLoopExit, rerootCommandSession } from "../auto.ts";
9
+ import { cleanupAfterLoopExit, rerootCommandSession, stopAuto } from "../auto.ts";
10
10
  import { autoSession } from "../auto-runtime-state.ts";
11
+ import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
12
+ import { WorktreeLifecycle } from "../worktree-lifecycle.ts";
11
13
 
12
14
  test("cleanupAfterLoopExit preserves paused auto badge after provider pause", async () => {
13
15
  const base = mkdtempSync(join(tmpdir(), "gsd-paused-cleanup-"));
@@ -41,7 +43,7 @@ test("cleanupAfterLoopExit preserves paused auto badge after provider pause", as
41
43
  }
42
44
  });
43
45
 
44
- test("cleanupAfterLoopExit clears status and widget when auto is not paused", async () => {
46
+ test("cleanupAfterLoopExit clears status without replacing the last auto surface", async () => {
45
47
  const statusCalls: unknown[] = [];
46
48
  const widgetCalls: unknown[] = [];
47
49
 
@@ -60,7 +62,16 @@ test("cleanupAfterLoopExit clears status and widget when auto is not paused", as
60
62
  } as any);
61
63
 
62
64
  assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
63
- assert.deepEqual(widgetCalls, [["gsd-progress", undefined]]);
65
+ assert.equal(
66
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
67
+ false,
68
+ "cleanup must not clear the last meaningful auto progress surface",
69
+ );
70
+ assert.equal(
71
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
72
+ false,
73
+ "cleanup must not replace the auto deck with a generic loop-ended card",
74
+ );
64
75
  assert.equal(autoSession.active, false);
65
76
  assert.equal(autoSession.paused, false);
66
77
  } finally {
@@ -68,6 +79,122 @@ test("cleanupAfterLoopExit clears status and widget when auto is not paused", as
68
79
  }
69
80
  });
70
81
 
82
+ test("cleanupAfterLoopExit preserves completion roll-up after stopAuto reset", async () => {
83
+ const statusCalls: unknown[] = [];
84
+ const widgetCalls: unknown[] = [];
85
+
86
+ autoSession.reset();
87
+ autoSession.active = true;
88
+ autoSession.paused = false;
89
+ autoSession.completionStopInProgress = true;
90
+ autoSession.resetAfterStop({ preserveCompletionSurface: true });
91
+
92
+ try {
93
+ await cleanupAfterLoopExit({
94
+ hasUI: true,
95
+ ui: {
96
+ setStatus: (...args: unknown[]) => statusCalls.push(args),
97
+ setWidget: (...args: unknown[]) => widgetCalls.push(args),
98
+ setHeader: () => {},
99
+ notify: () => {},
100
+ },
101
+ } as any);
102
+
103
+ assert.deepEqual(statusCalls, [["gsd-auto", undefined]]);
104
+ assert.equal(
105
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
106
+ false,
107
+ "completion cleanup must not clear the roll-up progress widget",
108
+ );
109
+ assert.equal(
110
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-outcome"),
111
+ false,
112
+ "completion cleanup must not replace the roll-up with a generic outcome card",
113
+ );
114
+ assert.equal(autoSession.completionStopInProgress, false);
115
+ } finally {
116
+ autoSession.reset();
117
+ }
118
+ });
119
+
120
+ test("cleanupAfterLoopExit restores project root through lifecycle and preserves chdir", async (t) => {
121
+ const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-lifecycle-"));
122
+ const worktree = join(base, ".gsd", "worktrees", "M001");
123
+ const previousCwd = process.cwd();
124
+ let restoreCalls = 0;
125
+ const originalRestore = WorktreeLifecycle.prototype.restoreToProjectRoot;
126
+ t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
127
+ restoreCalls += 1;
128
+ return originalRestore.call(this);
129
+ });
130
+
131
+ mkdirSync(worktree, { recursive: true });
132
+ autoSession.reset();
133
+ autoSession.active = true;
134
+ autoSession.basePath = worktree;
135
+ autoSession.originalBasePath = base;
136
+
137
+ try {
138
+ await cleanupAfterLoopExit({
139
+ ui: {
140
+ setStatus: () => {},
141
+ setWidget: () => {},
142
+ notify: () => {},
143
+ },
144
+ } as any);
145
+
146
+ assert.equal(restoreCalls, 1);
147
+ assert.equal(autoSession.basePath, base);
148
+ assert.equal(realpathSync(process.cwd()), realpathSync(base));
149
+ } finally {
150
+ autoSession.reset();
151
+ process.chdir(previousCwd);
152
+ rmSync(base, { recursive: true, force: true });
153
+ }
154
+ });
155
+
156
+ test("cleanupAfterLoopExit keeps cleanup best-effort when lifecycle restore throws", async (t) => {
157
+ const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-restore-throw-"));
158
+ const worktree = join(base, ".gsd", "worktrees", "M001");
159
+ const previousCwd = process.cwd();
160
+ let restoreCalls = 0;
161
+ // ADR-016 phase 3 (#5693): the real `restoreToProjectRoot` assigns
162
+ // `s.basePath = s.originalBasePath` BEFORE any throwable work
163
+ // (rebuildGitService, cache invalidation). Mirror that ordering in the
164
+ // mock so the throw scenario reflects production: basePath is restored
165
+ // even when the verb throws partway through.
166
+ t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
167
+ restoreCalls += 1;
168
+ (this as unknown as { s: { basePath: string; originalBasePath: string } })
169
+ .s.basePath = (this as unknown as { s: { originalBasePath: string } }).s.originalBasePath;
170
+ throw new Error("restore failed");
171
+ });
172
+
173
+ mkdirSync(worktree, { recursive: true });
174
+ autoSession.reset();
175
+ autoSession.active = true;
176
+ autoSession.basePath = worktree;
177
+ autoSession.originalBasePath = base;
178
+
179
+ try {
180
+ await cleanupAfterLoopExit({
181
+ ui: {
182
+ setStatus: () => {},
183
+ setWidget: () => {},
184
+ notify: () => {},
185
+ },
186
+ } as any);
187
+
188
+ assert.equal(restoreCalls, 1);
189
+ assert.equal(autoSession.basePath, base);
190
+ assert.equal(realpathSync(process.cwd()), realpathSync(base));
191
+ } finally {
192
+ autoSession.reset();
193
+ process.chdir(previousCwd);
194
+ rmSync(base, { recursive: true, force: true });
195
+ }
196
+ });
197
+
71
198
  test("rerootCommandSession refreshes command workspace to project root", async () => {
72
199
  const calls: string[] = [];
73
200
  const result = await rerootCommandSession(
@@ -83,3 +210,163 @@ test("rerootCommandSession refreshes command workspace to project root", async (
83
210
  assert.deepEqual(result, { status: "ok" });
84
211
  assert.deepEqual(calls, ["/project/root"]);
85
212
  });
213
+
214
+ test("stopAuto completion closeout reroots session, restores cwd, and preserves final widget", async (t) => {
215
+ const base = mkdtempSync(join(tmpdir(), "gsd-completion-stop-"));
216
+ const previousCwd = process.cwd();
217
+ const widgetCalls: Array<[string, unknown]> = [];
218
+ const notifications: string[] = [];
219
+ const newSessionWorkspaces: string[] = [];
220
+ let restoreCalls = 0;
221
+ const originalRestore = WorktreeLifecycle.prototype.restoreToProjectRoot;
222
+ t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function (this: WorktreeLifecycle) {
223
+ restoreCalls += 1;
224
+ return originalRestore.call(this);
225
+ });
226
+ const milestoneDir = join(base, ".gsd", "milestones", "M003");
227
+ mkdirSync(milestoneDir, { recursive: true });
228
+ writeFileSync(join(milestoneDir, "M003-SUMMARY.md"), [
229
+ "---",
230
+ "id: M003",
231
+ 'title: "Budget tracking"',
232
+ "status: complete",
233
+ "key_decisions:",
234
+ " - Keep completion closeout in the same TUI surface.",
235
+ "key_files:",
236
+ " - src/resources/extensions/gsd/auto-dashboard.ts",
237
+ "lessons_learned:",
238
+ " - Milestone endings need report output, not auto-loop status.",
239
+ "---",
240
+ "",
241
+ "# M003: Budget tracking",
242
+ "",
243
+ "**Added budget warning output and provider roll-up details.**",
244
+ "",
245
+ "## Success Criteria Results",
246
+ "",
247
+ "Budget warnings appear at milestone completion.",
248
+ "",
249
+ "## Definition of Done Results",
250
+ "",
251
+ "Completion leaves the report surface visible.",
252
+ "",
253
+ "## Requirement Outcomes",
254
+ "",
255
+ "Users can see what shipped without opening a fresh session.",
256
+ "",
257
+ "## Deviations",
258
+ "",
259
+ "None.",
260
+ "",
261
+ "## Follow-ups",
262
+ "",
263
+ "None.",
264
+ "",
265
+ ].join("\n"), "utf-8");
266
+
267
+ autoSession.reset();
268
+ openDatabase(join(base, "gsd-test.db"));
269
+ insertMilestone({ id: "M003", title: "Budget tracking", status: "complete" });
270
+ insertSlice({ id: "S01", milestoneId: "M003", title: "Complete slice", status: "complete", sequence: 1 });
271
+ insertSlice({ id: "S02", milestoneId: "M003", title: "Done slice", status: "done", sequence: 2 });
272
+ insertSlice({ id: "S03", milestoneId: "M003", title: "Pending slice", status: "active", sequence: 3 });
273
+
274
+ autoSession.active = true;
275
+ autoSession.paused = false;
276
+ autoSession.basePath = join(base, ".gsd", "worktrees", "M003");
277
+ autoSession.originalBasePath = base;
278
+ autoSession.currentMilestoneId = "M003";
279
+ autoSession.autoStartTime = Date.now() - 60_000;
280
+ autoSession.cmdCtx = {
281
+ newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
282
+ newSessionWorkspaces.push(workspaceRoot);
283
+ widgetCalls.push(["gsd-progress", undefined]);
284
+ return { cancelled: false };
285
+ },
286
+ sessionManager: {
287
+ getEntries: () => [
288
+ {
289
+ type: "message",
290
+ message: {
291
+ role: "assistant",
292
+ usage: { input: 100, cacheRead: 900 },
293
+ },
294
+ },
295
+ ],
296
+ },
297
+ getContextUsage: () => ({ percent: 0.9, contextWindow: 1_000_000 }),
298
+ model: { contextWindow: 1_000_000 },
299
+ } as any;
300
+
301
+ try {
302
+ await stopAuto(
303
+ {
304
+ hasUI: true,
305
+ ui: {
306
+ setStatus: () => {},
307
+ setWidget: (key: string, value: unknown) => {
308
+ widgetCalls.push([key, value]);
309
+ },
310
+ setHeader: () => {},
311
+ notify: (message: string) => {
312
+ notifications.push(message);
313
+ },
314
+ },
315
+ modelRegistry: { find: () => null },
316
+ } as any,
317
+ { events: { emit: () => {} } } as any,
318
+ "Milestone M003 complete",
319
+ {
320
+ completionWidget: {
321
+ milestoneId: "M003",
322
+ milestoneTitle: "Budget tracking",
323
+ },
324
+ },
325
+ );
326
+
327
+ assert.deepEqual(newSessionWorkspaces, [base], "completion stop must reroot command session to original project root");
328
+ assert.equal(restoreCalls, 1, "completion stop must restore project root through lifecycle");
329
+ assert.equal(realpathSync(process.cwd()), realpathSync(base), "completion stop must chdir back to project root");
330
+ assert.ok(
331
+ widgetCalls.some(([key, value]) => key === "gsd-progress" && typeof value === "function"),
332
+ "completion stop must install a final progress widget",
333
+ );
334
+ const lastProgressWidget = widgetCalls.filter(([key]) => key === "gsd-progress").at(-1);
335
+ assert.equal(typeof lastProgressWidget?.[1], "function", "completion stop must leave the final progress widget installed after reroot");
336
+ const factory = lastProgressWidget?.[1] as any;
337
+ const component = factory(
338
+ { requestRender() {} },
339
+ { fg: (_color: string, text: string) => text, bold: (text: string) => text },
340
+ );
341
+ const output = component.render(140).join("\n");
342
+ assert.match(output, /Milestone M003 roll-up/);
343
+ assert.match(output, /Outcome/);
344
+ assert.match(output, /Added budget warning output/);
345
+ assert.match(output, /Verification/);
346
+ assert.match(output, /Files: src\/resources\/extensions\/gsd\/auto-dashboard\.ts/);
347
+ assert.match(output, /Lessons: Milestone endings need report output/);
348
+ assert.match(output, /2\/3 slices/);
349
+ assert.match(output, /Next/);
350
+ assert.match(output, /Review the roll-up/);
351
+ assert.match(output, /\/gsd auto for next milestone/);
352
+ assert.doesNotMatch(output, /COMPLETE-MILESTONE/);
353
+ assert.doesNotMatch(output, /\/gsd auto to resume/);
354
+ assert.ok(
355
+ notifications.some(message => message.includes("Milestone M003 complete. Auto-mode finished this milestone.")),
356
+ "completion stop notification should describe completion, not an aborted pause",
357
+ );
358
+ assert.ok(
359
+ notifications.every(message => !message.includes("/gsd auto to resume")),
360
+ "completion stop notification must not tell users to resume a finished auto run",
361
+ );
362
+ assert.ok(
363
+ widgetCalls.every(([key, value]) => key !== "gsd-outcome" || value === undefined),
364
+ "completion stop should use the roll-up as the single final surface",
365
+ );
366
+ } finally {
367
+ try { closeDatabase(); } catch { /* noop */ }
368
+ autoSession.reset();
369
+ process.chdir(previousCwd);
370
+ rmSync(base, { recursive: true, force: true });
371
+ }
372
+ });
@@ -1,7 +1,11 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
- import { autoSession, getAutoRuntimeSnapshot } from "../auto-runtime-state.ts";
4
+ import {
5
+ autoSession,
6
+ clearToolInvocationError,
7
+ getAutoRuntimeSnapshot,
8
+ } from "../auto-runtime-state.ts";
5
9
 
6
10
  test("getAutoRuntimeSnapshot includes orchestration phase when available", () => {
7
11
  autoSession.reset();
@@ -28,6 +32,17 @@ test("getAutoRuntimeSnapshot includes orchestration phase when available", () =>
28
32
  autoSession.reset();
29
33
  });
30
34
 
35
+ test("clearToolInvocationError clears stale tool error state for active auto sessions", () => {
36
+ autoSession.reset();
37
+ autoSession.active = true;
38
+ autoSession.lastToolInvocationError = "gsd_task_complete: simulated transient tool error";
39
+
40
+ clearToolInvocationError();
41
+
42
+ assert.equal(autoSession.lastToolInvocationError, null);
43
+ autoSession.reset();
44
+ });
45
+
31
46
  test("getAutoRuntimeSnapshot omits orchestration phase when seam not wired", () => {
32
47
  autoSession.reset();
33
48
 
@@ -100,6 +100,14 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
100
100
  registerSigtermHandler: () => {},
101
101
  lockBase: () => base,
102
102
  buildLifecycle: () => ({
103
+ adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
104
+ s.basePath = sessionBase;
105
+ if (originalBase !== undefined) {
106
+ s.originalBasePath = originalBase;
107
+ } else if (!s.originalBasePath) {
108
+ s.originalBasePath = sessionBase;
109
+ }
110
+ },
103
111
  exitMilestone: (milestoneId: string) => {
104
112
  mergeCalls.push(milestoneId);
105
113
  return {
@@ -109,6 +117,16 @@ test("bootstrap aborts before starting next milestone when completed orphan merg
109
117
  };
110
118
  },
111
119
  enterMilestone: () => ({ ok: true, mode: "none", path: base }),
120
+ // ADR-016 phase 2 / B4 (#5622): the orphan-merge dance now goes
121
+ // through `adoptOrphanWorktree`. The mock invokes the callback
122
+ // and returns its result without exercising the swap-revert
123
+ // protocol — this test only cares about the merge call being
124
+ // recorded and the bootstrap returning `false` on failure.
125
+ adoptOrphanWorktree: <T extends { merged: boolean }>(
126
+ _mid: string,
127
+ _base: string,
128
+ run: () => T,
129
+ ): T => run(),
112
130
  }) as any,
113
131
  },
114
132
  {
@@ -0,0 +1,68 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Regression tests for auto-unit closeout activity classification.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import {
8
+ isSuspiciousGhostCompletion,
9
+ snapshotUnitActivity,
10
+ } from "../auto-unit-closeout.ts";
11
+
12
+ function makeCtx(entries: unknown[]) {
13
+ return {
14
+ sessionManager: {
15
+ getEntries: () => entries,
16
+ },
17
+ } as any;
18
+ }
19
+
20
+ test("isSuspiciousGhostCompletion rejects fast completions with no assistant output or tools", () => {
21
+ const startedAt = Date.now();
22
+ const ctx = makeCtx([]);
23
+
24
+ assert.equal(isSuspiciousGhostCompletion(ctx, startedAt, 500), true);
25
+ });
26
+
27
+ test("isSuspiciousGhostCompletion allows fast completions with assistant output", () => {
28
+ const startedAt = Date.now();
29
+ const ctx = makeCtx([
30
+ {
31
+ type: "message",
32
+ message: {
33
+ role: "assistant",
34
+ content: [{ type: "text", text: "Done." }],
35
+ },
36
+ },
37
+ ]);
38
+
39
+ assert.equal(isSuspiciousGhostCompletion(ctx, startedAt, 500), false);
40
+ });
41
+
42
+ test("snapshotUnitActivity counts assistant messages and tool calls", () => {
43
+ const ctx = makeCtx([
44
+ {
45
+ type: "message",
46
+ message: {
47
+ role: "assistant",
48
+ content: [
49
+ { type: "text", text: "Working." },
50
+ { type: "toolCall", name: "read_file" },
51
+ ],
52
+ },
53
+ },
54
+ {
55
+ type: "message",
56
+ message: {
57
+ role: "user",
58
+ content: "continue",
59
+ },
60
+ },
61
+ ]);
62
+
63
+ assert.deepEqual(snapshotUnitActivity(ctx, 1_000, 1_250), {
64
+ elapsedMs: 250,
65
+ toolCalls: 1,
66
+ assistantMessages: 1,
67
+ });
68
+ });
@@ -182,7 +182,6 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
182
182
  pruneQueueOrder: () => {},
183
183
  isInAutoWorktree: () => false,
184
184
  shouldUseWorktreeIsolation: () => false,
185
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
186
185
  teardownAutoWorktree: () => {},
187
186
  createAutoWorktree: () => "/tmp/wt",
188
187
  captureIntegrationBranch: () => {},
@@ -420,6 +419,10 @@ describe("Custom engine loop integration", () => {
420
419
  await autoLoop(ctx, pi, s, deps);
421
420
 
422
421
  assert.deepEqual(turnResults, [{ status: "completed", failureClass: "none", error: undefined }]);
422
+ assert.ok(
423
+ deps.callLog.includes("journal:iteration-end"),
424
+ `complete workflow should emit iteration-end; log=${deps.callLog.join(",")}`,
425
+ );
423
426
  assert.ok(
424
427
  deps.callLog.indexOf("turnResult:completed") < deps.callLog.indexOf("stopAuto:Workflow complete"),
425
428
  `turn should finalize before stopAuto; log=${deps.callLog.join(",")}`,
@@ -472,6 +475,10 @@ describe("Custom engine loop integration", () => {
472
475
  assert.equal(turnResults[0].status, "stopped");
473
476
  assert.equal(turnResults[0].failureClass, "manual-attention");
474
477
  assert.match(turnResults[0].error ?? "", /custom-engine-dispatch-stop/);
478
+ assert.ok(
479
+ deps.callLog.includes("journal:iteration-end"),
480
+ `blocked workflow should emit iteration-end; log=${deps.callLog.join(",")}`,
481
+ );
475
482
  assert.equal(s.currentTraceId, null);
476
483
  assert.equal(s.currentTurnId, null);
477
484
  assert.equal(pi.calls.length, 0, "blocked workflow should not dispatch a custom step");
@@ -664,11 +671,16 @@ describe("Custom engine loop integration", () => {
664
671
  activeRunDir: runDir,
665
672
  basePath: runDir,
666
673
  });
674
+ const journalEvents: Array<{ eventType: string; data?: any }> = [];
667
675
  const deps = makeMockDeps({
668
676
  stopAuto: async (_ctx, _pi, reason) => {
669
677
  deps.callLog.push(`stopAuto:${reason ?? "no-reason"}`);
670
678
  s.active = false;
671
679
  },
680
+ emitJournalEvent: (entry: any) => {
681
+ journalEvents.push(entry);
682
+ deps.callLog.push(`journal:${entry.eventType}`);
683
+ },
672
684
  });
673
685
 
674
686
  const resolver = setInterval(() => {
@@ -700,6 +712,21 @@ describe("Custom engine loop integration", () => {
700
712
  assert.match(stopEntry ?? "", /requested retry 4 times without passing/);
701
713
  const finalGraph = readGraph(runDir);
702
714
  assert.equal(finalGraph.steps[0]?.status, "active", "failed verification must not reconcile the step complete");
715
+
716
+ const unitEndIndexes = journalEvents
717
+ .map((entry, index) => entry.eventType === "unit-end" ? index : -1)
718
+ .filter((index) => index >= 0);
719
+ const iterationEndIndexes = journalEvents
720
+ .map((entry, index) => entry.eventType === "iteration-end" ? index : -1)
721
+ .filter((index) => index >= 0);
722
+ assert.equal(unitEndIndexes.length, 4, "each custom verification retry/stop attempt must emit unit-end");
723
+ assert.equal(iterationEndIndexes.length, 4, "each custom verification retry/stop iteration must close after unit-end");
724
+ for (const [i, unitEndIndex] of unitEndIndexes.entries()) {
725
+ assert.ok(
726
+ iterationEndIndexes[i]! > unitEndIndex,
727
+ `custom verification attempt ${i + 1} should emit iteration-end after unit-end`,
728
+ );
729
+ }
703
730
  });
704
731
 
705
732
  it("persists custom verification retry budget across a session restart", async () => {
@@ -273,7 +273,16 @@ test("deep project setup: bootstrap can start auto-mode without an active milest
273
273
  shouldUseWorktreeIsolation: () => false,
274
274
  registerSigtermHandler: () => {},
275
275
  lockBase: () => base,
276
- buildLifecycle: () => ({}) as any,
276
+ buildLifecycle: () => ({
277
+ adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
278
+ s.basePath = sessionBase;
279
+ if (originalBase !== undefined) {
280
+ s.originalBasePath = originalBase;
281
+ } else if (!s.originalBasePath) {
282
+ s.originalBasePath = sessionBase;
283
+ }
284
+ },
285
+ }) as any,
277
286
  },
278
287
  {
279
288
  classification: "none",
@@ -378,7 +387,16 @@ test("deep project setup: bootstrap continues queued M002 without milestone cont
378
387
  shouldUseWorktreeIsolation: () => false,
379
388
  registerSigtermHandler: () => {},
380
389
  lockBase: () => base,
381
- buildLifecycle: () => ({}) as any,
390
+ buildLifecycle: () => ({
391
+ adoptSessionRoot: (sessionBase: string, originalBase?: string) => {
392
+ s.basePath = sessionBase;
393
+ if (originalBase !== undefined) {
394
+ s.originalBasePath = originalBase;
395
+ } else if (!s.originalBasePath) {
396
+ s.originalBasePath = sessionBase;
397
+ }
398
+ },
399
+ }) as any,
382
400
  },
383
401
  {
384
402
  classification: "none",
@@ -1,3 +1,6 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Regression tests for complete-milestone dispatch guards.
3
+
1
4
  /**
2
5
  * dispatch-complete-milestone-guard.test.ts — #4324
3
6
  */
@@ -74,3 +77,44 @@ describe("completing-milestone dispatch guard (#4324)", () => {
74
77
  assert.equal(result?.unitId, "M001");
75
78
  });
76
79
  });
80
+
81
+ describe("complete phase dispatch guard (#5683)", () => {
82
+ let base = "";
83
+ const rule = DISPATCH_RULES.find((candidate) => candidate.name === "complete → stop");
84
+ assert.ok(rule, "complete phase terminal rule should exist");
85
+
86
+ afterEach(() => {
87
+ try { closeDatabase(); } catch { /* ignore */ }
88
+ if (base) rmSync(base, { recursive: true, force: true });
89
+ base = "";
90
+ });
91
+
92
+ test("dispatches complete-milestone when derived state is complete but DB milestone is still open", async () => {
93
+ base = makeBase();
94
+ openDatabase(join(base, ".gsd", "gsd.db"));
95
+ insertMilestone({ id: "M001", title: "Milestone One", status: "in_progress" });
96
+
97
+ const ctx = buildDispatchCtx(base);
98
+ ctx.state.phase = "complete";
99
+
100
+ const result = await rule.match(ctx);
101
+
102
+ assert.equal(result?.action, "dispatch");
103
+ assert.equal(result?.unitType, "complete-milestone");
104
+ assert.equal(result?.unitId, "M001");
105
+ });
106
+
107
+ test("stops when derived state is complete and DB milestone is closed", async () => {
108
+ base = makeBase();
109
+ openDatabase(join(base, ".gsd", "gsd.db"));
110
+ insertMilestone({ id: "M001", title: "Milestone One", status: "complete" });
111
+
112
+ const ctx = buildDispatchCtx(base);
113
+ ctx.state.phase = "complete";
114
+
115
+ const result = await rule.match(ctx);
116
+
117
+ assert.equal(result?.action, "stop");
118
+ assert.equal(result?.reason, "All milestones complete.");
119
+ });
120
+ });
@@ -0,0 +1,40 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Visual contract tests for the GSD watch header renderer.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { stripVTControlCharacters } from "node:util";
7
+ import { renderHeaderLines } from "../watch/header-renderer.ts";
8
+ import { splashPalette } from "../watch/splash-palette.ts";
9
+
10
+ function rgbPattern(hex: string): RegExp {
11
+ const cleaned = hex.replace("#", "");
12
+ const r = Number.parseInt(cleaned.slice(0, 2), 16);
13
+ const g = Number.parseInt(cleaned.slice(2, 4), 16);
14
+ const b = Number.parseInt(cleaned.slice(4, 6), 16);
15
+ return new RegExp(`\\x1b\\[38;2;${r};${g};${b}m`);
16
+ }
17
+
18
+ test("renderHeaderLines uses the command-center splash layout", () => {
19
+ const lines = renderHeaderLines(
20
+ {
21
+ model: "claude-sonnet-4-6",
22
+ provider: "anthropic",
23
+ directory: "~/Github/gsd-2",
24
+ branch: "feat/tui-refresh",
25
+ mcpServers: ["context7"],
26
+ },
27
+ 120,
28
+ );
29
+
30
+ const raw = lines.join("\n");
31
+ const plain = stripVTControlCharacters(raw);
32
+
33
+ assert.match(raw, rgbPattern(splashPalette.border), "logo and divider should use the recommended olive border");
34
+ assert.match(raw, rgbPattern(splashPalette.accent), "header accents should use the recommended blue");
35
+ assert.match(plain, /Project Console/);
36
+ assert.match(plain, /\/gsd start/);
37
+ assert.match(plain, /\/gsd templates/);
38
+ assert.match(plain, /claude-sonnet-4-6/);
39
+ assert.match(plain, /Context7 ✓/);
40
+ });
@@ -85,6 +85,11 @@ describe("headless milestone bootstrap — parity with interactive flow", () =>
85
85
  /Do not announce the ready phrase as something you are "about to" do/.test(section),
86
86
  "single-milestone pre-condition must include the 'do not announce intent' guard",
87
87
  );
88
+ assert.ok(/Next steps:/.test(section), "single-milestone handoff must include next steps");
89
+ assert.ok(/\/gsd auto/.test(section), "single-milestone handoff must mention /gsd auto");
90
+ assert.ok(/\/gsd status/.test(section), "single-milestone handoff must mention /gsd status");
91
+ assert.ok(/\/gsd visualize/.test(section), "single-milestone handoff must mention /gsd visualize");
92
+ assert.ok(/\/gsd notifications/.test(section), "single-milestone handoff must mention /gsd notifications");
88
93
  });
89
94
 
90
95
  test("discuss-headless multi-milestone pre-condition uses the non-bypassable checkbox format", () => {
@@ -113,5 +118,10 @@ describe("headless milestone bootstrap — parity with interactive flow", () =>
113
118
  /gates_completed === total/.test(multiSection),
114
119
  "multi-milestone pre-condition must still enforce gates_completed === total",
115
120
  );
121
+ assert.ok(/Next steps:/.test(multiSection), "multi-milestone handoff must include next steps");
122
+ assert.ok(/\/gsd auto/.test(multiSection), "multi-milestone handoff must mention /gsd auto");
123
+ assert.ok(/\/gsd status/.test(multiSection), "multi-milestone handoff must mention /gsd status");
124
+ assert.ok(/\/gsd visualize/.test(multiSection), "multi-milestone handoff must mention /gsd visualize");
125
+ assert.ok(/\/gsd notifications/.test(multiSection), "multi-milestone handoff must mention /gsd notifications");
116
126
  });
117
127
  });