@softerist/heuristic-mcp 3.2.2 → 3.2.4

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 (46) hide show
  1. package/README.md +387 -376
  2. package/config.jsonc +800 -800
  3. package/features/ann-config.js +102 -110
  4. package/features/clear-cache.js +81 -84
  5. package/features/find-similar-code.js +265 -286
  6. package/features/hybrid-search.js +487 -536
  7. package/features/index-codebase.js +3139 -3270
  8. package/features/lifecycle.js +1041 -1063
  9. package/features/package-version.js +277 -291
  10. package/features/register.js +351 -370
  11. package/features/resources.js +115 -130
  12. package/features/set-workspace.js +214 -240
  13. package/index.js +742 -762
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +465 -519
  16. package/lib/cache.js +1699 -1767
  17. package/lib/call-graph.js +396 -396
  18. package/lib/cli.js +232 -226
  19. package/lib/config.js +1483 -1495
  20. package/lib/constants.js +511 -492
  21. package/lib/embed-query-process.js +206 -212
  22. package/lib/embedding-process.js +434 -451
  23. package/lib/embedding-worker.js +862 -934
  24. package/lib/ignore-patterns.js +276 -316
  25. package/lib/json-worker.js +14 -14
  26. package/lib/json-writer.js +302 -310
  27. package/lib/logging.js +116 -127
  28. package/lib/memory-logger.js +13 -13
  29. package/lib/onnx-backend.js +188 -193
  30. package/lib/path-utils.js +18 -23
  31. package/lib/project-detector.js +82 -84
  32. package/lib/server-lifecycle.js +133 -145
  33. package/lib/settings-editor.js +738 -739
  34. package/lib/slice-normalize.js +25 -31
  35. package/lib/tokenizer.js +168 -203
  36. package/lib/utils.js +364 -409
  37. package/lib/vector-store-binary.js +811 -591
  38. package/lib/vector-store-sqlite.js +377 -414
  39. package/lib/workspace-env.js +32 -34
  40. package/mcp_config.json +9 -9
  41. package/package.json +86 -86
  42. package/scripts/clear-cache.js +20 -20
  43. package/scripts/download-model.js +43 -43
  44. package/scripts/mcp-launcher.js +49 -49
  45. package/scripts/postinstall.js +12 -12
  46. package/search-configs.js +36 -36
