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
@@ -1,3 +1,6 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Tests for the GSD health widget state and footer hint rendering.
3
+
1
4
  import test from "node:test";
2
5
  import assert from "node:assert/strict";
3
6
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
@@ -9,6 +12,7 @@ import {
9
12
  formatRelativeTime,
10
13
  type HealthWidgetData,
11
14
  } from "../health-widget-core.ts";
15
+ import { HEALTH_WIDGET_ACTIVE_HINTS } from "../health-widget.ts";
12
16
  import { registerHooks } from "../bootstrap/register-hooks.ts";
13
17
 
14
18
  function makeTempDir(prefix: string): string {
@@ -75,13 +79,11 @@ test("buildHealthLines: none state shows single onboarding line pointing at /gsd
75
79
  assert.match(lines[0]!, /\/gsd/);
76
80
  });
77
81
 
78
- test("buildHealthLines: initialized state shows single setup line pointing at /gsd", (t) => {
82
+ test("buildHealthLines: initialized state shows concise initialized line", (t) => {
79
83
  const lines = buildHealthLines(activeData({ projectState: "initialized" }));
80
84
  assert.equal(lines.length, 1, "renders exactly one line");
81
85
  assert.ok(!/System OK|Budget|Last commit/.test(lines[0]!), "no active-project chrome");
82
- // Distinct from "none" — must mention initialized/setup language and /gsd.
83
- assert.match(lines[0]!, /\/gsd/);
84
- assert.match(lines[0]!, /initiali[sz]ed|setup/i);
86
+ assert.equal(lines[0], " GSD Project Initialized");
85
87
  });
86
88
 
87
89
  test("buildHealthLines: active state with ledger-driven spend shows spent summary", (t) => {
@@ -91,6 +93,14 @@ test("buildHealthLines: active state with ledger-driven spend shows spent summar
91
93
  assert.match(lines[0]!, /Spent: 42\.0¢/);
92
94
  });
93
95
 
96
+ test("health widget active hints include visualization and notifications", () => {
97
+ assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd auto to run/);
98
+ assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd status for overview/);
99
+ assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd visualize to inspect/);
100
+ assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd notifications for history/);
101
+ assert.match(HEALTH_WIDGET_ACTIVE_HINTS, /\/gsd help/);
102
+ });
103
+
94
104
  test("buildHealthLines: active state with budget ceiling shows percent summary", (t) => {
95
105
  const lines = buildHealthLines(activeData({ budgetSpent: 2.5, budgetCeiling: 10 }));
96
106
  assert.equal(lines.length, 1);
@@ -608,6 +608,32 @@ describe('git-service', async () => {
608
608
  rmSync(repo, { recursive: true, force: true });
609
609
  });
610
610
 
611
+ test('GitServiceImpl: task context keyFiles ignores gitignored build outputs', () => {
612
+ const repo = initTempRepo();
613
+ const svc = new GitServiceImpl(repo);
614
+
615
+ createFile(repo, ".gitignore", "dist/\n");
616
+ runGit(repo, ["add", ".gitignore"]);
617
+ runGit(repo, ["commit", "-F", "-"], { input: "ignore dist" });
618
+
619
+ createFile(repo, "src/task.ts", "export const task = true;");
620
+ createFile(repo, "dist/task.js", "export const task = true;");
621
+
622
+ const msg = svc.autoCommit("execute-task", "M001/S01/T01", [], {
623
+ taskId: "S01/T01",
624
+ taskTitle: "implement scoped task",
625
+ oneLiner: "Added scoped task implementation",
626
+ keyFiles: ["src/task.ts", "dist/task.js"],
627
+ });
628
+ assert.ok(msg !== null, "autoCommit should commit non-ignored key files");
629
+
630
+ const committed = run("git show --name-only --format= HEAD", repo);
631
+ assert.ok(committed.includes("src/task.ts"), "non-ignored key file is committed");
632
+ assert.ok(!committed.includes("dist/task.js"), "ignored build output is not committed");
633
+
634
+ rmSync(repo, { recursive: true, force: true });
635
+ });
636
+
611
637
  // ─── GitServiceImpl: empty-after-staging guard ─────────────────────────
612
638
 
613
639
  test('GitServiceImpl: empty-after-staging guard', () => {
@@ -62,8 +62,8 @@ import {
62
62
  renderRoadmapCheckboxes,
63
63
  renderAllFromDb,
64
64
  detectStaleRenders,
65
- repairStaleRenders,
66
65
  } from "../../markdown-renderer.ts";
66
+ import { repairStaleRenders } from "../../state-reconciliation/drift/stale-render.ts";
67
67
 
68
68
  // ── State derivation ──────────────────────────────────────────────────────
69
69
  import {
@@ -85,7 +85,49 @@ function cleanup(dir: string): void {
85
85
  try { rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
86
86
  }
87
87
 
88
- /** Set up a milestone roadmap file in .gsd/milestones/<MID>/ */
88
+ /**
89
+ * Write `.gsd/preferences.md` with `git.isolation: branch` so the merge
90
+ * routes through the standalone Lifecycle merge body in branch mode rather
91
+ * than falling through `getIsolationMode === "none"` to a skipped result.
92
+ *
93
+ * Parallel-merge in production runs with `git.isolation: worktree` and a
94
+ * real auto-worktree on disk; these integration tests use branch mode as a
95
+ * simpler equivalent that exercises the same merge path without requiring
96
+ * `git worktree add` setup. ADR-016 phase 2 / A2 routes parallel-merge
97
+ * through the Module, which performs mode detection — branch mode keeps
98
+ * the test simple while still going through the Module.
99
+ */
100
+ function setupBranchIsolation(repo: string): void {
101
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
102
+ writeFileSync(
103
+ join(repo, ".gsd", "preferences.md"),
104
+ "## Git\n- isolation: branch\n",
105
+ );
106
+ // Commit on main so subsequent `git checkout main` restores the file.
107
+ // Without this commit, `createMilestoneBranch`'s checkout dance carries
108
+ // the uncommitted file onto the milestone branch and back-checking-out
109
+ // main loses the preference file before `mergeMilestoneStandalone`
110
+ // reads it (ADR-016 phase 2 / A2).
111
+ try {
112
+ run("git add .gsd/preferences.md", repo);
113
+ run('git commit -m "test: add branch-isolation preferences"', repo);
114
+ } catch { /* file may already be committed */ }
115
+ }
116
+
117
+ /**
118
+ * Set up a milestone roadmap file in .gsd/milestones/<MID>/ AND commit it on
119
+ * the current branch.
120
+ *
121
+ * The commit is required after ADR-016 phase 2 / A2 because the standalone
122
+ * Module-level merge (now used by parallel-merge) reads the roadmap _after_
123
+ * its branch checkout. Uncommitted roadmaps on `main` survive a checkout
124
+ * for the first milestone but get committed onto the milestone branch by
125
+ * `autoCommitDirtyState`, which then doesn't reproduce them on the next
126
+ * milestone branch's checkout.
127
+ *
128
+ * Real production runs commit roadmap files on each milestone branch as
129
+ * part of the unit's normal output; tests should mirror that.
130
+ */
89
131
  function setupRoadmap(repo: string, mid: string, title: string, slices: string[]): void {
90
132
  const dir = join(repo, ".gsd", "milestones", mid);
91
133
  mkdirSync(dir, { recursive: true });
@@ -94,6 +136,10 @@ function setupRoadmap(repo: string, mid: string, title: string, slices: string[]
94
136
  join(dir, `${mid}-ROADMAP.md`),
95
137
  `# ${mid}: ${title}\n\n## Slices\n${sliceLines}\n`,
96
138
  );
139
+ try {
140
+ run(`git add .gsd/milestones/${mid}/${mid}-ROADMAP.md`, repo);
141
+ run(`git commit -m "test: add roadmap for ${mid}"`, repo);
142
+ } catch { /* file may already be committed; not a git repo; etc. */ }
97
143
  }
98
144
 
99
145
  /** Create a milestone branch with file changes, then return to main. */
@@ -234,15 +280,36 @@ test("formatMergeResults — mixed results", () => {
234
280
  // ═══════════════════════════════════════════════════════════════════════════════
235
281
 
236
282
  test("mergeCompletedMilestone — missing roadmap returns error result", async () => {
237
- const base = join(tmpdir(), `parallel-merge-noroadmap-${Date.now()}`);
238
- mkdirSync(join(base, ".gsd"), { recursive: true });
283
+ const savedCwd = process.cwd();
284
+ // Use a real git repo so the standalone's branch-mode merge body can
285
+ // call `getCurrentBranch` without throwing. The roadmap is intentionally
286
+ // omitted so the merge ends in the "no-roadmap" branch.
287
+ const repo = createTempRepo();
239
288
  try {
240
- const result = await mergeCompletedMilestone(base, "M999");
289
+ setupBranchIsolation(repo);
290
+ // Create a milestone branch but no roadmap file — this is the
291
+ // condition under test.
292
+ run("git checkout -b milestone/M999", repo);
293
+ writeFileSync(join(repo, "feature.ts"), "export {};\n");
294
+ run("git add .", repo);
295
+ run('git commit -m "M999 feature"', repo);
296
+ run("git checkout main", repo);
297
+
298
+ process.chdir(repo);
299
+ const result = await mergeCompletedMilestone(repo, "M999");
300
+
241
301
  assert.equal(result.success, false);
242
- assert.ok(result.error?.includes("No roadmap found") || result.error?.includes("Could not read"));
302
+ // A2 widens the error surface vs. the legacy bypass: the standalone
303
+ // routes through the Module, so the no-roadmap path teardowns the
304
+ // (non-existent) worktree branch and surfaces a typed failure.
305
+ // What matters is `success: false`; the exact wording can shift as
306
+ // the standalone evolves.
307
+ assert.ok(result.error, "should report a non-empty error");
243
308
  assert.equal(result.milestoneId, "M999");
244
309
  } finally {
245
- cleanup(base);
310
+ process.chdir(savedCwd);
311
+ try { run("git reset --hard HEAD", repo); } catch { /* */ }
312
+ cleanup(repo);
246
313
  }
247
314
  });
248
315
 
@@ -251,14 +318,24 @@ test("mergeCompletedMilestone — clean merge, session status cleaned up", async
251
318
  const repo = createTempRepo();
252
319
 
253
320
  try {
321
+ // Route the merge through the Module's branch-mode path. With default
322
+ // `isolation: none`, the standalone returns `{ mode: "skipped" }`
323
+ // (ADR-016 phase 2 / A2). Branch mode exercises the same Module-level
324
+ // merge body without requiring an on-disk auto-worktree.
325
+ setupBranchIsolation(repo);
326
+
327
+ // Set up roadmap on main BEFORE the milestone branch is created so the
328
+ // roadmap is in the milestone branch's history. After A2, the standalone
329
+ // reads the roadmap _inside_ the merge body (after the branch checkout)
330
+ // — the file must therefore exist in the milestone branch's tree, not
331
+ // just as an uncommitted file on main.
332
+ setupRoadmap(repo, "M010", "Auth System", ["S01: JWT module"]);
333
+
254
334
  // Create milestone branch with a new file
255
335
  createMilestoneBranch(repo, "M010", [
256
336
  { name: "auth.ts", content: "export const auth = true;\n" },
257
337
  ]);
258
338
 
259
- // Set up roadmap
260
- setupRoadmap(repo, "M010", "Auth System", ["S01: JWT module"]);
261
-
262
339
  // Write session status to verify cleanup
263
340
  writeSessionStatus(repo, {
264
341
  milestoneId: "M010",
@@ -309,6 +386,11 @@ test("mergeCompletedMilestone — conflict returns structured error with file li
309
386
  const repo = createTempRepo();
310
387
 
311
388
  try {
389
+ setupBranchIsolation(repo);
390
+
391
+ // Roadmap committed on main BEFORE branching — see "clean merge" test.
392
+ setupRoadmap(repo, "M020", "Conflict Test", ["S01: Conflict scenario"]);
393
+
312
394
  // Create milestone branch that modifies README.md
313
395
  run("git checkout -b milestone/M020", repo);
314
396
  writeFileSync(join(repo, "README.md"), "# M020 version\n");
@@ -321,9 +403,6 @@ test("mergeCompletedMilestone — conflict returns structured error with file li
321
403
  run("git add .", repo);
322
404
  run('git commit -m "main changes README"', repo);
323
405
 
324
- // Set up roadmap
325
- setupRoadmap(repo, "M020", "Conflict Test", ["S01: Conflict scenario"]);
326
-
327
406
  process.chdir(repo);
328
407
  const result = await mergeCompletedMilestone(repo, "M020");
329
408
 
@@ -350,6 +429,12 @@ test("mergeAllCompleted — merges in sequential order", async () => {
350
429
  const repo = createTempRepo();
351
430
 
352
431
  try {
432
+ setupBranchIsolation(repo);
433
+
434
+ // Roadmaps committed on main BEFORE branching — see "clean merge" test.
435
+ setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
436
+ setupRoadmap(repo, "M002", "Dashboard", ["S01: Dashboard module"]);
437
+
353
438
  // M001: adds auth.ts
354
439
  createMilestoneBranch(repo, "M001", [
355
440
  { name: "auth.ts", content: "export const auth = true;\n" },
@@ -359,9 +444,6 @@ test("mergeAllCompleted — merges in sequential order", async () => {
359
444
  { name: "dashboard.ts", content: "export const dash = true;\n" },
360
445
  ]);
361
446
 
362
- setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
363
- setupRoadmap(repo, "M002", "Dashboard", ["S01: Dashboard module"]);
364
-
365
447
  const workers = [
366
448
  makeWorker({ milestoneId: "M002", startedAt: 100 }),
367
449
  makeWorker({ milestoneId: "M001", startedAt: 200 }),
@@ -373,9 +455,9 @@ test("mergeAllCompleted — merges in sequential order", async () => {
373
455
  // Both should succeed
374
456
  assert.equal(results.length, 2, "should have two results");
375
457
  assert.equal(results[0]!.milestoneId, "M001", "M001 merged first (sequential)");
376
- assert.equal(results[0]!.success, true, "M001 should succeed");
458
+ assert.equal(results[0]!.success, true, `M001 should succeed: ${results[0]!.error}`);
377
459
  assert.equal(results[1]!.milestoneId, "M002", "M002 merged second");
378
- assert.equal(results[1]!.success, true, "M002 should succeed");
460
+ assert.equal(results[1]!.success, true, `M002 should succeed: ${results[1]!.error}`);
379
461
 
380
462
  // Both files on main
381
463
  assert.ok(existsSync(join(repo, "auth.ts")), "auth.ts on main");
@@ -391,6 +473,12 @@ test("mergeAllCompleted — stops on first conflict, skips later milestones", as
391
473
  const repo = createTempRepo();
392
474
 
393
475
  try {
476
+ setupBranchIsolation(repo);
477
+
478
+ // Roadmaps committed on main BEFORE branching — see "clean merge" test.
479
+ setupRoadmap(repo, "M001", "Conflict milestone", ["S01: Conflict test"]);
480
+ setupRoadmap(repo, "M002", "Clean milestone", ["S01: Clean test"]);
481
+
394
482
  // M001: modifies README.md (will conflict with main)
395
483
  run("git checkout -b milestone/M001", repo);
396
484
  writeFileSync(join(repo, "README.md"), "# M001 version\n");
@@ -408,9 +496,6 @@ test("mergeAllCompleted — stops on first conflict, skips later milestones", as
408
496
  run("git add .", repo);
409
497
  run('git commit -m "main diverges README"', repo);
410
498
 
411
- setupRoadmap(repo, "M001", "Conflict milestone", ["S01: Conflict test"]);
412
- setupRoadmap(repo, "M002", "Clean milestone", ["S01: Clean test"]);
413
-
414
499
  const workers = [
415
500
  makeWorker({ milestoneId: "M001" }),
416
501
  makeWorker({ milestoneId: "M002" }),
@@ -442,6 +527,12 @@ test("mergeAllCompleted — by-completion order respects startedAt", async () =>
442
527
  const repo = createTempRepo();
443
528
 
444
529
  try {
530
+ setupBranchIsolation(repo);
531
+
532
+ // Roadmaps committed on main BEFORE branching — see "clean merge" test.
533
+ setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
534
+ setupRoadmap(repo, "M002", "Feature", ["S01: Feature module"]);
535
+
445
536
  // M001: adds auth.ts (started later)
446
537
  createMilestoneBranch(repo, "M001", [
447
538
  { name: "auth.ts", content: "export const auth = true;\n" },
@@ -451,9 +542,6 @@ test("mergeAllCompleted — by-completion order respects startedAt", async () =>
451
542
  { name: "feature.ts", content: "export const feature = true;\n" },
452
543
  ]);
453
544
 
454
- setupRoadmap(repo, "M001", "Auth", ["S01: Auth module"]);
455
- setupRoadmap(repo, "M002", "Feature", ["S01: Feature module"]);
456
-
457
545
  const workers = [
458
546
  makeWorker({ milestoneId: "M001", startedAt: 2000 }),
459
547
  makeWorker({ milestoneId: "M002", startedAt: 1000 }),
@@ -549,11 +637,15 @@ test("mergeAllCompleted — discovers DB-complete milestones when workers show e
549
637
  const repo = createTempRepo();
550
638
 
551
639
  try {
640
+ setupBranchIsolation(repo);
641
+
642
+ // Roadmap committed on main BEFORE branching — see "clean merge" test.
643
+ setupRoadmap(repo, "M011", "Feature System", ["S01: Feature module"]);
644
+
552
645
  // Create milestone branch with a file
553
646
  createMilestoneBranch(repo, "M011", [
554
647
  { name: "feature.ts", content: "export const feature = true;\n" },
555
648
  ]);
556
- setupRoadmap(repo, "M011", "Feature System", ["S01: Feature module"]);
557
649
 
558
650
  // Set up canonical DB showing M011 is complete
559
651
  setupCanonicalDbWithWorktree(repo, "M011");
@@ -75,7 +75,6 @@ function makeMockDeps(
75
75
  pruneQueueOrder: () => {},
76
76
  isInAutoWorktree: () => false,
77
77
  shouldUseWorktreeIsolation: () => false,
78
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
79
78
  teardownAutoWorktree: () => {},
80
79
  createAutoWorktree: () => "/tmp/wt",
81
80
  captureIntegrationBranch: () => {},
@@ -24,8 +24,8 @@ import {
24
24
  renderPlanFromDb,
25
25
  renderTaskPlanFromDb,
26
26
  detectStaleRenders,
27
- repairStaleRenders,
28
27
  } from '../markdown-renderer.ts';
28
+ import { repairStaleRenders } from '../state-reconciliation/drift/stale-render.ts';
29
29
  import {
30
30
  parseRoadmap,
31
31
  parsePlan,
@@ -29,20 +29,25 @@
29
29
 
30
30
  import { describe, test, beforeEach, afterEach } from "node:test";
31
31
  import assert from "node:assert/strict";
32
- import { mkdtempSync, rmSync, mkdirSync } from "node:fs";
32
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "node:fs";
33
33
  import { join } from "node:path";
34
34
  import { tmpdir } from "node:os";
35
35
 
36
- import { WorktreeLifecycle, type WorktreeLifecycleDeps } from "../worktree-lifecycle.ts";
36
+ import {
37
+ WorktreeLifecycle,
38
+ type WorktreeLifecycleDeps,
39
+ type WorktreeLifecycleTestOverrides,
40
+ } from "../worktree-lifecycle.ts";
37
41
  import { WorktreeStateProjection } from "../worktree-state-projection.ts";
38
42
  import { MergeConflictError } from "../git-service.ts";
39
43
  import type { AutoSession } from "../auto/session.ts";
40
44
 
41
- // Test-local: LegacyTestDeps had three fields Lifecycle does not need
42
- // (shouldUseWorktreeIsolation, syncWorktreeStateBack, captureIntegrationBranch).
43
- // Permit them in test fixtures so existing override patterns keep working —
44
- // Lifecycle ignores the extras via structural typing.
45
- type LegacyTestDeps = WorktreeLifecycleDeps & {
45
+ // Test-local: extras the WorktreeResolver-era fixture passed but Lifecycle
46
+ // does not need (shouldUseWorktreeIsolation, syncWorktreeStateBack,
47
+ // captureIntegrationBranch). Lifecycle ignores them via structural typing.
48
+ // The C1-C4-inlined primitive overrides come from
49
+ // `WorktreeLifecycleTestOverrides`, the test seam exported by the Module.
50
+ type LegacyTestDeps = WorktreeLifecycleDeps & WorktreeLifecycleTestOverrides & {
46
51
  shouldUseWorktreeIsolation?: () => boolean;
47
52
  syncWorktreeStateBack?: (
48
53
  mainBasePath: string,
@@ -50,6 +55,7 @@ type LegacyTestDeps = WorktreeLifecycleDeps & {
50
55
  milestoneId: string,
51
56
  ) => { synced: string[] };
52
57
  captureIntegrationBranch?: (basePath: string, mid: string | undefined) => void;
58
+ GitServiceImpl?: new (basePath: string, gitConfig: unknown) => unknown;
53
59
  };
54
60
 
55
61
  /**
@@ -96,7 +102,12 @@ function makeDeps(
96
102
  enterAutoWorktree: () => "",
97
103
  enterBranchModeForMilestone: () => undefined,
98
104
  getAutoWorktreePath: () => null,
99
- autoCommitCurrentBranch: () => undefined,
105
+ autoCommitCurrentBranch: (
106
+ _basePath: string,
107
+ _unitType: string,
108
+ _unitId: string,
109
+ _taskContext?: unknown,
110
+ ) => null,
100
111
  getCurrentBranch: () => "worktree/M001",
101
112
  checkoutBranch: () => undefined,
102
113
  autoWorktreeBranch: (mid: string) => `worktree/${mid}`,
@@ -109,6 +120,8 @@ function makeDeps(
109
120
  invalidateAllCaches: () => undefined,
110
121
  captureIntegrationBranch: () => undefined,
111
122
  worktreeProjection: new WorktreeStateProjection(),
123
+ // ADR-016 phase 2 / C4 (#5627): GitServiceImpl constructor → factory.
124
+ gitServiceFactory: () => ({}) as never,
112
125
  ...overrides,
113
126
  };
114
127
  }
@@ -128,14 +141,36 @@ function makeNotifyCtx(): {
128
141
 
129
142
  describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", () => {
130
143
  let baseDir: string;
144
+ const savedCwd = process.cwd();
131
145
 
132
146
  beforeEach(() => {
133
147
  baseDir = mkdtempSync(join(tmpdir(), "merge-conflict-stops-loop-"));
134
148
  // Fake out a milestone directory so mergeAndExit reaches mergeMilestoneToMain.
135
149
  mkdirSync(join(baseDir, ".gsd", "milestones", "M001"), { recursive: true });
150
+ // ADR-016 phase 2 / C1 (#5624): worktree-lifecycle.ts now calls
151
+ // node:fs.readFileSync directly (the dep was retired), so the roadmap
152
+ // file must exist on disk for the test to reach mergeMilestoneToMain.
153
+ writeFileSync(
154
+ join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
155
+ "# M001\n",
156
+ );
157
+ // ADR-016 phase 2 / C3 (#5626): `getIsolationMode` is also inlined.
158
+ // Without explicit isolation preferences the mode defaults to "none"
159
+ // and the merge short-circuits before the test's mocked
160
+ // `mergeMilestoneToMain` is reached. Write a preferences file so the
161
+ // standalone routes through worktree-mode merge.
162
+ writeFileSync(
163
+ join(baseDir, ".gsd", "preferences.md"),
164
+ "## Git\n- isolation: worktree\n",
165
+ );
136
166
  });
137
167
 
138
168
  afterEach(() => {
169
+ // ADR-016 phase 2 / C2 (#5625): the inlined `mergeMilestoneStandalone`
170
+ // chdirs into the project root before the merge body runs. Restore
171
+ // cwd before deleting `baseDir` so the next test's `process.cwd()`
172
+ // doesn't fail with ENOENT.
173
+ try { process.chdir(savedCwd); } catch { /* best-effort */ }
139
174
  try {
140
175
  rmSync(baseDir, { recursive: true, force: true });
141
176
  } catch {
@@ -147,7 +182,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
147
182
  const conflicted = ["src/feature.ts", "README.md"];
148
183
  const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
149
184
  const deps = makeDeps({
150
- resolveMilestoneFile: (_base, _mid, type) =>
185
+ resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
151
186
  type === "ROADMAP" ? roadmapPath : null,
152
187
  readFileSync: () => "# M001\n",
153
188
  mergeMilestoneToMain: () => {
@@ -178,7 +213,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
178
213
  const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
179
214
  class FakePermError extends Error {}
180
215
  const deps = makeDeps({
181
- resolveMilestoneFile: (_base, _mid, type) =>
216
+ resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
182
217
  type === "ROADMAP" ? roadmapPath : null,
183
218
  readFileSync: () => "# M001\n",
184
219
  mergeMilestoneToMain: () => {
@@ -204,7 +239,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
204
239
  test("successful merge does not throw", () => {
205
240
  const roadmapPath = join(baseDir, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
206
241
  const deps = makeDeps({
207
- resolveMilestoneFile: (_base, _mid, type) =>
242
+ resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
208
243
  type === "ROADMAP" ? roadmapPath : null,
209
244
  readFileSync: () => "# M001\n",
210
245
  mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
@@ -1,55 +1,53 @@
1
- // GSD Extension — Notification Overlay Tests
2
- // Tests for message wrapping in the notification panel.
3
- // Mirrors the private wrapText from notification-overlay.ts so its contract
4
- // can be exercised without exporting internals.
1
+ // Project/App: GSD-2
2
+ // File Purpose: Regression tests for notification overlay wrapping and width-safe rendering.
5
3
 
6
4
  import { describe, test } from "node:test";
7
5
  import assert from "node:assert/strict";
6
+ import { mkdtempSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
8
9
 
9
- import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
10
+ import { visibleWidth } from "@gsd/pi-tui";
11
+ import { appendNotification, initNotificationStore, _resetNotificationStore } from "../notification-store.ts";
12
+ import { GSDNotificationOverlay, notificationOverlayOptions } from "../notification-overlay.ts";
13
+ import { wrapVisibleText } from "../tui/render-kit.ts";
10
14
 
11
- // ── wrapText logic (mirrors the private function in notification-overlay.ts) ──
15
+ const fakeTheme = {
16
+ fg: (_color: string, text: string) => text,
17
+ bold: (text: string) => text,
18
+ };
12
19
 
13
- function wrapText(text: string, maxWidth: number): string[] {
14
- if (maxWidth <= 0) return [text];
15
- const lines = wrapTextWithAnsi(text, maxWidth);
16
- return lines.map((l) =>
17
- visibleWidth(l) > maxWidth ? truncateToWidth(l, maxWidth, "") : l,
18
- );
20
+ function assertLinesFit(lines: string[], width: number): void {
21
+ for (const line of lines) {
22
+ assert.ok(
23
+ visibleWidth(line) <= width,
24
+ `line exceeds maxWidth: visibleWidth=${visibleWidth(line)} max=${width}: "${line}"`,
25
+ );
26
+ }
19
27
  }
20
28
 
21
29
  describe("notification overlay — wrapText", () => {
22
30
  test("short text returns single line", () => {
23
- const result = wrapText("hello world", 80);
31
+ const result = wrapVisibleText("hello world", 80);
24
32
  assert.deepStrictEqual(result, ["hello world"]);
25
33
  });
26
34
 
27
35
  test("long text wraps at word boundaries without exceeding maxWidth", () => {
28
36
  const text = "This is a long notification message that should wrap across multiple lines";
29
- const result = wrapText(text, 40);
37
+ const result = wrapVisibleText(text, 40);
30
38
  assert.ok(result.length > 1, `expected multiple lines, got ${result.length}`);
31
- for (const line of result) {
32
- assert.ok(
33
- visibleWidth(line) <= 40,
34
- `line exceeds maxWidth: "${line}" (${visibleWidth(line)})`,
35
- );
36
- }
39
+ assertLinesFit(result, 40);
37
40
  });
38
41
 
39
42
  test("single word exceeding maxWidth is broken to fit column budget", () => {
40
- const result = wrapText("superlongwordthatexceedsmaxwidth", 10);
41
- for (const line of result) {
42
- assert.ok(
43
- visibleWidth(line) <= 10,
44
- `line exceeds maxWidth: "${line}" (${visibleWidth(line)})`,
45
- );
46
- }
43
+ const result = wrapVisibleText("superlongwordthatexceedsmaxwidth", 10);
44
+ assertLinesFit(result, 10);
47
45
  });
48
46
 
49
47
  test("preserves all words across wrapped lines", () => {
50
48
  const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot"];
51
49
  const text = words.join(" ");
52
- const result = wrapText(text, 15);
50
+ const result = wrapVisibleText(text, 15);
53
51
  const rejoined = result.join(" ");
54
52
  for (const w of words) {
55
53
  assert.ok(rejoined.includes(w), `missing word: ${w}`);
@@ -70,23 +68,62 @@ describe("notification overlay — wrapText", () => {
70
68
  "(expired — will auto-refresh) ✗ google — not configured " +
71
69
  "(aistudio.google.com/apikey) ✗ groq — not configured";
72
70
  const maxWidth = 118;
73
- const result = wrapText(msg, maxWidth);
74
- for (const line of result) {
75
- assert.ok(
76
- visibleWidth(line) <= maxWidth,
77
- `line exceeds column budget: visibleWidth=${visibleWidth(line)} max=${maxWidth}: "${line}"`,
78
- );
79
- }
71
+ const result = wrapVisibleText(msg, maxWidth);
72
+ assertLinesFit(result, maxWidth);
80
73
  });
81
74
 
82
75
  test("unbreakable long token (URL) is clamped to maxWidth", () => {
83
76
  const url = "https://example.com/" + "a".repeat(200);
84
- const result = wrapText(url, 40);
85
- for (const line of result) {
86
- assert.ok(
87
- visibleWidth(line) <= 40,
88
- `line exceeds maxWidth: visibleWidth=${visibleWidth(line)} line="${line}"`,
89
- );
77
+ const result = wrapVisibleText(url, 40);
78
+ assertLinesFit(result, 40);
79
+ });
80
+
81
+ test("real overlay render fits common terminal widths", (t) => {
82
+ const dir = mkdtempSync(join(tmpdir(), "gsd-notification-overlay-"));
83
+ t.after(() => {
84
+ _resetNotificationStore();
85
+ rmSync(dir, { recursive: true, force: true });
86
+ });
87
+
88
+ initNotificationStore(dir);
89
+ appendNotification("A long notification with " + "x".repeat(180), "warning");
90
+
91
+ const overlay = new GSDNotificationOverlay({ requestRender() {} }, fakeTheme as any, () => {});
92
+ t.after(() => overlay.dispose());
93
+
94
+ for (const width of [40, 80, 120]) {
95
+ assertLinesFit(overlay.render(width), width);
96
+ overlay.invalidate();
90
97
  }
91
98
  });
99
+
100
+ test("rendered height stays within the configured overlay max height", (t) => {
101
+ const dir = mkdtempSync(join(tmpdir(), "gsd-notification-overlay-height-"));
102
+ const originalRowsDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "rows");
103
+ Object.defineProperty(process.stdout, "rows", { value: 40, configurable: true });
104
+
105
+ t.after(() => {
106
+ if (originalRowsDescriptor) {
107
+ Object.defineProperty(process.stdout, "rows", originalRowsDescriptor);
108
+ } else {
109
+ delete (process.stdout as { rows?: number }).rows;
110
+ }
111
+ _resetNotificationStore();
112
+ rmSync(dir, { recursive: true, force: true });
113
+ });
114
+
115
+ initNotificationStore(dir);
116
+ for (let i = 0; i < 80; i++) {
117
+ appendNotification(`notification-${i + 1} with enough text to exercise clipping`, "warning");
118
+ }
119
+
120
+ const overlay = new GSDNotificationOverlay({ requestRender() {} }, fakeTheme as any, () => {});
121
+ t.after(() => overlay.dispose());
122
+
123
+ const rendered = overlay.render(100);
124
+ const maxHeight = Math.floor((40 * 52) / 100);
125
+ assert.ok(rendered.length <= maxHeight, `expected ${rendered.length} lines to fit maxHeight ${maxHeight}`);
126
+ assert.ok(rendered.at(-1)?.includes("╯"), "bottom border should remain visible");
127
+ assert.equal(notificationOverlayOptions().maxHeight, "52%");
128
+ });
92
129
  });