gsd-pi 2.18.0 → 2.20.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 (289) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
  9. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  10. package/dist/resources/extensions/gsd/auto.ts +690 -39
  11. package/dist/resources/extensions/gsd/captures.ts +384 -0
  12. package/dist/resources/extensions/gsd/commands.ts +654 -36
  13. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  14. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  15. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  16. package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  17. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  18. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  19. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  20. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  22. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  23. package/dist/resources/extensions/gsd/export.ts +81 -2
  24. package/dist/resources/extensions/gsd/files.ts +39 -9
  25. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  26. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  27. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  28. package/dist/resources/extensions/gsd/history.ts +0 -1
  29. package/dist/resources/extensions/gsd/index.ts +277 -1
  30. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  31. package/dist/resources/extensions/gsd/metrics.ts +84 -0
  32. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  33. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  34. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  35. package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  36. package/dist/resources/extensions/gsd/preferences.ts +198 -150
  37. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  39. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  41. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  42. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  43. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  44. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  45. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  46. package/dist/resources/extensions/gsd/quick.ts +156 -0
  47. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  48. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  49. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  50. package/dist/resources/extensions/gsd/state.ts +30 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  52. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  53. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  54. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  55. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  56. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  57. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  58. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  59. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  60. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  62. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  63. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  64. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  65. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  66. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  67. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  68. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  69. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  70. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  71. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  72. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  73. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  74. package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  75. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  76. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  77. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  78. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  79. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  80. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  81. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  82. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  83. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  84. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  85. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  86. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  87. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  88. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  89. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  90. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  91. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  92. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  93. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  94. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  95. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  96. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  97. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  98. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  99. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  100. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  101. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  102. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  103. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  104. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  105. package/dist/resources/extensions/gsd/types.ts +29 -0
  106. package/dist/resources/extensions/gsd/undo.ts +0 -1
  107. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  108. package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
  109. package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  110. package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
  111. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  112. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  113. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  114. package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  115. package/dist/resources/extensions/remote-questions/format.ts +166 -14
  116. package/dist/resources/extensions/remote-questions/manager.ts +14 -4
  117. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  118. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  119. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  120. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  121. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  122. package/dist/resources/extensions/voice/index.ts +4 -3
  123. package/package.json +1 -1
  124. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  131. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  137. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  139. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  140. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  142. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  146. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  148. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  150. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  153. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  156. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  158. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  161. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  162. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  163. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  164. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  165. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  166. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  167. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  168. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  169. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  170. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  171. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  172. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  173. package/src/resources/extensions/google-search/index.ts +164 -47
  174. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  175. package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
  176. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  177. package/src/resources/extensions/gsd/auto.ts +690 -39
  178. package/src/resources/extensions/gsd/captures.ts +384 -0
  179. package/src/resources/extensions/gsd/commands.ts +654 -36
  180. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  181. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  182. package/src/resources/extensions/gsd/context-store.ts +195 -0
  183. package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
  184. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  185. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  186. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  187. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  188. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  189. package/src/resources/extensions/gsd/doctor.ts +283 -2
  190. package/src/resources/extensions/gsd/export.ts +81 -2
  191. package/src/resources/extensions/gsd/files.ts +39 -9
  192. package/src/resources/extensions/gsd/git-service.ts +6 -0
  193. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  194. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  195. package/src/resources/extensions/gsd/history.ts +0 -1
  196. package/src/resources/extensions/gsd/index.ts +277 -1
  197. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  198. package/src/resources/extensions/gsd/metrics.ts +84 -0
  199. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  200. package/src/resources/extensions/gsd/model-router.ts +256 -0
  201. package/src/resources/extensions/gsd/notifications.ts +0 -1
  202. package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
  203. package/src/resources/extensions/gsd/preferences.ts +198 -150
  204. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  206. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  207. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  208. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  209. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  210. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  211. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  212. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  213. package/src/resources/extensions/gsd/quick.ts +156 -0
  214. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  215. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  216. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  217. package/src/resources/extensions/gsd/state.ts +30 -0
  218. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  219. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  220. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  221. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  222. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  223. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  224. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  226. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  227. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  228. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  229. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  230. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  231. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  232. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  233. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  235. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  236. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  237. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  238. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  239. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  240. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  241. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  242. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  243. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  244. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  245. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  246. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  247. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  248. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  249. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  250. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  251. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  252. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  253. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
  254. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  255. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  256. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  257. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  258. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  259. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  260. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  261. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  262. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  263. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  264. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
  265. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  266. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
  267. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  268. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  269. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  270. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  271. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  272. package/src/resources/extensions/gsd/types.ts +29 -0
  273. package/src/resources/extensions/gsd/undo.ts +0 -1
  274. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  275. package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
  276. package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
  277. package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
  278. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  279. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  280. package/src/resources/extensions/remote-questions/config.ts +4 -2
  281. package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
  282. package/src/resources/extensions/remote-questions/format.ts +166 -14
  283. package/src/resources/extensions/remote-questions/manager.ts +14 -4
  284. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  285. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  286. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  287. package/src/resources/extensions/remote-questions/types.ts +2 -1
  288. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  289. package/src/resources/extensions/voice/index.ts +4 -3
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![npm version](https://img.shields.io/npm/v/gsd-pi?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/gsd-pi)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/gsd-pi?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/gsd-pi)
9
9
  [![GitHub stars](https://img.shields.io/github/stars/gsd-build/GSD-2?style=for-the-badge&logo=github&color=181717)](https://github.com/gsd-build/GSD-2)
10
+ [![Discord](https://img.shields.io/badge/Discord-Join%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/gsd)
10
11
  [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
11
12
 
12
13
  The original GSD went viral as a prompt framework for Claude Code. It worked, but it was fighting the tool — injecting prompts through slash commands, hoping the LLM would follow instructions, with no actual control over context windows, sessions, or execution.
@@ -231,6 +232,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
231
232
  | `/gsd` | Step mode — executes one unit at a time, pauses between each |
232
233
  | `/gsd next` | Explicit step mode (same as bare `/gsd`) |
233
234
  | `/gsd auto` | Autonomous mode — researches, plans, executes, commits, repeats |
235
+ | `/gsd quick` | Execute a quick task with GSD guarantees, skip planning overhead |
234
236
  | `/gsd stop` | Stop auto mode gracefully |
235
237
  | `/gsd steer` | Hard-steer plan documents during execution |
236
238
  | `/gsd discuss` | Discuss architecture and decisions (works alongside auto mode) |
@@ -238,7 +240,9 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
238
240
  | `/gsd queue` | Queue future milestones (safe during auto mode) |
239
241
  | `/gsd prefs` | Model selection, timeouts, budget ceiling |
240
242
  | `/gsd migrate` | Migrate a v1 `.planning` directory to `.gsd` format |
241
- | `/gsd doctor` | Validate `.gsd/` integrity, find and fix issues |
243
+ | `/gsd help` | Categorized command reference for all GSD subcommands |
244
+ | `/gsd mode` | Switch workflow mode (solo/team) with coordinated defaults |
245
+ | `/gsd doctor` | Runtime health checks with auto-fix for common issues |
242
246
  | `/worktree` (`/wt`) | Git worktree lifecycle — create, switch, merge, remove |
243
247
  | `/voice` | Toggle real-time speech-to-text (macOS, Linux) |
244
248
  | `/exit` | Graceful shutdown — saves session state before exiting |
package/dist/cli.js CHANGED
@@ -105,8 +105,10 @@ ensureManagedTools(join(agentDir, 'bin'));
105
105
  const authStorage = AuthStorage.create(authFilePath);
106
106
  loadStoredEnvKeys(authStorage);
107
107
  migratePiCredentials(authStorage);
108
+ const modelRegistry = new ModelRegistry(authStorage);
109
+ const settingsManager = SettingsManager.create(agentDir);
108
110
  // Run onboarding wizard on first launch (no LLM provider configured)
109
- if (!isPrintMode && shouldRunOnboarding(authStorage)) {
111
+ if (!isPrintMode && shouldRunOnboarding(authStorage, settingsManager.getDefaultProvider())) {
110
112
  await runOnboarding(authStorage);
111
113
  // Clean up stdin state left by @clack/prompts.
112
114
  // readline.emitKeypressEvents() adds a permanent data listener and
@@ -126,8 +128,6 @@ if (!isPrintMode) {
126
128
  if (!isPrintMode && process.stdout.columns && process.stdout.columns < 40) {
127
129
  process.stderr.write(chalk.yellow(`[gsd] Terminal width is ${process.stdout.columns} columns (minimum recommended: 40). Output may be unreadable.\n`));
128
130
  }
129
- const modelRegistry = new ModelRegistry(authStorage);
130
- const settingsManager = SettingsManager.create(agentDir);
131
131
  // --list-models: print available models and exit (no TTY needed)
132
132
  if (cliFlags.listModels !== undefined) {
133
133
  const models = modelRegistry.getAvailable();
@@ -19,9 +19,11 @@ import type { AuthStorage } from '@gsd/pi-coding-agent';
19
19
  *
20
20
  * Returns false (skip wizard) when:
21
21
  * - Any LLM provider is already available via auth.json, env vars, runtime overrides, or fallback auth
22
+ * - A default provider is already configured in settings (covers extension-based providers
23
+ * that may not require credentials in auth.json)
22
24
  * - Not a TTY (piped input, subagent, CI)
23
25
  */
24
- export declare function shouldRunOnboarding(authStorage: AuthStorage): boolean;
26
+ export declare function shouldRunOnboarding(authStorage: AuthStorage, settingsDefaultProvider?: string): boolean;
25
27
  /**
26
28
  * Run the unified onboarding wizard.
27
29
  *
@@ -113,11 +113,15 @@ function isCancelError(p, err) {
113
113
  *
114
114
  * Returns false (skip wizard) when:
115
115
  * - Any LLM provider is already available via auth.json, env vars, runtime overrides, or fallback auth
116
+ * - A default provider is already configured in settings (covers extension-based providers
117
+ * that may not require credentials in auth.json)
116
118
  * - Not a TTY (piped input, subagent, CI)
117
119
  */
118
- export function shouldRunOnboarding(authStorage) {
120
+ export function shouldRunOnboarding(authStorage, settingsDefaultProvider) {
119
121
  if (!process.stdin.isTTY)
120
122
  return false;
123
+ if (settingsDefaultProvider)
124
+ return false;
121
125
  // Check if any LLM provider has credentials
122
126
  const hasLlmAuth = LLM_PROVIDER_IDS.some(id => authStorage.hasAuth(id));
123
127
  return !hasLlmAuth;
@@ -564,12 +568,13 @@ async function runRemoteQuestionsStep(p, pc, authStorage) {
564
568
  // Check existing config
565
569
  const hasDiscord = authStorage.has('discord_bot') && !!authStorage.get('discord_bot')?.key;
566
570
  const hasSlack = authStorage.has('slack_bot') && !!authStorage.get('slack_bot')?.key;
567
- const existingChannel = hasDiscord ? 'Discord' : hasSlack ? 'Slack' : null;
571
+ const hasTelegram = authStorage.has('telegram_bot') && !!authStorage.get('telegram_bot')?.key;
572
+ const existingChannel = hasDiscord ? 'Discord' : hasSlack ? 'Slack' : hasTelegram ? 'Telegram' : null;
568
573
  const options = [];
569
574
  if (existingChannel) {
570
575
  options.push({ value: 'keep', label: `Keep current (${existingChannel})`, hint: 'already configured' });
571
576
  }
572
- options.push({ value: 'discord', label: 'Discord', hint: 'receive questions in a Discord channel' }, { value: 'slack', label: 'Slack', hint: 'receive questions in a Slack channel' }, { value: 'skip', label: 'Skip for now', hint: 'use /gsd remote inside GSD later' });
577
+ options.push({ value: 'discord', label: 'Discord', hint: 'receive questions in a Discord channel' }, { value: 'slack', label: 'Slack', hint: 'receive questions in a Slack channel' }, { value: 'telegram', label: 'Telegram', hint: 'receive questions via Telegram bot' }, { value: 'skip', label: 'Skip for now', hint: 'use /gsd remote inside GSD later' });
573
578
  const choice = await p.select({
574
579
  message: 'Set up remote questions? (get notified when GSD needs input)',
575
580
  options,
@@ -638,6 +643,74 @@ async function runRemoteQuestionsStep(p, pc, authStorage) {
638
643
  p.log.success(`Slack channel: ${pc.green(channelId.trim())}`);
639
644
  return 'Slack';
640
645
  }
646
+ if (choice === 'telegram') {
647
+ const token = await p.password({
648
+ message: 'Paste your Telegram bot token (from @BotFather):',
649
+ mask: '●',
650
+ });
651
+ if (p.isCancel(token) || !token?.trim())
652
+ return null;
653
+ const trimmed = token.trim();
654
+ if (!/^\d+:[A-Za-z0-9_-]+$/.test(trimmed)) {
655
+ p.log.warn('Invalid token format — Telegram bot tokens look like 123456789:ABCdefGHI...');
656
+ return null;
657
+ }
658
+ // Validate
659
+ const s = p.spinner();
660
+ s.start('Validating Telegram bot token...');
661
+ try {
662
+ const res = await fetch(`https://api.telegram.org/bot${trimmed}/getMe`, {
663
+ signal: AbortSignal.timeout(15_000),
664
+ });
665
+ const data = await res.json();
666
+ if (!data?.ok || !data?.result?.id) {
667
+ s.stop('Telegram token validation failed');
668
+ return null;
669
+ }
670
+ s.stop(`Telegram bot: ${pc.green(data.result.first_name ?? data.result.username ?? 'bot')}`);
671
+ }
672
+ catch {
673
+ s.stop('Could not reach Telegram API');
674
+ return null;
675
+ }
676
+ authStorage.set('telegram_bot', { type: 'api_key', key: trimmed });
677
+ process.env.TELEGRAM_BOT_TOKEN = trimmed;
678
+ const chatId = await p.text({
679
+ message: 'Paste the Telegram chat ID (e.g. -1001234567890):',
680
+ validate: (val) => {
681
+ if (!val || !/^-?\d{5,20}$/.test(val.trim()))
682
+ return 'Expected a numeric chat ID (can be negative for groups)';
683
+ },
684
+ });
685
+ if (p.isCancel(chatId) || !chatId)
686
+ return null;
687
+ const trimmedChatId = chatId.trim();
688
+ // Test send
689
+ const ts = p.spinner();
690
+ ts.start('Testing message delivery...');
691
+ try {
692
+ const res = await fetch(`https://api.telegram.org/bot${trimmed}/sendMessage`, {
693
+ method: 'POST',
694
+ headers: { 'Content-Type': 'application/json' },
695
+ body: JSON.stringify({ chat_id: trimmedChatId, text: 'GSD remote questions connected.' }),
696
+ signal: AbortSignal.timeout(15_000),
697
+ });
698
+ const data = await res.json();
699
+ if (!data?.ok) {
700
+ ts.stop(`Could not send to chat: ${data?.description ?? 'unknown error'}`);
701
+ return null;
702
+ }
703
+ ts.stop('Test message sent');
704
+ }
705
+ catch {
706
+ ts.stop('Could not reach Telegram API');
707
+ return null;
708
+ }
709
+ const { saveRemoteQuestionsConfig } = await import('./remote-questions-config.js');
710
+ saveRemoteQuestionsConfig('telegram', trimmedChatId);
711
+ p.log.success(`Telegram chat: ${pc.green(trimmedChatId)}`);
712
+ return 'Telegram';
713
+ }
641
714
  return null;
642
715
  }
643
716
  async function runDiscordChannelStep(p, pc, token) {
@@ -756,6 +829,7 @@ export function loadStoredEnvKeys(authStorage) {
756
829
  ['jina', 'JINA_API_KEY'],
757
830
  ['slack_bot', 'SLACK_BOT_TOKEN'],
758
831
  ['discord_bot', 'DISCORD_BOT_TOKEN'],
832
+ ['telegram_bot', 'TELEGRAM_BOT_TOKEN'],
759
833
  ['groq', 'GROQ_API_KEY'],
760
834
  ['ollama-cloud', 'OLLAMA_API_KEY'],
761
835
  ['custom-openai', 'CUSTOM_OPENAI_API_KEY'],
@@ -7,4 +7,4 @@
7
7
  * but onboarding.ts is compiled by tsc — dynamic imports from compiled
8
8
  * JS to uncompiled .ts fail at runtime (#592).
9
9
  */
10
- export declare function saveRemoteQuestionsConfig(channel: "slack" | "discord", channelId: string): void;
10
+ export declare function saveRemoteQuestionsConfig(channel: "slack" | "discord" | "telegram", channelId: string): void;
@@ -68,6 +68,101 @@ async function getClient(): Promise<GoogleGenAIClient> {
68
68
  return client;
69
69
  }
70
70
 
71
+ /**
72
+ * Perform a search using OAuth credentials via the Cloud Code Assist API.
73
+ * This is used as a fallback when GEMINI_API_KEY is not set.
74
+ */
75
+ async function searchWithOAuth(
76
+ query: string,
77
+ accessToken: string,
78
+ projectId: string,
79
+ signal?: AbortSignal,
80
+ ): Promise<SearchResult> {
81
+ const model = process.env.GEMINI_SEARCH_MODEL || "gemini-2.5-flash";
82
+ const url = `https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent`;
83
+
84
+ const GEMINI_CLI_HEADERS = {
85
+ ideType: "IDE_UNSPECIFIED",
86
+ platform: "PLATFORM_UNSPECIFIED",
87
+ pluginType: "GEMINI",
88
+ };
89
+
90
+ const executeFetch = async (retries = 3): Promise<Response> => {
91
+ const response = await fetch(url, {
92
+ method: "POST",
93
+ headers: {
94
+ Authorization: `Bearer ${accessToken}`,
95
+ "Content-Type": "application/json",
96
+ "User-Agent": "google-cloud-sdk vscode_cloudshelleditor/0.1",
97
+ "X-Goog-Api-Client": "gl-node/22.17.0",
98
+ "Client-Metadata": JSON.stringify(GEMINI_CLI_HEADERS),
99
+ },
100
+ body: JSON.stringify({
101
+ project: projectId,
102
+ model,
103
+ request: {
104
+ contents: [{ parts: [{ text: query }] }],
105
+ tools: [{ googleSearch: {} }],
106
+ },
107
+ }),
108
+ signal,
109
+ });
110
+
111
+ if (!response.ok && retries > 0 && (response.status === 429 || response.status >= 500)) {
112
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (4 - retries)));
113
+ return executeFetch(retries - 1);
114
+ }
115
+
116
+ return response;
117
+ };
118
+
119
+ const response = await executeFetch();
120
+
121
+ if (!response.ok) {
122
+ const errorText = await response.text();
123
+ throw new Error(`Cloud Code Assist API error (${response.status}): ${errorText}`);
124
+ }
125
+
126
+ // Note: streamGenerateContent returns SSE; for now, we consume all chunks.
127
+ // For simplicity and to match the previous structure, we'll read to end.
128
+ const text = await response.text();
129
+ const jsonLines = text.split("\n")
130
+ .filter(l => l.startsWith("data:"))
131
+ .map(l => l.slice(5).trim())
132
+ .filter(l => l.length > 0);
133
+
134
+ let data;
135
+ if (jsonLines.length > 0) {
136
+ // Aggregate chunks if needed, but for now we take the last chunk or assume it's one
137
+ data = JSON.parse(jsonLines[jsonLines.length - 1]);
138
+ } else {
139
+ data = JSON.parse(text);
140
+ } const candidate = data.response?.candidates?.[0];
141
+ const answer = candidate?.content?.parts?.find((p: any) => p.text)?.text ?? "";
142
+ const grounding = candidate?.groundingMetadata;
143
+
144
+ const sources: SearchSource[] = [];
145
+ const seenTitles = new Set<string>();
146
+ if (grounding?.groundingChunks) {
147
+ for (const chunk of grounding.groundingChunks) {
148
+ if (chunk.web) {
149
+ const title = chunk.web.title ?? "Untitled";
150
+ if (seenTitles.has(title)) continue;
151
+ seenTitles.add(title);
152
+ const domain = chunk.web.domain ?? title;
153
+ sources.push({
154
+ title,
155
+ uri: chunk.web.uri ?? "",
156
+ domain,
157
+ });
158
+ }
159
+ }
160
+ }
161
+
162
+ const searchQueries = grounding?.webSearchQueries ?? [];
163
+ return { answer, sources, searchQueries, cached: false };
164
+ }
165
+
71
166
  // ── In-session cache ─────────────────────────────────────────────────────────
72
167
 
73
168
  const resultCache = new Map<string, SearchResult>();
@@ -87,7 +182,7 @@ export default function (pi: ExtensionAPI) {
87
182
  "Returns an AI-synthesized answer grounded in Google Search results, plus source URLs. " +
88
183
  "Use this when you need current information from the web: recent events, documentation, " +
89
184
  "product details, technical references, news, etc. " +
90
- "Requires GEMINI_API_KEY. Alternative to Brave-based search tools for users with Google Cloud credits.",
185
+ "Requires GEMINI_API_KEY or Google login. Alternative to Brave-based search tools.",
91
186
  promptSnippet: "Search the web via Google Search to get current information with sources",
92
187
  promptGuidelines: [
93
188
  "Use google_search when you need up-to-date web information that isn't in your training data.",
@@ -109,17 +204,33 @@ export default function (pi: ExtensionAPI) {
109
204
  ),
110
205
  }),
111
206
 
112
- async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
207
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
113
208
  const startTime = Date.now();
114
209
  const maxSources = Math.min(Math.max(params.maxSources ?? 5, 1), 10);
115
210
 
116
- // Check for API key
211
+ // Check for credentials
212
+ let oauthToken: string | undefined;
213
+ let projectId: string | undefined;
214
+
117
215
  if (!process.env.GEMINI_API_KEY) {
216
+ const oauthRaw = await ctx.modelRegistry.getApiKeyForProvider("google-gemini-cli");
217
+ if (oauthRaw) {
218
+ try {
219
+ const parsed = JSON.parse(oauthRaw);
220
+ oauthToken = parsed.token;
221
+ projectId = parsed.projectId;
222
+ } catch {
223
+ // Fall through to error
224
+ }
225
+ }
226
+ }
227
+
228
+ if (!process.env.GEMINI_API_KEY && (!oauthToken || !projectId)) {
118
229
  return {
119
230
  content: [
120
231
  {
121
232
  type: "text",
122
- text: "Error: GEMINI_API_KEY is not set. Please set this environment variable to use Google Search.\n\nExample: export GEMINI_API_KEY=your_key",
233
+ text: "Error: No authentication found for Google Search. Please set GEMINI_API_KEY or log in via Google.\n\nExample: export GEMINI_API_KEY=your_key or use /login google",
123
234
  },
124
235
  ],
125
236
  isError: true,
@@ -128,7 +239,7 @@ export default function (pi: ExtensionAPI) {
128
239
  sourceCount: 0,
129
240
  cached: false,
130
241
  durationMs: Date.now() - startTime,
131
- error: "auth_error: GEMINI_API_KEY not set",
242
+ error: "auth_error: No credentials set",
132
243
  } as SearchDetails,
133
244
  };
134
245
  }
@@ -152,49 +263,52 @@ export default function (pi: ExtensionAPI) {
152
263
  // Call Gemini with Google Search grounding
153
264
  let result: SearchResult;
154
265
  try {
155
- const ai = await getClient();
156
- const response = await ai.models.generateContent({
157
- model: process.env.GEMINI_SEARCH_MODEL || "gemini-2.5-flash",
158
- contents: params.query,
159
- config: {
160
- tools: [{ googleSearch: {} }],
161
- abortSignal: signal,
162
- },
163
- });
164
-
165
- // Extract answer text
166
- const answer = response.text ?? "";
167
-
168
- // Extract grounding metadata
169
- const candidate = response.candidates?.[0];
170
- const grounding = candidate?.groundingMetadata;
171
-
172
- // Parse sources from grounding chunks
173
- const sources: SearchSource[] = [];
174
- const seenTitles = new Set<string>();
175
- if (grounding?.groundingChunks) {
176
- for (const chunk of grounding.groundingChunks) {
177
- if (chunk.web) {
178
- const title = chunk.web.title ?? "Untitled";
179
- // Dedupe by title since URIs are redirect URLs that differ per call
180
- if (seenTitles.has(title)) continue;
181
- seenTitles.add(title);
182
- // domain field is not available via Gemini API, use title as fallback
183
- // (title is typically the domain name, e.g. "wikipedia.org")
184
- const domain = chunk.web.domain ?? title;
185
- sources.push({
186
- title,
187
- uri: chunk.web.uri ?? "",
188
- domain,
189
- });
266
+ if (process.env.GEMINI_API_KEY) {
267
+ const ai = await getClient();
268
+ const response = await ai.models.generateContent({
269
+ model: process.env.GEMINI_SEARCH_MODEL || "gemini-2.5-flash",
270
+ contents: params.query,
271
+ config: {
272
+ tools: [{ googleSearch: {} }],
273
+ abortSignal: signal,
274
+ },
275
+ });
276
+
277
+ // Extract answer text
278
+ const answer = response.text ?? "";
279
+
280
+ // Extract grounding metadata
281
+ const candidate = response.candidates?.[0];
282
+ const grounding = candidate?.groundingMetadata;
283
+
284
+ // Parse sources from grounding chunks
285
+ const sources: SearchSource[] = [];
286
+ const seenTitles = new Set<string>();
287
+ if (grounding?.groundingChunks) {
288
+ for (const chunk of grounding.groundingChunks) {
289
+ if (chunk.web) {
290
+ const title = chunk.web.title ?? "Untitled";
291
+ // Dedupe by title since URIs are redirect URLs that differ per call
292
+ if (seenTitles.has(title)) continue;
293
+ seenTitles.add(title);
294
+ // domain field is not available via Gemini API, use title as fallback
295
+ // (title is typically the domain name, e.g. "wikipedia.org")
296
+ const domain = chunk.web.domain ?? title;
297
+ sources.push({
298
+ title,
299
+ uri: chunk.web.uri ?? "",
300
+ domain,
301
+ });
302
+ }
190
303
  }
191
304
  }
192
- }
193
305
 
194
- // Extract search queries Gemini actually performed
195
- const searchQueries = grounding?.webSearchQueries ?? [];
196
-
197
- result = { answer, sources, searchQueries, cached: false };
306
+ // Extract search queries Gemini actually performed
307
+ const searchQueries = grounding?.webSearchQueries ?? [];
308
+ result = { answer, sources, searchQueries, cached: false };
309
+ } else {
310
+ result = await searchWithOAuth(params.query, oauthToken!, projectId!, signal);
311
+ }
198
312
  } catch (err: unknown) {
199
313
  const msg = err instanceof Error ? err.message : String(err);
200
314
 
@@ -287,9 +401,12 @@ export default function (pi: ExtensionAPI) {
287
401
  // ── Startup notification ─────────────────────────────────────────────────
288
402
 
289
403
  pi.on("session_start", async (_event, ctx) => {
290
- if (!process.env.GEMINI_API_KEY) {
404
+ if (process.env.GEMINI_API_KEY) return;
405
+
406
+ const hasOAuth = await ctx.modelRegistry.authStorage.hasAuth("google-gemini-cli");
407
+ if (!hasOAuth) {
291
408
  ctx.ui.notify(
292
- "Google Search: No GEMINI_API_KEY set. The google_search tool will not work until this is configured.",
409
+ "Google Search: No authentication set. Log in via Google or set GEMINI_API_KEY to use google_search.",
293
410
  "warning",
294
411
  );
295
412
  }
@@ -10,7 +10,7 @@ import type { ExtensionContext, ExtensionCommandContext } from "@gsd/pi-coding-a
10
10
  import type { GSDState } from "./types.js";
11
11
  import { getCurrentBranch } from "./worktree.js";
12
12
  import { getActiveHook } from "./post-unit-hooks.js";
13
- import { getLedger, getProjectTotals, formatCost, formatTokenCount } from "./metrics.js";
13
+ import { getLedger, getProjectTotals, formatCost, formatTokenCount, formatTierSavings } from "./metrics.js";
14
14
  import {
15
15
  resolveMilestoneFile,
16
16
  resolveSliceFile,
@@ -39,6 +39,8 @@ export interface AutoDashboardData {
39
39
  projectedRemainingCost?: number;
40
40
  /** Whether token profile has been auto-downgraded due to budget prediction */
41
41
  profileDowngraded?: boolean;
42
+ /** Number of pending captures awaiting triage (0 if none or file missing) */
43
+ pendingCaptureCount: number;
42
44
  }
43
45
 
44
46
  // ─── Unit Description Helpers ─────────────────────────────────────────────────
@@ -239,6 +241,7 @@ export function updateProgressWidget(
239
241
  unitId: string,
240
242
  state: GSDState,
241
243
  accessors: WidgetStateAccessors,
244
+ tierBadge?: string,
242
245
  ): void {
243
246
  if (!ctx.hasUI) return;
244
247
 
@@ -319,7 +322,8 @@ export function updateProgressWidget(
319
322
 
320
323
  const target = task ? `${task.id}: ${task.title}` : unitId;
321
324
  const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
322
- const phaseBadge = theme.fg("dim", phaseLabel);
325
+ const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
326
+ const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
323
327
  lines.push(rightAlign(actionLeft, phaseBadge, width));
324
328
  lines.push("");
325
329
 
@@ -414,6 +418,14 @@ export function updateProgressWidget(
414
418
  ? `${modelPhase}${theme.fg("dim", modelDisplay)}`
415
419
  : "";
416
420
  lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
421
+
422
+ // Dynamic routing savings summary
423
+ if (mLedger && mLedger.units.some(u => u.tier)) {
424
+ const savings = formatTierSavings(mLedger.units);
425
+ if (savings) {
426
+ lines.push(truncateToWidth(theme.fg("dim", `${pad}${savings}`), width));
427
+ }
428
+ }
417
429
  }
418
430
 
419
431
  const hintParts: string[] = [];