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,82 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Central retry policy for auto-mode verification redispatches.
3
+
4
+ import { createHash } from "node:crypto";
5
+
6
+ import type { PendingVerificationRetry } from "./session.js";
7
+
8
+ export const VERIFICATION_RETRY_BASE_DELAY_MS = 2_000;
9
+ export const VERIFICATION_RETRY_MAX_DELAY_MS = 30_000;
10
+ export const VERIFICATION_RETRY_JITTER_RATIO = 0.1;
11
+
12
+ export type VerificationRetryDecision =
13
+ | {
14
+ action: "delay";
15
+ key: string;
16
+ failureHash: string;
17
+ delayMs: number;
18
+ baseDelayMs: number;
19
+ }
20
+ | {
21
+ action: "pause";
22
+ reason: "missing-retry-context" | "duplicate-failure-context";
23
+ key?: string;
24
+ failureHash?: string;
25
+ };
26
+
27
+ export function verificationRetryKey(unitType: string, unitId: string): string {
28
+ return `${unitType}:${unitId}`;
29
+ }
30
+
31
+ export function hashVerificationFailureContext(failureContext: string): string {
32
+ const normalized = failureContext.replace(/\r\n/g, "\n").trim();
33
+ return createHash("sha256").update(normalized).digest("hex");
34
+ }
35
+
36
+ export function verificationRetryDelayMs(
37
+ attempt: number,
38
+ random: () => number = Math.random,
39
+ ): { delayMs: number; baseDelayMs: number } {
40
+ const safeAttempt = Math.max(1, Math.floor(attempt));
41
+ const baseDelayMs = Math.min(
42
+ VERIFICATION_RETRY_MAX_DELAY_MS,
43
+ VERIFICATION_RETRY_BASE_DELAY_MS * 2 ** (safeAttempt - 1),
44
+ );
45
+ const jitterSpanMs = Math.round(baseDelayMs * VERIFICATION_RETRY_JITTER_RATIO);
46
+ const jitterMs = Math.round((random() - 0.5) * 2 * jitterSpanMs);
47
+ const delayMs = Math.min(
48
+ VERIFICATION_RETRY_MAX_DELAY_MS,
49
+ Math.max(0, baseDelayMs + jitterMs),
50
+ );
51
+ return { delayMs, baseDelayMs };
52
+ }
53
+
54
+ export function decideVerificationRetry(input: {
55
+ unitType: string | undefined;
56
+ retryInfo: PendingVerificationRetry | null | undefined;
57
+ previousFailureHash: string | undefined;
58
+ random?: () => number;
59
+ }): VerificationRetryDecision {
60
+ const { retryInfo, unitType } = input;
61
+ if (!retryInfo || !unitType) {
62
+ return { action: "pause", reason: "missing-retry-context" };
63
+ }
64
+
65
+ const key = verificationRetryKey(unitType, retryInfo.unitId);
66
+ const failureHash = hashVerificationFailureContext(retryInfo.failureContext);
67
+ if (input.previousFailureHash === failureHash) {
68
+ return {
69
+ action: "pause",
70
+ reason: "duplicate-failure-context",
71
+ key,
72
+ failureHash,
73
+ };
74
+ }
75
+
76
+ return {
77
+ action: "delay",
78
+ key,
79
+ failureHash,
80
+ ...verificationRetryDelayMs(retryInfo.attempt, input.random),
81
+ };
82
+ }
@@ -11,14 +11,12 @@
11
11
  import type {
12
12
  ExtensionContext,
13
13
  ExtensionCommandContext,
14
- SessionMessageEntry,
15
14
  ReadonlyFooterDataProvider,
16
15
  Theme,
17
16
  } from "@gsd/pi-coding-agent";
18
17
  import type { GSDState } from "./types.js";
19
- import { getCurrentBranch } from "./worktree.js";
20
18
  import { getActiveHook } from "./post-unit-hooks.js";
21
- import { getLedger, getProjectTotals } from "./metrics.js";
19
+ import { getLedger } from "./metrics.js";
22
20
  import { getErrorMessage } from "./error-utils.js";
23
21
  import { nativeIsRepo } from "./native-git-bridge.js";
