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,5 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Registers GSD keyboard shortcuts for dashboard, notifications, and parallel overlays.
1
3
  import { existsSync } from "node:fs";
2
4
  import { join } from "node:path";
3
5
  import { Key } from "@gsd/pi-tui";
@@ -29,16 +31,10 @@ export function registerShortcuts(pi) {
29
31
  });
30
32
  };
31
33
  const openNotificationsOverlay = async (ctx) => {
32
- const { GSDNotificationOverlay } = await import("../notification-overlay.js");
34
+ const { GSDNotificationOverlay, notificationOverlayOptions } = await import("../notification-overlay.js");
33
35
  await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
34
36
  overlay: true,
35
- overlayOptions: {
36
- width: "80%",
37
- minWidth: 60,
38
- maxHeight: "88%",
39
- anchor: "center",
40
- backdrop: true,
41
- },
37
+ overlayOptions: notificationOverlayOptions(),
42
38
  });
43
39
  };
44
40
  const openParallelOverlay = async (ctx) => {
@@ -1,7 +1,7 @@
1
- // GSD Extension — /gsd notifications Command Handler
2
- // View, filter, and clear the persistent notification history.
1
+ // Project/App: GSD-2
2
+ // File Purpose: Handles /gsd notifications commands and opens the notification history overlay.
3
3
  import { readNotifications, clearNotifications, getUnreadCount, suppressPersistence, unsuppressPersistence, } from "../../notification-store.js";
4
- import { GSDNotificationOverlay } from "../../notification-overlay.js";
4
+ import { GSDNotificationOverlay, notificationOverlayOptions } from "../../notification-overlay.js";
5
5
  const MAX_INLINE_ENTRIES = 40;
6
6
  function severityIcon(severity) {
7
7
  switch (severity) {
@@ -79,13 +79,7 @@ export async function handleNotificationsCommand(args, ctx, pi) {
79
79
  try {
80
80
  const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
81
81
  overlay: true,
82
- overlayOptions: {
83
- width: "80%",
84
- minWidth: 60,
85
- maxHeight: "88%",
86
- anchor: "center",
87
- backdrop: true,
88
- },
82
+ overlayOptions: notificationOverlayOptions(),
89
83
  });
90
84
  if (result !== undefined) {
91
85
  return true;
@@ -2,6 +2,7 @@ import { getOrchestratorState, getWorkerStatuses, isParallelActive, pauseWorker,
2
2
  import { formatEligibilityReport } from "../../parallel-eligibility.js";
3
3
  import { formatMergeResults, mergeAllCompleted, mergeCompletedMilestone } from "../../parallel-merge.js";
4
4
  import { loadEffectiveGSDPreferences, resolveParallelConfig } from "../../preferences.js";
5
+ import { reconcileBeforeSpawn } from "../../state-reconciliation.js";
5
6
  import { projectRoot } from "../context.js";
6
7
  function emitParallelMessage(pi, content) {
7
8
  pi.sendMessage({ customType: "gsd-parallel", content, display: true });
@@ -26,6 +27,14 @@ export async function handleParallelCommand(trimmed, _ctx, pi) {
26
27
  emitParallelMessage(pi, `${report}\n\nNo milestones are eligible for parallel execution.`);
27
28
  return true;
28
29
  }
30
+ // ADR-017 #5707: reconcile before spawning so workers don't independently
31
+ // race on the same drift. Failures abort the spawn with an actionable
32
+ // user-visible message.
33
+ const gate = await reconcileBeforeSpawn(root);
34
+ if (!gate.ok) {
35
+ emitParallelMessage(pi, `${report}\n\nParallel orchestration aborted before spawn — ${gate.reason}`);
36
+ return true;
37
+ }
29
38
  const result = await startParallel(root, candidates.eligible.map((candidate) => candidate.milestoneId), loaded?.preferences);
30
39
  const lines = ["Parallel orchestration started.", `Workers: ${result.started.join(", ")}`];
31
40
  if (result.errors.length > 0) {
@@ -18,7 +18,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
18
18
  import { logWarning } from "./workflow-logger.js";
19
19
  import { detectWorktreeName, } from "./worktree.js";
20
20
  import { SLICE_BRANCH_RE, QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
21
- import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, nativeAddPaths, nativeResetSoft, nativeCommitSubject, _resetHasChangesCache, } from "./native-git-bridge.js";
21
+ import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, nativeAddPaths, nativeResetSoft, nativeCommitSubject, nativeIsIgnored, _resetHasChangesCache, } from "./native-git-bridge.js";
22
22
  import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
23
23
  import { getErrorMessage } from "./error-utils.js";
24
24
  import { isInfrastructureError } from "./auto/infra-errors.js";
@@ -570,6 +570,7 @@ export class GitServiceImpl {
570
570
  const normalized = keyFiles
571
571
  .map(file => normalizeRepoRelativePath(this.basePath, file))
572
572
  .filter((file) => file !== null)
573
+ .filter(file => !nativeIsIgnored(this.basePath, file))
573
574
  .filter(file => !isExcludedScopedPath(file, allExclusions));
574
575
  // Drop entries that don't exist on disk. The LLM occasionally lists files
575
576
  // it intended to write but didn't (or names them with wrong casing/path).
@@ -977,32 +977,16 @@ export function setSliceSketchFlag(milestoneId, sliceId, isSketch) {
977
977
  currentDb.prepare(`UPDATE slices SET is_sketch = :is_sketch WHERE milestone_id = :mid AND id = :sid`).run({ ":is_sketch": isSketch ? 1 : 0, ":mid": milestoneId, ":sid": sliceId });
978
978
  }
979
979
  /**
980
- * ADR-011 auto-heal: reconcile stale is_sketch=1 rows whose PLAN already exists.
981
- *
982
- * Callers pass a predicate that resolves whether a plan file exists for a slice.
983
- * The predicate MUST use the canonical path resolver (`resolveSliceFile`, etc.)
984
- * to keep path logic in one place — do not hand-roll the path inside the callback.
985
- *
986
- * Recovers from two scenarios:
987
- * 1. Crash between `gsd_plan_slice` write and the sketch flag flip.
988
- * 2. Flag-OFF downgrade path: when `progressive_planning` is off, the dispatch
989
- * rule routes sketch slices to plan-slice, which writes PLAN.md but leaves
990
- * `is_sketch=1` — the next state derivation auto-heals it to 0 here.
991
- *
992
- * Not aggressive in practice: PLAN.md is only written via the DB-backed
993
- * `gsd_plan_slice` tool (which also inserts tasks), so a "stale PLAN.md with
994
- * is_sketch=1" is extremely unlikely to indicate anything other than the two
995
- * recovery scenarios above.
980
+ * ADR-017 raw primitive: returns slice IDs in a milestone whose is_sketch flag
981
+ * is still 1. The stale-sketch-flag drift handler at
982
+ * `state-reconciliation/drift/sketch-flag.ts` composes this with PLAN.md
983
+ * existence checks to detect drift, then writes via `setSliceSketchFlag`.
996
984
  */
997
- export function autoHealSketchFlags(milestoneId, hasPlanFile) {
985
+ export function getSketchedSliceIds(milestoneId) {
998
986
  if (!currentDb)
999
- return;
987
+ return [];
1000
988
  const rows = currentDb.prepare(`SELECT id FROM slices WHERE milestone_id = :mid AND is_sketch = 1`).all({ ":mid": milestoneId });
1001
- for (const row of rows) {
1002
- if (hasPlanFile(row.id)) {
1003
- setSliceSketchFlag(milestoneId, row.id, false);
1004
- }
1005
- }
989
+ return rows.map((r) => r.id);
1006
990
  }
1007
991
  export function upsertSlicePlanning(milestoneId, sliceId, planning) {
1008
992
  if (!currentDb)
@@ -51,7 +51,7 @@ export function buildHealthLines(data, width) {
51
51
  return [" GSD No project loaded — run /gsd to start"];
52
52
  }
53
53
  if (data.projectState === "initialized") {
54
- return [" GSD Project initialized — run /gsd to continue setup"];
54
+ return [" GSD Project Initialized"];
55
55
  }
56
56
  const leftParts = [];
57
57
  const totalIssues = data.environmentErrorCount + data.environmentWarningCount + (data.providerIssue ? 1 : 0);
@@ -1,12 +1,5 @@
1
- /**
2
- * GSD Health Widget — always-on ambient health signal rendered belowEditor.
3
- *
4
- * Shows a compact 1-2 line summary: progress score, budget, provider key
5
- * status, and doctor/environment issue count. Refreshes every 60 seconds.
6
- * Quiet when everything is healthy; turns amber/red when issues arise.
7
- *
8
- * Widget key: "gsd-health", placement: "belowEditor"
9
- */
1
+ // Project/App: GSD-2
2
+ // File Purpose: Always-on ambient health signal rendered below the editor.
10
3
  import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.js";
11
4
  import { runEnvironmentChecks } from "./doctor-environment.js";
12
5
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -14,6 +7,7 @@ import { nativeIsRepo, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeComm
14
7
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
15
8
  import { projectRoot } from "./commands/context.js";
16
9
  import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
10
+ export const HEALTH_WIDGET_ACTIVE_HINTS = " /gsd auto to run · /gsd status for overview · /gsd visualize to inspect · /gsd notifications for history · /gsd help";
17
11
  // ── Data loader ────────────────────────────────────────────────────────────────
18
12
  function loadHealthWidgetData(basePath) {
19
13
  let budgetCeiling;
@@ -119,7 +113,7 @@ export function initHealthWidget(ctx) {
119
113
  if (!cachedLines || cachedWidth !== width) {
120
114
  cachedLines = buildHealthLines(data, width);
121
115
  if (data.projectState === "active") {
122
- cachedLines = [...cachedLines, _theme.fg("dim", " /gsd auto to run · /gsd status for overview · /gsd help")];
116
+ cachedLines = [...cachedLines, _theme.fg("dim", HEALTH_WIDGET_ACTIVE_HINTS)];
123
117
  }
124
118
  cachedWidth = width;
125
119
  }
@@ -734,101 +734,6 @@ export function detectStaleRenders(basePath) {
734
734
  }
735
735
  return stale;
736
736
  }
737
- // ─── Stale Repair ─────────────────────────────────────────────────────────
738
- /**
739
- * Repair all stale renders detected by `detectStaleRenders()`.
740
- *
741
- * For each stale entry, calls the appropriate render function:
742
- * - Roadmap checkbox mismatches → renderRoadmapCheckboxes()
743
- * - Plan checkbox mismatches → renderPlanCheckboxes()
744
- * - Missing task summaries → renderTaskSummary()
745
- * - Missing slice summaries/UATs → renderSliceSummary()
746
- *
747
- * Idempotent: calling twice with no DB changes produces zero repairs on the second call.
748
- *
749
- * @returns the number of files repaired
750
- */
751
- export async function repairStaleRenders(basePath) {
752
- const staleEntries = detectStaleRenders(basePath);
753
- if (staleEntries.length === 0)
754
- return 0;
755
- // Deduplicate: a single roadmap/plan file might appear multiple times
756
- // (once per mismatched checkbox). We only need to re-render it once.
757
- const repairedPaths = new Set();
758
- let repairCount = 0;
759
- for (const entry of staleEntries) {
760
- if (repairedPaths.has(entry.path))
761
- continue;
762
- // Normalize path separators for cross-platform regex matching
763
- const normPath = entry.path.replace(/\\/g, "/");
764
- try {
765
- // Determine repair action from the reason
766
- if (entry.reason.includes("in roadmap")) {
767
- // Roadmap checkbox mismatch — extract milestone ID from path
768
- const milestoneMatch = normPath.match(/milestones\/([^/]+)\//);
769
- if (milestoneMatch) {
770
- const ok = await renderRoadmapCheckboxes(basePath, milestoneMatch[1]);
771
- if (ok) {
772
- repairedPaths.add(entry.path);
773
- repairCount++;
774
- }
775
- }
776
- }
777
- else if (entry.reason.includes("in plan")) {
778
- // Plan checkbox mismatch — extract milestone + slice IDs from path
779
- const pathMatch = normPath.match(/milestones\/([^/]+)\/slices\/([^/]+)\//);
780
- if (pathMatch) {
781
- const ok = await renderPlanCheckboxes(basePath, pathMatch[1], pathMatch[2]);
782
- if (ok) {
783
- repairedPaths.add(entry.path);
784
- repairCount++;
785
- }
786
- }
787
- }
788
- else if (entry.reason.includes("SUMMARY.md missing") && entry.reason.match(/^T\d+/)) {
789
- // Missing task summary — extract IDs from path
790
- const pathMatch = normPath.match(/milestones\/([^/]+)\/slices\/([^/]+)\/tasks\//);
791
- const taskMatch = entry.reason.match(/^(T\d+)/);
792
- if (pathMatch && taskMatch) {
793
- const ok = await renderTaskSummary(basePath, pathMatch[1], pathMatch[2], taskMatch[1]);
794
- if (ok) {
795
- repairedPaths.add(entry.path);
796
- repairCount++;
797
- }
798
- }
799
- }
800
- else if (entry.reason.includes("SUMMARY.md missing") && entry.reason.match(/^S\d+/)) {
801
- // Missing slice summary — extract IDs from path
802
- const pathMatch = normPath.match(/milestones\/([^/]+)\/slices\/([^/]+)\//);
803
- if (pathMatch) {
804
- const ok = await renderSliceSummary(basePath, pathMatch[1], pathMatch[2]);
805
- if (ok) {
806
- repairedPaths.add(entry.path);
807
- repairCount++;
808
- }
809
- }
810
- }
811
- else if (entry.reason.includes("UAT.md missing")) {
812
- // Missing slice UAT — renderSliceSummary handles both SUMMARY + UAT
813
- const pathMatch = normPath.match(/milestones\/([^/]+)\/slices\/([^/]+)\//);
814
- if (pathMatch) {
815
- const ok = await renderSliceSummary(basePath, pathMatch[1], pathMatch[2]);
816
- if (ok) {
817
- repairedPaths.add(entry.path);
818
- repairCount++;
819
- }
820
- }
821
- }
822
- }
823
- catch (err) {
824
- logWarning("renderer", `repair failed for ${entry.path}: ${err.message}`);
825
- }
826
- }
827
- if (repairCount > 0) {
828
- process.stderr.write(`markdown-renderer: repaired ${repairCount} stale render(s)\n`);
829
- }
830
- return repairCount;
831
- }
832
737
  export async function renderReplanFromDb(basePath, milestoneId, sliceId, replanData) {
833
738
  const slicePath = resolveSlicePath(basePath, milestoneId, sliceId)
834
739
  ?? join(gsdRoot(basePath), "milestones", milestoneId, "slices", sliceId);
@@ -551,21 +551,21 @@ export function nativeAddAll(basePath) {
551
551
  export function nativeAddTracked(basePath) {
552
552
  gitFileExec(basePath, ["add", "-u"]);
553
553
  }
554
- function isDotGsdIgnored(basePath) {
555
- for (const path of [".gsd", ".gsd/"]) {
556
- try {
557
- execFileSync("git", ["check-ignore", "-q", path], {
558
- cwd: basePath,
559
- stdio: "pipe",
560
- env: GIT_NO_PROMPT_ENV,
561
- });
562
- return true;
563
- }
564
- catch {
565
- // exit 1 means this form is not ignored; try the next variant
566
- }
554
+ export function nativeIsIgnored(basePath, path) {
555
+ try {
556
+ execFileSync("git", ["check-ignore", "-q", "--", path], {
557
+ cwd: basePath,
558
+ stdio: "pipe",
559
+ env: GIT_NO_PROMPT_ENV,
560
+ });
561
+ return true;
562
+ }
563
+ catch {
564
+ return false;
567
565
  }
568
- return false;
566
+ }
567
+ function isDotGsdIgnored(basePath) {
568
+ return [".gsd", ".gsd/"].some(path => nativeIsIgnored(basePath, path));
569
569
  }
570
570
  /**
571
571
  * Determine whether the project opts out of GSD-managed `.gitignore` via
@@ -1,11 +1,25 @@
1
- // GSD Extension — Notification History Overlay
2
- // Scrollable panel showing all persisted notifications with severity filtering.
3
- // Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /gsd notifications.
4
- import { truncateToWidth, visibleWidth, wrapTextWithAnsi, matchesKey, Key } from "@gsd/pi-tui";
1
+ // Project/App: GSD-2
2
+ // File Purpose: Notification history overlay with severity filtering and width-safe TUI rendering.
3
+ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
5
4
  import { readNotifications, markAllRead, clearNotifications, onNotificationStoreChange, } from "./notification-store.js";
6
5
  import { formattedShortcutPair } from "./shortcut-defs.js";
7
- import { padRight, joinColumns } from "../shared/mod.js";
8
- const FILTER_CYCLE = ["all", "error", "warning", "info"];
6
+ import { padRightVisible, renderFrame, renderKeyHints, rightAlign, wrapVisibleText, } from "./tui/render-kit.js";
7
+ const FILTER_CYCLE = ["all", "error", "warning", "success", "info"];
8
+ const OVERLAY_WIDTH = "58%";
9
+ const OVERLAY_MIN_WIDTH = 68;
10
+ const OVERLAY_MAX_HEIGHT_PERCENT = 52;
11
+ const OVERLAY_MARGIN = { top: 2, right: 2, bottom: 6, left: 2 };
12
+ export function notificationOverlayOptions() {
13
+ return {
14
+ width: OVERLAY_WIDTH,
15
+ minWidth: OVERLAY_MIN_WIDTH,
16
+ maxHeight: `${OVERLAY_MAX_HEIGHT_PERCENT}%`,
17
+ anchor: "top-center",
18
+ row: "24%",
19
+ margin: OVERLAY_MARGIN,
20
+ backdrop: true,
21
+ };
22
+ }
9
23
  function severityIcon(severity) {
10
24
  switch (severity) {
11
25
  case "error": return "✗";
@@ -15,15 +29,6 @@ function severityIcon(severity) {
15
29
  default: return "●";
16
30
  }
17
31
  }
18
- /** Column-aware word wrap using pi-tui's native wrapper (handles unicode/ANSI). */
19
- function wrapText(text, maxWidth) {
20
- if (maxWidth <= 0)
21
- return [text];
22
- const lines = wrapTextWithAnsi(text, maxWidth);
23
- // Safety clamp: if any line still exceeds maxWidth (e.g. unbreakable long token),
24
- // truncate it with an ellipsis so it cannot bleed past the box border.
25
- return lines.map((l) => visibleWidth(l) > maxWidth ? truncateToWidth(l, maxWidth, "…") : l);
26
- }
27
32
  function formatTimestamp(ts) {
28
33
  try {
29
34
  const d = new Date(ts);
@@ -155,12 +160,15 @@ export class GSDNotificationOverlay {
155
160
  return this.cachedLines;
156
161
  }
157
162
  const content = this.buildContentLines(width);
158
- const maxVisibleRows = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24) - 2;
163
+ const terminalRows = process.stdout.rows || 32;
164
+ const availableRows = Math.max(1, terminalRows - OVERLAY_MARGIN.top - OVERLAY_MARGIN.bottom);
165
+ const overlayRows = Math.min(availableRows, Math.max(1, Math.floor((terminalRows * OVERLAY_MAX_HEIGHT_PERCENT) / 100)));
166
+ const maxVisibleRows = Math.max(5, overlayRows - 2);
159
167
  const visibleContentRows = Math.min(content.length, maxVisibleRows);
160
168
  const maxScroll = Math.max(0, content.length - visibleContentRows);
161
169
  this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
162
170
  const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
163
- const lines = this.wrapInBox(visibleContent, width);
171
+ const lines = renderFrame(this.theme, visibleContent, width);
164
172
  this.cachedWidth = width;
165
173
  this.cachedLines = lines;
166
174
  return lines;
@@ -192,30 +200,14 @@ export class GSDNotificationOverlay {
192
200
  this.tui.requestRender();
193
201
  }
194
202
  }
195
- wrapInBox(inner, width) {
196
- const th = this.theme;
197
- const border = (s) => th.fg("borderAccent", s);
198
- const innerWidth = width - 4;
199
- const lines = [];
200
- lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
201
- for (const line of inner) {
202
- const truncated = truncateToWidth(line, innerWidth);
203
- const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
204
- lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
205
- }
206
- lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
207
- return lines;
208
- }
209
203
  buildContentLines(width) {
210
204
  const th = this.theme;
211
- const shellWidth = width - 4;
212
- const contentWidth = Math.min(shellWidth, 128);
213
- const sidePad = Math.max(0, Math.floor((shellWidth - contentWidth) / 2));
214
- const leftMargin = " ".repeat(sidePad);
205
+ const shellWidth = Math.max(1, width - 4);
206
+ const contentWidth = shellWidth;
215
207
  const lines = [];
216
208
  const row = (content = "") => {
217
209
  const truncated = truncateToWidth(content, contentWidth);
218
- return leftMargin + padRight(truncated, contentWidth);
210
+ return padRightVisible(truncated, contentWidth);
219
211
  };
220
212
  const blank = () => row("");
221
213
  const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
@@ -223,13 +215,16 @@ export class GSDNotificationOverlay {
223
215
  const title = th.fg("accent", th.bold("Notifications"));
224
216
  const filterLabel = this.filter === "all"
225
217
  ? th.fg("dim", "all")
226
- : th.fg(this.filter === "error" ? "error" : this.filter === "warning" ? "warning" : "dim", this.filter);
218
+ : th.fg(this.filter === "error" ? "error"
219
+ : this.filter === "warning" ? "warning"
220
+ : this.filter === "success" ? "success"
221
+ : "dim", this.filter);
227
222
  const count = `${this.filteredEntries.length} entries`;
228
- lines.push(row(joinColumns(`${title} ${th.fg("dim", "filter:")} ${filterLabel}`, th.fg("dim", count), contentWidth)));
223
+ lines.push(row(rightAlign(`${title} ${th.fg("dim", "filter:")} ${filterLabel}`, th.fg("dim", count), contentWidth)));
229
224
  lines.push(hr());
230
225
  // Controls
231
226
  const closeShortcut = formattedShortcutPair("notifications");
232
- lines.push(row(th.fg("dim", `↑/↓ scroll f filter c clear Esc close (${closeShortcut})`)));
227
+ lines.push(row(renderKeyHints(th, ["↑/↓ scroll", "f filter", "c clear", `Esc/${closeShortcut} close`], contentWidth)));
233
228
  lines.push(blank());
234
229
  // Entries
235
230
  const filtered = this.filteredEntries;
@@ -254,7 +249,7 @@ export class GSDNotificationOverlay {
254
249
  const prefixWidth = visibleWidth(prefix);
255
250
  const msgMaxWidth = Math.max(10, contentWidth - prefixWidth);
256
251
  // Wrap long messages onto continuation lines indented to align with message start
257
- const msgLines = wrapText(entry.message, msgMaxWidth);
252
+ const msgLines = wrapVisibleText(entry.message, msgMaxWidth);
258
253
  const indent = " ".repeat(prefixWidth);
259
254
  for (let i = 0; i < msgLines.length; i++) {
260
255
  if (i === 0) {
@@ -7,9 +7,10 @@
7
7
  import { existsSync, readdirSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { spawnSync } from "node:child_process";
10
- import { loadFile } from "./files.js";
11
- import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
12
- import { mergeMilestoneToMain } from "./auto-worktree.js";
10
+ import { resolveGsdPathContract } from "./paths.js";
11
+ import { getAutoWorktreePath } from "./auto-worktree.js";
12
+ import { buildWorktreeLifecycleDeps } from "./auto.js";
13
+ import { mergeMilestoneStandalone, } from "./worktree-lifecycle.js";
13
14
  import { MergeConflictError } from "./git-service.js";
14
15
  import { removeSessionStatus } from "./session-status-io.js";
15
16
  import { getErrorMessage } from "./error-utils.js";
@@ -105,37 +106,42 @@ export function determineMergeOrder(workers, order = "sequential", basePath) {
105
106
  }
106
107
  /**
107
108
  * Attempt to merge a single milestone's worktree back to main.
108
- * Wraps mergeMilestoneToMain with error handling for parallel context.
109
+ *
110
+ * Routes through `WorktreeLifecycle.mergeMilestoneStandalone` so parallel
111
+ * callers get the same projection-finalize / roadmap-fallback / secondary-
112
+ * teardown invariants as the single-loop path. Closes the parallel-merge
113
+ * bypass that ADR-016 names (issue #5618).
109
114
  */
110
115
  export async function mergeCompletedMilestone(basePath, milestoneId) {
116
+ // Resolve the worktree path explicitly; parallel-merge has no AutoSession
117
+ // to read it from. Only use the worktree path when git actually knows
118
+ // about it (`getAutoWorktreePath` returns non-null). When the directory
119
+ // exists on disk but isn't a registered git worktree (e.g. a stale
120
+ // session-status marker dir), fall back to the project root and let the
121
+ // standalone's mode detection pick branch-mode or skipped — using the
122
+ // un-registered dir as `worktreeBasePath` would cause `getCurrentBranch`
123
+ // to fail with a "Worktree HEAD diverged" error.
124
+ const registeredWtPath = getAutoWorktreePath(basePath, milestoneId);
125
+ const worktreeBasePath = registeredWtPath ?? basePath;
126
+ let result;
111
127
  try {
112
- // Load the roadmap content (needed by mergeMilestoneToMain)
113
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
114
- if (!roadmapPath) {
115
- return {
116
- milestoneId,
117
- success: false,
118
- error: `No roadmap found for ${milestoneId}`,
119
- };
120
- }
121
- const roadmapContent = await loadFile(roadmapPath);
122
- if (!roadmapContent) {
123
- return {
124
- milestoneId,
125
- success: false,
126
- error: `Could not read roadmap for ${milestoneId}`,
127
- };
128
- }
129
- // Attempt the merge
130
- const result = mergeMilestoneToMain(basePath, milestoneId, roadmapContent);
131
- // Clean up parallel session status
132
- removeSessionStatus(basePath, milestoneId);
133
- return {
128
+ result = mergeMilestoneStandalone(buildWorktreeLifecycleDeps(), {
129
+ originalBasePath: basePath,
130
+ worktreeBasePath,
134
131
  milestoneId,
135
- success: true,
136
- commitMessage: result.commitMessage,
137
- pushed: result.pushed,
138
- };
132
+ // Parallel context never runs with degraded isolation — workers only
133
+ // exist when isolation succeeded. Pass `false` explicitly so the
134
+ // standalone's degraded-skip branch is not reached.
135
+ isolationDegraded: false,
136
+ notify: (msg, level) => {
137
+ // Surface user-visible messages from the standalone through the
138
+ // workflow logger so the parallel merge's progress is visible in
139
+ // the same channel as the rest of the parallel orchestration.
140
+ if (level === "error" || level === "warning") {
141
+ logWarning("parallel", `${milestoneId}: ${msg}`);
142
+ }
143
+ },
144
+ });
139
145
  }
140
146
  catch (err) {
141
147
  if (err instanceof MergeConflictError) {
@@ -152,6 +158,23 @@ export async function mergeCompletedMilestone(basePath, milestoneId) {
152
158
  error: getErrorMessage(err),
153
159
  };
154
160
  }
161
+ if (!result.merged) {
162
+ return {
163
+ milestoneId,
164
+ success: false,
165
+ error: result.mode === "skipped"
166
+ ? `Merge skipped for ${milestoneId} (mode=none or isolation degraded).`
167
+ : `No roadmap for ${milestoneId} — milestone branch preserved for manual merge.`,
168
+ };
169
+ }
170
+ // Clean up parallel session status — only on a real merge.
171
+ removeSessionStatus(basePath, milestoneId);
172
+ return {
173
+ milestoneId,
174
+ success: true,
175
+ commitMessage: result.commitMessage,
176
+ pushed: result.pushed,
177
+ };
155
178
  }
156
179
  /**
157
180
  * Merge all completed milestones in sequence.