gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.20aba06

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 (346) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +100 -3
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  16. package/dist/resources/extensions/github-sync/cli.js +284 -0
  17. package/dist/resources/extensions/github-sync/index.js +73 -0
  18. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  19. package/dist/resources/extensions/github-sync/sync.js +424 -0
  20. package/dist/resources/extensions/github-sync/templates.js +118 -0
  21. package/dist/resources/extensions/github-sync/types.js +7 -0
  22. package/dist/resources/extensions/google-search/package.json +3 -1
  23. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  24. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +923 -787
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +107 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +205 -51
  29. package/dist/resources/extensions/gsd/auto-start.js +19 -3
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +149 -100
  33. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  35. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  37. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  38. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  39. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  41. package/dist/resources/extensions/gsd/captures.js +9 -1
  42. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  44. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  45. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  49. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  50. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  51. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  52. package/dist/resources/extensions/gsd/commands-handlers.js +17 -4
  53. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  54. package/dist/resources/extensions/gsd/commands.js +8 -1169
  55. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  56. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  57. package/dist/resources/extensions/gsd/detection.js +1 -2
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  59. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  60. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  61. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  62. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  63. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  64. package/dist/resources/extensions/gsd/doctor.js +234 -12
  65. package/dist/resources/extensions/gsd/env-utils.js +29 -0
  66. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  67. package/dist/resources/extensions/gsd/export-html.js +46 -0
  68. package/dist/resources/extensions/gsd/export.js +1 -1
  69. package/dist/resources/extensions/gsd/files.js +48 -9
  70. package/dist/resources/extensions/gsd/forensics.js +1 -1
  71. package/dist/resources/extensions/gsd/git-service.js +30 -12
  72. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  73. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  74. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  75. package/dist/resources/extensions/gsd/health-widget.js +4 -87
  76. package/dist/resources/extensions/gsd/index.js +4 -1111
  77. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  78. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  79. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  80. package/dist/resources/extensions/gsd/package.json +1 -1
  81. package/dist/resources/extensions/gsd/paths.js +3 -0
  82. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  83. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  84. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  85. package/dist/resources/extensions/gsd/preferences.js +22 -11
  86. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  87. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  88. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  89. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  90. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  91. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  92. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  93. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  94. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  95. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  96. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  97. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  98. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  99. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  100. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  101. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  102. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  103. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  104. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  105. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  106. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  107. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  108. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  109. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  110. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  111. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  112. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  113. package/dist/resources/extensions/gsd/state.js +42 -23
  114. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  115. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  116. package/dist/resources/extensions/gsd/visualizer-data.js +27 -2
  117. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  118. package/dist/resources/extensions/gsd/worktree.js +35 -16
  119. package/dist/resources/extensions/mcp-client/index.js +14 -1
  120. package/dist/resources/extensions/remote-questions/status.js +4 -1
  121. package/dist/resources/extensions/remote-questions/store.js +4 -1
  122. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  123. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  124. package/dist/resources/extensions/subagent/index.js +12 -3
  125. package/dist/resources/extensions/subagent/isolation.js +2 -1
  126. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  127. package/dist/resources/extensions/universal-config/package.json +1 -1
  128. package/dist/welcome-screen.d.ts +13 -0
  129. package/dist/welcome-screen.js +97 -0
  130. package/package.json +1 -1
  131. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  132. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  133. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  134. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  135. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  137. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  140. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  143. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  147. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  149. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/skills.js +8 -2
  151. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  153. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/index.js +1 -1
  155. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  164. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  180. package/packages/pi-coding-agent/package.json +1 -1
  181. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  182. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  183. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  184. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/skills.ts +11 -2
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  189. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  190. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  192. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/async-jobs/index.ts +11 -0
  195. package/src/resources/extensions/browser-tools/index.ts +3 -0
  196. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  197. package/src/resources/extensions/cmux/index.ts +57 -1
  198. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  199. package/src/resources/extensions/github-sync/cli.ts +364 -0
  200. package/src/resources/extensions/github-sync/index.ts +93 -0
  201. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  202. package/src/resources/extensions/github-sync/sync.ts +556 -0
  203. package/src/resources/extensions/github-sync/templates.ts +183 -0
  204. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  205. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  206. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  207. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  208. package/src/resources/extensions/github-sync/types.ts +47 -0
  209. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  210. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  211. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  212. package/src/resources/extensions/gsd/auto-loop.ts +1285 -1138
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +90 -46
  214. package/src/resources/extensions/gsd/auto-prompts.ts +250 -53
  215. package/src/resources/extensions/gsd/auto-start.ts +24 -3
  216. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  217. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  218. package/src/resources/extensions/gsd/auto.ts +152 -111
  219. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  220. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  221. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  222. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  225. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  226. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  227. package/src/resources/extensions/gsd/captures.ts +10 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  229. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  230. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  231. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  232. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  233. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  234. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  235. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  236. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  237. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  238. package/src/resources/extensions/gsd/commands-handlers.ts +18 -3
  239. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  240. package/src/resources/extensions/gsd/commands.ts +10 -1307
  241. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  242. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  243. package/src/resources/extensions/gsd/detection.ts +2 -2
  244. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  245. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  246. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  247. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  248. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  249. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  250. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  251. package/src/resources/extensions/gsd/doctor.ts +243 -14
  252. package/src/resources/extensions/gsd/env-utils.ts +31 -0
  253. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  254. package/src/resources/extensions/gsd/export-html.ts +51 -0
  255. package/src/resources/extensions/gsd/export.ts +1 -1
  256. package/src/resources/extensions/gsd/files.ts +51 -11
  257. package/src/resources/extensions/gsd/forensics.ts +1 -1
  258. package/src/resources/extensions/gsd/git-service.ts +44 -10
  259. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  260. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  261. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  262. package/src/resources/extensions/gsd/health-widget.ts +4 -89
  263. package/src/resources/extensions/gsd/index.ts +12 -1307
  264. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  265. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  266. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  267. package/src/resources/extensions/gsd/paths.ts +4 -0
  268. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  269. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  270. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  271. package/src/resources/extensions/gsd/preferences.ts +25 -11
  272. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  273. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  274. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  276. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  277. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  278. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  279. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  280. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  281. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  282. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  283. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  284. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  285. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  286. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  287. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  288. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  289. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  290. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  291. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  292. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  293. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  294. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  295. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  296. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  297. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  298. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  299. package/src/resources/extensions/gsd/state.ts +39 -21
  300. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  301. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  302. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  303. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -77
  304. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  305. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  306. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  307. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  308. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  309. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  312. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  313. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  314. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  315. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  316. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  317. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  318. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  319. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  320. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  321. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  322. package/src/resources/extensions/gsd/types.ts +18 -1
  323. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  324. package/src/resources/extensions/gsd/visualizer-data.ts +52 -2
  325. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  326. package/src/resources/extensions/gsd/worktree.ts +35 -15
  327. package/src/resources/extensions/mcp-client/index.ts +17 -1
  328. package/src/resources/extensions/remote-questions/status.ts +5 -1
  329. package/src/resources/extensions/remote-questions/store.ts +5 -1
  330. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  331. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  332. package/src/resources/extensions/subagent/index.ts +12 -3
  333. package/src/resources/extensions/subagent/isolation.ts +3 -1
  334. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  335. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  336. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  337. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  338. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  339. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  340. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  341. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  342. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  343. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  344. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  345. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  346. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -1,336 +0,0 @@