24
22
  import {
@@ -31,23 +29,19 @@ import { execFileSync } from "node:child_process";
31
29
  import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
32
30
  import { makeUI } from "../shared/tui.js";
33
31
  import { GLYPH, INDENT } from "../shared/mod.js";
32
+ import { padRightVisible, renderFrame, renderProgressBar, rightAlign, wrapVisibleText } from "./tui/render-kit.js";
34
33
  import { computeProgressScore } from "./progress-score.js";
35
- import { getActiveWorktreeName } from "./worktree-command.js";
36
34
  import {
37
35
  getGlobalGSDPreferencesPath,
38
36
  getProjectGSDPreferencesPath,
39
37
  parsePreferencesMarkdown,
40
38
  } from "./preferences.js";
41
- import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.js";
42
39
  import { parseUnitId } from "./unit-id.js";
43
40
  import {
44
- formatRtkSavingsLabel,
45
- getRtkSessionSavings,
46
41
  type RtkSessionSavings,
47
42
  } from "../shared/rtk-session-stats.js";
48
43
  import { logWarning } from "./workflow-logger.js";
49
44
  import { formattedShortcutPair } from "./shortcut-defs.js";
50
- import { homedir } from "node:os";
51
45
  import { readUnitRuntimeRecord, type AutoUnitRuntimeRecord } from "./unit-runtime.js";
52
46
 
53
47
  // ─── UAT Slice Extraction ─────────────────────────────────────────────────────
@@ -90,6 +84,42 @@ export interface AutoDashboardData {
90
84
  remoteSession?: { pid: number; startedAt: string; unitType: string; unitId: string };
91
85
  }
92
86
 
87
+ export interface CompletionDashboardSnapshot {
88
+ milestoneId?: string | null;
89
+ milestoneTitle?: string | null;
90
+ oneLiner?: string | null;
91
+ successCriteriaResults?: string | null;
92
+ definitionOfDoneResults?: string | null;
93
+ requirementOutcomes?: string | null;
94
+ deviations?: string | null;
95
+ followUps?: string | null;
96
+ keyDecisions?: string[];
97
+ keyFiles?: string[];
98
+ lessonsLearned?: string[];
99
+ reason: string;
100
+ startedAt: number;
101
+ totalCost: number;
102
+ totalTokens: number;
103
+ unitCount: number;
104
+ cacheHitRate?: number | null;
105
+ contextPercent?: number | null;
106
+ contextWindow?: number | null;
107
+ completedSlices?: number | null;
108
+ totalSlices?: number | null;
109
+ allMilestonesComplete?: boolean;
110
+ basePath?: string | null;
111
+ }
112
+
113
+ export interface AutoOutcomeSurfaceSnapshot {
114
+ status: "paused" | "stopped" | "blocked" | "failed" | "complete" | "waiting" | "step";
115
+ title: string;
116
+ detail?: string | null;
117
+ unitLabel?: string | null;
118
+ nextAction: string;
119
+ commands?: string[];
120
+ startedAt?: number;
121
+ }
122
+
93
123
  // ─── Unit Description Helpers ─────────────────────────────────────────────────
94
124
 
95
125
  export function unitVerb(unitType: string): string {
@@ -601,6 +631,7 @@ export function updateProgressWidget(
601
631
  tierBadge?: string,
602
632
  ): void {
603
633
  if (!ctx.hasUI) return;
634
+ ctx.ui.setWidget("gsd-outcome", undefined);
604
635
 
605
636
  // Welcome header is a startup-only banner — permanently suppress it once
606
637
  // auto-mode activates. The dashboard widget owns all status from here.
@@ -635,54 +666,12 @@ export function updateProgressWidget(
635
666
  updateSliceProgressCache(accessors.getBasePath(), mid.id, slice?.id);
636
667
  }
637
668
 
638
- // Cache git branch at widget creation time (not per render)
639
- let cachedBranch: string | null = null;
640
- try { cachedBranch = getCurrentBranch(accessors.getBasePath()); } catch (err) { /* not in git repo */
641
- logWarning("dashboard", `git branch detection failed: ${err instanceof Error ? err.message : String(err)}`);
642
- }
643
-
644
- // Cache short pwd (last 2 path segments only) + worktree/branch info
645
- let widgetPwd: string;
646
- {
647
- let fullPwd = process.cwd();
648
- const widgetHome = homedir();
649
- if (widgetHome && (fullPwd === widgetHome || fullPwd.startsWith(widgetHome + "/") || fullPwd.startsWith(widgetHome + "\\"))) {
650
- fullPwd = `~${fullPwd.slice(widgetHome.length)}`;
651
- }
652
- const parts = fullPwd.split("/");
653
- widgetPwd = parts.length > 2 ? parts.slice(-2).join("/") : fullPwd;
654
- }
655
- const worktreeName = getActiveWorktreeName();
656
- if (worktreeName && cachedBranch) {
657
- widgetPwd = `${widgetPwd} (\u2387 ${cachedBranch})`;
658
- } else if (cachedBranch) {
659
- widgetPwd = `${widgetPwd} (${cachedBranch})`;
660
- }
661
-
662
- // Pre-fetch last commit for display
663
- refreshLastCommit(accessors.getBasePath());
664
-
665
- // Cache the effective service tier at widget creation time (reads preferences)
666
- const effectiveServiceTier = getEffectiveServiceTier();
667
-
668
669
  ctx.ui.setWidget("gsd-progress", (tui, theme) => {
669
670
  let pulseBright = true;
670
671
  let cachedLines: string[] | undefined;
671
672
  let cachedWidth: number | undefined;
672
- let cachedRtkLabel: string | null | undefined;
673
673
  let cachedRuntimeRecord: AutoUnitRuntimeRecord | null = null;
674
674
 
675
- const refreshRtkLabel = (): void => {
676
- try {
677
- const sessionId = ctx.sessionManager.getSessionId();
678
- const savings = sessionId ? getRtkSessionSavings(accessors.getBasePath(), sessionId) : null;
679
- cachedRtkLabel = formatRtkSavingsLabel(savings);
680
- } catch (err) {
681
- logWarning("dashboard", `RTK savings lookup failed: ${err instanceof Error ? (err as Error).message : String(err)}`);
682
- cachedRtkLabel = null;
683
- }
684
- };
685
-
686
675
  const refreshRuntimeRecord = (): void => {
687
676
  try {
688
677
  cachedRuntimeRecord = readUnitRuntimeRecord(accessors.getBasePath(), unitType, unitId);
@@ -691,7 +680,6 @@ export function updateProgressWidget(
691
680
  }
692
681
  };
693
682
 
694
- refreshRtkLabel();
695
683
  refreshRuntimeRecord();
696
684
 
697
685
  const pulseTimer = setInterval(() => {
@@ -709,7 +697,6 @@ export function updateProgressWidget(
709
697
  if (mid) {
710
698
  updateSliceProgressCache(accessors.getBasePath(), mid.id, slice?.id);
711
699
  }
712
- refreshRtkLabel();
713
700
  refreshRuntimeRecord();
714
701
  cachedLines = undefined;
715
702
  } catch (err) { /* non-fatal */
@@ -783,46 +770,6 @@ export function updateProgressWidget(
783
770
  }
784
771
  }
785
772
 
786
- // ── Gather stats (needed by multiple modes) ─────────────────────
787
- const cmdCtx = accessors.getCmdCtx();
788
- let totalInput = 0;
789
- let totalCacheRead = 0;
790
- if (cmdCtx) {
791
- for (const entry of cmdCtx.sessionManager.getEntries()) {
792
- if (entry.type === "message") {
793
- const msgEntry = entry as SessionMessageEntry;
794
- if (msgEntry.message?.role === "assistant") {
795
- const u = (msgEntry.message as any).usage;
796
- if (u) {
797
- totalInput += u.input || 0;
798
- totalCacheRead += u.cacheRead || 0;
799
- }
800
- }
801
- }
802
- }
803
- }
804
- const mLedger = getLedger();
805
- const autoTotals = mLedger ? getProjectTotals(mLedger.units) : null;
806
- const cumulativeCost = autoTotals?.cost ?? 0;
807
- const cxUsage = cmdCtx?.getContextUsage?.();
808
- const cxWindow = cxUsage?.contextWindow ?? cmdCtx?.model?.contextWindow ?? 0;
809
- const cxPctVal = cxUsage?.percent ?? 0;
810
- const cxPct = cxUsage?.percent !== null ? cxPctVal.toFixed(1) : "?";
811
-
812
- // Model display — prefer dispatched model ID (set after selectAndApplyModel
813
- // + hook overrides) over cmdCtx?.model which can be stale (#2899).
814
- const dispatchedModelId = accessors.getCurrentDispatchedModelId();
815
- const modelId = dispatchedModelId
816
- ? dispatchedModelId.split("/").slice(1).join("/") || dispatchedModelId
817
- : (cmdCtx?.model?.id ?? "");
818
- const modelProvider = dispatchedModelId
819
- ? dispatchedModelId.split("/")[0] || ""
820
- : (cmdCtx?.model?.provider ?? "");
821
- const tierIcon = resolveServiceTierIcon(effectiveServiceTier, modelId);
822
- const modelDisplay = (modelProvider && modelId
823
- ? `${modelProvider}/${modelId}`
824
- : modelId) + (tierIcon ? ` ${tierIcon}` : "");
825
-
826
773
  // ── Mode: off — return empty ──────────────────────────────────
827
774
  if (widgetMode === "off") {
828
775
  cachedLines = [];
@@ -838,7 +785,7 @@ export function updateProgressWidget(
838
785
  return lines;
839
786
  }
840
787
 
841
- // ── Mode: small — header + progress bar + compact stats ───────
788
+ // ── Mode: small — header + active work progress ───────────────
842
789
  if (widgetMode === "small") {
843
790
  lines.push("");
844
791
 
@@ -852,10 +799,7 @@ export function updateProgressWidget(
852
799
  if (shouldRenderRoadmapProgress(roadmapSlices)) {
853
800
  const { done, total, activeSliceTasks } = roadmapSlices;
854
801
  const barWidth = Math.max(6, Math.min(18, Math.floor(width * 0.25)));
855
- const pct = total > 0 ? done / total : 0;
856
- const filled = Math.round(pct * barWidth);
857
- const bar = theme.fg("success", "━".repeat(filled))
858
- + theme.fg("dim", "─".repeat(barWidth - filled));
802
+ const bar = renderProgressBar(theme, done, total, barWidth);
859
803
  let meta = `${theme.fg("text", `${done}`)}${theme.fg("dim", `/${total} slices`)}`;
860
804
  if (activeSliceTasks && activeSliceTasks.total > 0) {
861
805
  const tn = Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
@@ -864,17 +808,6 @@ export function updateProgressWidget(
864
808
  lines.push(`${pad}${bar} ${meta}`);
865
809
  }
866
810
 
867
- // Compact stats: cost + context only
868
- const smallStats: string[] = [];
869
- if (cumulativeCost) smallStats.push(theme.fg("warning", `$${cumulativeCost.toFixed(2)}`));
870
- const cxDisplay = `${cxPct}%ctx`;
871
- if (cxPctVal > 90) smallStats.push(theme.fg("error", cxDisplay));
872
- else if (cxPctVal > 70) smallStats.push(theme.fg("warning", cxDisplay));
873
- else smallStats.push(theme.fg("dim", cxDisplay));
874
- if (smallStats.length > 0) {
875
- lines.push(rightAlign("", smallStats.join(theme.fg("dim", " ")), width));
876
- }
877
-
878
811
  lines.push(...ui.bar());
879
812
  cachedLines = lines;
880
813
  cachedWidth = width;
@@ -884,11 +817,10 @@ export function updateProgressWidget(
884
817
  // ── Mode: full — complete two-column layout ───────────────────
885
818
  lines.push("");
886
819
 
887
- // Context section: milestone + slice + model
820
+ // Context section: milestone + slice. Footer owns model/cost/context.
888
821
  const hasContext = !!(mid || (slice && unitType !== "research-milestone" && unitType !== "plan-milestone"));
889
822
  if (mid) {
890
- const modelTag = modelDisplay ? theme.fg("muted", ` ${modelDisplay}`) : "";
891
- lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}${modelTag}`, width, "…"));
823
+ lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width, ""));
892
824
  }
893
825
  if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
894
826
  lines.push(truncateToWidth(
@@ -920,10 +852,7 @@ export function updateProgressWidget(
920
852
  if (shouldRenderRoadmapProgress(roadmapSlices)) {
921
853
  const { done, total, activeSliceTasks } = roadmapSlices;
922
854
  const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
923
- const pct = total > 0 ? done / total : 0;
924
- const filled = Math.round(pct * barWidth);
925
- const bar = theme.fg("success", "━".repeat(filled))
926
- + theme.fg("dim", "─".repeat(barWidth - filled));
855
+ const bar = renderProgressBar(theme, done, total, barWidth);
927
856
 
928
857
  let meta = `${theme.fg("text", `${done}`)}${theme.fg("dim", `/${total} slices`)}`;
929
858
  if (activeSliceTasks && activeSliceTasks.total > 0) {
@@ -984,7 +913,7 @@ export function updateProgressWidget(
984
913
  if (maxRows > 0) {
985
914
  lines.push("");
986
915
  for (let i = 0; i < maxRows; i++) {
987
- const left = padToWidth(truncateToWidth(leftLines[i] ?? "", leftColWidth, "…"), leftColWidth);
916
+ const left = padRightVisible(truncateToWidth(leftLines[i] ?? "", leftColWidth, "…"), leftColWidth);
988
917
  const right = rightLines[i] ?? "";
989
918
  lines.push(`${left}${right}`);
990
919
  }
@@ -996,53 +925,8 @@ export function updateProgressWidget(
996
925
  }
997
926
  }
998
927
 
999
- // ── Footer: simplified stats + pwd + last commit + hints ────────
928
+ // ── Auto controls. Footer owns cwd/branch/model/cost/context. ───
1000
929
  lines.push("");
1001
- {
1002
- const sp: string[] = [];
1003
- if (totalCacheRead + totalInput > 0) {
1004
- const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
1005
- const hitColor = hitRate >= 70 ? "success" : hitRate >= 40 ? "warning" : "error";
1006
- sp.push(theme.fg(hitColor, `${hitRate}%hit`));
1007
- }
1008
- if (cumulativeCost) sp.push(theme.fg("warning", `$${cumulativeCost.toFixed(2)}`));
1009
-
1010
- const CX_BAR_WIDTH = 8;
1011
- const cxBarFilled = Math.min(
1012
- CX_BAR_WIDTH,
1013
- Math.max(0, Math.round((cxPctVal / 100) * CX_BAR_WIDTH)),
1014
- );
1015
- const cxBarColor: "error" | "warning" | "success" =
1016
- cxPctVal > 90 ? "error" : cxPctVal > 70 ? "warning" : "success";
1017
- const cxBar =
1018
- theme.fg(cxBarColor, "━".repeat(cxBarFilled)) +
1019
- theme.fg("dim", "─".repeat(CX_BAR_WIDTH - cxBarFilled));
1020
- const cxPctText = `${cxPct}%/${formatWidgetTokens(cxWindow)}`;
1021
- const cxColorized =
1022
- cxPctVal > 90
1023
- ? theme.fg("error", cxPctText)
1024
- : cxPctVal > 70
1025
- ? theme.fg("warning", cxPctText)
1026
- : cxPctText;
1027
- sp.push(`${cxBar} ${cxColorized}`);
1028
-
1029
- const statsLine = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
1030
- .join(theme.fg("dim", " "));
1031
- if (statsLine) {
1032
- lines.push(rightAlign("", statsLine, width));
1033
- }
1034
- if (cachedRtkLabel) {
1035
- lines.push(rightAlign("", theme.fg("dim", cachedRtkLabel), width));
1036
- }
1037
- }
1038
- // Last commit info
1039
- const lastCommit = getLastCommit(accessors.getBasePath());
1040
- const maxCommitLen = 65;
1041
- const commitMsg = lastCommit
1042
- ? lastCommit.message.length > maxCommitLen
1043
- ? lastCommit.message.slice(0, maxCommitLen - 1) + "…"
1044
- : lastCommit.message
1045
- : "";
1046
930
  // Step-mode guidance — shown above keyboard hints when auto is paused
1047
931
  if (accessors.isStepMode()) {
1048
932
  lines.push(`${pad}${theme.fg("accent", "→")} ${theme.fg("dim", "Ctrl+N to advance to next step · /gsd status for overview")}`);
@@ -1054,15 +938,7 @@ export function updateProgressWidget(
1054
938
  hintParts.push(`${formattedShortcutPair("dashboard")} dashboard`);
1055
939
  hintParts.push(`${formattedShortcutPair("parallel")} parallel`);
1056
940
  const hintStr = theme.fg("dim", hintParts.join(" | "));
1057
- const commitStr = lastCommit
1058
- ? theme.fg("dim", `${lastCommit.timeAgo} ago: ${commitMsg}`)
1059
- : "";
1060
- const locationStr = theme.fg("dim", widgetPwd);
1061
- if (commitStr) {
1062
- lines.push(rightAlign(`${pad}${locationStr} · ${commitStr}`, hintStr, width));
1063
- } else {
1064
- lines.push(rightAlign(`${pad}${locationStr}`, hintStr, width));
1065
- }
941
+ lines.push(rightAlign("", hintStr, width));
1066
942
 
1067
943
  lines.push(...ui.bar());
1068
944
 
@@ -1082,19 +958,190 @@ export function updateProgressWidget(
1082
958
  });
1083
959
  }
1084
960
 
1085
- // ─── Right-align Helper ───────────────────────────────────────────────────────
961
+ export function setCompletionProgressWidget(
962
+ ctx: ExtensionContext,
963
+ snapshot: CompletionDashboardSnapshot,
964
+ ): void {
965
+ if (!ctx.hasUI) return;
966
+ ctx.ui.setWidget("gsd-outcome", undefined);
967
+
968
+ if (typeof ctx.ui?.setHeader === "function") {
969
+ ctx.ui.setHeader(() => ({
970
+ render(): string[] { return []; },
971
+ invalidate(): void {},
972
+ }));
973
+ }
974
+ if (typeof ctx.ui?.setStatus === "function") {
975
+ ctx.ui.setStatus("gsd-step", undefined);
976
+ }
977
+
978
+ ctx.ui.setWidget("gsd-progress", (_tui, theme) => ({
979
+ render(width: number): string[] {
980
+ const ui = makeUI(theme, width);
981
+ const pad = INDENT.base;
982
+ const lines: string[] = [];
983
+ const contentWidth = Math.max(20, width - visibleWidth(pad));
984
+ const add = (line = ""): void => {
985
+ lines.push(line ? truncateToWidth(`${pad}${line}`, width, "…") : "");
986
+ };
987
+ const addSection = (label: string, value: string | null | undefined, indent = ""): void => {
988
+ const clean = normalizeRollupText(value);
989
+ if (!clean) return;
990
+ add(`${indent}${theme.fg("accent", label)} ${theme.fg("text", truncateToWidth(clean, contentWidth - indent.length - label.length - 1, "…"))}`);
991
+ };
992
+ const addList = (label: string, values: string[] | undefined, limit: number, indent = ""): void => {
993
+ const clean = (values ?? []).map(normalizeRollupText).filter((v): v is string => !!v);
994
+ if (clean.length === 0) return;
995
+ const shown = clean.slice(0, limit);
996
+ const more = clean.length > shown.length ? ` (+${clean.length - shown.length} more)` : "";
997
+ add(`${indent}${theme.fg("accent", label)} ${theme.fg("text", truncateToWidth(shown.join("; ") + more, contentWidth - indent.length - label.length - 1, "…"))}`);
998
+ };
999
+
1000
+ lines.push(...ui.bar());
1001
+
1002
+ const elapsed = formatAutoElapsed(snapshot.startedAt);
1003
+ const heading = snapshot.allMilestonesComplete
1004
+ ? "All milestones complete"
1005
+ : snapshot.milestoneId
1006
+ ? `Milestone ${snapshot.milestoneId} roll-up`
1007
+ : "Milestone roll-up";
1008
+ lines.push(rightAlign(`${pad}${theme.fg("accent", theme.bold(heading))}`, elapsed ? theme.fg("dim", elapsed) : "", width));
1009
+
1010
+ if (snapshot.milestoneTitle) {
1011
+ add(theme.fg("text", snapshot.milestoneTitle));
1012
+ }
1013
+
1014
+ lines.push("");
1015
+ add(theme.fg("accent", "Outcome"));
1016
+ addSection("", snapshot.oneLiner, " ");
1017
+
1018
+ const changed = [
1019
+ ...(snapshot.successCriteriaResults ? [snapshot.successCriteriaResults] : []),
1020
+ ...(snapshot.requirementOutcomes ? [snapshot.requirementOutcomes] : []),
1021
+ ...(snapshot.keyDecisions ?? []),
1022
+ ].map(normalizeRollupText).filter((v): v is string => !!v).slice(0, 4);
1023
+ if (changed.length > 0) {
1024
+ lines.push("");
1025
+ add(theme.fg("accent", "What changed"));
1026
+ for (const item of changed) add(` - ${theme.fg("text", item)}`);
1027
+ }
1028
+
1029
+ const verification = [
1030
+ snapshot.definitionOfDoneResults,
1031
+ snapshot.deviations ? `Deviations: ${snapshot.deviations}` : null,
1032
+ snapshot.followUps ? `Follow-ups: ${snapshot.followUps}` : null,
1033
+ ].map(normalizeRollupText).filter((v): v is string => !!v);
1034
+ if (verification.length > 0 || (snapshot.keyFiles?.length ?? 0) > 0) {
1035
+ lines.push("");
1036
+ add(theme.fg("accent", "Verification"));
1037
+ for (const item of verification.slice(0, 3)) add(` - ${theme.fg("text", item)}`);
1038
+ addList("Files:", snapshot.keyFiles, 4, " ");
1039
+ }
1040
+
1041
+ if ((snapshot.lessonsLearned?.length ?? 0) > 0) {
1042
+ lines.push("");
1043
+ addList("Lessons:", snapshot.lessonsLearned, 2);
1044
+ }
1045
+
1046
+ const hasSliceTotals = typeof snapshot.completedSlices === "number" && typeof snapshot.totalSlices === "number" && snapshot.totalSlices > 0;
1047
+
1048
+ lines.push("");
1049
+ const stats: string[] = [];
1050
+ if (hasSliceTotals) stats.push(theme.fg("success", `${snapshot.completedSlices}/${snapshot.totalSlices} slices`));
1051
+ if (snapshot.unitCount > 0) stats.push(theme.fg("dim", `${snapshot.unitCount} units`));
1052
+ if (snapshot.totalTokens > 0) stats.push(theme.fg("dim", `${formatWidgetTokens(snapshot.totalTokens)} tokens`));
1053
+ if (snapshot.totalCost > 0) stats.push(theme.fg("warning", `$${snapshot.totalCost.toFixed(2)}`));
1054
+ if (typeof snapshot.cacheHitRate === "number") {
1055
+ const hitColor = snapshot.cacheHitRate >= 70 ? "success" : snapshot.cacheHitRate >= 40 ? "warning" : "error";
1056
+ stats.push(theme.fg(hitColor, `${Math.round(snapshot.cacheHitRate)}% cache hit`));
1057
+ }
1058
+ if (stats.length > 0) {
1059
+ add(`${theme.fg("accent", "Run totals")} ${stats.join(theme.fg("dim", " · "))}`);
1060
+ }
1061
+
1062
+ lines.push("");
1063
+ const nextAction = snapshot.allMilestonesComplete
1064
+ ? "Review the roll-up, then start a new milestone when ready."
1065
+ : "Review the roll-up, inspect status, or continue to the next milestone.";
1066
+ const commands = snapshot.allMilestonesComplete
1067
+ ? ["/gsd status for overview", "/gsd visualize to inspect", "/gsd notifications for history", "/gsd start for new work"]
1068
+ : ["/gsd status for overview", "/gsd visualize to inspect", "/gsd notifications for history", "/gsd auto for next milestone"];
1069
+ add(`${theme.fg("success", "Next")} ${theme.fg("text", nextAction)}`);
1070
+ add(theme.fg("dim", commands.join(" · ")));
1071
+
1072
+ const location = snapshot.basePath ? theme.fg("dim", snapshot.basePath) : "";
1073
+ const reason = theme.fg("dim", snapshot.reason);
1074
+ lines.push(rightAlign(`${pad}${truncateToWidth(location, Math.max(0, width - 32), "…")}`, reason, width));
1075
+ lines.push(...ui.bar());
1076
+
1077
+ return lines;
1078
+ },
1079
+ invalidate(): void {},
1080
+ dispose(): void {},
1081
+ }));
1082
+ }
1083
+
1084
+ export function setAutoOutcomeWidget(
1085
+ ctx: ExtensionContext,
1086
+ snapshot: AutoOutcomeSurfaceSnapshot,
1087
+ ): void {
1088
+ if (!ctx.hasUI) return;
1089
+
1090
+ ctx.ui.setWidget("gsd-outcome", (_tui, theme) => ({
1091
+ render(width: number): string[] {
1092
+ const color = snapshot.status === "failed" || snapshot.status === "blocked"
1093
+ ? "warning"
1094
+ : snapshot.status === "complete"
1095
+ ? "success"
1096
+ : "borderAccent";
1097
+ const icon = snapshot.status === "complete" ? "✓"
1098
+ : snapshot.status === "failed" ? "x"
1099
+ : snapshot.status === "blocked" ? "!"
1100
+ : snapshot.status === "paused" ? "||"
1101
+ : "●";
1102
+ const innerWidth = Math.max(8, width - 4);
1103
+ const maxLines = 7;
1104
+ const lines: string[] = [];
1105
+ const elapsed = snapshot.startedAt ? formatAutoElapsed(snapshot.startedAt) : "";
1106
+ const heading = `${theme.fg(color, icon)} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("text", snapshot.title)}`;
1107
+ lines.push(rightAlign(heading, elapsed ? theme.fg("dim", elapsed) : "", innerWidth));
1108
+ const commands = snapshot.commands?.filter(Boolean) ?? [];
1109
+ const commandLine = commands.length > 0 ? theme.fg("dim", commands.join(" · ")) : null;
1110
+
1111
+ const addWrapped = (text: string, prefix = ""): void => {
1112
+ const reserve = commandLine ? 1 : 0;
1113
+ const remaining = Math.max(0, maxLines - reserve - lines.length);
1114
+ if (remaining === 0) return;
1115
+ const available = Math.max(8, innerWidth - visibleWidth(prefix));
1116
+ for (const [idx, line] of wrapVisibleText(text, available).slice(0, remaining).entries()) {
1117
+ lines.push(`${idx === 0 ? prefix : " ".repeat(visibleWidth(prefix))}${line}`);
1118
+ }
1119
+ };
1120
+
1121
+ if (snapshot.detail) {
1122
+ addWrapped(snapshot.detail, `${theme.fg("dim", "Reason")} `);
1123
+ }
1124
+ if (snapshot.unitLabel) {
1125
+ addWrapped(snapshot.unitLabel, `${theme.fg("dim", "Last")} `);
1126
+ }
1127
+ addWrapped(snapshot.nextAction, `${theme.fg("success", "Next")} `);
1128
+
1129
+ if (commandLine && lines.length < maxLines) {
1130
+ lines.push(commandLine);
1131
+ }
1086
1132
 
1087
- /** Right-align helper: build a line with left content and right content. */
1088
- function rightAlign(left: string, right: string, width: number): string {
1089
- const leftVis = visibleWidth(left);
1090
- const rightVis = visibleWidth(right);
1091
- const gap = Math.max(1, width - leftVis - rightVis);
1092
- return truncateToWidth(left + " ".repeat(gap) + right, width, "…");
1133
+ return renderFrame(theme, lines, width, { borderColor: color, paddingX: 1 });
1134
+ },
1135
+ invalidate(): void {},
1136
+ dispose(): void {},
1137
+ }));
1093
1138
  }
1094
1139
 
1095
- /** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
1096
- function padToWidth(s: string, colWidth: number): string {
1097
- const vis = visibleWidth(s);
1098
- if (vis >= colWidth) return truncateToWidth(s, colWidth, "");
1099
- return s + " ".repeat(colWidth - vis);
1140
+ function normalizeRollupText(value: string | null | undefined): string | null {
1141
+ const clean = value
1142
+ ?.replace(/\s+/g, " ")
1143
+ .replace(/^[-*]\s+/, "")
1144
+ .trim();
1145
+ if (!clean || clean === "(none)" || clean === "None." || clean === "Not provided.") return null;
1146
+ return clean;
1100
1147
  }
@@ -1,3 +1,6 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Declarative auto-mode dispatch rules and dispatch resolver.
3
+
1
4
  /**
2
5
  * Auto-mode Dispatch Table — declarative phase → unit mapping.
3
6
  *
@@ -1418,8 +1421,19 @@ export const DISPATCH_RULES: DispatchRule[] = [
1418
1421
  },
1419
1422
  {
1420
1423
  name: "complete → stop",
1421
- match: async ({ state }) => {
1424
+ match: async ({ state, mid, midTitle, basePath }) => {
1422
1425
  if (state.phase !== "complete") return null;
1426
+ if (mid && isDbAvailable()) {
1427
+ const milestone = getMilestone(mid);
1428
+ if (milestone && !isClosedStatus(milestone.status)) {
1429
+ return {
1430
+ action: "dispatch",
1431
+ unitType: "complete-milestone",
1432
+ unitId: mid,
1433
+ prompt: await buildCompleteMilestonePrompt(mid, midTitle, basePath),
1434
+ };
1435
+ }
1436
+ }
1423
1437
  return {
1424
1438
  action: "stop",
1425
1439
  reason: "All milestones complete.",