snow-ai 0.2.17 → 0.2.19

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.
@@ -0,0 +1,822 @@
1
+ import { promises as fs } from 'fs';
2
+ import * as path from 'path';
3
+ /**
4
+ * Language-specific parsers configuration
5
+ */
6
+ const LANGUAGE_CONFIG = {
7
+ typescript: {
8
+ extensions: ['.ts', '.tsx'],
9
+ parser: 'typescript',
10
+ symbolPatterns: {
11
+ function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
12
+ class: /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
13
+ variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
14
+ import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
15
+ export: /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type|enum)\s+(\w+)/,
16
+ },
17
+ },
18
+ javascript: {
19
+ extensions: ['.js', '.jsx', '.mjs', '.cjs'],
20
+ parser: 'javascript',
21
+ symbolPatterns: {
22
+ function: /(?:export\s+)?(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
23
+ class: /(?:export\s+)?class\s+(\w+)/,
24
+ variable: /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/,
25
+ import: /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/,
26
+ export: /export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)/,
27
+ },
28
+ },
29
+ python: {
30
+ extensions: ['.py', '.pyx', '.pyi'],
31
+ parser: 'python',
32
+ symbolPatterns: {
33
+ function: /def\s+(\w+)\s*\(/,
34
+ class: /class\s+(\w+)\s*[(:]/,
35
+ variable: /(\w+)\s*=\s*[^=]/,
36
+ import: /(?:from\s+[\w.]+\s+)?import\s+([\w, ]+)/,
37
+ export: /^(\w+)\s*=\s*/, // Python doesn't have explicit exports
38
+ },
39
+ },
40
+ go: {
41
+ extensions: ['.go'],
42
+ parser: 'go',
43
+ symbolPatterns: {
44
+ function: /func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/,
45
+ class: /type\s+(\w+)\s+struct/,
46
+ variable: /(?:var|const)\s+(\w+)\s+/,
47
+ import: /import\s+(?:"([^"]+)"|[(]([^)]+)[)])/,
48
+ export: /^(?:func|type|var|const)\s+([A-Z]\w+)/, // Go exports start with capital letter
49
+ },
50
+ },
51
+ rust: {
52
+ extensions: ['.rs'],
53
+ parser: 'rust',
54
+ symbolPatterns: {
55
+ function: /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*[<(]/,
56
+ class: /(?:pub\s+)?struct\s+(\w+)|(?:pub\s+)?enum\s+(\w+)|(?:pub\s+)?trait\s+(\w+)/,
57
+ variable: /(?:pub\s+)?(?:static|const)\s+(\w+)\s*:/,
58
+ import: /use\s+([^;]+);/,
59
+ export: /pub\s+(?:fn|struct|enum|trait|const|static)\s+(\w+)/,
60
+ },
61
+ },
62
+ java: {
63
+ extensions: ['.java'],
64
+ parser: 'java',
65
+ symbolPatterns: {
66
+ function: /(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
67
+ class: /(?:public|private|protected)?\s*(?:abstract|final)?\s*class\s+(\w+)/,
68
+ variable: /(?:public|private|protected|static|final|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
69
+ import: /import\s+([\w.]+);/,
70
+ export: /public\s+(?:class|interface|enum)\s+(\w+)/,
71
+ },
72
+ },
73
+ csharp: {
74
+ extensions: ['.cs'],
75
+ parser: 'csharp',
76
+ symbolPatterns: {
77
+ function: /(?:public|private|protected|internal|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*\{/,
78
+ class: /(?:public|private|protected|internal)?\s*(?:abstract|sealed|static)?\s*class\s+(\w+)/,
79
+ variable: /(?:public|private|protected|internal|static|readonly|\s)+[\w<>\[\]]+\s+(\w+)\s*[=;]/,
80
+ import: /using\s+([\w.]+);/,
81
+ export: /public\s+(?:class|interface|enum|struct)\s+(\w+)/,
82
+ },
83
+ },
84
+ };
85
+ export class ACECodeSearchService {
86
+ constructor(basePath = process.cwd()) {
87
+ Object.defineProperty(this, "basePath", {
88
+ enumerable: true,
89
+ configurable: true,
90
+ writable: true,
91
+ value: void 0
92
+ });
93
+ Object.defineProperty(this, "indexCache", {
94
+ enumerable: true,
95
+ configurable: true,
96
+ writable: true,
97
+ value: new Map()
98
+ });
99
+ Object.defineProperty(this, "lastIndexTime", {
100
+ enumerable: true,
101
+ configurable: true,
102
+ writable: true,
103
+ value: 0
104
+ });
105
+ Object.defineProperty(this, "INDEX_CACHE_DURATION", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: 60000
110
+ }); // 1 minute
111
+ this.basePath = path.resolve(basePath);
112
+ }
113
+ /**
114
+ * Detect programming language from file extension
115
+ */
116
+ detectLanguage(filePath) {
117
+ const ext = path.extname(filePath).toLowerCase();
118
+ for (const [lang, config] of Object.entries(LANGUAGE_CONFIG)) {
119
+ if (config.extensions.includes(ext)) {
120
+ return lang;
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+ /**
126
+ * Parse file content to extract code symbols using regex patterns
127
+ */
128
+ async parseFileSymbols(filePath, content) {
129
+ const symbols = [];
130
+ const language = this.detectLanguage(filePath);
131
+ if (!language || !LANGUAGE_CONFIG[language]) {
132
+ return symbols;
133
+ }
134
+ const config = LANGUAGE_CONFIG[language];
135
+ const lines = content.split('\n');
136
+ // Parse each line for symbols
137
+ for (let i = 0; i < lines.length; i++) {
138
+ const line = lines[i];
139
+ if (!line)
140
+ continue;
141
+ const lineNumber = i + 1;
142
+ // Extract functions
143
+ if (config.symbolPatterns.function) {
144
+ const match = line.match(config.symbolPatterns.function);
145
+ if (match) {
146
+ const name = match[1] || match[2] || match[3];
147
+ if (name) {
148
+ // Get function signature (current line + next few lines)
149
+ const contextLines = lines.slice(i, Math.min(i + 3, lines.length));
150
+ const signature = contextLines.join('\n').trim();
151
+ symbols.push({
152
+ name,
153
+ type: 'function',
154
+ filePath: path.relative(this.basePath, filePath),
155
+ line: lineNumber,
156
+ column: line.indexOf(name) + 1,
157
+ signature,
158
+ language,
159
+ context: this.getContext(lines, i, 2),
160
+ });
161
+ }
162
+ }
163
+ }
164
+ // Extract classes
165
+ if (config.symbolPatterns.class) {
166
+ const match = line.match(config.symbolPatterns.class);
167
+ if (match) {
168
+ const name = match[1] || match[2] || match[3];
169
+ if (name) {
170
+ symbols.push({
171
+ name,
172
+ type: 'class',
173
+ filePath: path.relative(this.basePath, filePath),
174
+ line: lineNumber,
175
+ column: line.indexOf(name) + 1,
176
+ signature: line.trim(),
177
+ language,
178
+ context: this.getContext(lines, i, 2),
179
+ });
180
+ }
181
+ }
182
+ }
183
+ // Extract variables
184
+ if (config.symbolPatterns.variable) {
185
+ const match = line.match(config.symbolPatterns.variable);
186
+ if (match) {
187
+ const name = match[1];
188
+ if (name) {
189
+ symbols.push({
190
+ name,
191
+ type: 'variable',
192
+ filePath: path.relative(this.basePath, filePath),
193
+ line: lineNumber,
194
+ column: line.indexOf(name) + 1,
195
+ signature: line.trim(),
196
+ language,
197
+ context: this.getContext(lines, i, 1),
198
+ });
199
+ }
200
+ }
201
+ }
202
+ // Extract imports
203
+ if (config.symbolPatterns.import) {
204
+ const match = line.match(config.symbolPatterns.import);
205
+ if (match) {
206
+ const name = match[1] || match[2];
207
+ if (name) {
208
+ symbols.push({
209
+ name,
210
+ type: 'import',
211
+ filePath: path.relative(this.basePath, filePath),
212
+ line: lineNumber,
213
+ column: line.indexOf(name) + 1,
214
+ signature: line.trim(),
215
+ language,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ // Extract exports
221
+ if (config.symbolPatterns.export) {
222
+ const match = line.match(config.symbolPatterns.export);
223
+ if (match) {
224
+ const name = match[1];
225
+ if (name) {
226
+ symbols.push({
227
+ name,
228
+ type: 'export',
229
+ filePath: path.relative(this.basePath, filePath),
230
+ line: lineNumber,
231
+ column: line.indexOf(name) + 1,
232
+ signature: line.trim(),
233
+ language,
234
+ });
235
+ }
236
+ }
237
+ }
238
+ }
239
+ return symbols;
240
+ }
241
+ /**
242
+ * Get context lines around a specific line
243
+ */
244
+ getContext(lines, lineIndex, contextSize) {
245
+ const start = Math.max(0, lineIndex - contextSize);
246
+ const end = Math.min(lines.length, lineIndex + contextSize + 1);
247
+ return lines.slice(start, end).filter(l => l !== undefined).join('\n').trim();
248
+ }
249
+ /**
250
+ * Build or refresh the code symbol index
251
+ */
252
+ async buildIndex(forceRefresh = false) {
253
+ const now = Date.now();
254
+ // Use cache if available and not expired
255
+ if (!forceRefresh && this.indexCache.size > 0 && (now - this.lastIndexTime) < this.INDEX_CACHE_DURATION) {
256
+ return;
257
+ }
258
+ this.indexCache.clear();
259
+ const searchInDirectory = async (dirPath) => {
260
+ try {
261
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
262
+ for (const entry of entries) {
263
+ const fullPath = path.join(dirPath, entry.name);
264
+ if (entry.isDirectory()) {
265
+ // Skip common ignored directories
266
+ if (entry.name === 'node_modules' ||
267
+ entry.name === '.git' ||
268
+ entry.name === 'dist' ||
269
+ entry.name === 'build' ||
270
+ entry.name === '__pycache__' ||
271
+ entry.name === 'target' ||
272
+ entry.name.startsWith('.')) {
273
+ continue;
274
+ }
275
+ await searchInDirectory(fullPath);
276
+ }
277
+ else if (entry.isFile()) {
278
+ const language = this.detectLanguage(fullPath);
279
+ if (language) {
280
+ try {
281
+ const content = await fs.readFile(fullPath, 'utf-8');
282
+ const symbols = await this.parseFileSymbols(fullPath, content);
283
+ if (symbols.length > 0) {
284
+ this.indexCache.set(fullPath, symbols);
285
+ }
286
+ }
287
+ catch (error) {
288
+ // Skip files that cannot be read
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ catch (error) {
295
+ // Skip directories that cannot be accessed
296
+ }
297
+ };
298
+ await searchInDirectory(this.basePath);
299
+ this.lastIndexTime = now;
300
+ }
301
+ /**
302
+ * Search for symbols by name with fuzzy matching
303
+ */
304
+ async searchSymbols(query, symbolType, language, maxResults = 100) {
305
+ const startTime = Date.now();
306
+ await this.buildIndex();
307
+ const symbols = [];
308
+ const queryLower = query.toLowerCase();
309
+ // Fuzzy match scoring
310
+ const calculateScore = (symbolName) => {
311
+ const nameLower = symbolName.toLowerCase();
312
+ // Exact match
313
+ if (nameLower === queryLower)
314
+ return 100;
315
+ // Starts with
316
+ if (nameLower.startsWith(queryLower))
317
+ return 80;
318
+ // Contains
319
+ if (nameLower.includes(queryLower))
320
+ return 60;
321
+ // Camel case match (e.g., "gfc" matches "getFileContent")
322
+ const camelCaseMatch = symbolName.split(/(?=[A-Z])/).map(s => s[0]?.toLowerCase() || '').join('');
323
+ if (camelCaseMatch.includes(queryLower))
324
+ return 40;
325
+ // Fuzzy match
326
+ let score = 0;
327
+ let queryIndex = 0;
328
+ for (let i = 0; i < nameLower.length && queryIndex < queryLower.length; i++) {
329
+ if (nameLower[i] === queryLower[queryIndex]) {
330
+ score += 20;
331
+ queryIndex++;
332
+ }
333
+ }
334
+ if (queryIndex === queryLower.length)
335
+ return score;
336
+ return 0;
337
+ };
338
+ // Search through all indexed symbols
339
+ for (const fileSymbols of this.indexCache.values()) {
340
+ for (const symbol of fileSymbols) {
341
+ // Apply filters
342
+ if (symbolType && symbol.type !== symbolType)
343
+ continue;
344
+ if (language && symbol.language !== language)
345
+ continue;
346
+ const score = calculateScore(symbol.name);
347
+ if (score > 0) {
348
+ symbols.push({ ...symbol });
349
+ }
350
+ if (symbols.length >= maxResults)
351
+ break;
352
+ }
353
+ if (symbols.length >= maxResults)
354
+ break;
355
+ }
356
+ // Sort by relevance
357
+ symbols.sort((a, b) => calculateScore(b.name) - calculateScore(a.name));
358
+ const searchTime = Date.now() - startTime;
359
+ return {
360
+ query,
361
+ symbols,
362
+ references: [], // References would be populated by findReferences
363
+ totalResults: symbols.length,
364
+ searchTime,
365
+ };
366
+ }
367
+ /**
368
+ * Find all references to a symbol
369
+ */
370
+ async findReferences(symbolName, maxResults = 100) {
371
+ const references = [];
372
+ const searchInDirectory = async (dirPath) => {
373
+ try {
374
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
375
+ for (const entry of entries) {
376
+ if (references.length >= maxResults)
377
+ break;
378
+ const fullPath = path.join(dirPath, entry.name);
379
+ if (entry.isDirectory()) {
380
+ if (entry.name === 'node_modules' ||
381
+ entry.name === '.git' ||
382
+ entry.name === 'dist' ||
383
+ entry.name === 'build' ||
384
+ entry.name.startsWith('.')) {
385
+ continue;
386
+ }
387
+ await searchInDirectory(fullPath);
388
+ }
389
+ else if (entry.isFile()) {
390
+ const language = this.detectLanguage(fullPath);
391
+ if (language) {
392
+ try {
393
+ const content = await fs.readFile(fullPath, 'utf-8');
394
+ const lines = content.split('\n');
395
+ // Search for symbol usage
396
+ for (let i = 0; i < lines.length; i++) {
397
+ const line = lines[i];
398
+ if (!line)
399
+ continue;
400
+ const regex = new RegExp(`\\b${symbolName}\\b`, 'g');
401
+ let match;
402
+ while ((match = regex.exec(line)) !== null) {
403
+ if (references.length >= maxResults)
404
+ break;
405
+ // Determine reference type
406
+ let referenceType = 'usage';
407
+ if (line.includes('import') && line.includes(symbolName)) {
408
+ referenceType = 'import';
409
+ }
410
+ else if (line.match(new RegExp(`(?:function|class|const|let|var)\\s+${symbolName}`))) {
411
+ referenceType = 'definition';
412
+ }
413
+ else if (line.includes(':') && line.includes(symbolName)) {
414
+ referenceType = 'type';
415
+ }
416
+ references.push({
417
+ symbol: symbolName,
418
+ filePath: path.relative(this.basePath, fullPath),
419
+ line: i + 1,
420
+ column: match.index + 1,
421
+ context: this.getContext(lines, i, 1),
422
+ referenceType,
423
+ });
424
+ }
425
+ }
426
+ }
427
+ catch (error) {
428
+ // Skip files that cannot be read
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ catch (error) {
435
+ // Skip directories that cannot be accessed
436
+ }
437
+ };
438
+ await searchInDirectory(this.basePath);
439
+ return references;
440
+ }
441
+ /**
442
+ * Find symbol definition (go to definition)
443
+ */
444
+ async findDefinition(symbolName, contextFile) {
445
+ await this.buildIndex();
446
+ // Search in the same file first if context is provided
447
+ if (contextFile) {
448
+ const fullPath = path.resolve(this.basePath, contextFile);
449
+ const fileSymbols = this.indexCache.get(fullPath);
450
+ if (fileSymbols) {
451
+ const symbol = fileSymbols.find(s => s.name === symbolName && (s.type === 'function' || s.type === 'class' || s.type === 'variable'));
452
+ if (symbol)
453
+ return symbol;
454
+ }
455
+ }
456
+ // Search in all files
457
+ for (const fileSymbols of this.indexCache.values()) {
458
+ const symbol = fileSymbols.find(s => s.name === symbolName && (s.type === 'function' || s.type === 'class' || s.type === 'variable'));
459
+ if (symbol)
460
+ return symbol;
461
+ }
462
+ return null;
463
+ }
464
+ /**
465
+ * Fast text search using built-in Node.js (no external dependencies)
466
+ * Searches for text patterns across files with glob filtering
467
+ */
468
+ async textSearch(pattern, fileGlob, isRegex = false, maxResults = 100) {
469
+ const results = [];
470
+ // Compile search pattern
471
+ let searchRegex;
472
+ try {
473
+ if (isRegex) {
474
+ searchRegex = new RegExp(pattern, 'gi');
475
+ }
476
+ else {
477
+ // Escape special regex characters for literal search
478
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
479
+ searchRegex = new RegExp(escaped, 'gi');
480
+ }
481
+ }
482
+ catch (error) {
483
+ throw new Error(`Invalid regex pattern: ${pattern}`);
484
+ }
485
+ // Parse glob pattern if provided
486
+ const globRegex = fileGlob ? this.globToRegex(fileGlob) : null;
487
+ // Search recursively
488
+ const searchInDirectory = async (dirPath) => {
489
+ if (results.length >= maxResults)
490
+ return;
491
+ try {
492
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
493
+ for (const entry of entries) {
494
+ if (results.length >= maxResults)
495
+ break;
496
+ const fullPath = path.join(dirPath, entry.name);
497
+ if (entry.isDirectory()) {
498
+ // Skip ignored directories
499
+ if (entry.name === 'node_modules' ||
500
+ entry.name === '.git' ||
501
+ entry.name === 'dist' ||
502
+ entry.name === 'build' ||
503
+ entry.name === '__pycache__' ||
504
+ entry.name === 'target' ||
505
+ entry.name === '.next' ||
506
+ entry.name === '.nuxt' ||
507
+ entry.name === 'coverage' ||
508
+ entry.name.startsWith('.')) {
509
+ continue;
510
+ }
511
+ await searchInDirectory(fullPath);
512
+ }
513
+ else if (entry.isFile()) {
514
+ // Filter by glob if specified
515
+ if (globRegex && !globRegex.test(fullPath)) {
516
+ continue;
517
+ }
518
+ // Skip binary files
519
+ const ext = path.extname(entry.name).toLowerCase();
520
+ const binaryExts = [
521
+ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg',
522
+ '.pdf', '.zip', '.tar', '.gz', '.rar', '.7z',
523
+ '.exe', '.dll', '.so', '.dylib',
524
+ '.mp3', '.mp4', '.avi', '.mov',
525
+ '.woff', '.woff2', '.ttf', '.eot',
526
+ '.class', '.jar', '.war',
527
+ '.o', '.a', '.lib'
528
+ ];
529
+ if (binaryExts.includes(ext)) {
530
+ continue;
531
+ }
532
+ try {
533
+ const content = await fs.readFile(fullPath, 'utf-8');
534
+ const lines = content.split('\n');
535
+ for (let i = 0; i < lines.length; i++) {
536
+ if (results.length >= maxResults)
537
+ break;
538
+ const line = lines[i];
539
+ if (!line)
540
+ continue;
541
+ // Reset regex for each line
542
+ searchRegex.lastIndex = 0;
543
+ const match = searchRegex.exec(line);
544
+ if (match) {
545
+ results.push({
546
+ filePath: path.relative(this.basePath, fullPath),
547
+ line: i + 1,
548
+ column: match.index + 1,
549
+ content: line.trim(),
550
+ });
551
+ }
552
+ }
553
+ }
554
+ catch (error) {
555
+ // Skip files that cannot be read (binary, permissions, etc.)
556
+ }
557
+ }
558
+ }
559
+ }
560
+ catch (error) {
561
+ // Skip directories that cannot be accessed
562
+ }
563
+ };
564
+ await searchInDirectory(this.basePath);
565
+ return results;
566
+ }
567
+ /**
568
+ * Convert glob pattern to RegExp
569
+ * Supports: *, **, ?, [abc], {js,ts}
570
+ */
571
+ globToRegex(glob) {
572
+ // Escape special regex characters except glob wildcards
573
+ let pattern = glob
574
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
575
+ .replace(/\*\*/g, '<<<DOUBLESTAR>>>') // Temporarily replace **
576
+ .replace(/\*/g, '[^/]*') // * matches anything except /
577
+ .replace(/<<<DOUBLESTAR>>>/g, '.*') // ** matches everything
578
+ .replace(/\?/g, '[^/]'); // ? matches single char except /
579
+ // Handle {js,ts} alternatives
580
+ pattern = pattern.replace(/\\\{([^}]+)\\\}/g, (_, alternatives) => {
581
+ return '(' + alternatives.split(',').join('|') + ')';
582
+ });
583
+ // Handle [abc] character classes (already valid regex)
584
+ pattern = pattern.replace(/\\\[([^\]]+)\\\]/g, '[$1]');
585
+ return new RegExp(pattern, 'i');
586
+ }
587
+ /**
588
+ * Get code outline for a file (all symbols in the file)
589
+ */
590
+ async getFileOutline(filePath) {
591
+ const fullPath = path.resolve(this.basePath, filePath);
592
+ try {
593
+ const content = await fs.readFile(fullPath, 'utf-8');
594
+ return await this.parseFileSymbols(fullPath, content);
595
+ }
596
+ catch (error) {
597
+ throw new Error(`Failed to get outline for ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
598
+ }
599
+ }
600
+ /**
601
+ * Search with language-specific context (cross-reference search)
602
+ */
603
+ async semanticSearch(query, searchType = 'all', language, maxResults = 50) {
604
+ const startTime = Date.now();
605
+ // Get symbol search results
606
+ const symbolResults = await this.searchSymbols(query, undefined, language, maxResults);
607
+ // Get reference results if needed
608
+ let references = [];
609
+ if (searchType === 'usage' || searchType === 'all') {
610
+ // Find references for the top matching symbols
611
+ const topSymbols = symbolResults.symbols.slice(0, 5);
612
+ for (const symbol of topSymbols) {
613
+ const symbolRefs = await this.findReferences(symbol.name, maxResults);
614
+ references.push(...symbolRefs);
615
+ }
616
+ }
617
+ // Filter results based on search type
618
+ let filteredSymbols = symbolResults.symbols;
619
+ if (searchType === 'definition') {
620
+ filteredSymbols = symbolResults.symbols.filter(s => s.type === 'function' || s.type === 'class' || s.type === 'interface');
621
+ }
622
+ else if (searchType === 'usage') {
623
+ filteredSymbols = [];
624
+ }
625
+ else if (searchType === 'implementation') {
626
+ filteredSymbols = symbolResults.symbols.filter(s => s.type === 'function' || s.type === 'method' || s.type === 'class');
627
+ }
628
+ const searchTime = Date.now() - startTime;
629
+ return {
630
+ query,
631
+ symbols: filteredSymbols,
632
+ references,
633
+ totalResults: filteredSymbols.length + references.length,
634
+ searchTime,
635
+ };
636
+ }
637
+ /**
638
+ * Clear the symbol index cache
639
+ */
640
+ clearCache() {
641
+ this.indexCache.clear();
642
+ this.lastIndexTime = 0;
643
+ }
644
+ /**
645
+ * Get index statistics
646
+ */
647
+ getIndexStats() {
648
+ let totalSymbols = 0;
649
+ const languageBreakdown = {};
650
+ for (const symbols of this.indexCache.values()) {
651
+ totalSymbols += symbols.length;
652
+ for (const symbol of symbols) {
653
+ languageBreakdown[symbol.language] = (languageBreakdown[symbol.language] || 0) + 1;
654
+ }
655
+ }
656
+ return {
657
+ totalFiles: this.indexCache.size,
658
+ totalSymbols,
659
+ languageBreakdown,
660
+ cacheAge: Date.now() - this.lastIndexTime,
661
+ };
662
+ }
663
+ }
664
+ // Export a default instance
665
+ export const aceCodeSearchService = new ACECodeSearchService();
666
+ // MCP Tool definitions for integration
667
+ export const mcpTools = [
668
+ {
669
+ name: 'ace_search_symbols',
670
+ description: 'ACE Code Search: Intelligent symbol search across the codebase. Finds functions, classes, variables, and other code symbols with fuzzy matching. Supports multiple programming languages (TypeScript, JavaScript, Python, Go, Rust, Java, C#). Returns precise file locations with line numbers and context.',
671
+ inputSchema: {
672
+ type: 'object',
673
+ properties: {
674
+ query: {
675
+ type: 'string',
676
+ description: 'Symbol name to search for (supports fuzzy matching, e.g., "gfc" can match "getFileContent")',
677
+ },
678
+ symbolType: {
679
+ type: 'string',
680
+ enum: ['function', 'class', 'method', 'variable', 'constant', 'interface', 'type', 'enum', 'import', 'export'],
681
+ description: 'Filter by specific symbol type (optional)',
682
+ },
683
+ language: {
684
+ type: 'string',
685
+ enum: ['typescript', 'javascript', 'python', 'go', 'rust', 'java', 'csharp'],
686
+ description: 'Filter by programming language (optional)',
687
+ },
688
+ maxResults: {
689
+ type: 'number',
690
+ description: 'Maximum number of results to return (default: 100)',
691
+ default: 100,
692
+ },
693
+ },
694
+ required: ['query'],
695
+ },
696
+ },
697
+ {
698
+ name: 'ace_find_definition',
699
+ description: 'ACE Code Search: Find the definition of a symbol (Go to Definition). Locates where a function, class, or variable is defined in the codebase. Returns precise location with full signature and context.',
700
+ inputSchema: {
701
+ type: 'object',
702
+ properties: {
703
+ symbolName: {
704
+ type: 'string',
705
+ description: 'Name of the symbol to find definition for',
706
+ },
707
+ contextFile: {
708
+ type: 'string',
709
+ description: 'Current file path for context-aware search (optional, searches current file first)',
710
+ },
711
+ },
712
+ required: ['symbolName'],
713
+ },
714
+ },
715
+ {
716
+ name: 'ace_find_references',
717
+ description: 'ACE Code Search: Find all references to a symbol (Find All References). Shows where a function, class, or variable is used throughout the codebase. Categorizes references as definition, usage, import, or type reference.',
718
+ inputSchema: {
719
+ type: 'object',
720
+ properties: {
721
+ symbolName: {
722
+ type: 'string',
723
+ description: 'Name of the symbol to find references for',
724
+ },
725
+ maxResults: {
726
+ type: 'number',
727
+ description: 'Maximum number of references to return (default: 100)',
728
+ default: 100,
729
+ },
730
+ },
731
+ required: ['symbolName'],
732
+ },
733
+ },
734
+ {
735
+ name: 'ace_semantic_search',
736
+ description: 'ACE Code Search: Advanced semantic search with context understanding. Searches for symbols with intelligent filtering by search type (definition, usage, implementation, all). Combines symbol search with cross-reference analysis.',
737
+ inputSchema: {
738
+ type: 'object',
739
+ properties: {
740
+ query: {
741
+ type: 'string',
742
+ description: 'Search query (symbol name or pattern)',
743
+ },
744
+ searchType: {
745
+ type: 'string',
746
+ enum: ['definition', 'usage', 'implementation', 'all'],
747
+ description: 'Type of search: definition (find declarations), usage (find usages), implementation (find implementations), all (comprehensive search)',
748
+ default: 'all',
749
+ },
750
+ language: {
751
+ type: 'string',
752
+ enum: ['typescript', 'javascript', 'python', 'go', 'rust', 'java', 'csharp'],
753
+ description: 'Filter by programming language (optional)',
754
+ },
755
+ maxResults: {
756
+ type: 'number',
757
+ description: 'Maximum number of results to return (default: 50)',
758
+ default: 50,
759
+ },
760
+ },
761
+ required: ['query'],
762
+ },
763
+ },
764
+ {
765
+ name: 'ace_file_outline',
766
+ description: 'ACE Code Search: Get complete code outline for a file. Shows all functions, classes, variables, and other symbols defined in the file with their locations. Similar to VS Code\'s outline view.',
767
+ inputSchema: {
768
+ type: 'object',
769
+ properties: {
770
+ filePath: {
771
+ type: 'string',
772
+ description: 'Path to the file to get outline for (relative to workspace root)',
773
+ },
774
+ },
775
+ required: ['filePath'],
776
+ },
777
+ },
778
+ {
779
+ name: 'ace_text_search',
780
+ description: 'ACE Code Search: Fast text search across the entire codebase using Node.js built-in features (no external dependencies required). Search for exact patterns or regex across all files. Useful for finding strings, comments, TODOs, or any text patterns. Supports glob filtering.',
781
+ inputSchema: {
782
+ type: 'object',
783
+ properties: {
784
+ pattern: {
785
+ type: 'string',
786
+ description: 'Text pattern or regex to search for (e.g., "TODO:", "import.*from", "throw new Error")',
787
+ },
788
+ fileGlob: {
789
+ type: 'string',
790
+ description: 'Glob pattern to filter files (e.g., "*.ts" for TypeScript only, "**/*.{js,ts}" for JS and TS, "src/**/*.py" for Python in src)',
791
+ },
792
+ isRegex: {
793
+ type: 'boolean',
794
+ description: 'Whether the pattern is a regular expression (default: false for literal text search)',
795
+ default: false,
796
+ },
797
+ maxResults: {
798
+ type: 'number',
799
+ description: 'Maximum number of results to return (default: 100)',
800
+ default: 100,
801
+ },
802
+ },
803
+ required: ['pattern'],
804
+ },
805
+ },
806
+ {
807
+ name: 'ace_index_stats',
808
+ description: 'ACE Code Search: Get statistics about the code index. Shows number of indexed files, symbols, language breakdown, and cache status. Useful for understanding search coverage.',
809
+ inputSchema: {
810
+ type: 'object',
811
+ properties: {},
812
+ },
813
+ },
814
+ {
815
+ name: 'ace_clear_cache',
816
+ description: 'ACE Code Search: Clear the symbol index cache and force a full re-index on next search. Use when codebase has changed significantly or search results seem stale.',
817
+ inputSchema: {
818
+ type: 'object',
819
+ properties: {},
820
+ },
821
+ },
822
+ ];