gsd-pi 2.38.0-dev.bc2e21e → 2.38.0-dev.d533afb

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 (99) hide show
  1. package/dist/resource-loader.js +34 -1
  2. package/dist/resources/extensions/github-sync/cli.js +284 -0
  3. package/dist/resources/extensions/github-sync/index.js +73 -0
  4. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  5. package/dist/resources/extensions/github-sync/sync.js +424 -0
  6. package/dist/resources/extensions/github-sync/templates.js +118 -0
  7. package/dist/resources/extensions/github-sync/types.js +7 -0
  8. package/dist/resources/extensions/gsd/auto/session.js +3 -23
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  10. package/dist/resources/extensions/gsd/auto-loop.js +292 -263
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
  12. package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
  13. package/dist/resources/extensions/gsd/auto-start.js +7 -1
  14. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  15. package/dist/resources/extensions/gsd/auto.js +143 -80
  16. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  17. package/dist/resources/extensions/gsd/commands.js +2 -1
  18. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  19. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  20. package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
  21. package/dist/resources/extensions/gsd/doctor.js +20 -1
  22. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  23. package/dist/resources/extensions/gsd/files.js +4 -0
  24. package/dist/resources/extensions/gsd/git-service.js +15 -12
  25. package/dist/resources/extensions/gsd/guided-flow.js +82 -32
  26. package/dist/resources/extensions/gsd/index.js +22 -19
  27. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  28. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  29. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  30. package/dist/resources/extensions/gsd/preferences-validation.js +58 -10
  31. package/dist/resources/extensions/gsd/preferences.js +4 -2
  32. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
  33. package/dist/resources/extensions/gsd/repo-identity.js +19 -3
  34. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  35. package/dist/resources/extensions/mcp-client/index.js +14 -1
  36. package/package.json +1 -1
  37. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  38. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  39. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  40. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  41. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  42. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  43. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  44. package/src/resources/extensions/github-sync/cli.ts +364 -0
  45. package/src/resources/extensions/github-sync/index.ts +93 -0
  46. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  47. package/src/resources/extensions/github-sync/sync.ts +556 -0
  48. package/src/resources/extensions/github-sync/templates.ts +183 -0
  49. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  50. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  51. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  52. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  53. package/src/resources/extensions/github-sync/types.ts +47 -0
  54. package/src/resources/extensions/gsd/auto/session.ts +3 -25
  55. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  56. package/src/resources/extensions/gsd/auto-loop.ts +382 -360
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  58. package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
  59. package/src/resources/extensions/gsd/auto-start.ts +11 -1
  60. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  61. package/src/resources/extensions/gsd/auto.ts +139 -86
  62. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  63. package/src/resources/extensions/gsd/commands.ts +2 -2
  64. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  65. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  66. package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
  67. package/src/resources/extensions/gsd/doctor.ts +22 -1
  68. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  69. package/src/resources/extensions/gsd/files.ts +3 -1
  70. package/src/resources/extensions/gsd/git-service.ts +20 -10
  71. package/src/resources/extensions/gsd/guided-flow.ts +110 -38
  72. package/src/resources/extensions/gsd/index.ts +21 -16
  73. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  74. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  75. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  76. package/src/resources/extensions/gsd/preferences-validation.ts +50 -10
  77. package/src/resources/extensions/gsd/preferences.ts +3 -2
  78. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
  79. package/src/resources/extensions/gsd/repo-identity.ts +20 -3
  80. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  82. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  83. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  84. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  85. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  86. package/src/resources/extensions/gsd/types.ts +0 -1
  87. package/src/resources/extensions/mcp-client/index.ts +17 -1
  88. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  89. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  90. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  91. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  92. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  93. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  94. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  95. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  96. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  97. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  98. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  99. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -1,508 +0,0 @@
