ai-mind-map 1.1.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.
- package/LICENSE +21 -0
- package/README.md +554 -0
- package/dist/change-tracker/change-log.d.ts +160 -0
- package/dist/change-tracker/change-log.d.ts.map +1 -0
- package/dist/change-tracker/change-log.js +507 -0
- package/dist/change-tracker/change-log.js.map +1 -0
- package/dist/change-tracker/diff-engine.d.ts +149 -0
- package/dist/change-tracker/diff-engine.d.ts.map +1 -0
- package/dist/change-tracker/diff-engine.js +530 -0
- package/dist/change-tracker/diff-engine.js.map +1 -0
- package/dist/change-tracker/watcher.d.ts +137 -0
- package/dist/change-tracker/watcher.d.ts.map +1 -0
- package/dist/change-tracker/watcher.js +300 -0
- package/dist/change-tracker/watcher.js.map +1 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +937 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +222 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compressor.d.ts +49 -0
- package/dist/context/compressor.d.ts.map +1 -0
- package/dist/context/compressor.js +769 -0
- package/dist/context/compressor.js.map +1 -0
- package/dist/context/progressive-disclosure.d.ts +71 -0
- package/dist/context/progressive-disclosure.d.ts.map +1 -0
- package/dist/context/progressive-disclosure.js +470 -0
- package/dist/context/progressive-disclosure.js.map +1 -0
- package/dist/context/token-budget.d.ts +121 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +282 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +944 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +66 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +946 -0
- package/dist/install.js.map +1 -0
- package/dist/knowledge-graph/architecture.d.ts +213 -0
- package/dist/knowledge-graph/architecture.d.ts.map +1 -0
- package/dist/knowledge-graph/architecture.js +585 -0
- package/dist/knowledge-graph/architecture.js.map +1 -0
- package/dist/knowledge-graph/cypher.d.ts +113 -0
- package/dist/knowledge-graph/cypher.d.ts.map +1 -0
- package/dist/knowledge-graph/cypher.js +1051 -0
- package/dist/knowledge-graph/cypher.js.map +1 -0
- package/dist/knowledge-graph/dead-code.d.ts +121 -0
- package/dist/knowledge-graph/dead-code.d.ts.map +1 -0
- package/dist/knowledge-graph/dead-code.js +331 -0
- package/dist/knowledge-graph/dead-code.js.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts +167 -0
- package/dist/knowledge-graph/flow-analyzer.d.ts.map +1 -0
- package/dist/knowledge-graph/flow-analyzer.js +739 -0
- package/dist/knowledge-graph/flow-analyzer.js.map +1 -0
- package/dist/knowledge-graph/graph.d.ts +291 -0
- package/dist/knowledge-graph/graph.d.ts.map +1 -0
- package/dist/knowledge-graph/graph.js +978 -0
- package/dist/knowledge-graph/graph.js.map +1 -0
- package/dist/knowledge-graph/index.d.ts +17 -0
- package/dist/knowledge-graph/index.d.ts.map +1 -0
- package/dist/knowledge-graph/index.js +14 -0
- package/dist/knowledge-graph/index.js.map +1 -0
- package/dist/knowledge-graph/indexer.d.ts +112 -0
- package/dist/knowledge-graph/indexer.d.ts.map +1 -0
- package/dist/knowledge-graph/indexer.js +506 -0
- package/dist/knowledge-graph/indexer.js.map +1 -0
- package/dist/knowledge-graph/pagerank.d.ts +141 -0
- package/dist/knowledge-graph/pagerank.d.ts.map +1 -0
- package/dist/knowledge-graph/pagerank.js +493 -0
- package/dist/knowledge-graph/pagerank.js.map +1 -0
- package/dist/knowledge-graph/parser.d.ts +55 -0
- package/dist/knowledge-graph/parser.d.ts.map +1 -0
- package/dist/knowledge-graph/parser.js +1090 -0
- package/dist/knowledge-graph/parser.js.map +1 -0
- package/dist/knowledge-graph/snapshot.d.ts +107 -0
- package/dist/knowledge-graph/snapshot.d.ts.map +1 -0
- package/dist/knowledge-graph/snapshot.js +435 -0
- package/dist/knowledge-graph/snapshot.js.map +1 -0
- package/dist/memory/decision-log.d.ts +151 -0
- package/dist/memory/decision-log.d.ts.map +1 -0
- package/dist/memory/decision-log.js +482 -0
- package/dist/memory/decision-log.js.map +1 -0
- package/dist/memory/persistent-memory.d.ts +182 -0
- package/dist/memory/persistent-memory.d.ts.map +1 -0
- package/dist/memory/persistent-memory.js +579 -0
- package/dist/memory/persistent-memory.js.map +1 -0
- package/dist/memory/session-memory.d.ts +165 -0
- package/dist/memory/session-memory.d.ts.map +1 -0
- package/dist/memory/session-memory.js +382 -0
- package/dist/memory/session-memory.js.map +1 -0
- package/dist/stress-test.d.ts +10 -0
- package/dist/stress-test.d.ts.map +1 -0
- package/dist/stress-test.js +258 -0
- package/dist/stress-test.js.map +1 -0
- package/dist/tools/advanced-tools.d.ts +32 -0
- package/dist/tools/advanced-tools.d.ts.map +1 -0
- package/dist/tools/advanced-tools.js +480 -0
- package/dist/tools/advanced-tools.js.map +1 -0
- package/dist/tools/change-tools.d.ts +76 -0
- package/dist/tools/change-tools.d.ts.map +1 -0
- package/dist/tools/change-tools.js +93 -0
- package/dist/tools/change-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +68 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +141 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/debug-tools.d.ts +25 -0
- package/dist/tools/debug-tools.d.ts.map +1 -0
- package/dist/tools/debug-tools.js +286 -0
- package/dist/tools/debug-tools.js.map +1 -0
- package/dist/tools/evolving-tools.d.ts +23 -0
- package/dist/tools/evolving-tools.d.ts.map +1 -0
- package/dist/tools/evolving-tools.js +207 -0
- package/dist/tools/evolving-tools.js.map +1 -0
- package/dist/tools/flow-tools.d.ts +24 -0
- package/dist/tools/flow-tools.d.ts.map +1 -0
- package/dist/tools/flow-tools.js +265 -0
- package/dist/tools/flow-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +71 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +165 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +62 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +195 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/smart-tools.d.ts +23 -0
- package/dist/tools/smart-tools.d.ts.map +1 -0
- package/dist/tools/smart-tools.js +482 -0
- package/dist/tools/smart-tools.js.map +1 -0
- package/dist/tools/snapshot-tools.d.ts +19 -0
- package/dist/tools/snapshot-tools.d.ts.map +1 -0
- package/dist/tools/snapshot-tools.js +149 -0
- package/dist/tools/snapshot-tools.js.map +1 -0
- package/dist/types.d.ts +181 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +142 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/token-counter.d.ts +51 -0
- package/dist/utils/token-counter.d.ts.map +1 -0
- package/dist/utils/token-counter.js +181 -0
- package/dist/utils/token-counter.js.map +1 -0
- package/install.ps1 +321 -0
- package/install.sh +345 -0
- package/package.json +94 -0
- package/setup.bat +62 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Mind Map — Content-Aware Compression Engine
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent content compression that understands different
|
|
5
|
+
* content types and applies appropriate strategies.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by context-mode (98% reduction) and context-mem's content-aware
|
|
8
|
+
* summarisers. Each content type gets a specialised compressor that
|
|
9
|
+
* preserves the most semantically valuable information while aggressively
|
|
10
|
+
* stripping noise.
|
|
11
|
+
*
|
|
12
|
+
* Three compression levels:
|
|
13
|
+
* - `minimal` – whitespace / comment cleanup
|
|
14
|
+
* - `moderate` – content-specific compression
|
|
15
|
+
* - `aggressive` – maximum reduction (signatures-only, errors-only, etc.)
|
|
16
|
+
*/
|
|
17
|
+
import { estimateTokens } from '../utils/token-counter.js';
|
|
18
|
+
import { Logger } from '../utils/logger.js';
|
|
19
|
+
const logger = new Logger({ prefix: 'Compressor' });
|
|
20
|
+
// ── Public API ───────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Auto-detect the content type of arbitrary text.
|
|
23
|
+
*
|
|
24
|
+
* Applies a series of heuristics in priority order. Returns
|
|
25
|
+
* `'plain_text'` as a fallback.
|
|
26
|
+
*/
|
|
27
|
+
export function detectContentType(text) {
|
|
28
|
+
if (!text || text.trim().length === 0) {
|
|
29
|
+
return 'plain_text';
|
|
30
|
+
}
|
|
31
|
+
const trimmed = text.trim();
|
|
32
|
+
// ── Diff ──────────────────────────────────────────────
|
|
33
|
+
if (trimmed.startsWith('diff --git') ||
|
|
34
|
+
trimmed.startsWith('--- a/') ||
|
|
35
|
+
trimmed.startsWith('+++ b/') ||
|
|
36
|
+
/^@@\s+-\d+/.test(trimmed)) {
|
|
37
|
+
return 'diff';
|
|
38
|
+
}
|
|
39
|
+
// ── Stack trace ───────────────────────────────────────
|
|
40
|
+
if (/^\s*at\s+.+\(.+:\d+:\d+\)/m.test(trimmed) || // Node / JS
|
|
41
|
+
/^\s*File ".+", line \d+/m.test(trimmed) || // Python
|
|
42
|
+
/^\s+at .+\(.*\.java:\d+\)/m.test(trimmed) || // Java
|
|
43
|
+
/^Traceback \(most recent call last\)/m.test(trimmed) || // Python header
|
|
44
|
+
/panic:.*goroutine/m.test(trimmed) // Go
|
|
45
|
+
) {
|
|
46
|
+
return 'stack_trace';
|
|
47
|
+
}
|
|
48
|
+
// ── Test output ───────────────────────────────────────
|
|
49
|
+
if (/^(PASS|FAIL|ok|not ok|Tests:)/m.test(trimmed) ||
|
|
50
|
+
/\d+ (passing|failing|passed|failed)/m.test(trimmed) ||
|
|
51
|
+
/^(Test Suite|RUNS?|✓|✗|✘|●)/m.test(trimmed) ||
|
|
52
|
+
/(PASSED|FAILED|ERROR)\s+\[/m.test(trimmed) ||
|
|
53
|
+
/^={3,}\s*(FAILURES|ERRORS)\s*={3,}/m.test(trimmed)) {
|
|
54
|
+
return 'test_output';
|
|
55
|
+
}
|
|
56
|
+
// ── Build log ─────────────────────────────────────────
|
|
57
|
+
if (/^(warning|error|note)\s*:/mi.test(trimmed) ||
|
|
58
|
+
/^\[\d{2}:\d{2}:\d{2}\]/m.test(trimmed) ||
|
|
59
|
+
/^(>|\\$)\s+(tsc|npm|yarn|pnpm|make|cmake|gradle|cargo|go\s+build)/m.test(trimmed) ||
|
|
60
|
+
/^(BUILD|COMPILE)\s+(SUCCEEDED|FAILED)/m.test(trimmed) ||
|
|
61
|
+
/error TS\d+:/m.test(trimmed)) {
|
|
62
|
+
return 'build_log';
|
|
63
|
+
}
|
|
64
|
+
// ── JSON data ─────────────────────────────────────────
|
|
65
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
66
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
67
|
+
try {
|
|
68
|
+
JSON.parse(trimmed);
|
|
69
|
+
return 'json_data';
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Not valid JSON – fall through.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ── Config file ───────────────────────────────────────
|
|
76
|
+
if (/^\[[\w.-]+\]\s*$/m.test(trimmed) || // INI/TOML section
|
|
77
|
+
/^[\w_][\w._-]*\s*[:=]\s*/m.test(trimmed) || // key=value
|
|
78
|
+
/^(apiVersion|kind|metadata|spec):/m.test(trimmed) // K8s YAML
|
|
79
|
+
) {
|
|
80
|
+
return 'config_file';
|
|
81
|
+
}
|
|
82
|
+
// ── Markdown ──────────────────────────────────────────
|
|
83
|
+
if (/^#{1,6}\s+.+/m.test(trimmed) ||
|
|
84
|
+
/^\*{3,}$|^-{3,}$/m.test(trimmed) ||
|
|
85
|
+
/^\s*[-*+]\s+.+/m.test(trimmed) && /^#{1,6}\s/m.test(trimmed)) {
|
|
86
|
+
return 'markdown';
|
|
87
|
+
}
|
|
88
|
+
// ── Source code ───────────────────────────────────────
|
|
89
|
+
if (/^(import|export|from|require|use|using|package|module)\s/m.test(trimmed) ||
|
|
90
|
+
/^(function|class|interface|type|enum|const|let|var|def|fn|pub|func)\s/m.test(trimmed) ||
|
|
91
|
+
/^(public|private|protected|static|abstract|async)\s/m.test(trimmed) ||
|
|
92
|
+
/[{};]\s*$/m.test(trimmed) ||
|
|
93
|
+
/^\s*(\/\/|#|\/\*|\*|""")/m.test(trimmed)) {
|
|
94
|
+
return 'source_code';
|
|
95
|
+
}
|
|
96
|
+
return 'plain_text';
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compress content intelligently based on its type and the chosen level.
|
|
100
|
+
*
|
|
101
|
+
* @param text - Raw content to compress.
|
|
102
|
+
* @param level - Compression level (`minimal`, `moderate`, `aggressive`).
|
|
103
|
+
* @param contentType - If known; otherwise auto-detected.
|
|
104
|
+
* @returns A {@link CompressionResult} with the compressed text and metrics.
|
|
105
|
+
*/
|
|
106
|
+
export function compress(text, level = 'moderate', contentType) {
|
|
107
|
+
const detectedType = contentType ?? detectContentType(text);
|
|
108
|
+
const originalTokens = estimateTokens(text);
|
|
109
|
+
if (!text || text.trim().length === 0) {
|
|
110
|
+
return buildResult('', originalTokens, detectedType, level);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
let compressed;
|
|
114
|
+
switch (level) {
|
|
115
|
+
case 'minimal':
|
|
116
|
+
compressed = applyMinimal(text);
|
|
117
|
+
break;
|
|
118
|
+
case 'moderate':
|
|
119
|
+
compressed = applyModerate(text, detectedType);
|
|
120
|
+
break;
|
|
121
|
+
case 'aggressive':
|
|
122
|
+
compressed = applyAggressive(text, detectedType);
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
compressed = applyModerate(text, detectedType);
|
|
126
|
+
}
|
|
127
|
+
return buildResult(compressed, originalTokens, detectedType, level);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
logger.warn('Compression failed, returning minimally cleaned text', err);
|
|
131
|
+
return buildResult(applyMinimal(text), originalTokens, detectedType, level);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ── Minimal compression ──────────────────────────────────────
|
|
135
|
+
/** Level 1: whitespace / comment cleanup only. */
|
|
136
|
+
function applyMinimal(text) {
|
|
137
|
+
let result = text;
|
|
138
|
+
// Remove trailing whitespace on every line.
|
|
139
|
+
result = result.replace(/[ \t]+$/gm, '');
|
|
140
|
+
// Collapse multiple blank lines into at most one.
|
|
141
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
142
|
+
// Reduce block-comment verbosity (collapse multi-line `*` padding).
|
|
143
|
+
result = result.replace(/^[ \t]*\*[ \t]*\n/gm, '');
|
|
144
|
+
return result.trim();
|
|
145
|
+
}
|
|
146
|
+
// ── Moderate compression ─────────────────────────────────────
|
|
147
|
+
/** Level 2: content-type-specific compression. */
|
|
148
|
+
function applyModerate(text, type) {
|
|
149
|
+
// Always start with minimal cleanup.
|
|
150
|
+
const cleaned = applyMinimal(text);
|
|
151
|
+
switch (type) {
|
|
152
|
+
case 'source_code':
|
|
153
|
+
return compressSourceModerate(cleaned);
|
|
154
|
+
case 'build_log':
|
|
155
|
+
return compressBuildLog(cleaned, false);
|
|
156
|
+
case 'test_output':
|
|
157
|
+
return compressTestOutput(cleaned, false);
|
|
158
|
+
case 'stack_trace':
|
|
159
|
+
return compressStackTrace(cleaned, false);
|
|
160
|
+
case 'json_data':
|
|
161
|
+
return compressJson(cleaned, false);
|
|
162
|
+
case 'markdown':
|
|
163
|
+
return compressMarkdown(cleaned, false);
|
|
164
|
+
case 'diff':
|
|
165
|
+
return compressDiff(cleaned, false);
|
|
166
|
+
case 'config_file':
|
|
167
|
+
return compressConfig(cleaned, false);
|
|
168
|
+
case 'plain_text':
|
|
169
|
+
default:
|
|
170
|
+
return cleaned;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// ── Aggressive compression ───────────────────────────────────
|
|
174
|
+
/** Level 3: maximum reduction. */
|
|
175
|
+
function applyAggressive(text, type) {
|
|
176
|
+
const cleaned = applyMinimal(text);
|
|
177
|
+
switch (type) {
|
|
178
|
+
case 'source_code':
|
|
179
|
+
return compressSourceAggressive(cleaned);
|
|
180
|
+
case 'build_log':
|
|
181
|
+
return compressBuildLog(cleaned, true);
|
|
182
|
+
case 'test_output':
|
|
183
|
+
return compressTestOutput(cleaned, true);
|
|
184
|
+
case 'stack_trace':
|
|
185
|
+
return compressStackTrace(cleaned, true);
|
|
186
|
+
case 'json_data':
|
|
187
|
+
return compressJson(cleaned, true);
|
|
188
|
+
case 'markdown':
|
|
189
|
+
return compressMarkdown(cleaned, true);
|
|
190
|
+
case 'diff':
|
|
191
|
+
return compressDiff(cleaned, true);
|
|
192
|
+
case 'config_file':
|
|
193
|
+
return compressConfig(cleaned, true);
|
|
194
|
+
case 'plain_text':
|
|
195
|
+
default:
|
|
196
|
+
return compressPlainTextAggressive(cleaned);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// ================================================================
|
|
200
|
+
// Content-specific compressors
|
|
201
|
+
// ================================================================
|
|
202
|
+
// ── Source Code ──────────────────────────────────────────────
|
|
203
|
+
/**
|
|
204
|
+
* Moderate source-code compression:
|
|
205
|
+
* - Keep signatures & doc comments.
|
|
206
|
+
* - Replace function/method bodies with `{ ... }`.
|
|
207
|
+
* - Keep import/export statements.
|
|
208
|
+
*/
|
|
209
|
+
function compressSourceModerate(text) {
|
|
210
|
+
const lines = text.split('\n');
|
|
211
|
+
const output = [];
|
|
212
|
+
let braceDepth = 0;
|
|
213
|
+
let insideBody = false;
|
|
214
|
+
let bodyStartDepth = 0;
|
|
215
|
+
let lastWasSignature = false;
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const trimmedLine = line.trim();
|
|
218
|
+
// Always keep imports, exports, and empty structural lines.
|
|
219
|
+
if (isImportOrExport(trimmedLine)) {
|
|
220
|
+
output.push(line);
|
|
221
|
+
lastWasSignature = false;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// Keep doc comments / JSDoc.
|
|
225
|
+
if (isDocComment(trimmedLine)) {
|
|
226
|
+
output.push(line);
|
|
227
|
+
lastWasSignature = false;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// Keep single-line type declarations, interfaces with no body.
|
|
231
|
+
if (isTypeDeclaration(trimmedLine)) {
|
|
232
|
+
output.push(line);
|
|
233
|
+
lastWasSignature = false;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Detect function / class / method signatures.
|
|
237
|
+
if (!insideBody && isSignatureLine(trimmedLine)) {
|
|
238
|
+
output.push(line);
|
|
239
|
+
lastWasSignature = true;
|
|
240
|
+
// If the signature opens a body on the same line…
|
|
241
|
+
const opens = countChar(trimmedLine, '{');
|
|
242
|
+
const closes = countChar(trimmedLine, '}');
|
|
243
|
+
if (opens > closes) {
|
|
244
|
+
insideBody = true;
|
|
245
|
+
bodyStartDepth = braceDepth + opens - closes;
|
|
246
|
+
braceDepth += opens - closes;
|
|
247
|
+
// Replace the body start.
|
|
248
|
+
if (!trimmedLine.endsWith('{')) {
|
|
249
|
+
// Body opener is embedded – keep the line as-is.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// Track brace depth.
|
|
255
|
+
const opens = countChar(trimmedLine, '{');
|
|
256
|
+
const closes = countChar(trimmedLine, '}');
|
|
257
|
+
braceDepth += opens - closes;
|
|
258
|
+
if (insideBody) {
|
|
259
|
+
if (braceDepth < bodyStartDepth) {
|
|
260
|
+
// Body closed — emit placeholder and closing brace.
|
|
261
|
+
if (lastWasSignature) {
|
|
262
|
+
output.push(`${indentOf(line)} // ... implementation`);
|
|
263
|
+
}
|
|
264
|
+
output.push(line); // closing brace
|
|
265
|
+
insideBody = false;
|
|
266
|
+
lastWasSignature = false;
|
|
267
|
+
}
|
|
268
|
+
// Skip lines inside the body.
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// Keep everything else (structural code outside bodies).
|
|
272
|
+
output.push(line);
|
|
273
|
+
lastWasSignature = false;
|
|
274
|
+
}
|
|
275
|
+
return output.join('\n');
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Aggressive source-code compression:
|
|
279
|
+
* - Signatures only (no doc comments, no implementations).
|
|
280
|
+
*/
|
|
281
|
+
function compressSourceAggressive(text) {
|
|
282
|
+
const lines = text.split('\n');
|
|
283
|
+
const output = [];
|
|
284
|
+
let braceDepth = 0;
|
|
285
|
+
let insideBody = false;
|
|
286
|
+
let bodyStartDepth = 0;
|
|
287
|
+
for (const line of lines) {
|
|
288
|
+
const trimmedLine = line.trim();
|
|
289
|
+
// Keep imports (collapsed).
|
|
290
|
+
if (isImportOrExport(trimmedLine)) {
|
|
291
|
+
output.push(line);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
// Skip all comments in aggressive mode.
|
|
295
|
+
if (isComment(trimmedLine)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
// Type declarations – keep.
|
|
299
|
+
if (isTypeDeclaration(trimmedLine)) {
|
|
300
|
+
output.push(line);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (!insideBody && isSignatureLine(trimmedLine)) {
|
|
304
|
+
// Emit signature with ellipsis body.
|
|
305
|
+
const opens = countChar(trimmedLine, '{');
|
|
306
|
+
const closes = countChar(trimmedLine, '}');
|
|
307
|
+
if (opens > closes) {
|
|
308
|
+
insideBody = true;
|
|
309
|
+
bodyStartDepth = braceDepth + opens - closes;
|
|
310
|
+
braceDepth += opens - closes;
|
|
311
|
+
}
|
|
312
|
+
// Emit a one-liner skeleton.
|
|
313
|
+
const signaturePart = trimmedLine.replace(/\{.*$/, '').trimEnd();
|
|
314
|
+
output.push(`${indentOf(line)}${signaturePart} { ... }`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const opens = countChar(trimmedLine, '{');
|
|
318
|
+
const closes = countChar(trimmedLine, '}');
|
|
319
|
+
braceDepth += opens - closes;
|
|
320
|
+
if (insideBody) {
|
|
321
|
+
if (braceDepth < bodyStartDepth) {
|
|
322
|
+
insideBody = false;
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
// Keep structural lines (class declarations, etc.)
|
|
327
|
+
if (trimmedLine.length > 0) {
|
|
328
|
+
output.push(line);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return output.join('\n');
|
|
332
|
+
}
|
|
333
|
+
// ── Build Log ───────────────────────────────────────────────
|
|
334
|
+
function compressBuildLog(text, aggressive) {
|
|
335
|
+
const lines = text.split('\n');
|
|
336
|
+
const seen = new Set();
|
|
337
|
+
const output = [];
|
|
338
|
+
for (const line of lines) {
|
|
339
|
+
const trimmedLine = line.trim();
|
|
340
|
+
if (!trimmedLine)
|
|
341
|
+
continue;
|
|
342
|
+
// Strip timestamps.
|
|
343
|
+
const noTimestamp = trimmedLine
|
|
344
|
+
.replace(/^\[\d{2}:\d{2}:\d{2}[^\]]*\]\s*/, '')
|
|
345
|
+
.replace(/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[^\s]*\s*/, '');
|
|
346
|
+
// Keep errors.
|
|
347
|
+
if (/\b(error|fatal|exception)\b/i.test(noTimestamp)) {
|
|
348
|
+
if (!seen.has(noTimestamp)) {
|
|
349
|
+
seen.add(noTimestamp);
|
|
350
|
+
output.push(noTimestamp);
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
// Keep warnings (unless aggressive).
|
|
355
|
+
if (!aggressive && /\b(warning|warn)\b/i.test(noTimestamp)) {
|
|
356
|
+
if (!seen.has(noTimestamp)) {
|
|
357
|
+
seen.add(noTimestamp);
|
|
358
|
+
output.push(noTimestamp);
|
|
359
|
+
}
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
// In non-aggressive mode, keep summary / result lines.
|
|
363
|
+
if (!aggressive &&
|
|
364
|
+
(/^(BUILD|COMPILE|Finished|Successfully)/i.test(noTimestamp) ||
|
|
365
|
+
/\d+\s+(error|warning)/i.test(noTimestamp))) {
|
|
366
|
+
output.push(noTimestamp);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (output.length === 0) {
|
|
370
|
+
return aggressive ? '[No errors found]' : '[No errors or warnings found]';
|
|
371
|
+
}
|
|
372
|
+
const header = aggressive ? '=== Errors ===' : '=== Errors & Warnings ===';
|
|
373
|
+
return `${header}\n${output.join('\n')}`;
|
|
374
|
+
}
|
|
375
|
+
// ── Test Output ─────────────────────────────────────────────
|
|
376
|
+
function compressTestOutput(text, aggressive) {
|
|
377
|
+
const lines = text.split('\n');
|
|
378
|
+
const failures = [];
|
|
379
|
+
const summary = [];
|
|
380
|
+
let capturingFailure = false;
|
|
381
|
+
let failureBuffer = [];
|
|
382
|
+
for (const line of lines) {
|
|
383
|
+
const trimmedLine = line.trim();
|
|
384
|
+
// Detect failure start.
|
|
385
|
+
if (/^(FAIL|✗|✘|✕|×|not ok|FAILED)/i.test(trimmedLine) ||
|
|
386
|
+
/AssertionError|Error:|Expected|Received/i.test(trimmedLine)) {
|
|
387
|
+
if (!capturingFailure) {
|
|
388
|
+
capturingFailure = true;
|
|
389
|
+
failureBuffer = [];
|
|
390
|
+
}
|
|
391
|
+
failureBuffer.push(line);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
// If capturing, keep context lines.
|
|
395
|
+
if (capturingFailure) {
|
|
396
|
+
if (trimmedLine === '' ||
|
|
397
|
+
/^(PASS|✓|ok |Test Suites:|Tests:)/i.test(trimmedLine)) {
|
|
398
|
+
// End of failure block.
|
|
399
|
+
failures.push(failureBuffer.join('\n'));
|
|
400
|
+
capturingFailure = false;
|
|
401
|
+
failureBuffer = [];
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
failureBuffer.push(line);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Keep summary lines.
|
|
409
|
+
if (/^(Test Suites?:|Tests:|Snapshots:|Time:)/i.test(trimmedLine) ||
|
|
410
|
+
/\d+\s+(passing|failing|passed|failed|skipped)/i.test(trimmedLine)) {
|
|
411
|
+
summary.push(trimmedLine);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Flush any remaining failure.
|
|
415
|
+
if (failureBuffer.length > 0) {
|
|
416
|
+
failures.push(failureBuffer.join('\n'));
|
|
417
|
+
}
|
|
418
|
+
const parts = [];
|
|
419
|
+
if (failures.length > 0) {
|
|
420
|
+
parts.push(`=== Failures (${failures.length}) ===`);
|
|
421
|
+
if (aggressive) {
|
|
422
|
+
// In aggressive mode, limit to first 5 failures.
|
|
423
|
+
parts.push(...failures.slice(0, 5));
|
|
424
|
+
if (failures.length > 5) {
|
|
425
|
+
parts.push(`... and ${failures.length - 5} more failures`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
parts.push(...failures);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (summary.length > 0) {
|
|
433
|
+
parts.push('');
|
|
434
|
+
parts.push('=== Summary ===');
|
|
435
|
+
parts.push(...summary);
|
|
436
|
+
}
|
|
437
|
+
return parts.length > 0 ? parts.join('\n') : '[All tests passed]';
|
|
438
|
+
}
|
|
439
|
+
// ── Stack Trace ─────────────────────────────────────────────
|
|
440
|
+
/** Patterns for framework / library frames to skip. */
|
|
441
|
+
const FRAMEWORK_FRAME_RE = /node_modules|internal\/|node:|<anonymous>|webpack|__webpack|\.next\/|turbopack|jest-|mocha|karma|angular[\\/]core|react-dom|zone\.js/i;
|
|
442
|
+
function compressStackTrace(text, aggressive) {
|
|
443
|
+
const lines = text.split('\n');
|
|
444
|
+
const output = [];
|
|
445
|
+
let consecutiveSkipped = 0;
|
|
446
|
+
for (const line of lines) {
|
|
447
|
+
const trimmedLine = line.trim();
|
|
448
|
+
// Keep the error message line(s) at the top.
|
|
449
|
+
if (!trimmedLine.startsWith('at ') &&
|
|
450
|
+
!trimmedLine.startsWith('File "') &&
|
|
451
|
+
!/^\s+at\s/.test(line)) {
|
|
452
|
+
if (consecutiveSkipped > 0) {
|
|
453
|
+
output.push(` ... ${consecutiveSkipped} framework frames`);
|
|
454
|
+
consecutiveSkipped = 0;
|
|
455
|
+
}
|
|
456
|
+
output.push(line);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
// It's a stack frame. Skip framework frames.
|
|
460
|
+
if (FRAMEWORK_FRAME_RE.test(trimmedLine)) {
|
|
461
|
+
consecutiveSkipped++;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
// Keep user frames.
|
|
465
|
+
if (consecutiveSkipped > 0) {
|
|
466
|
+
output.push(` ... ${consecutiveSkipped} framework frames`);
|
|
467
|
+
consecutiveSkipped = 0;
|
|
468
|
+
}
|
|
469
|
+
output.push(line);
|
|
470
|
+
}
|
|
471
|
+
if (consecutiveSkipped > 0) {
|
|
472
|
+
output.push(` ... ${consecutiveSkipped} framework frames`);
|
|
473
|
+
}
|
|
474
|
+
// Aggressive: deduplicate identical traces.
|
|
475
|
+
if (aggressive) {
|
|
476
|
+
return deduplicateLines(output).join('\n');
|
|
477
|
+
}
|
|
478
|
+
return output.join('\n');
|
|
479
|
+
}
|
|
480
|
+
// ── JSON Data ───────────────────────────────────────────────
|
|
481
|
+
function compressJson(text, aggressive) {
|
|
482
|
+
try {
|
|
483
|
+
const parsed = JSON.parse(text);
|
|
484
|
+
return compressJsonValue(parsed, aggressive, 0);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// Not valid JSON – just apply minimal.
|
|
488
|
+
return text;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function compressJsonValue(value, aggressive, depth) {
|
|
492
|
+
const indent = ' '.repeat(depth);
|
|
493
|
+
const childIndent = ' '.repeat(depth + 1);
|
|
494
|
+
if (value === null || value === undefined) {
|
|
495
|
+
return `${indent}null`;
|
|
496
|
+
}
|
|
497
|
+
if (typeof value === 'string') {
|
|
498
|
+
const truncated = value.length > 100 ? `${value.slice(0, 100)}... (${value.length} chars)` : value;
|
|
499
|
+
return `${indent}"${truncated}"`;
|
|
500
|
+
}
|
|
501
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
502
|
+
return `${indent}${String(value)}`;
|
|
503
|
+
}
|
|
504
|
+
if (Array.isArray(value)) {
|
|
505
|
+
if (value.length === 0)
|
|
506
|
+
return `${indent}[]`;
|
|
507
|
+
// Show schema + first example + count.
|
|
508
|
+
const first = value[0];
|
|
509
|
+
const firstStr = compressJsonValue(first, aggressive, depth + 1);
|
|
510
|
+
if (aggressive && value.length > 1) {
|
|
511
|
+
return `${indent}[\n${firstStr}\n${childIndent}// ... ${value.length - 1} more items (schema same as above)\n${indent}]`;
|
|
512
|
+
}
|
|
513
|
+
if (value.length <= 3 && !aggressive) {
|
|
514
|
+
const items = value
|
|
515
|
+
.map((v) => compressJsonValue(v, aggressive, depth + 1))
|
|
516
|
+
.join(',\n');
|
|
517
|
+
return `${indent}[\n${items}\n${indent}]`;
|
|
518
|
+
}
|
|
519
|
+
return `${indent}[\n${firstStr},\n${childIndent}// ... ${value.length - 1} more items\n${indent}]`;
|
|
520
|
+
}
|
|
521
|
+
if (typeof value === 'object') {
|
|
522
|
+
const entries = Object.entries(value);
|
|
523
|
+
if (entries.length === 0)
|
|
524
|
+
return `${indent}{}`;
|
|
525
|
+
if (aggressive && depth > 1) {
|
|
526
|
+
const keys = entries.map(([k]) => k).join(', ');
|
|
527
|
+
return `${indent}{ ${keys} }`;
|
|
528
|
+
}
|
|
529
|
+
const maxEntries = aggressive ? 5 : 15;
|
|
530
|
+
const shown = entries.slice(0, maxEntries);
|
|
531
|
+
const lines = shown.map(([k, v]) => {
|
|
532
|
+
const vStr = typeof v === 'object' && v !== null
|
|
533
|
+
? compressJsonValue(v, aggressive, depth + 1).trimStart()
|
|
534
|
+
: JSON.stringify(v);
|
|
535
|
+
return `${childIndent}"${k}": ${vStr}`;
|
|
536
|
+
});
|
|
537
|
+
if (entries.length > maxEntries) {
|
|
538
|
+
lines.push(`${childIndent}// ... ${entries.length - maxEntries} more keys`);
|
|
539
|
+
}
|
|
540
|
+
return `${indent}{\n${lines.join(',\n')}\n${indent}}`;
|
|
541
|
+
}
|
|
542
|
+
return `${indent}${String(value)}`;
|
|
543
|
+
}
|
|
544
|
+
// ── Markdown ────────────────────────────────────────────────
|
|
545
|
+
function compressMarkdown(text, aggressive) {
|
|
546
|
+
let result = text;
|
|
547
|
+
// Strip images.
|
|
548
|
+
result = result.replace(/!\[[^\]]*\]\([^)]*\)/g, '');
|
|
549
|
+
// Strip external links but keep link text.
|
|
550
|
+
result = result.replace(/\[([^\]]*)\]\(https?:\/\/[^)]*\)/g, aggressive ? '' : '$1');
|
|
551
|
+
// Strip HTML tags.
|
|
552
|
+
result = result.replace(/<[^>]+>/g, '');
|
|
553
|
+
// Strip horizontal rules.
|
|
554
|
+
result = result.replace(/^[-*_]{3,}\s*$/gm, '');
|
|
555
|
+
if (aggressive) {
|
|
556
|
+
// Flatten heading hierarchy to max 2 levels.
|
|
557
|
+
result = result.replace(/^#{3,}\s+/gm, '## ');
|
|
558
|
+
// Remove emphasis markers.
|
|
559
|
+
result = result.replace(/\*{1,3}|_{1,3}/g, '');
|
|
560
|
+
// Strip code fences — keep only the code.
|
|
561
|
+
result = result.replace(/^```\w*\n?/gm, '');
|
|
562
|
+
// Collapse list nesting.
|
|
563
|
+
result = result.replace(/^(\s{4,})[-*+]/gm, ' -');
|
|
564
|
+
}
|
|
565
|
+
// Collapse multiple blank lines.
|
|
566
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
567
|
+
return result.trim();
|
|
568
|
+
}
|
|
569
|
+
// ── Diff ────────────────────────────────────────────────────
|
|
570
|
+
function compressDiff(text, aggressive) {
|
|
571
|
+
const lines = text.split('\n');
|
|
572
|
+
const output = [];
|
|
573
|
+
const contextLines = aggressive ? 1 : 2;
|
|
574
|
+
let contextBuffer = [];
|
|
575
|
+
for (const line of lines) {
|
|
576
|
+
// Always keep diff headers.
|
|
577
|
+
if (line.startsWith('diff ') ||
|
|
578
|
+
line.startsWith('---') ||
|
|
579
|
+
line.startsWith('+++') ||
|
|
580
|
+
line.startsWith('@@')) {
|
|
581
|
+
// Flush context buffer – only keep last N lines.
|
|
582
|
+
if (contextBuffer.length > contextLines) {
|
|
583
|
+
output.push(` ... ${contextBuffer.length - contextLines} unchanged lines`);
|
|
584
|
+
}
|
|
585
|
+
output.push(...contextBuffer.slice(-contextLines));
|
|
586
|
+
contextBuffer = [];
|
|
587
|
+
output.push(line);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
// Changed lines — always keep.
|
|
591
|
+
if (line.startsWith('+') || line.startsWith('-')) {
|
|
592
|
+
// Flush context before the change.
|
|
593
|
+
if (contextBuffer.length > contextLines) {
|
|
594
|
+
output.push(` ... ${contextBuffer.length - contextLines} unchanged lines`);
|
|
595
|
+
}
|
|
596
|
+
output.push(...contextBuffer.slice(-contextLines));
|
|
597
|
+
contextBuffer = [];
|
|
598
|
+
output.push(line);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
// Context (unchanged) line — buffer it.
|
|
602
|
+
contextBuffer.push(line);
|
|
603
|
+
}
|
|
604
|
+
// Flush remaining context.
|
|
605
|
+
if (contextBuffer.length > contextLines) {
|
|
606
|
+
output.push(` ... ${contextBuffer.length - contextLines} unchanged lines`);
|
|
607
|
+
output.push(...contextBuffer.slice(-contextLines));
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
output.push(...contextBuffer);
|
|
611
|
+
}
|
|
612
|
+
return output.join('\n');
|
|
613
|
+
}
|
|
614
|
+
// ── Config Files ────────────────────────────────────────────
|
|
615
|
+
function compressConfig(text, aggressive) {
|
|
616
|
+
const lines = text.split('\n');
|
|
617
|
+
const output = [];
|
|
618
|
+
for (const line of lines) {
|
|
619
|
+
const trimmedLine = line.trim();
|
|
620
|
+
if (!trimmedLine || isComment(trimmedLine)) {
|
|
621
|
+
if (!aggressive)
|
|
622
|
+
output.push(line);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
// Section headers — always keep.
|
|
626
|
+
if (/^\[.*\]$/.test(trimmedLine) || /^[\w.]+:$/.test(trimmedLine)) {
|
|
627
|
+
output.push(line);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
// Key-value lines.
|
|
631
|
+
const kvMatch = trimmedLine.match(/^([\w][\w._-]*)\s*[:=]\s*(.*)/);
|
|
632
|
+
if (kvMatch) {
|
|
633
|
+
const key = kvMatch[1];
|
|
634
|
+
const value = kvMatch[2].trim();
|
|
635
|
+
if (aggressive) {
|
|
636
|
+
// Show key and inferred type only.
|
|
637
|
+
const type = inferValueType(value);
|
|
638
|
+
output.push(`${indentOf(line)}${key}: <${type}>`);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
// Keep keys and values, but truncate long values.
|
|
642
|
+
const truncValue = value.length > 80
|
|
643
|
+
? `${value.slice(0, 80)}... (${value.length} chars)`
|
|
644
|
+
: value;
|
|
645
|
+
output.push(`${indentOf(line)}${key} = ${truncValue}`);
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!aggressive) {
|
|
650
|
+
output.push(line);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (aggressive) {
|
|
654
|
+
return `[Config structure — ${output.length} entries]\n${output.join('\n')}`;
|
|
655
|
+
}
|
|
656
|
+
return output.join('\n');
|
|
657
|
+
}
|
|
658
|
+
// ── Plain Text (aggressive only) ────────────────────────────
|
|
659
|
+
function compressPlainTextAggressive(text) {
|
|
660
|
+
const lines = text.split('\n').filter((l) => l.trim().length > 0);
|
|
661
|
+
if (lines.length <= 10) {
|
|
662
|
+
return lines.join('\n');
|
|
663
|
+
}
|
|
664
|
+
// Keep first 5 and last 5 lines.
|
|
665
|
+
const head = lines.slice(0, 5);
|
|
666
|
+
const tail = lines.slice(-5);
|
|
667
|
+
const skipped = lines.length - 10;
|
|
668
|
+
return [...head, `\n... ${skipped} lines omitted ...\n`, ...tail].join('\n');
|
|
669
|
+
}
|
|
670
|
+
// ================================================================
|
|
671
|
+
// Helpers
|
|
672
|
+
// ================================================================
|
|
673
|
+
function buildResult(compressed, originalTokens, contentType, level) {
|
|
674
|
+
const compressedTokens = estimateTokens(compressed);
|
|
675
|
+
const ratio = originalTokens > 0
|
|
676
|
+
? Math.round((1 - compressedTokens / originalTokens) * 10000) / 10000
|
|
677
|
+
: 0;
|
|
678
|
+
return {
|
|
679
|
+
compressed,
|
|
680
|
+
originalTokens,
|
|
681
|
+
compressedTokens,
|
|
682
|
+
ratio,
|
|
683
|
+
contentType,
|
|
684
|
+
level,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function isImportOrExport(line) {
|
|
688
|
+
return /^(import|export|from|require)\b/.test(line);
|
|
689
|
+
}
|
|
690
|
+
function isDocComment(line) {
|
|
691
|
+
return (line.startsWith('/**') ||
|
|
692
|
+
line.startsWith(' *') ||
|
|
693
|
+
line.startsWith('///') ||
|
|
694
|
+
line.startsWith('#:') ||
|
|
695
|
+
line.startsWith('"""') ||
|
|
696
|
+
line.startsWith("'''"));
|
|
697
|
+
}
|
|
698
|
+
function isComment(line) {
|
|
699
|
+
return (line.startsWith('//') ||
|
|
700
|
+
line.startsWith('#') ||
|
|
701
|
+
line.startsWith('/*') ||
|
|
702
|
+
line.startsWith('*') ||
|
|
703
|
+
line.startsWith('*/') ||
|
|
704
|
+
line.startsWith('"""') ||
|
|
705
|
+
line.startsWith("'''"));
|
|
706
|
+
}
|
|
707
|
+
function isTypeDeclaration(line) {
|
|
708
|
+
return /^(export\s+)?(type|interface|enum)\s+\w/.test(line);
|
|
709
|
+
}
|
|
710
|
+
function isSignatureLine(line) {
|
|
711
|
+
return (/^(export\s+)?(async\s+)?(function|class)\s/.test(line) ||
|
|
712
|
+
/^(public|private|protected|static|abstract|async)\s/.test(line) ||
|
|
713
|
+
/^(def|fn|func|pub\s+fn|pub\s+func)\s/.test(line) ||
|
|
714
|
+
/^\w[\w<>,\s]*\(.*\)\s*(:\s*\w)?\s*\{?\s*$/.test(line) ||
|
|
715
|
+
/^(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/.test(line) ||
|
|
716
|
+
/^(const|let|var)\s+\w+\s*=\s*(async\s+)?function/.test(line));
|
|
717
|
+
}
|
|
718
|
+
function countChar(str, char) {
|
|
719
|
+
let count = 0;
|
|
720
|
+
for (let i = 0; i < str.length; i++) {
|
|
721
|
+
if (str[i] === char)
|
|
722
|
+
count++;
|
|
723
|
+
}
|
|
724
|
+
return count;
|
|
725
|
+
}
|
|
726
|
+
function indentOf(line) {
|
|
727
|
+
const match = line.match(/^(\s*)/);
|
|
728
|
+
return match ? match[1] : '';
|
|
729
|
+
}
|
|
730
|
+
function inferValueType(value) {
|
|
731
|
+
if (value === 'true' || value === 'false')
|
|
732
|
+
return 'boolean';
|
|
733
|
+
if (/^-?\d+$/.test(value))
|
|
734
|
+
return 'integer';
|
|
735
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
736
|
+
return 'float';
|
|
737
|
+
if (/^\[/.test(value))
|
|
738
|
+
return 'array';
|
|
739
|
+
if (/^\{/.test(value))
|
|
740
|
+
return 'object';
|
|
741
|
+
if (/^["']/.test(value))
|
|
742
|
+
return 'string';
|
|
743
|
+
if (value === '' || value === 'null' || value === 'nil')
|
|
744
|
+
return 'null';
|
|
745
|
+
return 'string';
|
|
746
|
+
}
|
|
747
|
+
function deduplicateLines(lines) {
|
|
748
|
+
const seen = new Set();
|
|
749
|
+
const output = [];
|
|
750
|
+
let duplicateCount = 0;
|
|
751
|
+
for (const line of lines) {
|
|
752
|
+
const key = line.trim();
|
|
753
|
+
if (seen.has(key) && key.length > 0) {
|
|
754
|
+
duplicateCount++;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (duplicateCount > 0) {
|
|
758
|
+
output.push(` ... ${duplicateCount} duplicate lines`);
|
|
759
|
+
duplicateCount = 0;
|
|
760
|
+
}
|
|
761
|
+
seen.add(key);
|
|
762
|
+
output.push(line);
|
|
763
|
+
}
|
|
764
|
+
if (duplicateCount > 0) {
|
|
765
|
+
output.push(` ... ${duplicateCount} duplicate lines`);
|
|
766
|
+
}
|
|
767
|
+
return output;
|
|
768
|
+
}
|
|
769
|
+
//# sourceMappingURL=compressor.js.map
|