gsd-pi 2.19.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 (249) 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-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Context budget engine — proportional allocation, section-boundary truncation,
3
+ * and executor context window resolution.
4
+ *
5
+ * All functions are pure or near-pure (dependency-injected). No global state, no I/O.
6
+ * Budget ratios are module-level constants for easy tuning.
7
+ *
8
+ * @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
9
+ */
10
+
11
+ // ─── Budget ratio constants ──────────────────────────────────────────────────
12
+ // Percentages of total context window allocated to each budget category.
13
+ // These are applied after tokens→chars conversion.
14
+
15
+ /** Proportion of context window for dependency/prior-task summaries */
16
+ const SUMMARY_RATIO = 0.15;
17
+
18
+ /** Proportion of context window for inline context (plans, decisions, code) */
19
+ const INLINE_CONTEXT_RATIO = 0.40;
20
+
21
+ /** Proportion of context window for verification sections in prompts */
22
+ const VERIFICATION_RATIO = 0.10;
23
+
24
+ /** Approximate chars-per-token conversion factor */
25
+ const CHARS_PER_TOKEN = 4;
26
+
27
+ /** Default context window when none can be resolved (D002) */
28
+ const DEFAULT_CONTEXT_WINDOW = 200_000;
29
+
30
+ /** Percentage of context consumed before suggesting a continue-here checkpoint */
31
+ const CONTINUE_THRESHOLD_PERCENT = 70;
32
+
33
+ // ─── Task count bounds ───────────────────────────────────────────────────────
34
+ // Task count range scales with context window. Smaller windows get fewer tasks
35
+ // to avoid overloading the executor.
36
+
37
+ const TASK_COUNT_MIN = 2;
38
+
39
+ /** Task count ceiling tiers: [contextWindowThreshold, maxTasks] */
40
+ const TASK_COUNT_TIERS: [number, number][] = [
41
+ [500_000, 8], // 500K+ tokens → up to 8 tasks
42
+ [200_000, 6], // 200K+ tokens → up to 6 tasks
43
+ [128_000, 5], // 128K+ tokens → up to 5 tasks
44
+ [0, 3], // anything smaller → up to 3 tasks
45
+ ];
46
+
47
+ // ─── Types ───────────────────────────────────────────────────────────────────
48
+
49
+ export interface TruncationResult {
50
+ /** The (possibly truncated) content string */
51
+ content: string;
52
+ /** Number of sections dropped during truncation; 0 when content fits */
53
+ droppedSections: number;
54
+ }
55
+
56
+ export interface BudgetAllocation {
57
+ /** Character budget for dependency/prior-task summaries */
58
+ summaryBudgetChars: number;
59
+ /** Character budget for inline context (plans, decisions, code snippets) */
60
+ inlineContextBudgetChars: number;
61
+ /** Recommended task count range for the executor at this context window */
62
+ taskCountRange: { min: number; max: number };
63
+ /** Percentage of context consumed before suggesting a continue-here checkpoint */
64
+ continueThresholdPercent: number;
65
+ /** Character budget for verification sections */
66
+ verificationBudgetChars: number;
67
+ }
68
+
69
+ // ─── Minimal interface slices for dependency injection ───────────────────────
70
+ // These avoid coupling to full ModelRegistry/GSDPreferences types in tests.
71
+
72
+ export interface MinimalModel {
73
+ id: string;
74
+ provider: string;
75
+ contextWindow: number;
76
+ }
77
+
78
+ export interface MinimalModelRegistry {
79
+ getAll(): MinimalModel[];
80
+ }
81
+
82
+ export interface MinimalPreferences {
83
+ models?: {
84
+ execution?: string | { model: string; fallbacks?: string[] };
85
+ };
86
+ }
87
+
88
+ // ─── Public API ──────────────────────────────────────────────────────────────
89
+
90
+ /**
91
+ * Compute proportional budget allocations from a context window size (in tokens).
92
+ *
93
+ * Returns deterministic output for any given input. Invalid inputs (≤ 0)
94
+ * silently default to 200K (D002).
95
+ */
96
+ export function computeBudgets(contextWindow: number): BudgetAllocation {
97
+ const effectiveWindow = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
98
+ const totalChars = effectiveWindow * CHARS_PER_TOKEN;
99
+
100
+ return {
101
+ summaryBudgetChars: Math.floor(totalChars * SUMMARY_RATIO),
102
+ inlineContextBudgetChars: Math.floor(totalChars * INLINE_CONTEXT_RATIO),
103
+ verificationBudgetChars: Math.floor(totalChars * VERIFICATION_RATIO),
104
+ continueThresholdPercent: CONTINUE_THRESHOLD_PERCENT,
105
+ taskCountRange: {
106
+ min: TASK_COUNT_MIN,
107
+ max: resolveTaskCountMax(effectiveWindow),
108
+ },
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Truncate content at markdown section boundaries to fit within a character budget.
114
+ *
115
+ * Splits on `### ` headings and `---` dividers. Keeps whole sections that fit.
116
+ * Appends `[...truncated N sections]` when content is dropped.
117
+ * Returns content unchanged when it fits within budget.
118
+ *
119
+ * @see D003 — section-boundary truncation is mandatory; mid-section cuts are unacceptable.
120
+ */
121
+ export function truncateAtSectionBoundary(content: string, budgetChars: number): TruncationResult {
122
+ if (!content || content.length <= budgetChars) {
123
+ return { content, droppedSections: 0 };
124
+ }
125
+
126
+ // Split on section markers: ### headings or --- dividers (on their own line)
127
+ const sections = splitIntoSections(content);
128
+
129
+ if (sections.length <= 1) {
130
+ // No section markers — keep as much as fits from the start
131
+ const truncated = content.slice(0, budgetChars);
132
+ return { content: truncated + "\n\n[...truncated 1 sections]", droppedSections: 1 };
133
+ }
134
+
135
+ // Greedily keep sections that fit
136
+ let usedChars = 0;
137
+ let keptCount = 0;
138
+
139
+ for (const section of sections) {
140
+ const sectionLen = section.length;
141
+ if (usedChars + sectionLen > budgetChars && keptCount > 0) {
142
+ break;
143
+ }
144
+ // Always keep at least the first section (even if it exceeds budget)
145
+ usedChars += sectionLen;
146
+ keptCount++;
147
+ if (usedChars >= budgetChars) break;
148
+ }
149
+
150
+ const droppedCount = sections.length - keptCount;
151
+ if (droppedCount === 0) {
152
+ return { content, droppedSections: 0 };
153
+ }
154
+
155
+ const kept = sections.slice(0, keptCount).join("");
156
+ return {
157
+ content: kept.trimEnd() + `\n\n[...truncated ${droppedCount} sections]`,
158
+ droppedSections: droppedCount,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Resolve the executor model's context window size using a fallback chain:
164
+ *
165
+ * 1. Look up the configured executor model ID in preferences → find in registry → return contextWindow
166
+ * 2. Fall back to sessionContextWindow if provided
167
+ * 3. Fall back to 200K default (D002)
168
+ *
169
+ * Supports "provider/model" format in preferences for explicit provider targeting.
170
+ */
171
+ export function resolveExecutorContextWindow(
172
+ registry: MinimalModelRegistry | undefined,
173
+ preferences: MinimalPreferences | undefined,
174
+ sessionContextWindow?: number,
175
+ ): number {
176
+ // Step 1: Try configured executor model
177
+ if (preferences?.models?.execution && registry) {
178
+ const executionConfig = preferences.models.execution;
179
+ const modelId = typeof executionConfig === "string"
180
+ ? executionConfig
181
+ : executionConfig.model;
182
+
183
+ if (modelId) {
184
+ const model = findModelById(registry, modelId);
185
+ if (model && model.contextWindow > 0) {
186
+ return model.contextWindow;
187
+ }
188
+ }
189
+ }
190
+
191
+ // Step 2: Fall back to session context window
192
+ if (sessionContextWindow && sessionContextWindow > 0) {
193
+ return sessionContextWindow;
194
+ }
195
+
196
+ // Step 3: Fall back to default (D002)
197
+ return DEFAULT_CONTEXT_WINDOW;
198
+ }
199
+
200
+ // ─── Internal helpers ────────────────────────────────────────────────────────
201
+
202
+ /**
203
+ * Resolve task count ceiling from context window size.
204
+ * Larger windows support more tasks per slice.
205
+ */
206
+ function resolveTaskCountMax(contextWindow: number): number {
207
+ for (const [threshold, max] of TASK_COUNT_TIERS) {
208
+ if (contextWindow >= threshold) return max;
209
+ }
210
+ return 3; // fallback — unreachable given tiers include 0
211
+ }
212
+
213
+ /**
214
+ * Split content into sections at `### ` headings or `---` dividers.
215
+ * Each section includes its leading marker.
216
+ */
217
+ function splitIntoSections(content: string): string[] {
218
+ // Match section boundaries: ### heading or --- divider at start of line
219
+ const pattern = /^(?=### |\-{3,}\s*$)/m;
220
+ const parts = content.split(pattern).filter(p => p.length > 0);
221
+ return parts;
222
+ }
223
+
224
+ /**
225
+ * Find a model in the registry by ID string.
226
+ * Supports "provider/model" format for explicit provider targeting,
227
+ * or bare model ID (first match wins).
228
+ */
229
+ function findModelById(registry: MinimalModelRegistry, modelId: string): MinimalModel | undefined {
230
+ const allModels = registry.getAll();
231
+ const slashIdx = modelId.indexOf("/");
232
+
233
+ if (slashIdx !== -1) {
234
+ const provider = modelId.substring(0, slashIdx).toLowerCase();
235
+ const id = modelId.substring(slashIdx + 1).toLowerCase();
236
+ return allModels.find(
237
+ m => m.provider.toLowerCase() === provider && m.id.toLowerCase() === id,
238
+ );
239
+ }
240
+
241
+ // Bare ID — first match
242
+ return allModels.find(m => m.id === modelId);
243
+ }
@@ -0,0 +1,195 @@
1
+ // GSD Context Store — Query Layer & Formatters
2
+ //
3
+ // Typed query functions for decisions and requirements from the DB views,
4
+ // with optional filtering. Format functions produce prompt-injectable markdown.
5
+ // All functions degrade gracefully: return empty results when DB unavailable, never throw.
6
+
7
+ import { isDbAvailable, _getAdapter } from './gsd-db.js';
8
+ import type { Decision, Requirement } from './types.js';
9
+
10
+ // ─── Query Functions ───────────────────────────────────────────────────────
11
+
12
+ export interface DecisionQueryOpts {
13
+ milestoneId?: string;
14
+ scope?: string;
15
+ }
16
+
17
+ export interface RequirementQueryOpts {
18
+ sliceId?: string;
19
+ status?: string;
20
+ }
21
+
22
+ /**
23
+ * Query active (non-superseded) decisions with optional filters.
24
+ * - milestoneId: filters where when_context LIKE '%milestoneId%'
25
+ * - scope: filters where scope = :scope (exact match)
26
+ *
27
+ * Returns [] if DB is not available. Never throws.
28
+ */
29
+ export function queryDecisions(opts?: DecisionQueryOpts): Decision[] {
30
+ if (!isDbAvailable()) return [];
31
+ const adapter = _getAdapter();
32
+ if (!adapter) return [];
33
+
34
+ try {
35
+ const clauses: string[] = ['superseded_by IS NULL'];
36
+ const params: Record<string, unknown> = {};
37
+
38
+ if (opts?.milestoneId) {
39
+ clauses.push('when_context LIKE :milestone_pattern');
40
+ params[':milestone_pattern'] = `%${opts.milestoneId}%`;
41
+ }
42
+
43
+ if (opts?.scope) {
44
+ clauses.push('scope = :scope');
45
+ params[':scope'] = opts.scope;
46
+ }
47
+
48
+ const sql = `SELECT * FROM decisions WHERE ${clauses.join(' AND ')} ORDER BY seq`;
49
+ const rows = adapter.prepare(sql).all(params);
50
+
51
+ return rows.map(row => ({
52
+ seq: row['seq'] as number,
53
+ id: row['id'] as string,
54
+ when_context: row['when_context'] as string,
55
+ scope: row['scope'] as string,
56
+ decision: row['decision'] as string,
57
+ choice: row['choice'] as string,
58
+ rationale: row['rationale'] as string,
59
+ revisable: row['revisable'] as string,
60
+ superseded_by: null,
61
+ }));
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Query active (non-superseded) requirements with optional filters.
69
+ * - sliceId: filters where primary_owner LIKE '%sliceId%' OR supporting_slices LIKE '%sliceId%'
70
+ * - status: filters where status = :status (exact match)
71
+ *
72
+ * Returns [] if DB is not available. Never throws.
73
+ */
74
+ export function queryRequirements(opts?: RequirementQueryOpts): Requirement[] {
75
+ if (!isDbAvailable()) return [];
76
+ const adapter = _getAdapter();
77
+ if (!adapter) return [];
78
+
79
+ try {
80
+ const clauses: string[] = ['superseded_by IS NULL'];
81
+ const params: Record<string, unknown> = {};
82
+
83
+ if (opts?.sliceId) {
84
+ clauses.push('(primary_owner LIKE :slice_pattern OR supporting_slices LIKE :slice_pattern)');
85
+ params[':slice_pattern'] = `%${opts.sliceId}%`;
86
+ }
87
+
88
+ if (opts?.status) {
89
+ clauses.push('status = :status');
90
+ params[':status'] = opts.status;
91
+ }
92
+
93
+ const sql = `SELECT * FROM requirements WHERE ${clauses.join(' AND ')} ORDER BY id`;
94
+ const rows = adapter.prepare(sql).all(params);
95
+
96
+ return rows.map(row => ({
97
+ id: row['id'] as string,
98
+ class: row['class'] as string,
99
+ status: row['status'] as string,
100
+ description: row['description'] as string,
101
+ why: row['why'] as string,
102
+ source: row['source'] as string,
103
+ primary_owner: row['primary_owner'] as string,
104
+ supporting_slices: row['supporting_slices'] as string,
105
+ validation: row['validation'] as string,
106
+ notes: row['notes'] as string,
107
+ full_content: row['full_content'] as string,
108
+ superseded_by: null,
109
+ }));
110
+ } catch {
111
+ return [];
112
+ }
113
+ }
114
+
115
+ // ─── Format Functions ──────────────────────────────────────────────────────
116
+
117
+ /**
118
+ * Format decisions as a markdown table matching DECISIONS.md format.
119
+ * Returns empty string for empty input.
120
+ */
121
+ export function formatDecisionsForPrompt(decisions: Decision[]): string {
122
+ if (decisions.length === 0) return '';
123
+
124
+ const header = '| # | When | Scope | Decision | Choice | Rationale | Revisable? |';
125
+ const separator = '|---|------|-------|----------|--------|-----------|------------|';
126
+ const rows = decisions.map(d =>
127
+ `| ${d.id} | ${d.when_context} | ${d.scope} | ${d.decision} | ${d.choice} | ${d.rationale} | ${d.revisable} |`,
128
+ );
129
+
130
+ return [header, separator, ...rows].join('\n');
131
+ }
132
+
133
+ /**
134
+ * Format requirements as structured H3 sections matching REQUIREMENTS.md format.
135
+ * Returns empty string for empty input.
136
+ */
137
+ export function formatRequirementsForPrompt(requirements: Requirement[]): string {
138
+ if (requirements.length === 0) return '';
139
+
140
+ return requirements.map(r => {
141
+ const lines: string[] = [
142
+ `### ${r.id}: ${r.description}`,
143
+ '',
144
+ `- **Class:** ${r.class}`,
145
+ `- **Status:** ${r.status}`,
146
+ `- **Why:** ${r.why}`,
147
+ `- **Source:** ${r.source}`,
148
+ `- **Primary Owner:** ${r.primary_owner}`,
149
+ ];
150
+
151
+ if (r.supporting_slices) {
152
+ lines.push(`- **Supporting Slices:** ${r.supporting_slices}`);
153
+ }
154
+
155
+ lines.push(`- **Validation:** ${r.validation}`);
156
+
157
+ if (r.notes) {
158
+ lines.push(`- **Notes:** ${r.notes}`);
159
+ }
160
+
161
+ return lines.join('\n');
162
+ }).join('\n\n');
163
+ }
164
+
165
+ // ─── Artifact Query Functions ──────────────────────────────────────────────
166
+
167
+ /**
168
+ * Query a hierarchy artifact by its relative path.
169
+ * Returns the full_content string or null if not found/unavailable.
170
+ * Never throws.
171
+ */
172
+ export function queryArtifact(path: string): string | null {
173
+ if (!isDbAvailable()) return null;
174
+ const adapter = _getAdapter();
175
+ if (!adapter) return null;
176
+
177
+ try {
178
+ const row = adapter.prepare('SELECT full_content FROM artifacts WHERE path = :path').get({ ':path': path });
179
+ if (!row) return null;
180
+ const content = row['full_content'] as string;
181
+ return content || null;
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Query PROJECT.md content from the artifacts table.
189
+ * PROJECT.md is stored with the relative path 'PROJECT.md' by the importer.
190
+ * Returns the content string or null if not found/unavailable.
191
+ * Never throws.
192
+ */
193
+ export function queryProject(): string | null {
194
+ return queryArtifact('PROJECT.md');
195
+ }
@@ -15,6 +15,7 @@ import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
15
15
  import {
16
16
  getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
17
17
  aggregateByModel, formatCost, formatTokenCount, formatCostProjection,
18
+ type UnitMetrics,
18
19
  } from "./metrics.js";
19
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
20
21
  import { getActiveWorktreeName } from "./worktree-command.js";
@@ -413,11 +414,33 @@ export class GSDDashboardOverlay {
413
414
  lines.push(row(th.fg("text", th.bold("Completed"))));
414
415
  lines.push(blank());
415
416
 
417
+ // Build ledger lookup for budget indicators (last entry wins for retries)
418
+ const ledgerLookup = new Map<string, UnitMetrics>();
419
+ const currentLedger = getLedger();
420
+ if (currentLedger) {
421
+ for (const lu of currentLedger.units) {
422
+ ledgerLookup.set(`${lu.type}:${lu.id}`, lu);
423
+ }
424
+ }
425
+
416
426
  const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
417
427
  for (const u of recent) {
418
428
  const left = ` ${th.fg("success", "✓")} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
429
+
430
+ // Budget indicators from ledger
431
+ const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`);
432
+ let budgetMarkers = "";
433
+ if (ledgerEntry) {
434
+ if (ledgerEntry.truncationSections && ledgerEntry.truncationSections > 0) {
435
+ budgetMarkers += th.fg("warning", ` ▼${ledgerEntry.truncationSections}`);
436
+ }
437
+ if (ledgerEntry.continueHereFired === true) {
438
+ budgetMarkers += th.fg("error", " → wrap-up");
439
+ }
440
+ }
441
+
419
442
  const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
420
- lines.push(row(joinColumns(left, right, contentWidth)));
443
+ lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth)));
421
444
  }
422
445
 
423
446
  if (this.dashData.completedUnits.length > 10) {
@@ -448,6 +471,18 @@ export class GSDDashboardOverlay {
448
471
  `${th.fg("dim", "cache-w:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheWrite))}`,
449
472
  ], contentWidth, " ")));
450
473
 
474
+ // Budget aggregate line — only when data exists
475
+ if (totals.totalTruncationSections > 0 || totals.continueHereFiredCount > 0) {
476
+ const budgetParts: string[] = [];
477
+ if (totals.totalTruncationSections > 0) {
478
+ budgetParts.push(th.fg("warning", `${totals.totalTruncationSections} sections truncated`));
479
+ }
480
+ if (totals.continueHereFiredCount > 0) {
481
+ budgetParts.push(th.fg("error", `${totals.continueHereFiredCount} continue-here fired`));
482
+ }
483
+ lines.push(row(budgetParts.join(` ${th.fg("dim", "·")} `)));
484
+ }
485
+
451
486
  const phases = aggregateByPhase(ledger.units);
452
487
  if (phases.length > 0) {
453
488
  lines.push(blank());
@@ -492,14 +527,17 @@ export class GSDDashboardOverlay {
492
527
  }
493
528
 
494
529
  const models = aggregateByModel(ledger.units);
495
- if (models.length > 1) {
530
+ if (models.length >= 1) {
496
531
  lines.push(blank());
497
532
  lines.push(row(th.fg("dim", "By Model")));
498
533
  for (const m of models) {
499
534
  const pct = totals.cost > 0 ? Math.round((m.cost / totals.cost) * 100) : 0;
500
535
  const modelName = truncateToWidth(m.model, 38);
536
+ const ctxWindow = m.contextWindowTokens !== undefined
537
+ ? th.fg("dim", ` [${formatTokenCount(m.contextWindowTokens)}]`)
538
+ : "";
501
539
  const left = ` ${th.fg("text", modelName.padEnd(38))}${th.fg("warning", formatCost(m.cost).padStart(8))}`;
502
- const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`);
540
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`) + ctxWindow;
503
541
  lines.push(row(joinColumns(left, right, contentWidth)));
504
542
  }
505
543
  }