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
@@ -0,0 +1,56 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Visual contract tests for the recommended indented assistant message rail design.
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import stripAnsi from "strip-ansi";
7
+ import type { AssistantMessage } from "@gsd/pi-ai";
8
+
9
+ import { initTheme } from "../../theme/theme.js";
10
+ import { AssistantMessageComponent } from "../assistant-message.js";
11
+ import { formatTimestamp } from "../timestamp.js";
12
+
13
+ initTheme("dark", false);
14
+
15
+ describe("AssistantMessageComponent recommended rail design", () => {
16
+ test("renders assistant content with a lightly indented left rail", () => {
17
+ const message = {
18
+ id: "m1",
19
+ role: "assistant",
20
+ provider: "test",
21
+ model: "gpt-test",
22
+ timestamp: 1,
23
+ content: [{ type: "text", text: "I will update the renderer and run verification." }],
24
+ } as unknown as AssistantMessage;
25
+
26
+ const component = new AssistantMessageComponent(message, true);
27
+ const raw = component.render(80);
28
+ const plain = raw.map((line) => stripAnsi(line));
29
+ const joined = plain.join("\n");
30
+
31
+ assert.ok(plain.some((line) => line.startsWith(" ┃ ")), `expected indented rail-prefixed lines:\n${joined}`);
32
+ assert.ok(raw.some((line) => line.includes("\x1b[48;")), `expected faint assistant block background:\n${raw.join("\n")}`);
33
+ assert.match(joined, /^\s*┃\s*$/m, "assistant block should include vertical padding rows");
34
+ assert.match(joined, /GSD/);
35
+ assert.match(joined, /gpt-test/);
36
+ assert.match(joined, /update the renderer/);
37
+ assert.doesNotMatch(joined, /^┃/m, "assistant rail should be slightly indented from the left edge");
38
+ assert.doesNotMatch(joined, /^╭/m, "assistant messages should not use rounded card borders");
39
+ });
40
+
41
+ test("renders metadata for a zero timestamp", () => {
42
+ const message = {
43
+ id: "m1",
44
+ role: "assistant",
45
+ provider: "test",
46
+ model: "gpt-test",
47
+ timestamp: 0,
48
+ content: [{ type: "text", text: "Finished." }],
49
+ } as unknown as AssistantMessage;
50
+
51
+ const component = new AssistantMessageComponent(message, true);
52
+ const joined = component.render(80).map((line) => stripAnsi(line)).join("\n");
53
+
54
+ assert.match(joined, new RegExp(formatTimestamp(0)));
55
+ });
56
+ });
@@ -1,4 +1,5 @@
1
- // GSD-2 Interactive Tool Execution Rendering Tests
1
+ // Project/App: GSD-2
2
+ // File Purpose: Tests for interactive terminal tool execution rendering.
2
3
  import { describe, test } from "node:test";
3
4
  import assert from "node:assert/strict";
4
5
  import stripAnsi from "strip-ansi";
@@ -60,6 +61,23 @@ describe("ToolExecutionComponent", () => {
60
61
  assert.match(rendered, /running · \d+(ms|s)/);
61
62
  });
62
63
 
