gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.98b44dc

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 (217) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/extension-registry.js +2 -2
  4. package/dist/remote-questions-config.js +2 -2
  5. package/dist/resource-loader.js +34 -1
  6. package/dist/resources/extensions/browser-tools/index.js +3 -1
  7. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  8. package/dist/resources/extensions/env-utils.js +29 -0
  9. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  10. package/dist/resources/extensions/github-sync/cli.js +284 -0
  11. package/dist/resources/extensions/github-sync/index.js +73 -0
  12. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  13. package/dist/resources/extensions/github-sync/sync.js +424 -0
  14. package/dist/resources/extensions/github-sync/templates.js +118 -0
  15. package/dist/resources/extensions/github-sync/types.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  18. package/dist/resources/extensions/gsd/auto-loop.js +636 -594
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  20. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  21. package/dist/resources/extensions/gsd/auto-start.js +7 -1
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
  23. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  24. package/dist/resources/extensions/gsd/auto.js +143 -96
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  27. package/dist/resources/extensions/gsd/commands.js +4 -2
  28. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  29. package/dist/resources/extensions/gsd/detection.js +1 -2
  30. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  31. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  32. package/dist/resources/extensions/gsd/doctor.js +20 -1
  33. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  34. package/dist/resources/extensions/gsd/export.js +1 -1
  35. package/dist/resources/extensions/gsd/files.js +48 -9
  36. package/dist/resources/extensions/gsd/forensics.js +1 -1
  37. package/dist/resources/extensions/gsd/git-service.js +30 -12
  38. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  39. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  40. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  41. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  42. package/dist/resources/extensions/gsd/index.js +24 -20
  43. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  44. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  46. package/dist/resources/extensions/gsd/paths.js +3 -0
  47. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  48. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  49. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  50. package/dist/resources/extensions/gsd/preferences.js +22 -11
  51. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  52. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  55. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  56. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  59. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  62. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  63. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  68. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  69. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  72. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  73. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  74. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  75. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  76. package/dist/resources/extensions/gsd/state.js +42 -23
  77. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  78. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  79. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  80. package/dist/resources/extensions/mcp-client/index.js +14 -1
  81. package/dist/resources/extensions/remote-questions/status.js +4 -1
  82. package/dist/resources/extensions/remote-questions/store.js +4 -1
  83. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  84. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  85. package/dist/resources/extensions/subagent/isolation.js +2 -1
  86. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  87. package/package.json +1 -1
  88. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  89. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  90. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  91. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  93. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  95. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  97. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  99. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/index.js +1 -1
  101. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  102. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  103. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  104. package/packages/pi-coding-agent/src/index.ts +1 -0
  105. package/src/resources/extensions/browser-tools/index.ts +3 -0
  106. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  107. package/src/resources/extensions/env-utils.ts +31 -0
  108. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  109. package/src/resources/extensions/github-sync/cli.ts +364 -0
  110. package/src/resources/extensions/github-sync/index.ts +93 -0
  111. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  112. package/src/resources/extensions/github-sync/sync.ts +556 -0
  113. package/src/resources/extensions/github-sync/templates.ts +183 -0
  114. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  115. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  116. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  117. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  118. package/src/resources/extensions/github-sync/types.ts +47 -0
  119. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  120. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  121. package/src/resources/extensions/gsd/auto-loop.ts +526 -545
  122. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  123. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  124. package/src/resources/extensions/gsd/auto-start.ts +11 -1
  125. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
  126. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  127. package/src/resources/extensions/gsd/auto.ts +139 -101
  128. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  129. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  130. package/src/resources/extensions/gsd/commands.ts +5 -3
  131. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  132. package/src/resources/extensions/gsd/detection.ts +2 -2
  133. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  134. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  135. package/src/resources/extensions/gsd/doctor.ts +22 -1
  136. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  137. package/src/resources/extensions/gsd/export.ts +1 -1
  138. package/src/resources/extensions/gsd/files.ts +51 -11
  139. package/src/resources/extensions/gsd/forensics.ts +1 -1
  140. package/src/resources/extensions/gsd/git-service.ts +44 -10
  141. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  142. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  143. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  144. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  145. package/src/resources/extensions/gsd/index.ts +24 -17
  146. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  147. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  148. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  149. package/src/resources/extensions/gsd/paths.ts +4 -0
  150. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  151. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  152. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  153. package/src/resources/extensions/gsd/preferences.ts +25 -11
  154. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  155. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  156. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  157. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  158. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  159. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  160. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  161. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  162. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  165. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  166. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  170. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  174. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  175. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  176. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  177. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  178. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  179. package/src/resources/extensions/gsd/state.ts +39 -21
  180. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  181. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  182. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  183. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  184. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  186. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  187. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  188. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  189. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  190. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  191. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  192. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  193. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  194. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  195. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  196. package/src/resources/extensions/gsd/types.ts +18 -1
  197. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  198. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  199. package/src/resources/extensions/mcp-client/index.ts +17 -1
  200. package/src/resources/extensions/remote-questions/status.ts +5 -1
  201. package/src/resources/extensions/remote-questions/store.ts +5 -1
  202. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  203. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  204. package/src/resources/extensions/subagent/isolation.ts +3 -1
  205. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  206. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  207. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  208. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  209. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  210. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  211. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  212. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  213. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  214. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  215. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  216. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  217. 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
- }