mcp-code-context 2.1.3 → 2.3.1

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 (128) hide show
  1. package/.io/RULES.md +12 -0
  2. package/CHANGELOG.md +118 -58
  3. package/CONTRIBUTING.md +243 -0
  4. package/README.md +58 -7
  5. package/RELEASE_NOTES_v2.2.0.md +286 -0
  6. package/RELEASE_NOTES_v2.3.0.md +228 -0
  7. package/RELEASE_NOTES_v2.3.1.md +110 -0
  8. package/dist/src/ast/writers/safeRename.d.ts +14 -0
  9. package/dist/src/ast/writers/safeRename.js +258 -0
  10. package/dist/src/ast/writers/safeRename.js.map +1 -0
  11. package/dist/src/ast/writers/symbolWriter.d.ts +2 -0
  12. package/dist/src/ast/writers/symbolWriter.js +9 -14
  13. package/dist/src/ast/writers/symbolWriter.js.map +1 -1
  14. package/dist/src/cache/astCache.d.ts +33 -0
  15. package/dist/src/cache/astCache.js +113 -0
  16. package/dist/src/cache/astCache.js.map +1 -0
  17. package/dist/src/handlers/readHandlers.d.ts +70 -0
  18. package/dist/src/handlers/readHandlers.js +431 -0
  19. package/dist/src/handlers/readHandlers.js.map +1 -0
  20. package/dist/src/handlers/utilHandlers.d.ts +31 -0
  21. package/dist/src/handlers/utilHandlers.js +69 -0
  22. package/dist/src/handlers/utilHandlers.js.map +1 -0
  23. package/dist/src/handlers/writeHandlers.d.ts +57 -0
  24. package/dist/src/handlers/writeHandlers.js +475 -0
  25. package/dist/src/handlers/writeHandlers.js.map +1 -0
  26. package/dist/src/index.d.ts +6 -107
  27. package/dist/src/index.js +147 -965
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/tools/readFileLines.d.ts +26 -0
  30. package/dist/src/tools/readFileLines.js +77 -0
  31. package/dist/src/tools/readFileLines.js.map +1 -0
  32. package/dist/src/tools/searchCodePattern.d.ts +32 -0
  33. package/dist/src/tools/searchCodePattern.js +80 -0
  34. package/dist/src/tools/searchCodePattern.js.map +1 -0
  35. package/dist/src/utils/arrayUtils.d.ts +12 -0
  36. package/dist/src/utils/arrayUtils.js +27 -0
  37. package/dist/src/utils/arrayUtils.js.map +1 -0
  38. package/dist/src/utils/backupManager.d.ts +13 -6
  39. package/dist/src/utils/backupManager.js +73 -8
  40. package/dist/src/utils/backupManager.js.map +1 -1
  41. package/dist/src/utils/constants.d.ts +46 -0
  42. package/dist/src/utils/constants.js +112 -0
  43. package/dist/src/utils/constants.js.map +1 -0
  44. package/dist/src/utils/ignoreManager.d.ts +8 -0
  45. package/dist/src/utils/ignoreManager.js +49 -0
  46. package/dist/src/utils/ignoreManager.js.map +1 -1
  47. package/dist/src/utils/normalization.d.ts +49 -0
  48. package/dist/src/utils/normalization.js +173 -0
  49. package/dist/src/utils/normalization.js.map +1 -0
  50. package/dist/src/utils/projectRoot.d.ts +17 -0
  51. package/dist/src/utils/projectRoot.js +42 -0
  52. package/dist/src/utils/projectRoot.js.map +1 -0
  53. package/dist/src/utils/secureValidation.d.ts +15 -0
  54. package/dist/src/utils/secureValidation.js +41 -0
  55. package/dist/src/utils/secureValidation.js.map +1 -0
  56. package/dist/src/utils/transactionManager.d.ts +45 -0
  57. package/dist/src/utils/transactionManager.js +102 -0
  58. package/dist/src/utils/transactionManager.js.map +1 -0
  59. package/dist/src/utils/validation.d.ts +87 -0
  60. package/dist/src/utils/validation.js +231 -0
  61. package/dist/src/utils/validation.js.map +1 -0
  62. package/docs/ARCHITECTURE.md +300 -0
  63. package/docs/architecture/ADR-001-modular-handlers.md +119 -0
  64. package/llms.txt +62 -7
  65. package/package.json +5 -5
  66. package/AGENT_INSTRUCTIONS.md +0 -135
  67. package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
  68. package/dist/src/tests/test-confirmation-flow.d.ts +0 -1
  69. package/dist/src/tests/test-confirmation-flow.js +0 -43
  70. package/dist/src/tests/test-confirmation-flow.js.map +0 -1
  71. package/dist/tests/debug-replace.d.ts +0 -1
  72. package/dist/tests/debug-replace.js +0 -17
  73. package/dist/tests/debug-replace.js.map +0 -1
  74. package/dist/tests/test-confirmation-flow.d.ts +0 -1
  75. package/dist/tests/test-confirmation-flow.js +0 -43
  76. package/dist/tests/test-confirmation-flow.js.map +0 -1
  77. package/dist/tests/test-dart.d.ts +0 -1
  78. package/dist/tests/test-dart.js +0 -242
  79. package/dist/tests/test-dart.js.map +0 -1
  80. package/dist/tests/test-php-ast.d.ts +0 -1
  81. package/dist/tests/test-php-ast.js +0 -152
  82. package/dist/tests/test-php-ast.js.map +0 -1
  83. package/dist/tests/test-php.d.ts +0 -1
  84. package/dist/tests/test-php.js +0 -234
  85. package/dist/tests/test-php.js.map +0 -1
  86. package/dist/tests/test-smoke.d.ts +0 -5
  87. package/dist/tests/test-smoke.js +0 -83
  88. package/dist/tests/test-smoke.js.map +0 -1
  89. package/dist/tests/writers/test-crlf-handling.d.ts +0 -4
  90. package/dist/tests/writers/test-crlf-handling.js +0 -73
  91. package/dist/tests/writers/test-crlf-handling.js.map +0 -1
  92. package/dist/tests/writers/test-write-smoke.d.ts +0 -9
  93. package/dist/tests/writers/test-write-smoke.js +0 -346
  94. package/dist/tests/writers/test-write-smoke.js.map +0 -1
  95. package/src/ast/dartCompressor.ts +0 -1013
  96. package/src/ast/phpCompressor.ts +0 -814
  97. package/src/ast/semanticCompressor.ts +0 -808
  98. package/src/ast/writers/dartWriter.ts +0 -648
  99. package/src/ast/writers/phpWriter.ts +0 -508
  100. package/src/ast/writers/pyWriter.ts +0 -446
  101. package/src/ast/writers/symbolWriter.ts +0 -277
  102. package/src/ast/writers/tsWriter.ts +0 -418
  103. package/src/index.ts +0 -1553
  104. package/src/utils/backupManager.ts +0 -66
  105. package/src/utils/confirmationCache.ts +0 -61
  106. package/src/utils/diffEngine.ts +0 -223
  107. package/src/utils/fuzzyMatch.ts +0 -86
  108. package/src/utils/ignoreManager.ts +0 -144
  109. package/tests/test-confirmation-flow.ts +0 -49
  110. package/tests/test-dart.d.ts +0 -1
  111. package/tests/test-dart.js +0 -209
  112. package/tests/test-dart.js.map +0 -1
  113. package/tests/test-dart.ts +0 -265
  114. package/tests/test-php-ast.d.ts +0 -1
  115. package/tests/test-php-ast.js +0 -152
  116. package/tests/test-php-ast.js.map +0 -1
  117. package/tests/test-php-ast.ts +0 -155
  118. package/tests/test-php.d.ts +0 -1
  119. package/tests/test-php.js +0 -234
  120. package/tests/test-php.js.map +0 -1
  121. package/tests/test-php.ts +0 -261
  122. package/tests/test-smoke.d.ts +0 -5
  123. package/tests/test-smoke.js +0 -83
  124. package/tests/test-smoke.js.map +0 -1
  125. package/tests/test-smoke.ts +0 -93
  126. package/tests/writers/test-crlf-handling.ts +0 -100
  127. package/tests/writers/test-write-smoke.ts +0 -568
  128. package/tsconfig.json +0 -19