package/lib/call-graph.js CHANGED
@@ -1,396 +1,396 @@
1
- /**
2
- * Call Graph Extractor
3
- *
4
- * Lightweight regex-based extraction of function definitions and calls.
5
- * Works across multiple languages without external dependencies.
6
- */
7
-
8
- import path from 'path';
9
-
10
- // Language-specific patterns for function/method definitions
11
- const DEFINITION_PATTERNS = {
12
- javascript: [
13
- // function declarations: function name() or async function name()
14
- /(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
15
- // arrow functions: const name = () => or const name = async () =>
16
- /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
17
- // class declarations
18
- /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
19
- // method definitions: name() { or async name() {
20
- /^\s*(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm,
21
- // object method shorthand: name() { inside object
22
- /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g,
23
- ],
24
- python: [
25
- // def name():
26
- /def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
27
- // class Name:
28
- /class\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[:(]/g,
29
- ],
30
- go: [
31
- // func name() or func (r Receiver) name()
32
- /func\s+(?:\([^)]*\)\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
33
- ],
34
- rust: [
35
- // fn name()
36
- /fn\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[<(]/g,
37
- // impl Name
38
- /impl(?:\s*<[^>]*>)?\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
39
- ],
40
- java: [
41
- // public void name() or private static String name()
42
- /(?:public|private|protected)?\s*(?:static)?\s*(?:\w+)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
43
- // class Name
44
- /class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
45
- ],
46
- };
47
-
48
- // Pattern for function calls (language-agnostic, catches most cases)
49
- const CALL_PATTERN = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
50
-
51
- // Common built-ins to exclude from call detection (all lowercase for case-insensitive matching)
52
- const BUILTIN_EXCLUSIONS = new Set([
53
- // JavaScript
54
- 'if',
55
- 'for',
56
- 'while',
57
- 'switch',
58
- 'catch',
59
- 'function',
60
- 'async',
61
- 'await',
62
- 'return',
63
- 'throw',
64
- 'new',
65
- 'typeof',
66
- 'instanceof',
67
- 'delete',
68
- 'void',
69
- 'console',
70
- 'require',
71
- 'import',
72
- 'export',
73
- 'super',
74
- 'this',
75
- // Common functions that aren't meaningful for call graphs
76
- 'parseint',
77
- 'parsefloat',
78
- 'string',
79
- 'number',
80
- 'boolean',
81
- 'array',
82
- 'object',
83
- 'map',
84
- 'set',
85
- 'promise',
86
- 'error',
87
- 'json',
88
- 'math',
89
- 'date',
90
- 'regexp',
91
- // Python
92
- 'def',
93
- 'class',
94
- 'print',
95
- 'len',
96
- 'range',
97
- 'str',
98
- 'int',
99
- 'float',
100
- 'list',
101
- 'dict',
102
- 'tuple',
103
- 'bool',
104
- 'type',
105
- 'isinstance',
106
- 'hasattr',
107
- 'getattr',
108
- 'setattr',
109
- // Go
110
- 'func',
111
- 'make',
112
- 'append',
113
- 'cap',
114
- 'panic',
115
- 'recover',
116
- // Control flow that looks like function calls
117
- 'else',
118
- 'try',
119
- 'finally',
120
- 'with',
121
- 'assert',
122
- 'raise',
123
- 'yield',
124
- // Test frameworks
125
- 'describe',
126
- 'it',
127
- 'test',
128
- 'expect',
129
- 'beforeeach',
130
- 'aftereach',
131
- 'beforeall',
132
- 'afterall',
133
- // Common prototypes / methods (too noisy)
134
- 'match',
135
- 'exec',
136
- 'replace',
137
- 'split',
138
- 'join',
139
- 'slice',
140
- 'splice',
141
- 'push',
142
- 'pop',
143
- 'shift',
144
- 'unshift',
145
- 'includes',
146
- 'indexof',
147
- 'foreach',
148
- 'filter',
149
- 'reduce',
150
- 'find',
151
- 'some',
152
- 'every',
153
- 'sort',
154
- 'keys',
155
- 'values',
156
- 'entries',
157
- 'from',
158
- 'then',
159
- 'catch',
160
- 'finally',
161
- 'all',
162
- 'race',
163
- 'resolve',
164
- 'reject',
165
- ]);
166
-
167
- /**
168
- * Detect language from file extension
169
- */
170
- function detectLanguage(file) {
171
- const ext = path.extname(file).toLowerCase();
172
- const langMap = {
173
- '.js': 'javascript',
174
- '.jsx': 'javascript',
175
- '.ts': 'javascript',
176
- '.tsx': 'javascript',
177
- '.mjs': 'javascript',
178
- '.cjs': 'javascript',
179
- '.py': 'python',
180
- '.pyw': 'python',
181
- '.go': 'go',
182
- '.rs': 'rust',
183
- '.java': 'java',
184
- '.kt': 'java',
185
- '.scala': 'java',
186
- };
187
- if (langMap[ext]) {
188
- return langMap[ext];
189
- } else {
190
- return 'javascript'; // Default to JS patterns
191
- }
192
- }
193
-
194
- /**
195
- * Extract function/class definitions from content
196
- * Exported for testing; treat as internal helper.
197
- */
198
- export function extractDefinitions(content, file) {
199
- const language = detectLanguage(file);
200
- const patterns = DEFINITION_PATTERNS[language];
201
- const definitions = new Set();
202
-
203
- for (const pattern of patterns) {
204
- // Reset regex state
205
- pattern.lastIndex = 0;
206
- let match;
207
- while ((match = pattern.exec(content)) !== null) {
208
- const name = match[1];
209
- if (name && name.length > 1 && !BUILTIN_EXCLUSIONS.has(name.toLowerCase())) {
210
- definitions.add(name);
211
- }
212
- }
213
- }
214
-
215
- return Array.from(definitions);
216
- }
217
-
218
- /**
219
- * Extract function calls from content
220
- * Exported for testing; treat as internal helper.
221
- */
222
- export function extractCalls(content, file) {
223
- const calls = new Set();
224
-
225
- // Remove string literals and comments to avoid false positives
226
- const cleanContent = removeStringsAndComments(content, file);
227
-
228
- CALL_PATTERN.lastIndex = 0;
229
- let match;
230
- while ((match = CALL_PATTERN.exec(cleanContent)) !== null) {
231
- const name = match[1];
232
- if (name && name.length > 1 && !BUILTIN_EXCLUSIONS.has(name.toLowerCase())) {
233
- calls.add(name);
234
- }
235
- }
236
-
237
- return Array.from(calls);
238
- }
239
-
240
- /**
241
- * Remove string literals and comments to improve extraction accuracy
242
- */
243
- function removeStringsAndComments(content, file) {
244
- const ext = path.extname(file).toLowerCase();
245
-
246
- // Remove single-line comments
247
- let cleaned = content.replace(/\/\/.*$/gm, '');
248
-
249
- // Remove multi-line comments
250
- cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, '');
251
-
252
- // Remove Python comments
253
- if (ext === '.py' || ext === '.pyw') {
254
- cleaned = cleaned.replace(/#.*$/gm, '');
255
- // Remove triple-quoted strings (docstrings)
256
- cleaned = cleaned.replace(/"""[\s\S]*?"""/g, '');
257
- cleaned = cleaned.replace(/'''[\s\S]*?'''/g, '');
258
- }
259
-
260
- // Remove string literals (simplified - handles most cases)
261
- cleaned = cleaned.replace(/"(?:[^"\\]|\\.)*"/g, '""');
262
- cleaned = cleaned.replace(/'(?:[^'\\]|\\.)*'/g, "''");
263
- cleaned = cleaned.replace(/`(?:[^`\\]|\\.)*`/g, '``');
264
-
265
- return cleaned;
266
- }
267
-
268
- /**
269
- * Extract both definitions and calls from a file
270
- */
271
- export function extractCallData(content, file) {
272
- const definitions = extractDefinitions(content, file);
273
- const calls = extractCalls(content, file);
274
-
275
- // Remove self-references (calls to functions defined in same file)
276
- const definitionSet = new Set(definitions);
277
- const externalCalls = calls.filter((c) => !definitionSet.has(c));
278
-
279
- return {
280
- definitions,
281
- calls: externalCalls,
282
- };
283
- }
284
-
285
- /**
286
- * Build a call graph from file data
287
- */
288
- export function buildCallGraph(fileCallData) {
289
- const defines = new Map(); // symbol -> files that define it
290
- const calledBy = new Map(); // symbol -> files that call it
291
- const fileCalls = new Map(); // file -> symbols it calls
292
-
293
- for (const [file, data] of fileCallData.entries()) {
294
- // Record definitions
295
- for (const def of data.definitions) {
296
- if (!defines.has(def)) {
297
- defines.set(def, []);
298
- }
299
- defines.get(def).push(file);
300
- }
301
-
302
- // Record calls
303
- fileCalls.set(file, data.calls);
304
- for (const call of data.calls) {
305
- if (!calledBy.has(call)) {
306
- calledBy.set(call, []);
307
- }
308
- calledBy.get(call).push(file);
309
- }
310
- }
311
-
312
- return { defines, calledBy, fileCalls };
313
- }
314
-
315
- /**
316
- * Get files related to a set of symbols (callers + callees)
317
- */
318
- export function getRelatedFiles(callGraph, symbols, maxHops = 1) {
319
- const related = new Map(); // file -> proximity score (1 = direct, 0.5 = indirect)
320
- const visited = new Set();
321
-
322
- function explore(currentSymbols, hop) {
323
- if (hop > maxHops) return;
324
- const score = 1 / (hop + 1); // Decay with distance
325
-
326
- for (const symbol of currentSymbols) {
327
- // Files that define this symbol
328
- const definers = callGraph.defines.get(symbol) || [];
329
- for (const file of definers) {
330
- if (!visited.has(file)) {
331
- related.set(file, Math.max(related.get(file) || 0, score));
332
- }
333
- }
334
-
335
- // Files that call this symbol
336
- const callers = callGraph.calledBy.get(symbol) || [];
337
- for (const file of callers) {
338
- if (!visited.has(file)) {
339
- related.set(file, Math.max(related.get(file) || 0, score));
340
- }
341
- }
342
-
343
- // For next hop, find what these files call/define
344
- if (hop < maxHops) {
345
- const nextSymbols = new Set();
346
- for (const file of [...definers, ...callers]) {
347
- visited.add(file);
348
- const calls = callGraph.fileCalls.get(file) || [];
349
- for (const c of calls) nextSymbols.add(c);
350
- }
351
- explore(nextSymbols, hop + 1);
352
- }
353
- }
354
- }
355
-
356
- explore(symbols, 0);
357
- return related;
358
- }
359
-
360
- // Patterns for extracting symbols from content
361
- const SYMBOL_PATTERNS = [
362
- // function name()
363
- /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
364
- // class Name
365
- /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
366
- // const/let/var name = ...
367
- /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
368
- // Python def/class
369
- /def\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
370
- /class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
371
- // Go func
372
- /func\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
373
- // Rust fn
374
- /fn\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
375
- // Java/C# methods (simplified)
376
- /(?:public|private|protected|static)\s+\w+\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
377
- ];
378
-
379
- /**
380
- * Extract symbols (function/class names) from search results
381
- */
382
- export function extractSymbolsFromContent(content) {
383
- const symbols = new Set();
384
-
385
- for (const pattern of SYMBOL_PATTERNS) {
386
- pattern.lastIndex = 0;
387
- let match;
388
- while ((match = pattern.exec(content)) !== null) {
389
- if (match[1] && match[1].length > 2) {
390
- symbols.add(match[1]);
391
- }
392
- }
393
- }
394
-
395
- return Array.from(symbols);
396
- }
1
+ /**
2
+ * Call Graph Extractor
3
+ *
4
+ * Lightweight regex-based extraction of function definitions and calls.
5
+ * Works across multiple languages without external dependencies.
6
+ */
7
+
8
+ import path from 'path';
9
+
10
+ // Language-specific patterns for function/method definitions
11
+ const DEFINITION_PATTERNS = {
12
+ javascript: [
13
+ // function declarations: function name() or async function name()
14
+ /(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g,
15
+ // arrow functions: const name = () => or const name = async () =>
16
+ /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
17
+ // class declarations
18
+ /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
19
+ // method definitions: name() { or async name() {
20
+ /^\s*(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/gm,
21
+ // object method shorthand: name() { inside object
22
+ /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*\{/g,
23
+ ],
24
+ python: [
25
+ // def name():
26
+ /def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
27
+ // class Name:
28
+ /class\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[:(]/g,
29
+ ],
30
+ go: [
31
+ // func name() or func (r Receiver) name()
32
+ /func\s+(?:\([^)]*\)\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
33
+ ],
34
+ rust: [
35
+ // fn name()
36
+ /fn\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[<(]/g,
37
+ // impl Name
38
+ /impl(?:\s*<[^>]*>)?\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
39
+ ],
40
+ java: [
41
+ // public void name() or private static String name()
42
+ /(?:public|private|protected)?\s*(?:static)?\s*(?:\w+)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
43
+ // class Name
44
+ /class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
45
+ ],
46
+ };
47
+
48
+ // Pattern for function calls (language-agnostic, catches most cases)
49
+ const CALL_PATTERN = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
50
+
51
+ // Common built-ins to exclude from call detection (all lowercase for case-insensitive matching)
52
+ const BUILTIN_EXCLUSIONS = new Set([
53
+ // JavaScript
54
+ 'if',
55
+ 'for',
56
+ 'while',
57
+ 'switch',
58
+ 'catch',
59
+ 'function',
60
+ 'async',
61
+ 'await',
62
+ 'return',
63
+ 'throw',
64
+ 'new',
65
+ 'typeof',
66
+ 'instanceof',
67
+ 'delete',
68
+ 'void',
69
+ 'console',
70
+ 'require',
71
+ 'import',
72
+ 'export',
73
+ 'super',
74
+ 'this',
75
+ // Common functions that aren't meaningful for call graphs
76
+ 'parseint',
77
+ 'parsefloat',
78
+ 'string',
79
+ 'number',
80
+ 'boolean',
81
+ 'array',
82
+ 'object',
83
+ 'map',
84
+ 'set',
85
+ 'promise',
86
+ 'error',
87
+ 'json',
88
+ 'math',
89
+ 'date',
90
+ 'regexp',
91
+ // Python
92
+ 'def',
93
+ 'class',
94
+ 'print',
95
+ 'len',
96
+ 'range',
97
+ 'str',
98
+ 'int',
99
+ 'float',
100
+ 'list',
101
+ 'dict',
102
+ 'tuple',
103
+ 'bool',
104
+ 'type',
105
+ 'isinstance',
106
+ 'hasattr',
107
+ 'getattr',
108
+ 'setattr',
109
+ // Go
110
+ 'func',
111
+ 'make',
112
+ 'append',
113
+ 'cap',
114
+ 'panic',
115
+ 'recover',
116
+ // Control flow that looks like function calls
117
+ 'else',
118
+ 'try',
119
+ 'finally',
120
+ 'with',
121
+ 'assert',
122
+ 'raise',
123
+ 'yield',
124
+ // Test frameworks
125
+ 'describe',
126
+ 'it',
127
+ 'test',
128
+ 'expect',
129
+ 'beforeeach',
130
+ 'aftereach',
131
+ 'beforeall',
132
+ 'afterall',
133
+ // Common prototypes / methods (too noisy)
134
+ 'match',
135
+ 'exec',
136
+ 'replace',
137
+ 'split',
138
+ 'join',
139
+ 'slice',
140
+ 'splice',
141
+ 'push',
142
+ 'pop',
143
+ 'shift',
144
+ 'unshift',
145
+ 'includes',
146
+ 'indexof',
147
+ 'foreach',
148
+ 'filter',
149
+ 'reduce',
150
+ 'find',
151
+ 'some',
152
+ 'every',
153
+ 'sort',
154
+ 'keys',
155
+ 'values',
156
+ 'entries',
157
+ 'from',
158
+ 'then',
159
+ 'catch',
160
+ 'finally',
161
+ 'all',
162
+ 'race',
163
+ 'resolve',
164
+ 'reject',
165
+ ]);
166
+
167
+ /**
168
+ * Detect language from file extension
169
+ */
170
+ function detectLanguage(file) {
171
+ const ext = path.extname(file).toLowerCase();
172
+ const langMap = {
173
+ '.js': 'javascript',
174
+ '.jsx': 'javascript',
175
+ '.ts': 'javascript',
176
+ '.tsx': 'javascript',
177
+ '.mjs': 'javascript',
178
+ '.cjs': 'javascript',
179
+ '.py': 'python',
180
+ '.pyw': 'python',
181
+ '.go': 'go',
182
+ '.rs': 'rust',
183
+ '.java': 'java',
184
+ '.kt': 'java',
185
+ '.scala': 'java',
186
+ };
187
+ if (langMap[ext]) {
188
+ return langMap[ext];
189
+ } else {
190
+ return 'javascript'; // Default to JS patterns
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Extract function/class definitions from content
196
+ * Exported for testing; treat as internal helper.
197
+ */
198
+ export function extractDefinitions(content, file) {
199
+ const language = detectLanguage(file);
200
+ const patterns = DEFINITION_PATTERNS[language];
201
+ const definitions = new Set();
202
+
203
+ for (const pattern of patterns) {
204
+ // Reset regex state
205
+ pattern.lastIndex = 0;
206
+ let match;
207
+ while ((match = pattern.exec(content)) !== null) {
208
+ const name = match[1];
209
+ if (name && name.length > 1 && !BUILTIN_EXCLUSIONS.has(name.toLowerCase())) {
210
+ definitions.add(name);
211
+ }
212
+ }
213
+ }
214
+
215
+ return Array.from(definitions);
216
+ }
217
+
218
+ /**
219
+ * Extract function calls from content
220
+ * Exported for testing; treat as internal helper.
221
+ */
222
+ export function extractCalls(content, file) {
223
+ const calls = new Set();
224
+
225
+ // Remove string literals and comments to avoid false positives
226
+ const cleanContent = removeStringsAndComments(content, file);
227
+
228
+ CALL_PATTERN.lastIndex = 0;
229
+ let match;
230
+ while ((match = CALL_PATTERN.exec(cleanContent)) !== null) {
231
+ const name = match[1];
232
+ if (name && name.length > 1 && !BUILTIN_EXCLUSIONS.has(name.toLowerCase())) {
233
+ calls.add(name);
234
+ }
235
+ }
236
+
237
+ return Array.from(calls);
238
+ }
239
+
240
+ /**
241
+ * Remove string literals and comments to improve extraction accuracy
242
+ */
243
+ function removeStringsAndComments(content, file) {
244
+ const ext = path.extname(file).toLowerCase();
245
+
246
+ // Remove single-line comments
247
+ let cleaned = content.replace(/\/\/.*$/gm, '');
248
+
249
+ // Remove multi-line comments
250
+ cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, '');
251
+
252
+ // Remove Python comments
253
+ if (ext === '.py' || ext === '.pyw') {
254
+ cleaned = cleaned.replace(/#.*$/gm, '');
255
+ // Remove triple-quoted strings (docstrings)
256
+ cleaned = cleaned.replace(/"""[\s\S]*?"""/g, '');
257
+ cleaned = cleaned.replace(/'''[\s\S]*?'''/g, '');
258
+ }
259
+
260
+ // Remove string literals (simplified - handles most cases)
261
+ cleaned = cleaned.replace(/"(?:[^"\\]|\\.)*"/g, '""');
262
+ cleaned = cleaned.replace(/'(?:[^'\\]|\\.)*'/g, "''");
263
+ cleaned = cleaned.replace(/`(?:[^`\\]|\\.)*`/g, '``');
264
+
265
+ return cleaned;
266
+ }
267
+
268
+ /**
269
+ * Extract both definitions and calls from a file
270
+ */
271
+ export function extractCallData(content, file) {
272
+ const definitions = extractDefinitions(content, file);
273
+ const calls = extractCalls(content, file);
274
+
275
+ // Remove self-references (calls to functions defined in same file)
276
+ const definitionSet = new Set(definitions);
277
+ const externalCalls = calls.filter((c) => !definitionSet.has(c));
278
+
279
+ return {
280
+ definitions,
281
+ calls: externalCalls,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Build a call graph from file data
287
+ */
288
+ export function buildCallGraph(fileCallData) {
289
+ const defines = new Map(); // symbol -> files that define it
290
+ const calledBy = new Map(); // symbol -> files that call it
291
+ const fileCalls = new Map(); // file -> symbols it calls
292
+
293
+ for (const [file, data] of fileCallData.entries()) {
294
+ // Record definitions
295
+ for (const def of data.definitions) {
296
+ if (!defines.has(def)) {
297
+ defines.set(def, []);
298
+ }
299
+ defines.get(def).push(file);
300
+ }
301
+
302
+ // Record calls
303
+ fileCalls.set(file, data.calls);
304
+ for (const call of data.calls) {
305
+ if (!calledBy.has(call)) {
306
+ calledBy.set(call, []);
307
+ }
308
+ calledBy.get(call).push(file);
309
+ }
310
+ }
311
+
312
+ return { defines, calledBy, fileCalls };
313
+ }
314
+
315
+ /**
316
+ * Get files related to a set of symbols (callers + callees)
317
+ */
318
+ export function getRelatedFiles(callGraph, symbols, maxHops = 1) {
319
+ const related = new Map(); // file -> proximity score (1 = direct, 0.5 = indirect)
320
+ const visited = new Set();
321
+
322
+ function explore(currentSymbols, hop) {
323
+ if (hop > maxHops) return;
324
+ const score = 1 / (hop + 1); // Decay with distance
325
+
326
+ for (const symbol of currentSymbols) {
327
+ // Files that define this symbol
328
+ const definers = callGraph.defines.get(symbol) || [];
329
+ for (const file of definers) {
330
+ if (!visited.has(file)) {
331
+ related.set(file, Math.max(related.get(file) || 0, score));
332
+ }
333
+ }
334
+
335
+ // Files that call this symbol
336
+ const callers = callGraph.calledBy.get(symbol) || [];
337
+ for (const file of callers) {
338
+ if (!visited.has(file)) {
339
+ related.set(file, Math.max(related.get(file) || 0, score));
340
+ }
341
+ }
342
+
343
+ // For next hop, find what these files call/define
344
+ if (hop < maxHops) {
345
+ const nextSymbols = new Set();
346
+ for (const file of [...definers, ...callers]) {
347
+ visited.add(file);
348
+ const calls = callGraph.fileCalls.get(file) || [];
349
+ for (const c of calls) nextSymbols.add(c);
350
+ }
351
+ explore(nextSymbols, hop + 1);
352
+ }
353
+ }
354
+ }
355
+
356
+ explore(symbols, 0);
357
+ return related;
358
+ }
359
+
360
+ // Patterns for extracting symbols from content
361
+ const SYMBOL_PATTERNS = [
362
+ // function name()
363
+ /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
364
+ // class Name
365
+ /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
366
+ // const/let/var name = ...
367
+ /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g,
368
+ // Python def/class
369
+ /def\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
370
+ /class\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
371
+ // Go func
372
+ /func\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
373
+ // Rust fn
374
+ /fn\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
375
+ // Java/C# methods (simplified)
376
+ /(?:public|private|protected|static)\s+\w+\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g,
377
+ ];
378
+
379
+ /**
380
+ * Extract symbols (function/class names) from search results
381
+ */
382
+ export function extractSymbolsFromContent(content) {
383
+ const symbols = new Set();
384
+
385
+ for (const pattern of SYMBOL_PATTERNS) {
386
+ pattern.lastIndex = 0;
387
+ let match;
388
+ while ((match = pattern.exec(content)) !== null) {
389
+ if (match[1] && match[1].length > 2) {
390
+ symbols.add(match[1]);
391
+ }
392
+ }
393
+ }
394
+
395
+ return Array.from(symbols);
396
+ }