1
- // GSD Extension — Semantic Chunker with TF-IDF Relevance Scoring
2
- // Splits code/text into semantic chunks and selects the most relevant ones for a given task.
3
- // Pure TypeScript — no external dependencies.
4
-
5
- // ─── Types ──────────────────────────────────────────────────────────────────
6
-
7
- export interface Chunk {
8
- content: string;
9
- startLine: number;
10
- endLine: number;
11
- score: number;
12
- }
13
-
14
- export interface ChunkResult {
15
- chunks: Chunk[];
16
- totalChunks: number;
17
- omittedChunks: number;
18
- savingsPercent: number;
19
- }
20
-
21
- interface ChunkOptions {
22
- minLines?: number;
23
- maxLines?: number;
24
- }
25
-
26
- interface RelevanceOptions {
27
- maxChunks?: number;
28
- minChunkLines?: number;
29
- maxChunkLines?: number;
30
- minScore?: number;
31
- }
32
-
33
- // ─── Constants ──────────────────────────────────────────────────────────────
34
-
35
- const CODE_BOUNDARY_RE = /^(export\s+)?(async\s+)?(function|class|interface|type|const|enum)\s/;
36
-
37
- const MARKDOWN_HEADING_RE = /^#{1,6}\s/;
38
-
39
- const STOP_WORDS = new Set([
40
- "the", "a", "an", "is", "are", "was", "were", "be", "to", "of", "in",
41
- "for", "on", "with", "at", "by", "from", "this", "that", "it", "as",
42
- "or", "and", "not", "but", "if", "do", "no", "so", "up", "its", "has",
43
- "had", "get", "set", "can", "may", "all", "use", "new", "one", "two",
44
- "also", "each", "than", "been", "into", "most", "only", "over", "such",
45
- "how", "some", "any", "our", "his", "her", "out", "did", "let", "say", "she",
46
- ]);
47
-
48
- const DEFAULT_MIN_LINES = 3;
49
- const DEFAULT_MAX_LINES = 80;
50
- const DEFAULT_MAX_CHUNKS = 5;
51
- const DEFAULT_MIN_SCORE = 0.1;
52
-
53
- // ─── Content Type Detection ─────────────────────────────────────────────────
54
-
55
- type ContentType = "code" | "markdown" | "text";
56
-
57
- function detectContentType(lines: string[]): ContentType {
58
- let codeSignals = 0;
59
- let mdSignals = 0;
60
- const sampleSize = Math.min(lines.length, 50);
61
-
62
- for (let i = 0; i < sampleSize; i++) {
63
- const line = lines[i];
64
- if (CODE_BOUNDARY_RE.test(line) || /^\s*import\s/.test(line)) {
65
- codeSignals++;
66
- }
67
- if (MARKDOWN_HEADING_RE.test(line)) {
68
- mdSignals++;
69
- }
70
- }
71
-
72
- if (mdSignals >= 2 && mdSignals > codeSignals) return "markdown";
73
- if (codeSignals >= 2) return "code";
74
- return "text";
75
- }
76
-
77
- // ─── Tokenizer ──────────────────────────────────────────────────────────────
78
-
79
- function tokenize(text: string): string[] {
80
- return text
81
- .toLowerCase()
82
- .split(/[\s\W]+/)
83
- .filter((w) => w.length >= 2 && !STOP_WORDS.has(w));
84
- }
85
-
86
- // ─── splitIntoChunks ────────────────────────────────────────────────────────
87
-
88
- export function splitIntoChunks(
89
- content: string,
90
- options?: ChunkOptions,
91
- ): Chunk[] {
92
- if (!content || content.trim().length === 0) return [];
93
-
94
- const minLines = options?.minLines ?? DEFAULT_MIN_LINES;
95
- const maxLines = options?.maxLines ?? DEFAULT_MAX_LINES;
96
- const lines = content.split("\n");
97
-
98
- if (lines.length === 0) return [];
99
-
100
- const contentType = detectContentType(lines);
101
- let boundaries: number[];
102
-
103
- switch (contentType) {
104
- case "code":
105
- boundaries = findCodeBoundaries(lines);
106
- break;
107
- case "markdown":
108
- boundaries = findMarkdownBoundaries(lines);
109
- break;
110
- default:
111
- boundaries = findTextBoundaries(lines);
112
- break;
113
- }
114
-
115
- // Always include 0 as first boundary
116
- if (boundaries.length === 0 || boundaries[0] !== 0) {
117
- boundaries.unshift(0);
118
- }
119
-
120
- // Build raw chunks from boundaries
121
- const rawChunks: Chunk[] = [];
122
- for (let i = 0; i < boundaries.length; i++) {
123
- const start = boundaries[i];
124
- const end = i + 1 < boundaries.length ? boundaries[i + 1] - 1 : lines.length - 1;
125
- const chunkLines = lines.slice(start, end + 1);
126
- rawChunks.push({
127
- content: chunkLines.join("\n"),
128
- startLine: start + 1, // 1-based
129
- endLine: end + 1, // 1-based
130
- score: 0,
131
- });
132
- }
133
-
134
- // Split oversized chunks at maxLines
135
- const splitChunks: Chunk[] = [];
136
- for (const chunk of rawChunks) {
137
- const chunkLineCount = chunk.endLine - chunk.startLine + 1;
138
- if (chunkLineCount <= maxLines) {
139
- splitChunks.push(chunk);
140
- } else {
141
- const chunkLines = chunk.content.split("\n");
142
- for (let offset = 0; offset < chunkLines.length; offset += maxLines) {
143
- const slice = chunkLines.slice(offset, offset + maxLines);
144
- splitChunks.push({
145
- content: slice.join("\n"),
146
- startLine: chunk.startLine + offset,
147
- endLine: chunk.startLine + offset + slice.length - 1,
148
- score: 0,
149
- });
150
- }
151
- }
152
- }
153
-
154
- // Merge tiny chunks into predecessor
155
- const merged: Chunk[] = [];
156
- for (const chunk of splitChunks) {
157
- const chunkLineCount = chunk.endLine - chunk.startLine + 1;
158
- if (chunkLineCount < minLines && merged.length > 0) {
159
- const prev = merged[merged.length - 1];
160
- prev.content += "\n" + chunk.content;
161
- prev.endLine = chunk.endLine;
162
- } else {
163
- merged.push({ ...chunk });
164
- }
165
- }
166
-
167
- return merged;
168
- }
169
-
170
- function findCodeBoundaries(lines: string[]): number[] {
171
- const boundaries: number[] = [];
172
- for (let i = 0; i < lines.length; i++) {
173
- if (CODE_BOUNDARY_RE.test(lines[i])) {
174
- // Also consider a blank line before a boundary marker
175
- if (i > 0 && lines[i - 1].trim() === "" && !boundaries.includes(i)) {
176
- boundaries.push(i);
177
- } else if (!boundaries.includes(i)) {
178
- boundaries.push(i);
179
- }
180
- }
181
- }
182
- return boundaries;
183
- }
184
-
185
- function findMarkdownBoundaries(lines: string[]): number[] {
186
- const boundaries: number[] = [];
187
- for (let i = 0; i < lines.length; i++) {
188
- if (MARKDOWN_HEADING_RE.test(lines[i])) {
189
- boundaries.push(i);
190
- }
191
- }
192
- return boundaries;
193
- }
194
-
195
- function findTextBoundaries(lines: string[]): number[] {
196
- const boundaries: number[] = [0];
197
- for (let i = 1; i < lines.length; i++) {
198
- if (lines[i - 1].trim() === "" && lines[i].trim() !== "") {
199
- boundaries.push(i);
200
- }
201
- }
202
- return boundaries;
203
- }
204
-
205
- // ─── scoreChunks ────────────────────────────────────────────────────────────
206
-
207
- export function scoreChunks(chunks: Chunk[], query: string): Chunk[] {
208
- if (chunks.length === 0) return [];
209
-
210
- const queryTerms = tokenize(query);
211
- if (queryTerms.length === 0) {
212
- return chunks.map((c) => ({ ...c, score: 0 }));
213
- }
214
-
215
- const totalChunks = chunks.length;
216
-
217
- // Pre-compute IDF for each query term
218
- const termChunkCounts = new Map<string, number>();
219
- const chunkTokenSets: Set<string>[] = [];
220
-
221
- for (const chunk of chunks) {
222
- const tokens = new Set(tokenize(chunk.content));
223
- chunkTokenSets.push(tokens);
224
- for (const term of queryTerms) {
225
- if (tokens.has(term)) {
226
- termChunkCounts.set(term, (termChunkCounts.get(term) ?? 0) + 1);
227
- }
228
- }
229
- }
230
-
231
- const idf = new Map<string, number>();
232
- for (const term of queryTerms) {
233
- const df = termChunkCounts.get(term) ?? 0;
234
- idf.set(term, Math.log(1 + totalChunks / (1 + df)));
235
- }
236
-
237
- // Score each chunk
238
- const scored = chunks.map((chunk, idx) => {
239
- const chunkTokens = tokenize(chunk.content);
240
- const totalTerms = chunkTokens.length;
241
- if (totalTerms === 0) return { ...chunk, score: 0 };
242
-
243
- // Count term frequencies
244
- const termFreq = new Map<string, number>();
245
- for (const token of chunkTokens) {
246
- termFreq.set(token, (termFreq.get(token) ?? 0) + 1);
247
- }
248
-
249
- let score = 0;
250
- for (const term of queryTerms) {
251
- const tf = (termFreq.get(term) ?? 0) / totalTerms;
252
- const termIdf = idf.get(term) ?? 0;
253
- score += tf * termIdf;
254
- }
255
-
256
- return { ...chunk, score };
257
- });
258
-
259
- // Normalize to 0-1
260
- const maxScore = Math.max(...scored.map((c) => c.score));
261
- if (maxScore > 0) {
262
- for (const chunk of scored) {
263
- chunk.score = chunk.score / maxScore;
264
- }
265
- }
266
-
267
- return scored;
268
- }
269
-
270
- // ─── chunkByRelevance ───────────────────────────────────────────────────────
271
-
272
- export function chunkByRelevance(
273
- content: string,
274
- query: string,
275
- options?: RelevanceOptions,
276
- ): ChunkResult {
277
- const maxChunks = options?.maxChunks ?? DEFAULT_MAX_CHUNKS;
278
- const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
279
- const minLines = options?.minChunkLines ?? DEFAULT_MIN_LINES;
280
- const maxLines = options?.maxChunkLines ?? DEFAULT_MAX_LINES;
281
-
282
- const rawChunks = splitIntoChunks(content, { minLines, maxLines });
283
- if (rawChunks.length === 0) {
284
- return { chunks: [], totalChunks: 0, omittedChunks: 0, savingsPercent: 0 };
285
- }
286
-
287
- const scored = scoreChunks(rawChunks, query);
288
-
289
- // Filter by minScore and take top maxChunks by score
290
- const qualifying = scored
291
- .filter((c) => c.score >= minScore)
292
- .sort((a, b) => b.score - a.score)
293
- .slice(0, maxChunks);
294
-
295
- // Return in original document order (by startLine)
296
- const selected = qualifying.sort((a, b) => a.startLine - b.startLine);
297
-
298
- const totalChars = content.length;
299
- const selectedChars = selected.reduce((sum, c) => sum + c.content.length, 0);
300
- const savingsPercent = totalChars > 0
301
- ? Math.round(((totalChars - selectedChars) / totalChars) * 100)
302
- : 0;
303
-
304
- return {
305
- chunks: selected,
306
- totalChunks: rawChunks.length,
307
- omittedChunks: rawChunks.length - selected.length,
308
- savingsPercent: Math.max(0, savingsPercent),
309
- };
310
- }
311
-
312
- // ─── formatChunks ───────────────────────────────────────────────────────────
313
-
314
- export function formatChunks(result: ChunkResult, filePath: string): string {
315
- if (result.chunks.length === 0) {
316
- return `[${filePath}: empty or no relevant chunks]`;
317
- }
318
-
319
- const parts: string[] = [];
320
- let lastEndLine = 0;
321
-
322
- for (const chunk of result.chunks) {
323
- // Show omission gap
324
- if (lastEndLine > 0 && chunk.startLine > lastEndLine + 1) {
325
- const gapLines = chunk.startLine - lastEndLine - 1;
326
- parts.push(`[...${gapLines} lines omitted...]`);
327
- }
328
-
329
- parts.push(`[Lines ${chunk.startLine}-${chunk.endLine}]`);
330
- parts.push(chunk.content);
331
-
332
- lastEndLine = chunk.endLine;
333
- }
334
-
335
- return parts.join("\n");
336
- }
@@ -1,258 +0,0 @@
1
- /**
2
- * Summary distiller — extracts essential structured data from SUMMARY.md files,
3
- * dropping verbose prose to save context budget.
4
- */
5
-
6
- export interface DistillationResult {
7
- content: string;
8
- summaryCount: number;
9
- savingsPercent: number;
10
- originalChars: number;
11
- distilledChars: number;
12
- }
13
-
14
- interface ParsedFrontmatter {
15
- id: string;
16
- provides: string[];
17
- requires: string[];
18
- key_files: string[];
19
- key_decisions: string[];
20
- patterns_established: string[];
21
- }
22
-
23
- interface DistilledEntry {
24
- id: string;
25
- oneLiner: string;
26
- provides: string[];
27
- requires: string[];
28
- key_files: string[];
29
- key_decisions: string[];
30
- patterns: string[];
31
- }
32
-
33
- // ─── Frontmatter parsing ─────────────────────────────────────────────────────
34
-
35
- function parseFrontmatter(raw: string): ParsedFrontmatter {
36
- const result: ParsedFrontmatter = {
37
- id: "",
38
- provides: [],
39
- requires: [],
40
- key_files: [],
41
- key_decisions: [],
42
- patterns_established: [],
43
- };
44
-
45
- // Extract frontmatter block between --- markers
46
- const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
47
- if (!fmMatch) return result;
48
-
49
- const fmBlock = fmMatch[1];
50
- const lines = fmBlock.split(/\r?\n/);
51
-
52
- let currentKey: string | null = null;
53
-
54
- for (const line of lines) {
55
- // Scalar value: key: value
56
- const scalarMatch = line.match(/^(\w[\w_]*):\s*(.+)$/);
57
- if (scalarMatch) {
58
- const [, key, value] = scalarMatch;
59
- currentKey = key;
60
- setScalar(result, key, value.trim());
61
- continue;
62
- }
63
-
64
- // Array-start key with empty value: key:\n or key: []\n
65
- const arrayStartMatch = line.match(/^(\w[\w_]*):\s*(\[\])?\s*$/);
66
- if (arrayStartMatch) {
67
- currentKey = arrayStartMatch[1];
68
- continue;
69
- }
70
-
71
- // Array item: - value
72
- const itemMatch = line.match(/^\s+-\s+(.+)$/);
73
- if (itemMatch && currentKey) {
74
- pushItem(result, currentKey, itemMatch[1].trim());
75
- continue;
76
- }
77
- }
78
-
79
- return result;
80
- }
81
-
82
- function setScalar(fm: ParsedFrontmatter, key: string, value: string): void {
83
- if (key === "id") fm.id = value;
84
- }
85
-
86
- function pushItem(fm: ParsedFrontmatter, key: string, value: string): void {
87
- switch (key) {
88
- case "provides": fm.provides.push(value); break;
89
- case "requires": fm.requires.push(value); break;
90
- case "key_files": fm.key_files.push(value); break;
91
- case "key_decisions": fm.key_decisions.push(value); break;
92
- case "patterns_established": fm.patterns_established.push(value); break;
93
- }
94
- }
95
-
96
- // ─── Body parsing ────────────────────────────────────────────────────────────
97
-
98
- function extractTitleAndOneLiner(body: string): { id: string; oneLiner: string } {
99
- const lines = body.split(/\r?\n/);
100
- let titleId = "";
101
- let oneLiner = "";
102
- let foundTitle = false;
103
-
104
- for (const line of lines) {
105
- const titleMatch = line.match(/^#\s+(\S+):\s*(.*)$/);
106
- if (titleMatch && !foundTitle) {
107
- titleId = titleMatch[1];
108
- // If the title line itself has text after "S01: ", use that as a fallback
109
- if (titleMatch[2].trim()) {
110
- oneLiner = titleMatch[2].trim();
111
- }
112
- foundTitle = true;
113
- continue;
114
- }
115
-
116
- // First non-empty line after the title is the one-liner
117
- if (foundTitle && !oneLiner && line.trim() && !line.startsWith("#")) {
118
- oneLiner = line.trim();
119
- break;
120
- }
121
- }
122
-
123
- return { id: titleId, oneLiner };
124
- }
125
-
126
- function getBodyAfterFrontmatter(raw: string): string {
127
- const fmMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
128
- if (fmMatch) {
129
- return raw.slice(fmMatch[0].length);
130
- }
131
- return raw;
132
- }
133
-
134
- // ─── Public API ──────────────────────────────────────────────────────────────
135
-
136
- /**
137
- * Distill a single SUMMARY.md content string into a compact structured block.
138
- */
139
- export function distillSingle(summary: string): string {
140
- const fm = parseFrontmatter(summary);
141
- const body = getBodyAfterFrontmatter(summary);
142
- const { id: titleId, oneLiner } = extractTitleAndOneLiner(body);
143
-
144
- const id = fm.id || titleId || "???";
145
-
146
- return formatEntry({
147
- id,
148
- oneLiner,
149
- provides: fm.provides,
150
- requires: fm.requires,
151
- key_files: fm.key_files,
152
- key_decisions: fm.key_decisions,
153
- patterns: fm.patterns_established,
154
- });
155
- }
156
-
157
- function formatEntry(entry: DistilledEntry): string {
158
- return formatEntryWithDropLevel(entry, 0);
159
- }
160
-
161
- /**
162
- * Format an entry, progressively dropping fields based on dropLevel:
163
- * 0 = full output
164
- * 1 = drop patterns
165
- * 2 = drop patterns + key_decisions
166
- * 3 = drop patterns + key_decisions + key_files
167
- */
168
- function formatEntryWithDropLevel(entry: DistilledEntry, dropLevel: number): string {
169
- const lines: string[] = [];
170
- lines.push(`## ${entry.id}: ${entry.oneLiner}`);
171
-
172
- if (entry.provides.length > 0) {
173
- lines.push(`provides: ${entry.provides.join(", ")}`);
174
- }
175
- if (entry.requires.length > 0) {
176
- lines.push(`requires: ${entry.requires.join(", ")}`);
177
- }
178
- if (dropLevel < 3 && entry.key_files.length > 0) {
179
- lines.push(`key_files: ${entry.key_files.join(", ")}`);
180
- }
181
- if (dropLevel < 2 && entry.key_decisions.length > 0) {
182
- lines.push(`key_decisions: ${entry.key_decisions.join(", ")}`);
183
- }
184
- if (dropLevel < 1 && entry.patterns.length > 0) {
185
- lines.push(`patterns: ${entry.patterns.join(", ")}`);
186
- }
187
-
188
- return lines.join("\n");
189
- }
190
-
191
- /**
192
- * Distill multiple SUMMARY.md contents into a budget-constrained output.
193
- */
194
- export function distillSummaries(summaries: string[], budgetChars: number): DistillationResult {
195
- const originalChars = summaries.reduce((sum, s) => sum + s.length, 0);
196
-
197
- if (summaries.length === 0) {
198
- return {
199
- content: "",
200
- summaryCount: 0,
201
- savingsPercent: 0,
202
- originalChars: 0,
203
- distilledChars: 0,
204
- };
205
- }
206
-
207
- // Parse all entries up front
208
- const entries: DistilledEntry[] = summaries.map((summary) => {
209
- const fm = parseFrontmatter(summary);
210
- const body = getBodyAfterFrontmatter(summary);
211
- const { id: titleId, oneLiner } = extractTitleAndOneLiner(body);
212
- return {
213
- id: fm.id || titleId || "???",
214
- oneLiner,
215
- provides: fm.provides,
216
- requires: fm.requires,
217
- key_files: fm.key_files,
218
- key_decisions: fm.key_decisions,
219
- patterns: fm.patterns_established,
220
- };
221
- });
222
-
223
- // Try progressively more aggressive dropping until it fits
224
- for (let dropLevel = 0; dropLevel <= 3; dropLevel++) {
225
- const blocks = entries.map((e) => formatEntryWithDropLevel(e, dropLevel));
226
- const content = blocks.join("\n\n");
227
- if (content.length <= budgetChars) {
228
- const distilledChars = content.length;
229
- return {
230
- content,
231
- summaryCount: summaries.length,
232
- savingsPercent: originalChars > 0
233
- ? Math.round((1 - distilledChars / originalChars) * 100)
234
- : 0,
235
- originalChars,
236
- distilledChars,
237
- };
238
- }
239
- }
240
-
241
- // Even at max drop level it doesn't fit — truncate
242
- const blocks = entries.map((e) => formatEntryWithDropLevel(e, 3));
243
- let content = blocks.join("\n\n");
244
- if (content.length > budgetChars) {
245
- content = content.slice(0, Math.max(0, budgetChars - 15)) + "\n[...truncated]";
246
- }
247
-
248
- const distilledChars = content.length;
249
- return {
250
- content,
251
- summaryCount: summaries.length,
252
- savingsPercent: originalChars > 0
253
- ? Math.round((1 - distilledChars / originalChars) * 100)
254
- : 0,
255
- originalChars,
256
- distilledChars,
257
- };
258
- }