@@ -1,808 +0,0 @@
1
- /**
2
- * SemanticCompressor — The AST engine that extracts structural signatures
3
- * from source files, stripping implementation bodies to save tokens.
4
- *
5
- * Supports:
6
- * - TypeScript / JavaScript (via ts-morph AST)
7
- * - PHP (via php-parser AST — Glayzzle)
8
- * - Dart (via regex + brace-counting)
9
- * - Python (via regex-based extraction)
10
- * - Generic files (passthrough for small configs, summary for large files)
11
- */
12
-
13
- import {
14
- Project,
15
- SyntaxKind,
16
- ts,
17
- type SourceFile,
18
- type FunctionDeclaration,
19
- type ClassDeclaration,
20
- type MethodDeclaration,
21
- type ArrowFunction,
22
- type FunctionExpression,
23
- } from "ts-morph";
24
- import * as path from "node:path";
25
- import { compressPHP, extractPHPSymbol } from "./phpCompressor.js";
26
- import { compressDart, extractDartSymbol } from "./dartCompressor.js";
27
-
28
- // ─── Supported extensions ───────────────────────────────────────────
29
-
30
- const TS_EXTENSIONS = new Set([
31
- ".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs",
32
- ]);
33
-
34
- const PY_EXTENSIONS = new Set([".py", ".pyi"]);
35
-
36
- const PHP_EXTENSIONS = new Set([".php", ".phtml"]);
37
-
38
- const DART_EXTENSIONS = new Set([".dart"]);
39
-
40
- const CONFIG_EXTENSIONS = new Set([
41
- ".json", ".yaml", ".yml", ".toml", ".ini", ".env",
42
- ".xml", ".html", ".css", ".scss", ".less",
43
- ".md", ".txt", ".svg",
44
- ]);
45
-
46
- // ─── Singleton ts-morph Project (reusable, in-memory) ───────────────
47
-
48
- let _project: Project | null = null;
49
-
50
- function getProject(): Project {
51
- if (!_project) {
52
- _project = new Project({
53
- useInMemoryFileSystem: true,
54
- compilerOptions: {
55
- target: ts.ScriptTarget.ESNext,
56
- module: ts.ModuleKind.ESNext,
57
- jsx: ts.JsxEmit.ReactJSX,
58
- allowJs: true,
59
- strict: false,
60
- noEmit: true,
61
- skipLibCheck: true,
62
- },
63
- });
64
- }
65
- return _project;
66
- }
67
-
68
- // ─── Public API ─────────────────────────────────────────────────────
69
-
70
- /**
71
- * Compress a source file into its semantic skeleton.
72
- * Returns only structural signatures — no function bodies.
73
- */
74
- export function compressFile(filePath: string, content: string): string {
75
- const ext = path.extname(filePath).toLowerCase();
76
-
77
- if (TS_EXTENSIONS.has(ext)) {
78
- return compressTypeScript(content, filePath);
79
- }
80
-
81
- if (PHP_EXTENSIONS.has(ext)) {
82
- return compressPHP(content);
83
- }
84
-
85
- if (DART_EXTENSIONS.has(ext)) {
86
- return compressDart(content);
87
- }
88
-
89
- if (PY_EXTENSIONS.has(ext)) {
90
- return compressPython(content);
91
- }
92
-
93
- return compressGeneric(content, filePath);
94
- }
95
-
96
- /**
97
- * Extract the full source code of a named symbol from a file.
98
- * When className is provided, scopes method search to within that class only.
99
- * Returns null if the symbol is not found.
100
- */
101
- export function extractSymbol(
102
- content: string,
103
- symbolName: string,
104
- filePath: string,
105
- className?: string,
106
- ): string | null {
107
- const ext = path.extname(filePath).toLowerCase();
108
-
109
- if (TS_EXTENSIONS.has(ext)) {
110
- return extractTSSymbol(content, symbolName, filePath, className);
111
- }
112
-
113
- if (PHP_EXTENSIONS.has(ext)) {
114
- return extractPHPSymbol(content, symbolName, className);
115
- }
116
-
117
- if (DART_EXTENSIONS.has(ext)) {
118
- return extractDartSymbol(content, symbolName, className);
119
- }
120
-
121
- if (PY_EXTENSIONS.has(ext)) {
122
- return extractPySymbol(content, symbolName, className);
123
- }
124
-
125
- return null;
126
- }
127
-
128
- // ─── TypeScript / JavaScript Compression ────────────────────────────
129
-
130
- function compressTypeScript(content: string, filePath: string): string {
131
- const project = getProject();
132
- const ext = path.extname(filePath);
133
- const tempName = `temp_${Date.now()}${ext}`;
134
-
135
- let sourceFile: SourceFile;
136
- try {
137
- sourceFile = project.createSourceFile(tempName, content, {
138
- overwrite: true,
139
- });
140
- } catch {
141
- return compressGeneric(content, filePath);
142
- }
143
-
144
- try {
145
- const chunks: string[] = [];
146
-
147
- // 1) Imports — keep for dependency context
148
- const imports = sourceFile.getImportDeclarations();
149
- if (imports.length > 0) {
150
- chunks.push("// ── Imports ──");
151
- for (const imp of imports) {
152
- chunks.push(imp.getText());
153
- }
154
- chunks.push("");
155
- }
156
-
157
- // 2) Type aliases
158
- for (const ta of sourceFile.getTypeAliases()) {
159
- chunks.push(ta.getText());
160
- }
161
-
162
- // 3) Interfaces (already just signatures)
163
- for (const iface of sourceFile.getInterfaces()) {
164
- chunks.push(iface.getText());
165
- }
166
-
167
- // 4) Enums
168
- for (const enumDecl of sourceFile.getEnums()) {
169
- chunks.push(enumDecl.getText());
170
- }
171
-
172
- // 5) Function declarations (signature only, body stripped)
173
- for (const func of sourceFile.getFunctions()) {
174
- chunks.push(buildFunctionSignature(func));
175
- }
176
-
177
- // 6) Class declarations (signatures only)
178
- for (const cls of sourceFile.getClasses()) {
179
- chunks.push(buildClassSignature(cls));
180
- }
181
-
182
- // 7) Exported variable statements (arrow fns, consts, etc.)
183
- for (const varStmt of sourceFile.getVariableStatements()) {
184
- if (!varStmt.isExported()) continue;
185
-
186
- for (const decl of varStmt.getDeclarations()) {
187
- const init = decl.getInitializer();
188
- if (!init) continue;
189
-
190
- const kind = init.getKind();
191
- if (
192
- kind === SyntaxKind.ArrowFunction ||
193
- kind === SyntaxKind.FunctionExpression
194
- ) {
195
- const fn = init as ArrowFunction | FunctionExpression;
196
- const params = fn
197
- .getParameters()
198
- .map((p) => p.getText())
199
- .join(", ");
200
- const retNode = fn.getReturnTypeNode();
201
- const retType = retNode ? `: ${retNode.getText()}` : "";
202
- const asyncMod = fn.isAsync() ? "async " : "";
203
- const declKind = varStmt.getDeclarationKind();
204
- chunks.push(
205
- `export ${declKind} ${asyncMod}${decl.getName()} = (${params})${retType} => { /* ... */ };`,
206
- );
207
- } else {
208
- const declKind = varStmt.getDeclarationKind();
209
- const typeNode = decl.getTypeNode();
210
- const typeStr = typeNode ? `: ${typeNode.getText()}` : "";
211
- chunks.push(
212
- `export ${declKind} ${decl.getName()}${typeStr};`,
213
- );
214
- }
215
- }
216
- }
217
-
218
- // 8) Re-export declarations
219
- for (const exp of sourceFile.getExportDeclarations()) {
220
- chunks.push(exp.getText());
221
- }
222
-
223
- // 9) Export assignments (export default ...)
224
- const exportAssignment = sourceFile.getExportAssignment(
225
- (ea) => !ea.isExportEquals(),
226
- );
227
- if (exportAssignment) {
228
- chunks.push(exportAssignment.getText());
229
- }
230
-
231
- return chunks.filter((c) => c.trim()).join("\n\n");
232
- } finally {
233
- // Clean up the in-memory source file to avoid memory leaks
234
- try {
235
- project.removeSourceFile(sourceFile);
236
- } catch {
237
- // Ignore cleanup errors
238
- }
239
- }
240
- }
241
-
242
- function buildFunctionSignature(func: FunctionDeclaration): string {
243
- const name = func.getName() || "anonymous";
244
- const params = func
245
- .getParameters()
246
- .map((p) => p.getText())
247
- .join(", ");
248
- const retNode = func.getReturnTypeNode();
249
- const retType = retNode ? `: ${retNode.getText()}` : "";
250
- const exported = func.isExported() ? "export " : "";
251
- const isDefault = func.isDefaultExport() ? "default " : "";
252
- const asyncMod = func.isAsync() ? "async " : "";
253
- const generics = func
254
- .getTypeParameters()
255
- .map((tp) => tp.getText())
256
- .join(", ");
257
- const genericStr = generics ? `<${generics}>` : "";
258
- const overloads = func.getOverloads();
259
-
260
- const parts: string[] = [];
261
- for (const overload of overloads) {
262
- parts.push(overload.getText());
263
- }
264
- parts.push(
265
- `${exported}${isDefault}${asyncMod}function ${name}${genericStr}(${params})${retType} { /* ... */ }`,
266
- );
267
- return parts.join("\n");
268
- }
269
-
270
- function buildClassSignature(cls: ClassDeclaration): string {
271
- const lines: string[] = [];
272
- const name = cls.getName() || "AnonymousClass";
273
- const exported = cls.isExported() ? "export " : "";
274
- const isDefault = cls.isDefaultExport() ? "default " : "";
275
- const isAbstract = cls.isAbstract() ? "abstract " : "";
276
- const extendsExpr = cls.getExtends();
277
- const implementsClauses = cls.getImplements();
278
- const generics = cls
279
- .getTypeParameters()
280
- .map((tp) => tp.getText())
281
- .join(", ");
282
- const genericStr = generics ? `<${generics}>` : "";
283
-
284
- let header = `${exported}${isDefault}${isAbstract}class ${name}${genericStr}`;
285
- if (extendsExpr) header += ` extends ${extendsExpr.getText()}`;
286
- if (implementsClauses.length > 0) {
287
- header += ` implements ${implementsClauses.map((i) => i.getText()).join(", ")}`;
288
- }
289
- header += " {";
290
- lines.push(header);
291
-
292
- // Constructor
293
- for (const ctor of cls.getConstructors()) {
294
- const params = ctor
295
- .getParameters()
296
- .map((p) => p.getText())
297
- .join(", ");
298
- lines.push(` constructor(${params}) { /* ... */ }`);
299
- }
300
-
301
- // Properties
302
- for (const prop of cls.getProperties()) {
303
- const mods = prop
304
- .getModifiers()
305
- .map((m) => m.getText())
306
- .join(" ");
307
- const modStr = mods ? `${mods} ` : "";
308
- const typeNode = prop.getTypeNode();
309
- const typeStr = typeNode ? `: ${typeNode.getText()}` : "";
310
- const optional = prop.hasQuestionToken() ? "?" : "";
311
- const isReadonly = prop.isReadonly() ? "readonly " : "";
312
- lines.push(
313
- ` ${modStr}${isReadonly}${prop.getName()}${optional}${typeStr};`,
314
- );
315
- }
316
-
317
- // Methods (signature only)
318
- for (const method of cls.getMethods()) {
319
- lines.push(` ${buildMethodSignature(method)}`);
320
- }
321
-
322
- // Getters
323
- for (const getter of cls.getGetAccessors()) {
324
- const retNode = getter.getReturnTypeNode();
325
- const retType = retNode ? `: ${retNode.getText()}` : "";
326
- lines.push(` get ${getter.getName()}()${retType} { /* ... */ }`);
327
- }
328
-
329
- // Setters
330
- for (const setter of cls.getSetAccessors()) {
331
- const params = setter
332
- .getParameters()
333
- .map((p) => p.getText())
334
- .join(", ");
335
- lines.push(` set ${setter.getName()}(${params}) { /* ... */ }`);
336
- }
337
-
338
- lines.push("}");
339
- return lines.join("\n");
340
- }
341
-
342
- function buildMethodSignature(method: MethodDeclaration): string {
343
- const mods = method
344
- .getModifiers()
345
- .map((m) => m.getText())
346
- .join(" ");
347
- const modStr = mods ? `${mods} ` : "";
348
- const asyncMod = method.isAsync() ? "async " : "";
349
- const name = method.getName();
350
- const params = method
351
- .getParameters()
352
- .map((p) => p.getText())
353
- .join(", ");
354
- const retNode = method.getReturnTypeNode();
355
- const retType = retNode ? `: ${retNode.getText()}` : "";
356
- const generics = method
357
- .getTypeParameters()
358
- .map((tp) => tp.getText())
359
- .join(", ");
360
- const genericStr = generics ? `<${generics}>` : "";
361
-
362
- return `${modStr}${asyncMod}${name}${genericStr}(${params})${retType} { /* ... */ }`;
363
- }
364
-
365
- // ─── Python Compression ─────────────────────────────────────────────
366
-
367
- function compressPython(content: string): string {
368
- const lines = content.split("\n");
369
- const result: string[] = [];
370
- let i = 0;
371
-
372
- // Phase 1: Collect imports at the top
373
- const importBlock: string[] = [];
374
- for (const line of lines) {
375
- const t = line.trim();
376
- if (t.startsWith("import ") || t.startsWith("from ")) {
377
- importBlock.push(line);
378
- }
379
- }
380
- if (importBlock.length > 0) {
381
- result.push("# ── Imports ──");
382
- result.push(...importBlock);
383
- result.push("");
384
- }
385
-
386
- // Phase 2: Walk the file extracting signatures
387
- while (i < lines.length) {
388
- const line = lines[i];
389
- const trimmed = line.trim();
390
- const indent = measureIndent(line);
391
-
392
- // Skip import lines (already captured)
393
- if (trimmed.startsWith("import ") || trimmed.startsWith("from ")) {
394
- i++;
395
- continue;
396
- }
397
-
398
- // Decorators — pass through
399
- if (trimmed.startsWith("@")) {
400
- result.push(line);
401
- i++;
402
- continue;
403
- }
404
-
405
- // Class definition
406
- if (/^class\s+\w+/.test(trimmed)) {
407
- // Capture the full class signature (may span multiple lines)
408
- let signature = line;
409
- i++;
410
- while (i < lines.length && !signature.trimEnd().endsWith(":")) {
411
- signature += "\n" + lines[i];
412
- i++;
413
- }
414
- result.push(signature);
415
-
416
- // Capture docstring
417
- i = captureDocstring(lines, i, result);
418
-
419
- // Now extract methods within this class
420
- while (i < lines.length) {
421
- const innerLine = lines[i];
422
- const innerTrimmed = innerLine.trim();
423
-
424
- // Empty line within class is OK
425
- if (innerTrimmed === "") {
426
- i++;
427
- continue;
428
- }
429
-
430
- // Exited the class (non-indented non-empty line)
431
- if (measureIndent(innerLine) <= indent && innerTrimmed !== "") {
432
- break;
433
- }
434
-
435
- // Decorator before method
436
- if (innerTrimmed.startsWith("@")) {
437
- result.push(innerLine);
438
- i++;
439
- continue;
440
- }
441
-
442
- // Method definition
443
- if (/^\s*(async\s+)?def\s+\w+/.test(innerLine)) {
444
- let methodSig = innerLine;
445
- i++;
446
- while (i < lines.length && !methodSig.trimEnd().endsWith(":")) {
447
- methodSig += "\n" + lines[i];
448
- i++;
449
- }
450
- result.push(methodSig);
451
-
452
- // Capture method docstring
453
- i = captureDocstring(lines, i, result);
454
-
455
- // Placeholder for body
456
- const methodIndent = measureIndent(innerLine);
457
- result.push(" ".repeat(methodIndent + 4) + "...");
458
- result.push("");
459
-
460
- // Skip method body
461
- while (i < lines.length) {
462
- const bodyLine = lines[i];
463
- if (bodyLine.trim() === "") {
464
- i++;
465
- continue;
466
- }
467
- if (measureIndent(bodyLine) > methodIndent) {
468
- i++;
469
- } else {
470
- break;
471
- }
472
- }
473
- continue;
474
- }
475
-
476
- // Class-level attribute with type hint
477
- if (/^\s+\w+\s*:/.test(innerLine) && !innerTrimmed.startsWith("#")) {
478
- result.push(innerLine);
479
- }
480
-
481
- i++;
482
- }
483
- result.push("");
484
- continue;
485
- }
486
-
487
- // Top-level function definition
488
- if (/^(async\s+)?def\s+\w+/.test(trimmed)) {
489
- let signature = line;
490
- i++;
491
- while (i < lines.length && !signature.trimEnd().endsWith(":")) {
492
- signature += "\n" + lines[i];
493
- i++;
494
- }
495
- result.push(signature);
496
-
497
- // Capture docstring
498
- i = captureDocstring(lines, i, result);
499
-
500
- // Placeholder
501
- result.push(" ...");
502
- result.push("");
503
-
504
- // Skip body
505
- while (i < lines.length) {
506
- const bodyLine = lines[i];
507
- if (bodyLine.trim() === "") {
508
- i++;
509
- continue;
510
- }
511
- if (bodyLine.startsWith(" ") || bodyLine.startsWith("\t")) {
512
- i++;
513
- } else {
514
- break;
515
- }
516
- }
517
- continue;
518
- }
519
-
520
- // Top-level typed variable
521
- if (indent === 0 && /^\w+\s*:/.test(trimmed) && !trimmed.startsWith("#")) {
522
- result.push(line);
523
- i++;
524
- continue;
525
- }
526
-
527
- // Top-level assignment of special objects
528
- if (
529
- indent === 0 &&
530
- /^\w+\s*=/.test(trimmed) &&
531
- !trimmed.startsWith("#")
532
- ) {
533
- // Keep only single-line assignments to constants, named tuples, etc.
534
- if (!trimmed.includes("(") || trimmed.endsWith(")")) {
535
- result.push(line);
536
- }
537
- i++;
538
- continue;
539
- }
540
-
541
- i++;
542
- }
543
-
544
- return result.join("\n");
545
- }
546
-
547
- function captureDocstring(
548
- lines: string[],
549
- i: number,
550
- result: string[],
551
- ): number {
552
- if (i >= lines.length) return i;
553
-
554
- const trimmed = lines[i].trim();
555
- if (!trimmed.startsWith('"""') && !trimmed.startsWith("'''")) {
556
- return i;
557
- }
558
-
559
- const quoteType = trimmed.substring(0, 3);
560
- result.push(lines[i]);
561
-
562
- // Single-line docstring: """Something."""
563
- if (trimmed.length > 6 && trimmed.endsWith(quoteType)) {
564
- return i + 1;
565
- }
566
-
567
- // Multi-line docstring
568
- i++;
569
- while (i < lines.length) {
570
- result.push(lines[i]);
571
- if (lines[i].trim().endsWith(quoteType)) {
572
- return i + 1;
573
- }
574
- i++;
575
- }
576
- return i;
577
- }
578
-
579
- function measureIndent(line: string): number {
580
- const match = line.match(/^(\s*)/);
581
- return match ? match[1].length : 0;
582
- }
583
-
584
- // ─── Generic Compression ────────────────────────────────────────────
585
-
586
- function compressGeneric(content: string, filePath: string): string {
587
- const ext = path.extname(filePath).toLowerCase();
588
- const lines = content.split("\n");
589
- const lineCount = lines.length;
590
-
591
- // Small config/data files: return as-is
592
- if (CONFIG_EXTENSIONS.has(ext) && lineCount <= 150) {
593
- return content;
594
- }
595
-
596
- // Larger files: provide a summary + head
597
- if (lineCount > 150) {
598
- const head = lines.slice(0, 60).join("\n");
599
- return [
600
- `// File: ${path.basename(filePath)} (${lineCount} lines)`,
601
- `// Language not supported for semantic compression.`,
602
- `// Showing first 60 lines:`,
603
- ``,
604
- head,
605
- ``,
606
- `// ... (${lineCount - 60} more lines truncated)`,
607
- ].join("\n");
608
- }
609
-
610
- return content;
611
- }
612
-
613
- // ─── Symbol Extraction (Full Source) ────────────────────────────────
614
-
615
- function extractTSSymbol(
616
- content: string,
617
- symbolName: string,
618
- filePath: string,
619
- className?: string,
620
- ): string | null {
621
- const project = getProject();
622
- const ext = path.extname(filePath);
623
- const tempName = `extract_${Date.now()}${ext}`;
624
-
625
- let sourceFile: SourceFile;
626
- try {
627
- sourceFile = project.createSourceFile(tempName, content, {
628
- overwrite: true,
629
- });
630
- } catch {
631
- return null;
632
- }
633
-
634
- try {
635
- // If className is provided, scope search to within that class
636
- if (className) {
637
- const cls = sourceFile.getClass(className);
638
- if (!cls) return null;
639
-
640
- const method = cls.getMethod(symbolName);
641
- if (method) return method.getFullText().trim();
642
-
643
- const getter = cls.getGetAccessor(symbolName);
644
- if (getter) return getter.getFullText().trim();
645
-
646
- const setter = cls.getSetAccessor(symbolName);
647
- if (setter) return setter.getFullText().trim();
648
-
649
- const prop = cls.getProperty(symbolName);
650
- if (prop) return prop.getFullText().trim();
651
-
652
- return null;
653
- }
654
-
655
- // Search top-level functions
656
- const func = sourceFile.getFunction(symbolName);
657
- if (func) return func.getFullText().trim();
658
-
659
- // Search classes
660
- const cls = sourceFile.getClass(symbolName);
661
- if (cls) return cls.getFullText().trim();
662
-
663
- // Search interfaces
664
- const iface = sourceFile.getInterface(symbolName);
665
- if (iface) return iface.getFullText().trim();
666
-
667
- // Search type aliases
668
- const typeAlias = sourceFile.getTypeAlias(symbolName);
669
- if (typeAlias) return typeAlias.getFullText().trim();
670
-
671
- // Search enums
672
- const enumDecl = sourceFile.getEnum(symbolName);
673
- if (enumDecl) return enumDecl.getFullText().trim();
674
-
675
- // Search methods inside classes
676
- for (const c of sourceFile.getClasses()) {
677
- const method = c.getMethod(symbolName);
678
- if (method) return method.getFullText().trim();
679
-
680
- const getter = c.getGetAccessor(symbolName);
681
- if (getter) return getter.getFullText().trim();
682
-
683
- const setter = c.getSetAccessor(symbolName);
684
- if (setter) return setter.getFullText().trim();
685
- }
686
-
687
- // Search variable declarations (arrow functions, consts)
688
- for (const varStmt of sourceFile.getVariableStatements()) {
689
- for (const decl of varStmt.getDeclarations()) {
690
- if (decl.getName() === symbolName) {
691
- return varStmt.getFullText().trim();
692
- }
693
- }
694
- }
695
-
696
- return null;
697
- } finally {
698
- try {
699
- project.removeSourceFile(sourceFile);
700
- } catch {
701
- // Ignore
702
- }
703
- }
704
- }
705
-
706
- function extractPySymbol(
707
- content: string,
708
- symbolName: string,
709
- className?: string,
710
- ): string | null {
711
- const lines = content.split("\n");
712
- const escapedName = symbolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
713
-
714
- // If className is provided, find the class first, then search within it
715
- if (className) {
716
- const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
717
- for (let i = 0; i < lines.length; i++) {
718
- const trimmed = lines[i].trim();
719
- if (!new RegExp(`^class\\s+${escapedClass}[\\s(:]`).test(trimmed)) continue;
720
-
721
- // Found the class — now search for the method within it
722
- const classIndent = measureIndent(lines[i]);
723
- let j = i + 1;
724
-
725
- while (j < lines.length) {
726
- const bodyLine = lines[j];
727
- if (bodyLine.trim() === "") { j++; continue; }
728
- if (measureIndent(bodyLine) <= classIndent && bodyLine.trim() !== "") break;
729
-
730
- const bodyTrimmed = bodyLine.trim();
731
- const isFunc = new RegExp(
732
- `^(?:async\\s+)?def\\s+${escapedName}\\s*\\(`,
733
- ).test(bodyTrimmed);
734
-
735
- if (isFunc) {
736
- const startLine = j;
737
- const baseIndent = measureIndent(bodyLine);
738
- const blockLines: string[] = [bodyLine];
739
- j++;
740
-
741
- while (j < lines.length) {
742
- const innerLine = lines[j];
743
- if (innerLine.trim() === "") { blockLines.push(innerLine); j++; continue; }
744
- if (measureIndent(innerLine) > baseIndent) { blockLines.push(innerLine); j++; }
745
- else break;
746
- }
747
-
748
- while (blockLines.length > 0 && blockLines[blockLines.length - 1].trim() === "") {
749
- blockLines.pop();
750
- }
751
- return blockLines.join("\n");
752
- }
753
- j++;
754
- }
755
- return null; // Method not found within class
756
- }
757
- return null; // Class not found
758
- }
759
-
760
- // Original behavior when no className is provided
761
- for (let i = 0; i < lines.length; i++) {
762
- const line = lines[i];
763
- const trimmed = line.trim();
764
-
765
- // Match class or function by exact name
766
- const isClass = new RegExp(`^class\\s+${escapedName}[\\s(:]`).test(
767
- trimmed,
768
- );
769
- const isFunc = new RegExp(
770
- `^(?:async\\s+)?def\\s+${escapedName}\\s*\\(`,
771
- ).test(trimmed);
772
-
773
- if (!isClass && !isFunc) continue;
774
-
775
- const startLine = i;
776
- const baseIndent = measureIndent(line);
777
- const blockLines: string[] = [line];
778
- i++;
779
-
780
- // Capture the entire block (including body)
781
- while (i < lines.length) {
782
- const bodyLine = lines[i];
783
- if (bodyLine.trim() === "") {
784
- blockLines.push(bodyLine);
785
- i++;
786
- continue;
787
- }
788
- if (measureIndent(bodyLine) > baseIndent) {
789
- blockLines.push(bodyLine);
790
- i++;
791
- } else {
792
- break;
793
- }
794
- }
795
-
796
- // Trim trailing empty lines
797
- while (
798
- blockLines.length > 0 &&
799
- blockLines[blockLines.length - 1].trim() === ""
800
- ) {
801
- blockLines.pop();
802
- }
803
-
804
- return blockLines.join("\n");
805
- }
806
-
807
- return null;
808
- }