64
+ test("does not duplicate running generic tool labels before args", () => {
65
+ const rendered = renderToolCollapsed(
66
+ "Agent",
67
+ {
68
+ description: "Scout habit tracker codebase",
69
+ subagent_type: "Explore",
70
+ prompt: "Read these files and give me a concise summary of each.",
71
+ },
72
+ );
73
+
74
+ const labelMatches = rendered.match(/Agent/g) ?? [];
75
+ assert.equal(labelMatches.length, 1, `expected only the card title to contain Agent:\n${rendered}`);
76
+ assert.doesNotMatch(rendered, /description="Scout habit tracker codebase"/);
77
+ assert.doesNotMatch(rendered, /subagent_type="Explore"/);
78
+ assert.match(rendered, /running · \d+(ms|s)/);
79
+ });
80
+
63
81
  test("renders framed header with failed status for failed tool result", () => {
64
82
  const rendered = renderTool(
65
83
  "mcp__demo__do_thing",
@@ -87,6 +105,19 @@ describe("ToolExecutionComponent", () => {
87
105
  assert.doesNotMatch(rendered, /ok=true/);
88
106
  });
89
107
 
108
+ test("does not duplicate generic tool labels in collapsed cards", () => {
109
+ const rendered = renderToolCollapsed(
110
+ "TodoWrite",
111
+ { todos: [{ content: "Ship it", status: "pending" }] },
112
+ { content: [{ type: "text", text: "TodoWrite" }], isError: false },
113
+ );
114
+
115
+ const labelMatches = rendered.match(/TodoWrite/g) ?? [];
116
+ assert.equal(labelMatches.length, 1, `expected only the card title to contain TodoWrite:\n${rendered}`);
117
+ assert.match(rendered, /output hidden/);
118
+ assert.match(rendered, /ctrl\+o expand/);
119
+ });
120
+
90
121
  test("exposes phase metadata for successful low-signal tool rows", () => {
91
122
  const component = new ToolExecutionComponent(
92
123
  "gsd_requirement_update",
@@ -132,8 +163,41 @@ describe("ToolExecutionComponent", () => {
132
163
  },
133
164
  );
134
165
 
135
- assert.match(rendered, /read .*src\/Inspector\.tsx:4-12/);
166
+ assert.match(rendered, /Read/);
167
+ assert.match(rendered, /src\/Inspector\.tsx:4-12/);
136
168
  assert.doesNotMatch(rendered, /source/);
169
+ assert.doesNotMatch(rendered, /output hidden\n\s*│\s*ctrl\+o expand/);
170
+ });
171
+
172
+ test("renders compact capitalized read rows from file_path args", () => {
173
+ const rendered = renderToolCollapsed(
174
+ "Read",
175
+ { file_path: "~/Github/gsd-2/src/resources/extensions/gsd/health-widget-core.ts" },
176
+ { content: [{ type: "text", text: "hidden body output" }], isError: false },
177
+ );
178
+
179
+ assert.match(rendered, /Read/);
180
+ assert.match(rendered, /health-widget-core\.ts/);
181
+ assert.doesNotMatch(rendered, /hidden body output/);
182
+ });
183
+
184
+ test("renders compact read rows from direct result details path", () => {
185
+ const rendered = renderToolCollapsed(
186
+ "read",
187
+ {},
188
+ {
189
+ content: [{ type: "text", text: "hidden body output" }],
190
+ isError: false,
191
+ details: {
192
+ path: "/tmp/project/src/resources/extensions/gsd/health-widget-core.ts",
193
+ range: { start: 1, end: 12 },
194
+ },
195
+ },
196
+ );
197
+
198
+ assert.match(rendered, /Read/);
199
+ assert.match(rendered, /health-widget-core\.ts:1-12/);
200
+ assert.doesNotMatch(rendered, /hidden body output/);
137
201
  });
138
202
 
139
203
  test("renders compact edit rows with target metadata", () => {
@@ -155,10 +219,21 @@ describe("ToolExecutionComponent", () => {
155
219
  },
156
220
  );
157
221
 
158
- assert.match(rendered, /edit .*src\/Inspector\.tsx:42/);
222
+ assert.match(rendered, /Edit/);
223
+ assert.match(rendered, /src\/Inspector\.tsx:42/);
159
224
  assert.doesNotMatch(rendered, /Updated src\/Inspector\.tsx/);
160
225
  });
161
226
 
227
+ test("renders running edit rows with title and target on the top line", () => {
228
+ const rendered = renderToolCollapsed("edit", { path: "src/Inspector.tsx" });
229
+
230
+ const labelMatches = rendered.match(/Edit/g) ?? [];
231
+ assert.equal(labelMatches.length, 1, `expected tool name only in the card title:\n${rendered}`);
232
+ assert.match(rendered, /src\/Inspector\.tsx/);
233
+ assert.match(rendered, /Edit src\/Inspector\.tsx/);
234
+ assert.match(rendered, /running · \d+(ms|s)/);
235
+ });
236
+
162
237
  test("renders compact write rows with target metadata", () => {
163
238
  const rendered = renderToolCollapsed(
164
239
  "write",
@@ -177,10 +252,36 @@ describe("ToolExecutionComponent", () => {
177
252
  },
178
253
  );
179
254
 
180
- assert.match(rendered, /write .*src\/output\.ts/);
255
+ assert.match(rendered, /Write/);
256
+ assert.match(rendered, /src\/output\.ts/);
181
257
  assert.doesNotMatch(rendered, /Successfully wrote/);
182
258
  });
183
259
 
260
+ test("omits default cwd placeholders for collapsed search tools", () => {
261
+ const rendered = renderToolCollapsed(
262
+ "Grep",
263
+ {},
264
+ { content: [{ type: "text", text: "hidden body output" }], isError: false },
265
+ );
266
+
267
+ assert.match(rendered, /Grep/);
268
+ assert.doesNotMatch(rendered, /^│\.\s+│/m, `expected no placeholder cwd body:\n${rendered}`);
269
+ assert.match(rendered, /output hidden/);
270
+ assert.doesNotMatch(rendered, /hidden body output/);
271
+ assert.doesNotMatch(rendered, /^│\s+output hidden/m, `expected compact footer text on the top row:\n${rendered}`);
272
+ });
273
+
274
+ test("keeps meaningful collapsed search targets", () => {
275
+ const rendered = renderToolCollapsed(
276
+ "Grep",
277
+ { pattern: "Project Initialized", path: "src/resources/extensions/gsd", glob: "*.ts" },
278
+ { content: [{ type: "text", text: "hidden body output" }], isError: false },
279
+ );
280
+
281
+ assert.match(rendered, /Project Initialized in src\/resources\/extensions\/gsd \(\*\.ts\)/);
282
+ assert.doesNotMatch(rendered, /hidden body output/);
283
+ });
284
+
184
285
  test("renders compact bash rows with command preview", () => {
185
286
  const rendered = renderToolCollapsed(
186
287
  "bash",
@@ -188,7 +289,8 @@ describe("ToolExecutionComponent", () => {
188
289
  { content: [{ type: "text", text: "ok" }], isError: false, details: { cwd: "/tmp/project" } },
189
290
  );
190
291
 
191
- assert.match(rendered, /bash npm run typecheck -- --watch false/);
292
+ assert.match(rendered, /\$ npm run typecheck -- --watch false/);
293
+ assert.doesNotMatch(rendered, /├/, "collapsed command cards should not include internal divider lines");
192
294
  assert.doesNotMatch(rendered, /\bok\b/);
193
295
  });
194
296
 
@@ -339,7 +441,7 @@ describe("ToolExecutionComponent", () => {
339
441
  assert.doesNotMatch(rendered, /gsd_requirement_update/);
340
442
  });
341
443
 
342
- test("formatCompactArgs truncates long string values inline instead of dumping JSON", () => {
444
+ test("collapsed generic running tools hide primitive args", () => {
343
445
  const longPath = "/Users/alice/.gsd/projects/4dce7b775013/worktrees/slice-S03-some-long-path-that-exceeds-limit";
344
446
  const rendered = renderToolCollapsed("gsd_slice_complete", {
345
447
  sliceId: "S03",
@@ -347,9 +449,11 @@ describe("ToolExecutionComponent", () => {
347
449
  worktree: longPath,
348
450
  });
349
451
 
350
- assert.match(rendered, /sliceId="S03"/);
351
- assert.match(rendered, /milestoneId="M001"/);
352
- assert.match(rendered, /worktree=".*…"/);
452
+ assert.match(rendered, /Slice Complete/);
453
+ assert.match(rendered, /running · \d+(ms|s)/);
454
+ assert.doesNotMatch(rendered, /sliceId="S03"/);
455
+ assert.doesNotMatch(rendered, /milestoneId="M001"/);
456
+ assert.doesNotMatch(rendered, /worktree=/);
353
457
  assert.doesNotMatch(rendered, /"sliceId":\s*"S03"/);
354
458
  });
355
459
 
@@ -0,0 +1,48 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Visual contract tests for left-edge user chat rails.
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { stripVTControlCharacters } from "node:util";
7
+ import type { AssistantMessage } from "@gsd/pi-ai";
8
+
9
+ import { initTheme } from "../../theme/theme.js";
10
+ import { AssistantMessageComponent } from "../assistant-message.js";
11
+ import { UserMessageComponent } from "../user-message.js";
12
+
13
+ initTheme("dark", false);
14
+
15
+ describe("UserMessageComponent chat rail design", () => {
16
+ test("renders user messages against the left edge", () => {
17
+ const component = new UserMessageComponent("Can we make the transcript feel like chat?", undefined, 1, "date-time-iso");
18
+ const raw = component.render(100);
19
+ const plain = raw.map((line) => stripVTControlCharacters(line));
20
+ const joined = plain.join("\n");
21
+
22
+ assert.ok(plain.some((line) => /^┃ You/.test(line)), `expected left-edge user rail header:\n${joined}`);
23
+ assert.ok(plain.some((line) => /^┃ Can we make the transcript feel like chat\?/.test(line)), `expected left-edge user rail body:\n${joined}`);
24
+ assert.ok(raw.some((line) => line.includes("\x1b[48;")), `expected faint user block background:\n${raw.join("\n")}`);
25
+ assert.match(joined, /^┃\s*$/m, "user block should include vertical padding rows");
26
+ assert.match(joined, /feel like chat/);
27
+ assert.doesNotMatch(joined, /[╭╮╰╯]/, "user rail should not use boxed bubble corners");
28
+ assert.doesNotMatch(joined, /^ ┃ You/m, "user rail should not be indented like GSD messages");
29
+ });
30
+
31
+ test("uses a different faint background than GSD messages", () => {
32
+ const user = new UserMessageComponent("Use a distinct user color.");
33
+ const assistant = new AssistantMessageComponent({
34
+ id: "m1",
35
+ role: "assistant",
36
+ provider: "test",
37
+ model: "gpt-test",
38
+ content: [{ type: "text", text: "GSD message." }],
39
+ } as unknown as AssistantMessage);
40
+
41
+ const userBg = user.render(100).join("\n").match(/\x1b\[48;[^m]+m/)?.[0];
42
+ const assistantBg = assistant.render(100).join("\n").match(/\x1b\[48;[^m]+m/)?.[0];
43
+
44
+ assert.ok(userBg, "expected user message background color");
45
+ assert.ok(assistantBg, "expected assistant message background color");
46
+ assert.notEqual(userBg, assistantBg, "user and GSD message backgrounds should be distinct");
47
+ });
48
+ });
@@ -1,4 +1,5 @@
1
- // GSD2 - Runtime tests for adaptive terminal layout rendering
1
+ // Project/App: GSD-2
2
+ // File Purpose: Runtime tests for adaptive command-center terminal layout rendering.
2
3
 
3
4
  import assert from "node:assert/strict";
4
5
  import { describe, it, before } from "node:test";
@@ -15,7 +16,7 @@ function render(component: AdaptiveLayoutComponent, width: number): string {
15
16
  }
16
17
 
17
18
  describe("AdaptiveLayoutComponent", () => {
18
- it("renders workflow command center and inspector on wide terminals", () => {
19
+ it("renders a rounded workflow command center on wide terminals", () => {
19
20
  const component = new AdaptiveLayoutComponent(() => ({
20
21
  override: "workflow",
21
22
  activeToolCount: 2,
@@ -26,8 +27,13 @@ describe("AdaptiveLayoutComponent", () => {
26
27
 
27
28
  const output = render(component, 132);
28
29
  assert.match(output, /GSD Command Center/);
29
- assert.match(output, /signals/);
30
+ assert.match(output, /workflow · ready/);
30
31
  assert.match(output, /2 running/);
32
+ assert.match(output, /watch tool output/);
33
+ assert.doesNotMatch(output, /signals/);
34
+ assert.doesNotMatch(output, /inspector/);
35
+ assert.doesNotMatch(output, /\bauto\b/i);
36
+ assert.match(output, /^╭─+╮/m);
31
37
  });
32
38
 
33
39
  it("falls back to a single compact row for narrow workflow terminals", () => {
@@ -41,6 +47,7 @@ describe("AdaptiveLayoutComponent", () => {
41
47
  const output = render(component, 68);
42
48
  assert.match(output, /GSD compact/);
43
49
  assert.doesNotMatch(output, /signals/);
50
+ assert.doesNotMatch(output, /\bauto\b/i);
44
51
  });
45
52
 
46
53
  it("renders blocking failure context in debug mode", () => {
@@ -1,9 +1,11 @@
1
- // GSD2 - Adaptive terminal mode dashboard for the interactive TUI
1
+ // Project/App: GSD-2
2
+ // File Purpose: Adaptive command-center dashboard for the interactive TUI.
2
3
 
3
- import { style, truncateToWidth, visibleWidth, type Component } from "@gsd/pi-tui";
4
+ import { style, truncateToWidth, type Component } from "@gsd/pi-tui";
4
5
  import type { TuiAdaptiveMode, TuiMode } from "../tui-mode.js";
5
6
  import { resolveTuiMode } from "../tui-mode.js";
6
7
  import { theme, type ThemeColor } from "../theme/theme.js";
8
+ import { alignRight, breakpoint, keyValue, roundedPanel } from "./tui-style-kit.js";
7
9
 
8
10
  export interface AdaptiveLayoutState {
9
11
  override: TuiAdaptiveMode;
@@ -41,34 +43,44 @@ export class AdaptiveLayoutComponent implements Component {
41
43
  }
42
44
 
43
45
  private renderWorkflow(width: number, state: AdaptiveLayoutState): string[] {
44
- if (width < 112) return this.renderCompact(width, "workflow", state);
46
+ if (width < 72) return this.renderCompact(width, "workflow", state);
45
47
 
46
- const leftWidth = Math.max(44, Math.floor(width * 0.56));
47
- const rightWidth = Math.max(32, width - leftWidth - 2);
48
48
  const phase = state.gsdPhase ?? "Ready";
49
- const left = this.frame(
50
- [
51
- this.metric("Active", phase, "modeWorkflow"),
52
- this.metric("Tools", state.activeToolCount > 0 ? `${state.activeToolCount} running` : "idle", "toolRunning"),
53
- this.metric("Mode", state.override === "auto" ? "auto workflow" : state.override, "modeWorkflow"),
54
- ],
55
- leftWidth,
56
- "GSD Command Center",
57
- "workflow",
58
- "modeWorkflow",
59
- );
60
- const right = this.frame(
61
- [
62
- `Session ${state.sessionName ?? "current"}`,
63
- `Path ${this.basename(state.cwd)}`,
64
- `Next ${state.activeToolCount > 0 ? "watch tool output" : "continue from prompt"}`,
65
- ],
66
- rightWidth,
67
- "signals",
68
- "inspector",
69
- "surfaceAccent",
70
- );
71
- return this.columns(left, right, leftWidth);
49
+ const tools = state.activeToolCount > 0 ? `${state.activeToolCount} running` : "idle";
50
+ const next = state.activeToolCount > 0 ? "watch tool output" : "continue from prompt";
51
+ const modeLabel = state.override === "auto" ? "workflow" : state.override;
52
+ const bp = breakpoint(width);
53
+
54
+ const rows = bp === "regular"
55
+ ? [
56
+ keyValue("Status", phase, "modeWorkflow"),
57
+ keyValue("Tools", tools, state.activeToolCount > 0 ? "toolRunning" : "toolMuted"),
58
+ keyValue("Session", state.sessionName ?? "current", "text"),
59
+ keyValue("Next", next, "surfaceAccent"),
60
+ ]
61
+ : [
62
+ alignRight(
63
+ keyValue("Status", phase, "modeWorkflow"),
64
+ keyValue("Tools", tools, state.activeToolCount > 0 ? "toolRunning" : "toolMuted"),
65
+ Math.max(1, width - 2),
66
+ ),
67
+ alignRight(
68
+ keyValue("Session", state.sessionName ?? "current", "text"),
69
+ keyValue("Path", this.basename(state.cwd), "text"),
70
+ Math.max(1, width - 2),
71
+ ),
72
+ alignRight(
73
+ keyValue("Next", next, "surfaceAccent"),
74
+ keyValue("Mode", modeLabel, "modeWorkflow"),
75
+ Math.max(1, width - 2),
76
+ ),
77
+ ];
78
+
79
+ return roundedPanel(rows, width, {
80
+ title: "GSD Command Center",
81
+ rightTitle: `${modeLabel} · ${state.lastError ? "blocked" : "ready"}`,
82
+ tone: state.lastError ? "error" : "default",
83
+ });
72
84
  }
73
85
 
74
86
  private renderValidation(width: number, state: AdaptiveLayoutState): string[] {
@@ -140,21 +152,10 @@ export class AdaptiveLayoutComponent implements Component {
140
152
  return `${theme.fg("surfaceMuted", `${label.padEnd(8)} `)}${theme.fg(color, value)}`;
141
153
  }
142
154
 
143
- private columns(left: string[], right: string[], leftWidth: number): string[] {
144
- const rows = Math.max(left.length, right.length);
145
- const output: string[] = [];
146
- for (let i = 0; i < rows; i++) {
147
- const leftLine = left[i] ?? "";
148
- const rightLine = right[i] ?? "";
149
- const gap = " ".repeat(Math.max(2, leftWidth - visibleWidth(leftLine) + 2));
150
- output.push(`${leftLine}${gap}${rightLine}`);
151
- }
152
- return output;
153
- }
154
-
155
155
  private basename(cwd: string): string {
156
- const trimmed = cwd.replace(/\/+$/, "");
157
- const slash = trimmed.lastIndexOf("/");
156
+ const trimmed = cwd.replace(/[\\/]+$/, "");
157
+ if (!trimmed) return cwd.includes("\\") ? "\\" : "/";
158
+ const slash = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
158
159
  return slash === -1 ? trimmed : trimmed.slice(slash + 1);
159
160
  }
160
161
  }
@@ -1,9 +1,11 @@
1
- // GSD2 TUI - Assistant message card renderer for interactive terminal sessions.
1
+ // Project/App: GSD-2
2
+ // File Purpose: Assistant message rail renderer for interactive terminal sessions.
2
3
  import type { AssistantMessage } from "@gsd/pi-ai";
3
4
  import { Container, Markdown, type MarkdownTheme, Spacer, Text } from "@gsd/pi-tui";
4
5
  import { getMarkdownTheme, theme } from "../theme/theme.js";
5
6
  import { type TimestampFormat } from "./timestamp.js";
6
- import { renderChatFrame } from "./chat-frame.js";
7
+ import { formatTimestamp } from "./timestamp.js";
8
+ import { renderAssistantRail } from "./transcript-design.js";
7
9
 
8
10
  export interface ContentRange {
9
11
  startIndex: number;
@@ -163,19 +165,17 @@ export class AssistantMessageComponent extends Container {
163
165
 
164
166
  override render(width: number): string[] {
165
167
  const frameWidth = Math.max(20, width);
166
- const contentWidth = Math.max(1, frameWidth - 4);
168
+ const contentWidth = Math.max(1, frameWidth - 2);
167
169
  const lines = super.render(contentWidth);
168
- const headerLabel = this.lastMessage?.model ? `GSD - ${this.lastMessage.model}` : "GSD";
169
- const framed = renderChatFrame(lines, frameWidth, {
170
- label: headerLabel,
171
- tone: "assistant",
172
- timestamp: this.lastMessage?.timestamp,
173
- timestampFormat: this.timestampFormat,
174
- showTimestamp: this.showMetadata,
175
- });
176
- if (framed.length === 0) {
177
- return framed;
170
+ const metaParts = [];
171
+ if (this.lastMessage?.model) metaParts.push(this.lastMessage.model);
172
+ if (this.showMetadata && this.lastMessage?.timestamp != null) {
173
+ metaParts.push(formatTimestamp(this.lastMessage.timestamp, this.timestampFormat));
178
174
  }
179
- return ["", ...framed];
175
+ const rendered = renderAssistantRail(lines, frameWidth, {
176
+ label: "GSD",
177
+ meta: metaParts.length > 0 ? `· ${metaParts.join(" · ")}` : undefined,
178
+ });
179
+ return rendered.length > 0 ? ["", ...rendered] : rendered;
180
180
  }
181
181
  }
@@ -1,6 +1,5 @@
1
- /**
2
- * Component for displaying bash command execution with streaming output.
3
- */
1
+ // Project/App: GSD-2
2
+ // File Purpose: Interactive terminal bash execution renderer with streaming output and recommended command cards.
4
3
 
5
4
  import { Container, Loader, Spacer, Text, type TUI } from "@gsd/pi-tui";
6
5
  import stripAnsi from "strip-ansi";
@@ -13,6 +12,7 @@ import {
13
12
  import { theme } from "../theme/theme.js";
14
13
  import { DynamicBorder } from "./dynamic-border.js";
15
14
  import { editorKey, keyHint } from "./keybinding-hints.js";
15
+ import { renderCommandCard, renderTranscriptCard, type StatusTone } from "./transcript-design.js";
16
16
  import { truncateToVisualLines } from "./visual-truncate.js";
17
17
 
18
18
  // Preview line limit when not expanded (matches tool execution behavior)
@@ -120,6 +120,67 @@ export class BashExecutionComponent extends Container {
120
120
  this.updateDisplay();
121
121
  }
122
122
 
123
+ override render(width: number): string[] {
124
+ const frameWidth = Math.max(20, width);
125
+ const elapsedStatus =
126
+ this.status === "running"
127
+ ? "running"
128
+ : this.status === "complete"
129
+ ? "success"
130
+ : this.status === "cancelled"
131
+ ? "cancelled"
132
+ : `failed${this.exitCode !== undefined ? ` · exit ${this.exitCode}` : ""}`;
133
+ const tone: StatusTone =
134
+ this.status === "running"
135
+ ? "running"
136
+ : this.status === "complete"
137
+ ? "success"
138
+ : this.status === "cancelled"
139
+ ? "warning"
140
+ : "error";
141
+
142
+ if (!this.expanded && this.status !== "error") {
143
+ return [
144
+ "",
145
+ ...renderCommandCard(this.command.replace(/\s+/g, " ").trim(), frameWidth, {
146
+ status: elapsedStatus,
147
+ tone,
148
+ }),
149
+ ];
150
+ }
151
+
152
+ const output = this.outputLines.join("\n");
153
+ const contextTruncation = truncateTail(output, {
154
+ maxLines: DEFAULT_MAX_LINES,
155
+ maxBytes: DEFAULT_MAX_BYTES,
156
+ });
157
+ const truncationResult = this.truncationResult ?? contextTruncation;
158
+ const fullOutputPath = this.fullOutputPath;
159
+ const availableLines = contextTruncation.content ? contextTruncation.content.split("\n") : [];
160
+ const preview = this.expanded ? availableLines : availableLines.slice(-PREVIEW_LINES);
161
+ const hidden = Math.max(0, availableLines.length - preview.length);
162
+ const truncationWarning =
163
+ (truncationResult.truncated || contextTruncation.truncated) && fullOutputPath
164
+ ? [theme.fg("warning", `Output truncated. Full output: ${fullOutputPath}`)]
165
+ : [];
166
+ const body = [
167
+ theme.fg("toolTitle", `$ ${this.command}`),
168
+ ...preview.map((line) => theme.fg("toolOutput", line)),
169
+ ...(hidden > 0 ? [theme.fg("muted", `... ${hidden} earlier lines`)] : []),
170
+ ...truncationWarning,
171
+ ];
172
+ return [
173
+ "",
174
+ ...renderTranscriptCard(body, frameWidth, {
175
+ title: "command",
176
+ right: elapsedStatus,
177
+ tone,
178
+ footerLeft: this.expanded ? "output expanded" : "output preview",
179
+ footerRight: this.expanded ? "ctrl+o collapse" : "ctrl+o expand",
180
+ }),
181
+ ];
182
+ }
183
+
123
184
  private updateDisplay(): void {
124
185
  // Apply truncation for LLM context limits (same limits as bash tool)
125
186
  const fullOutput = this.outputLines.join("\n");
@@ -1,5 +1,5 @@
1
1
  import * as Diff from "diff";
2
- import { theme } from "../theme/theme.js";
2
+ import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js";
3
3
 
4
4
  /**
5
5
  * Parse diff line to extract prefix, line number, and content.
@@ -66,19 +66,25 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
66
66
  }
67
67
 
68
68
  export interface RenderDiffOptions {
69
- /** File path (unused, kept for API compatibility) */
69
+ /** File path used to choose syntax highlighting for changed content. */
70
70
  filePath?: string;
71
71
  }
72
72
 
73
+ function syntaxLine(content: string, lang: string | undefined): string {
74
+ if (!lang) return content;
75
+ return highlightCode(content, lang)[0] ?? content;
76
+ }
77
+
73
78
  /**
74
79
  * Render a diff string with colored lines and intra-line change highlighting.
75
80
  * - Context lines: dim/gray
76
81
  * - Removed lines: red, with inverse on changed tokens
77
82
  * - Added lines: green, with inverse on changed tokens
78
83
  */
79
- export function renderDiff(diffText: string, _options: RenderDiffOptions = {}): string {
84
+ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): string {
80
85
  const lines = diffText.split("\n");
81
86
  const result: string[] = [];
87
+ const lang = options.filePath ? getLanguageFromPath(options.filePath) : undefined;
82
88
 
83
89
  let i = 0;
84
90
  while (i < lines.length) {
@@ -126,19 +132,19 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
126
132
  } else {
127
133
  // Show all removed lines first, then all added lines
128
134
  for (const removed of removedLines) {
129
- result.push(theme.fg("toolDiffRemoved", `-${removed.lineNum} ${replaceTabs(removed.content)}`));
135
+ result.push(`${theme.fg("toolDiffRemoved", `-${removed.lineNum} `)}${syntaxLine(replaceTabs(removed.content), lang)}`);
130
136
  }
131
137
  for (const added of addedLines) {
132
- result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${replaceTabs(added.content)}`));
138
+ result.push(`${theme.fg("toolDiffAdded", `+${added.lineNum} `)}${syntaxLine(replaceTabs(added.content), lang)}`);
133
139
  }
134
140
  }
135
141
  } else if (parsed.prefix === "+") {
136
142
  // Standalone added line
137
- result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`));
143
+ result.push(`${theme.fg("toolDiffAdded", `+${parsed.lineNum} `)}${syntaxLine(replaceTabs(parsed.content), lang)}`);
138
144
  i++;
139
145
  } else {
140
146
  // Context line
141
- result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
147
+ result.push(`${theme.fg("toolDiffContext", ` ${parsed.lineNum} `)}${syntaxLine(replaceTabs(parsed.content), lang)}`);
142
148
  i++;
143
149
  }
144
150
  }