1
- /**
2
- * Prompt Compressor — deterministic text compression for context reduction.
3
- *
4
- * Applies a series of lossless and near-lossless transformations to reduce
5
- * token count while preserving semantic meaning. No LLM calls, no external
6
- * dependencies. Sub-millisecond for typical prompt sizes.
7
- *
8
- * Compression techniques (applied in order):
9
- * 1. Redundant whitespace normalization
10
- * 2. Markdown formatting reduction (collapse verbose tables, lists)
11
- * 3. Common phrase abbreviation
12
- * 4. Repeated pattern deduplication
13
- * 5. Low-information content removal (empty sections, boilerplate)
14
- */
15
-
16
- export type CompressionLevel = "light" | "moderate" | "aggressive";
17
-
18
- export interface CompressionResult {
19
- /** The compressed content */
20
- content: string;
21
- /** Original character count */
22
- originalChars: number;
23
- /** Compressed character count */
24
- compressedChars: number;
25
- /** Savings percentage (0-100) */
26
- savingsPercent: number;
27
- /** Which compression level was applied */
28
- level: CompressionLevel;
29
- /** Number of transformations applied */
30
- transformationsApplied: number;
31
- }
32
-
33
- export interface CompressionOptions {
34
- /** Compression intensity. Default: "moderate" */
35
- level?: CompressionLevel;
36
- /** Preserve markdown headings (useful for section-boundary truncation). Default: true */
37
- preserveHeadings?: boolean;
38
- /** Preserve code blocks verbatim. Default: true */
39
- preserveCodeBlocks?: boolean;
40
- /** Target character count (compression stops when achieved). Default: no target */
41
- targetChars?: number;
42
- }
43
-
44
- // ─── Phrase Abbreviation Map ────────────────────────────────────────────────
45
-
46
- /**
47
- * Build a regex that matches a verbose phrase even when split across lines.
48
- * Whitespace between words is matched with \s+ to handle line wrapping.
49
- */
50
- function phraseRegex(phrase: string): RegExp {
51
- const words = phrase.split(/\s+/);
52
- const pattern = `\\b${words.join("\\s+")}\\b`;
53
- return new RegExp(pattern, "gi");
54
- }
55
-
56
- const VERBOSE_PHRASES: Array<[RegExp, string]> = [
57
- [phraseRegex("In order to"), "To"],
58
- [phraseRegex("It is important to note that"), "Note:"],
59
- [phraseRegex("As mentioned previously"), "(see above)"],
60
- [phraseRegex("The following"), "These"],
61
- [phraseRegex("In addition to"), "Also,"],
62
- [phraseRegex("Due to the fact that"), "Because"],
63
- [phraseRegex("At this point in time"), "Now"],
64
- [phraseRegex("For the purpose of"), "For"],
65
- [phraseRegex("In the event that"), "If"],
66
- [phraseRegex("With regard to"), "Re:"],
67
- [phraseRegex("Prior to"), "Before"],
68
- [phraseRegex("Subsequent to"), "After"],
69
- [phraseRegex("In accordance with"), "Per"],
70
- [phraseRegex("A number of"), "Several"],
71
- [phraseRegex("In the case of"), "For"],
72
- [phraseRegex("On the basis of"), "Based on"],
73
- ];
74
-
75
- // ─── Code Block Extraction ──────────────────────────────────────────────────
76
-
77
- interface ExtractedBlocks {
78
- text: string;
79
- blocks: Map<string, string>;
80
- }
81
-
82
- function extractCodeBlocks(content: string): ExtractedBlocks {
83
- const blocks = new Map<string, string>();
84
- let counter = 0;
85
-
86
- const text = content.replace(/```[\s\S]*?```/g, (match) => {
87
- const placeholder = `\x00CODEBLOCK_${counter++}\x00`;
88
- blocks.set(placeholder, match);
89
- return placeholder;
90
- });
91
-
92
- return { text, blocks };
93
- }
94
-
95
- function restoreCodeBlocks(text: string, blocks: Map<string, string>): string {
96
- let result = text;
97
- for (const [placeholder, block] of blocks) {
98
- result = result.replace(placeholder, block);
99
- }
100
- return result;
101
- }
102
-
103
- // ─── Light Transformations ──────────────────────────────────────────────────
104
-
105
- function normalizeWhitespace(content: string): string {
106
- // Collapse 3+ consecutive blank lines to 2
107
- let result = content.replace(/(\n\s*){3,}\n/g, "\n\n");
108
- // Trim trailing whitespace on every line
109
- result = result.replace(/[ \t]+$/gm, "");
110
- return result;
111
- }
112
-
113
- function removeMarkdownComments(content: string): string {
114
- return content.replace(/<!--[\s\S]*?-->/g, "");
115
- }
116
-
117
- function removeHorizontalRules(content: string): string {
118
- // Remove horizontal rules (---, ***, ___) that stand alone on a line
119
- return content.replace(/^\s*[-*_]{3,}\s*$/gm, "");
120
- }
121
-
122
- function collapseEmptyListItems(content: string): string {
123
- // Collapse repeated empty list items (- \n- \n- \n) into one
124
- return content.replace(/(^[ \t]*[-*+]\s*$\n){2,}/gm, "$1");
125
- }
126
-
127
- function applyLightTransformations(content: string): { content: string; count: number } {
128
- let count = 0;
129
- let result = content;
130
-
131
- const after1 = normalizeWhitespace(result);
132
- if (after1 !== result) count++;
133
- result = after1;
134
-
135
- const after2 = removeMarkdownComments(result);
136
- if (after2 !== result) count++;
137
- result = after2;
138
-
139
- const after3 = removeHorizontalRules(result);
140
- if (after3 !== result) count++;
141
- result = after3;
142
-
143
- const after4 = collapseEmptyListItems(result);
144
- if (after4 !== result) count++;
145
- result = after4;
146
-
147
- return { content: result, count };
148
- }
149
-
150
- // ─── Moderate Transformations ───────────────────────────────────────────────
151
-
152
- function abbreviateVerbosePhrases(content: string): { content: string; count: number } {
153
- let count = 0;
154
- let result = content;
155
-
156
- for (const [pattern, replacement] of VERBOSE_PHRASES) {
157
- const after = result.replace(pattern, replacement);
158
- if (after !== result) count++;
159
- result = after;
160
- }
161
-
162
- return { content: result, count };
163
- }
164
-
165
- function removeBoilerplateLines(content: string): string {
166
- const lines = content.split("\n");
167
- const filtered = lines.filter((line) => {
168
- const trimmed = line.trim();
169
- // Remove lines that are just N/A, (none), (empty), (not applicable)
170
- if (/^(?:N\/A|\(none\)|\(empty\)|\(not applicable\))$/i.test(trimmed)) {
171
- return false;
172
- }
173
- return true;
174
- });
175
- return filtered.join("\n");
176
- }
177
-
178
- function deduplicateConsecutiveLines(content: string): string {
179
- const lines = content.split("\n");
180
- const result: string[] = [];
181
-
182
- for (let i = 0; i < lines.length; i++) {
183
- if (i === 0 || lines[i] !== lines[i - 1] || lines[i].trim() === "") {
184
- result.push(lines[i]);
185
- }
186
- }
187
-
188
- return result.join("\n");
189
- }
190
-
191
- function collapseTableFormatting(content: string): string {
192
- // Remove excessive padding in markdown table cells
193
- // Matches table rows like | cell | cell | and collapses to | cell | cell |
194
- return content.replace(/\|[ \t]{2,}([^|\n]*?)[ \t]{2,}\|/g, (_, cellContent) => {
195
- return `| ${cellContent.trim()} |`;
196
- });
197
- }
198
-
199
- function applyModerateTransformations(content: string): { content: string; count: number } {
200
- let count = 0;
201
- let result = content;
202
-
203
- const phraseResult = abbreviateVerbosePhrases(result);
204
- count += phraseResult.count;
205
- result = phraseResult.content;
206
-
207
- const after1 = removeBoilerplateLines(result);
208
- if (after1 !== result) count++;
209
- result = after1;
210
-
211
- const after2 = deduplicateConsecutiveLines(result);
212
- if (after2 !== result) count++;
213
- result = after2;
214
-
215
- const after3 = collapseTableFormatting(result);
216
- if (after3 !== result) count++;
217
- result = after3;
218
-
219
- return { content: result, count };
220
- }
221
-
222
- // ─── Aggressive Transformations ─────────────────────────────────────────────
223
-
224
- function removeMarkdownEmphasis(content: string): string {
225
- // Bold: **text** or __text__
226
- let result = content.replace(/\*\*(.+?)\*\*/g, "$1");
227
- result = result.replace(/__(.+?)__/g, "$1");
228
- // Italic: *text* or _text_ (single, not inside words)
229
- result = result.replace(/(?<!\w)\*([^*\n]+?)\*(?!\w)/g, "$1");
230
- result = result.replace(/(?<!\w)_([^_\n]+?)_(?!\w)/g, "$1");
231
- return result;
232
- }
233
-
234
- function removeMarkdownLinks(content: string): string {
235
- // [text](url) → text
236
- return content.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
237
- }
238
-
239
- function truncateLongLines(content: string): string {
240
- const lines = content.split("\n");
241
- const result = lines.map((line) => {
242
- if (line.length <= 300) return line;
243
- // Find a sentence boundary (. ! ?) near the 300 char mark
244
- const truncateZone = line.slice(0, 300);
245
- const lastSentenceEnd = Math.max(
246
- truncateZone.lastIndexOf(". "),
247
- truncateZone.lastIndexOf("! "),
248
- truncateZone.lastIndexOf("? "),
249
- );
250
- if (lastSentenceEnd > 150) {
251
- return line.slice(0, lastSentenceEnd + 1);
252
- }
253
- // Fallback: cut at last space before 300
254
- const lastSpace = truncateZone.lastIndexOf(" ");
255
- if (lastSpace > 150) {
256
- return line.slice(0, lastSpace);
257
- }
258
- return truncateZone;
259
- });
260
- return result.join("\n");
261
- }
262
-
263
- function removeBulletMarkers(content: string): string {
264
- // Remove bullet markers: - , * , + , numbered (1. 2. etc)
265
- return content.replace(/^[ \t]*(?:[-*+]|\d+\.)\s+/gm, "");
266
- }
267
-
268
- function removeBlockquoteMarkers(content: string): string {
269
- return content.replace(/^[ \t]*>+\s?/gm, "");
270
- }
271
-
272
- function deduplicateStructuralPatterns(content: string): string {
273
- // Deduplicate consecutive lines that match the same "Key: value" pattern
274
- const lines = content.split("\n");
275
- const result: string[] = [];
276
- const seen = new Set<string>();
277
- let lastWasStructural = false;
278
-
279
- for (const line of lines) {
280
- const trimmed = line.trim();
281
- // Detect structural patterns: "Key: value"
282
- const structMatch = trimmed.match(/^(\w[\w\s]*?):\s+(.+)$/);
283
- if (structMatch) {
284
- if (seen.has(trimmed)) {
285
- lastWasStructural = true;
286
- continue;
287
- }
288
- seen.add(trimmed);
289
- lastWasStructural = true;
290
- } else {
291
- // Reset seen set when structural block ends
292
- if (!lastWasStructural || trimmed === "") {
293
- seen.clear();
294
- }
295
- lastWasStructural = false;
296
- }
297
- result.push(line);
298
- }
299
-
300
- return result.join("\n");
301
- }
302
-
303
- function applyAggressiveTransformations(
304
- content: string,
305
- preserveHeadings: boolean,
306
- ): { content: string; count: number } {
307
- let count = 0;
308
- let result = content;
309
-
310
- const after1 = removeMarkdownEmphasis(result);
311
- if (after1 !== result) count++;
312
- result = after1;
313
-
314
- const after2 = removeMarkdownLinks(result);
315
- if (after2 !== result) count++;
316
- result = after2;
317
-
318
- const after3 = truncateLongLines(result);
319
- if (after3 !== result) count++;
320
- result = after3;
321
-
322
- const after4 = removeBulletMarkers(result);
323
- if (after4 !== result) count++;
324
- result = after4;
325
-
326
- const after5 = removeBlockquoteMarkers(result);
327
- if (after5 !== result) count++;
328
- result = after5;
329
-
330
- const after6 = deduplicateStructuralPatterns(result);
331
- if (after6 !== result) count++;
332
- result = after6;
333
-
334
- return { content: result, count };
335
- }
336
-
337
- // ─── Heading Preservation ───────────────────────────────────────────────────
338
-
339
- interface ExtractedHeadings {
340
- text: string;
341
- headings: Map<string, string>;
342
- }
343
-
344
- function extractHeadings(content: string): ExtractedHeadings {
345
- const headings = new Map<string, string>();
346
- let counter = 0;
347
-
348
- const text = content.replace(/^(#{1,6}\s.+)$/gm, (match) => {
349
- const placeholder = `\x00HEADING_${counter++}\x00`;
350
- headings.set(placeholder, match);
351
- return placeholder;
352
- });
353
-
354
- return { text, headings };
355
- }
356
-
357
- function restoreHeadings(text: string, headings: Map<string, string>): string {
358
- let result = text;
359
- for (const [placeholder, heading] of headings) {
360
- result = result.replace(placeholder, heading);
361
- }
362
- return result;
363
- }
364
-
365
- // ─── Public API ─────────────────────────────────────────────────────────────
366
-
367
- /**
368
- * Compress prompt content using deterministic text transformations.
369
- */
370
- export function compressPrompt(content: string, options?: CompressionOptions): CompressionResult {
371
- const level = options?.level ?? "moderate";
372
- const preserveHeadings = options?.preserveHeadings ?? true;
373
- const preserveCodeBlocks = options?.preserveCodeBlocks ?? true;
374
-
375
- if (content === "") {
376
- return {
377
- content: "",
378
- originalChars: 0,
379
- compressedChars: 0,
380
- savingsPercent: 0,
381
- level,
382
- transformationsApplied: 0,
383
- };
384
- }
385
-
386
- const originalChars = content.length;
387
- let working = content;
388
- let totalTransformations = 0;
389
-
390
- // Extract code blocks if preserving
391
- let codeBlocks: Map<string, string> | null = null;
392
- if (preserveCodeBlocks) {
393
- const extracted = extractCodeBlocks(working);
394
- working = extracted.text;
395
- codeBlocks = extracted.blocks;
396
- }
397
-
398
- // Extract headings if preserving
399
- let headings: Map<string, string> | null = null;
400
- if (preserveHeadings) {
401
- const extracted = extractHeadings(working);
402
- working = extracted.text;
403
- headings = extracted.headings;
404
- }
405
-
406
- // Apply light transformations (always)
407
- const lightResult = applyLightTransformations(working);
408
- working = lightResult.content;
409
- totalTransformations += lightResult.count;
410
-
411
- // Check target
412
- if (options?.targetChars && getRestoredLength(working, codeBlocks, headings) <= options.targetChars) {
413
- return buildResult(working, originalChars, level, totalTransformations, codeBlocks, headings);
414
- }
415
-
416
- // Apply moderate transformations
417
- if (level === "moderate" || level === "aggressive") {
418
- const modResult = applyModerateTransformations(working);
419
- working = modResult.content;
420
- totalTransformations += modResult.count;
421
-
422
- if (options?.targetChars && getRestoredLength(working, codeBlocks, headings) <= options.targetChars) {
423
- return buildResult(working, originalChars, level, totalTransformations, codeBlocks, headings);
424
- }
425
- }
426
-
427
- // Apply aggressive transformations
428
- if (level === "aggressive") {
429
- const aggResult = applyAggressiveTransformations(working, preserveHeadings);
430
- working = aggResult.content;
431
- totalTransformations += aggResult.count;
432
- }
433
-
434
- return buildResult(working, originalChars, level, totalTransformations, codeBlocks, headings);
435
- }
436
-
437
- /**
438
- * Compress with a target size — applies progressively more aggressive
439
- * compression until the target is reached or all transformations exhausted.
440
- */
441
- export function compressToTarget(content: string, targetChars: number): CompressionResult {
442
- if (content.length <= targetChars) {
443
- return {
444
- content,
445
- originalChars: content.length,
446
- compressedChars: content.length,
447
- savingsPercent: 0,
448
- level: "light",
449
- transformationsApplied: 0,
450
- };
451
- }
452
-
453
- const levels: CompressionLevel[] = ["light", "moderate", "aggressive"];
454
-
455
- for (const level of levels) {
456
- const result = compressPrompt(content, { level, targetChars });
457
- if (result.compressedChars <= targetChars) {
458
- return result;
459
- }
460
- // If aggressive and still over target, return best effort
461
- if (level === "aggressive") {
462
- return result;
463
- }
464
- }
465
-
466
- // Unreachable, but satisfy TypeScript
467
- return compressPrompt(content, { level: "aggressive" });
468
- }
469
-
470
- // ─── Helpers ────────────────────────────────────────────────────────────────
471
-
472
- function getRestoredLength(
473
- text: string,
474
- codeBlocks: Map<string, string> | null,
475
- headings: Map<string, string> | null,
476
- ): number {
477
- let result = text;
478
- if (headings) result = restoreHeadings(result, headings);
479
- if (codeBlocks) result = restoreCodeBlocks(result, codeBlocks);
480
- return result.length;
481
- }
482
-
483
- function buildResult(
484
- working: string,
485
- originalChars: number,
486
- level: CompressionLevel,
487
- transformationsApplied: number,
488
- codeBlocks: Map<string, string> | null,
489
- headings: Map<string, string> | null,
490
- ): CompressionResult {
491
- let content = working;
492
- if (headings) content = restoreHeadings(content, headings);
493
- if (codeBlocks) content = restoreCodeBlocks(content, codeBlocks);
494
-
495
- const compressedChars = content.length;
496
- const savingsPercent = originalChars > 0
497
- ? Math.round(((originalChars - compressedChars) / originalChars) * 10000) / 100
498
- : 0;
499
-
500
- return {
501
- content,
502
- originalChars,
503
- compressedChars,
504
- savingsPercent,
505
- level,
506
- transformationsApplied,
507
- };
508
- }