snow-ai 0.2.28 → 0.3.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/dist/mcp/aceCodeSearch.d.ts +7 -0
- package/dist/mcp/aceCodeSearch.js +217 -76
- package/package.json +1 -1
|
@@ -48,6 +48,9 @@ export declare class ACECodeSearchService {
|
|
|
48
48
|
private fileModTimes;
|
|
49
49
|
private customExcludes;
|
|
50
50
|
private excludesLoaded;
|
|
51
|
+
private regexCache;
|
|
52
|
+
private fileContentCache;
|
|
53
|
+
private readonly FILE_CONTENT_CACHE_SIZE;
|
|
51
54
|
private readonly DEFAULT_EXCLUDES;
|
|
52
55
|
constructor(basePath?: string);
|
|
53
56
|
/**
|
|
@@ -62,6 +65,10 @@ export declare class ACECodeSearchService {
|
|
|
62
65
|
* Detect programming language from file extension
|
|
63
66
|
*/
|
|
64
67
|
private detectLanguage;
|
|
68
|
+
/**
|
|
69
|
+
* Read file with LRU cache to reduce repeated file system access
|
|
70
|
+
*/
|
|
71
|
+
private readFileWithCache;
|
|
65
72
|
/**
|
|
66
73
|
* Parse file content to extract code symbols using regex patterns
|
|
67
74
|
*/
|
|
@@ -121,8 +121,8 @@ export class ACECodeSearchService {
|
|
|
121
121
|
enumerable: true,
|
|
122
122
|
configurable: true,
|
|
123
123
|
writable: true,
|
|
124
|
-
value:
|
|
125
|
-
});
|
|
124
|
+
value: new Set()
|
|
125
|
+
}); // 使用 Set 提高查找性能 O(1)
|
|
126
126
|
Object.defineProperty(this, "fileModTimes", {
|
|
127
127
|
enumerable: true,
|
|
128
128
|
configurable: true,
|
|
@@ -141,6 +141,26 @@ export class ACECodeSearchService {
|
|
|
141
141
|
writable: true,
|
|
142
142
|
value: false
|
|
143
143
|
}); // Track if exclusions have been loaded
|
|
144
|
+
// 预编译的正则表达式缓存
|
|
145
|
+
Object.defineProperty(this, "regexCache", {
|
|
146
|
+
enumerable: true,
|
|
147
|
+
configurable: true,
|
|
148
|
+
writable: true,
|
|
149
|
+
value: new Map()
|
|
150
|
+
});
|
|
151
|
+
// 文件内容缓存(用于减少重复读取)
|
|
152
|
+
Object.defineProperty(this, "fileContentCache", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
writable: true,
|
|
156
|
+
value: new Map()
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(this, "FILE_CONTENT_CACHE_SIZE", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
configurable: true,
|
|
161
|
+
writable: true,
|
|
162
|
+
value: 50
|
|
163
|
+
}); // 限制缓存大小
|
|
144
164
|
// Default exclusion directories
|
|
145
165
|
Object.defineProperty(this, "DEFAULT_EXCLUDES", {
|
|
146
166
|
enumerable: true,
|
|
@@ -230,18 +250,24 @@ export class ACECodeSearchService {
|
|
|
230
250
|
for (const pattern of this.customExcludes) {
|
|
231
251
|
// Simple pattern matching: exact match or glob-style wildcards
|
|
232
252
|
if (pattern.includes('*')) {
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
// 使用缓存的正则表达式,避免重复编译
|
|
254
|
+
let regex = this.regexCache.get(pattern);
|
|
255
|
+
if (!regex) {
|
|
256
|
+
const regexPattern = pattern
|
|
257
|
+
.replace(/\./g, '\\.')
|
|
258
|
+
.replace(/\*/g, '.*');
|
|
259
|
+
regex = new RegExp(`^${regexPattern}$`);
|
|
260
|
+
this.regexCache.set(pattern, regex);
|
|
261
|
+
}
|
|
238
262
|
if (regex.test(relativePath) || regex.test(dirName)) {
|
|
239
263
|
return true;
|
|
240
264
|
}
|
|
241
265
|
}
|
|
242
266
|
else {
|
|
243
267
|
// Exact match
|
|
244
|
-
if (relativePath === pattern ||
|
|
268
|
+
if (relativePath === pattern ||
|
|
269
|
+
dirName === pattern ||
|
|
270
|
+
relativePath.startsWith(pattern + '/')) {
|
|
245
271
|
return true;
|
|
246
272
|
}
|
|
247
273
|
}
|
|
@@ -260,6 +286,30 @@ export class ACECodeSearchService {
|
|
|
260
286
|
}
|
|
261
287
|
return null;
|
|
262
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Read file with LRU cache to reduce repeated file system access
|
|
291
|
+
*/
|
|
292
|
+
async readFileWithCache(filePath) {
|
|
293
|
+
const stats = await fs.stat(filePath);
|
|
294
|
+
const mtime = stats.mtimeMs;
|
|
295
|
+
// Check cache
|
|
296
|
+
const cached = this.fileContentCache.get(filePath);
|
|
297
|
+
if (cached && cached.mtime === mtime) {
|
|
298
|
+
return cached.content;
|
|
299
|
+
}
|
|
300
|
+
// Read file
|
|
301
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
302
|
+
// Manage cache size (simple LRU: remove oldest if over limit)
|
|
303
|
+
if (this.fileContentCache.size >= this.FILE_CONTENT_CACHE_SIZE) {
|
|
304
|
+
const firstKey = this.fileContentCache.keys().next().value;
|
|
305
|
+
if (firstKey) {
|
|
306
|
+
this.fileContentCache.delete(firstKey);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Cache the content
|
|
310
|
+
this.fileContentCache.set(filePath, { content, mtime });
|
|
311
|
+
return content;
|
|
312
|
+
}
|
|
263
313
|
/**
|
|
264
314
|
* Parse file content to extract code symbols using regex patterns
|
|
265
315
|
*/
|
|
@@ -382,7 +432,11 @@ export class ACECodeSearchService {
|
|
|
382
432
|
getContext(lines, lineIndex, contextSize) {
|
|
383
433
|
const start = Math.max(0, lineIndex - contextSize);
|
|
384
434
|
const end = Math.min(lines.length, lineIndex + contextSize + 1);
|
|
385
|
-
return lines
|
|
435
|
+
return lines
|
|
436
|
+
.slice(start, end)
|
|
437
|
+
.filter(l => l !== undefined)
|
|
438
|
+
.join('\n')
|
|
439
|
+
.trim();
|
|
386
440
|
}
|
|
387
441
|
/**
|
|
388
442
|
* Check if a directory is a Git repository
|
|
@@ -401,7 +455,7 @@ export class ACECodeSearchService {
|
|
|
401
455
|
* Check if a command is available in the system PATH
|
|
402
456
|
*/
|
|
403
457
|
isCommandAvailable(command) {
|
|
404
|
-
return new Promise(
|
|
458
|
+
return new Promise(resolve => {
|
|
405
459
|
try {
|
|
406
460
|
let child;
|
|
407
461
|
if (process.platform === 'win32') {
|
|
@@ -418,7 +472,7 @@ export class ACECodeSearchService {
|
|
|
418
472
|
stdio: 'ignore',
|
|
419
473
|
});
|
|
420
474
|
}
|
|
421
|
-
child.on('close',
|
|
475
|
+
child.on('close', code => resolve(code === 0));
|
|
422
476
|
child.on('error', () => resolve(false));
|
|
423
477
|
}
|
|
424
478
|
catch {
|
|
@@ -468,7 +522,9 @@ export class ACECodeSearchService {
|
|
|
468
522
|
async buildIndex(forceRefresh = false) {
|
|
469
523
|
const now = Date.now();
|
|
470
524
|
// Use cache if available and not expired
|
|
471
|
-
if (!forceRefresh &&
|
|
525
|
+
if (!forceRefresh &&
|
|
526
|
+
this.indexCache.size > 0 &&
|
|
527
|
+
now - this.lastIndexTime < this.INDEX_CACHE_DURATION) {
|
|
472
528
|
return;
|
|
473
529
|
}
|
|
474
530
|
// Load exclusion patterns
|
|
@@ -477,7 +533,8 @@ export class ACECodeSearchService {
|
|
|
477
533
|
if (forceRefresh) {
|
|
478
534
|
this.indexCache.clear();
|
|
479
535
|
this.fileModTimes.clear();
|
|
480
|
-
this.allIndexedFiles
|
|
536
|
+
this.allIndexedFiles.clear();
|
|
537
|
+
this.fileContentCache.clear();
|
|
481
538
|
}
|
|
482
539
|
const filesToProcess = [];
|
|
483
540
|
const searchInDirectory = async (dirPath) => {
|
|
@@ -506,9 +563,7 @@ export class ACECodeSearchService {
|
|
|
506
563
|
this.fileModTimes.set(fullPath, currentMtime);
|
|
507
564
|
}
|
|
508
565
|
// Track all indexed files (even if not modified)
|
|
509
|
-
|
|
510
|
-
this.allIndexedFiles.push(fullPath);
|
|
511
|
-
}
|
|
566
|
+
this.allIndexedFiles.add(fullPath);
|
|
512
567
|
}
|
|
513
568
|
catch (error) {
|
|
514
569
|
// If we can't stat the file, skip it
|
|
@@ -522,24 +577,32 @@ export class ACECodeSearchService {
|
|
|
522
577
|
}
|
|
523
578
|
};
|
|
524
579
|
await searchInDirectory(this.basePath);
|
|
525
|
-
// Process
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
580
|
+
// Process files in batches for better performance
|
|
581
|
+
const BATCH_SIZE = 10; // 并发处理批次大小
|
|
582
|
+
const batches = [];
|
|
583
|
+
for (let i = 0; i < filesToProcess.length; i += BATCH_SIZE) {
|
|
584
|
+
batches.push(filesToProcess.slice(i, i + BATCH_SIZE));
|
|
585
|
+
}
|
|
586
|
+
// Process batches concurrently
|
|
587
|
+
for (const batch of batches) {
|
|
588
|
+
await Promise.all(batch.map(async (fullPath) => {
|
|
589
|
+
try {
|
|
590
|
+
const content = await this.readFileWithCache(fullPath);
|
|
591
|
+
const symbols = await this.parseFileSymbols(fullPath, content);
|
|
592
|
+
if (symbols.length > 0) {
|
|
593
|
+
this.indexCache.set(fullPath, symbols);
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
// Remove entry if no symbols found
|
|
597
|
+
this.indexCache.delete(fullPath);
|
|
598
|
+
}
|
|
532
599
|
}
|
|
533
|
-
|
|
534
|
-
// Remove
|
|
600
|
+
catch (error) {
|
|
601
|
+
// Remove from index if file cannot be read
|
|
535
602
|
this.indexCache.delete(fullPath);
|
|
603
|
+
this.fileModTimes.delete(fullPath);
|
|
536
604
|
}
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
// Remove from index if file cannot be read
|
|
540
|
-
this.indexCache.delete(fullPath);
|
|
541
|
-
this.fileModTimes.delete(fullPath);
|
|
542
|
-
}
|
|
605
|
+
}));
|
|
543
606
|
}
|
|
544
607
|
// Clean up deleted files from cache
|
|
545
608
|
for (const cachedPath of Array.from(this.indexCache.keys())) {
|
|
@@ -550,10 +613,7 @@ export class ACECodeSearchService {
|
|
|
550
613
|
// File no longer exists, remove from cache
|
|
551
614
|
this.indexCache.delete(cachedPath);
|
|
552
615
|
this.fileModTimes.delete(cachedPath);
|
|
553
|
-
|
|
554
|
-
if (fileIndex !== -1) {
|
|
555
|
-
this.allIndexedFiles.splice(fileIndex, 1);
|
|
556
|
-
}
|
|
616
|
+
this.allIndexedFiles.delete(cachedPath);
|
|
557
617
|
}
|
|
558
618
|
}
|
|
559
619
|
this.lastIndexTime = now;
|
|
@@ -654,7 +714,6 @@ export class ACECodeSearchService {
|
|
|
654
714
|
* Fallback symbol search using manual fuzzy matching
|
|
655
715
|
*/
|
|
656
716
|
async searchSymbolsManual(query, symbolType, language, maxResults = 100, startTime = Date.now()) {
|
|
657
|
-
const symbols = [];
|
|
658
717
|
const queryLower = query.toLowerCase();
|
|
659
718
|
// Fuzzy match scoring
|
|
660
719
|
const calculateScore = (symbolName) => {
|
|
@@ -669,7 +728,10 @@ export class ACECodeSearchService {
|
|
|
669
728
|
if (nameLower.includes(queryLower))
|
|
670
729
|
return 60;
|
|
671
730
|
// Camel case match (e.g., "gfc" matches "getFileContent")
|
|
672
|
-
const camelCaseMatch = symbolName
|
|
731
|
+
const camelCaseMatch = symbolName
|
|
732
|
+
.split(/(?=[A-Z])/)
|
|
733
|
+
.map(s => s[0]?.toLowerCase() || '')
|
|
734
|
+
.join('');
|
|
673
735
|
if (camelCaseMatch.includes(queryLower))
|
|
674
736
|
return 40;
|
|
675
737
|
// Fuzzy match
|
|
@@ -685,7 +747,8 @@ export class ACECodeSearchService {
|
|
|
685
747
|
return score;
|
|
686
748
|
return 0;
|
|
687
749
|
};
|
|
688
|
-
// Search through all indexed symbols
|
|
750
|
+
// Search through all indexed symbols with score caching
|
|
751
|
+
const symbolsWithScores = [];
|
|
689
752
|
for (const fileSymbols of this.indexCache.values()) {
|
|
690
753
|
for (const symbol of fileSymbols) {
|
|
691
754
|
// Apply filters
|
|
@@ -695,16 +758,20 @@ export class ACECodeSearchService {
|
|
|
695
758
|
continue;
|
|
696
759
|
const score = calculateScore(symbol.name);
|
|
697
760
|
if (score > 0) {
|
|
698
|
-
|
|
761
|
+
symbolsWithScores.push({ symbol: { ...symbol }, score });
|
|
699
762
|
}
|
|
700
|
-
if (
|
|
701
|
-
break;
|
|
763
|
+
if (symbolsWithScores.length >= maxResults * 2)
|
|
764
|
+
break; // 获取更多候选以便排序
|
|
702
765
|
}
|
|
703
|
-
if (
|
|
766
|
+
if (symbolsWithScores.length >= maxResults * 2)
|
|
704
767
|
break;
|
|
705
768
|
}
|
|
706
|
-
// Sort by
|
|
707
|
-
|
|
769
|
+
// Sort by score (避免重复计算)
|
|
770
|
+
symbolsWithScores.sort((a, b) => b.score - a.score);
|
|
771
|
+
// Extract top results
|
|
772
|
+
const symbols = symbolsWithScores
|
|
773
|
+
.slice(0, maxResults)
|
|
774
|
+
.map(item => item.symbol);
|
|
708
775
|
const searchTime = Date.now() - startTime;
|
|
709
776
|
return {
|
|
710
777
|
query,
|
|
@@ -760,7 +827,8 @@ export class ACECodeSearchService {
|
|
|
760
827
|
else if (line.match(new RegExp(`(?:function|class|const|let|var)\\s+${symbolName}`))) {
|
|
761
828
|
referenceType = 'definition';
|
|
762
829
|
}
|
|
763
|
-
else if (line.includes(':') &&
|
|
830
|
+
else if (line.includes(':') &&
|
|
831
|
+
line.includes(symbolName)) {
|
|
764
832
|
referenceType = 'type';
|
|
765
833
|
}
|
|
766
834
|
references.push({
|
|
@@ -798,14 +866,20 @@ export class ACECodeSearchService {
|
|
|
798
866
|
const fullPath = path.resolve(this.basePath, contextFile);
|
|
799
867
|
const fileSymbols = this.indexCache.get(fullPath);
|
|
800
868
|
if (fileSymbols) {
|
|
801
|
-
const symbol = fileSymbols.find(s => s.name === symbolName &&
|
|
869
|
+
const symbol = fileSymbols.find(s => s.name === symbolName &&
|
|
870
|
+
(s.type === 'function' ||
|
|
871
|
+
s.type === 'class' ||
|
|
872
|
+
s.type === 'variable'));
|
|
802
873
|
if (symbol)
|
|
803
874
|
return symbol;
|
|
804
875
|
}
|
|
805
876
|
}
|
|
806
877
|
// Search in all files
|
|
807
878
|
for (const fileSymbols of this.indexCache.values()) {
|
|
808
|
-
const symbol = fileSymbols.find(s => s.name === symbolName &&
|
|
879
|
+
const symbol = fileSymbols.find(s => s.name === symbolName &&
|
|
880
|
+
(s.type === 'function' ||
|
|
881
|
+
s.type === 'class' ||
|
|
882
|
+
s.type === 'variable'));
|
|
809
883
|
if (symbol)
|
|
810
884
|
return symbol;
|
|
811
885
|
}
|
|
@@ -816,7 +890,14 @@ export class ACECodeSearchService {
|
|
|
816
890
|
*/
|
|
817
891
|
async gitGrepSearch(pattern, fileGlob, maxResults = 100) {
|
|
818
892
|
return new Promise((resolve, reject) => {
|
|
819
|
-
const args = [
|
|
893
|
+
const args = [
|
|
894
|
+
'grep',
|
|
895
|
+
'--untracked',
|
|
896
|
+
'-n',
|
|
897
|
+
'-E',
|
|
898
|
+
'--ignore-case',
|
|
899
|
+
pattern,
|
|
900
|
+
];
|
|
820
901
|
if (fileGlob) {
|
|
821
902
|
args.push('--', fileGlob);
|
|
822
903
|
}
|
|
@@ -826,12 +907,12 @@ export class ACECodeSearchService {
|
|
|
826
907
|
});
|
|
827
908
|
const stdoutChunks = [];
|
|
828
909
|
const stderrChunks = [];
|
|
829
|
-
child.stdout.on('data',
|
|
830
|
-
child.stderr.on('data',
|
|
831
|
-
child.on('error',
|
|
910
|
+
child.stdout.on('data', chunk => stdoutChunks.push(chunk));
|
|
911
|
+
child.stderr.on('data', chunk => stderrChunks.push(chunk));
|
|
912
|
+
child.on('error', err => {
|
|
832
913
|
reject(new Error(`Failed to start git grep: ${err.message}`));
|
|
833
914
|
});
|
|
834
|
-
child.on('close',
|
|
915
|
+
child.on('close', code => {
|
|
835
916
|
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
836
917
|
const stderrData = Buffer.concat(stderrChunks).toString('utf8');
|
|
837
918
|
if (code === 0) {
|
|
@@ -853,7 +934,7 @@ export class ACECodeSearchService {
|
|
|
853
934
|
*/
|
|
854
935
|
async systemGrepSearch(pattern, fileGlob, maxResults = 100) {
|
|
855
936
|
// Prefer ripgrep (rg) over grep if available
|
|
856
|
-
const grepCommand = await this.isCommandAvailable('rg') ? 'rg' : 'grep';
|
|
937
|
+
const grepCommand = (await this.isCommandAvailable('rg')) ? 'rg' : 'grep';
|
|
857
938
|
const isRipgrep = grepCommand === 'rg';
|
|
858
939
|
return new Promise((resolve, reject) => {
|
|
859
940
|
const args = isRipgrep
|
|
@@ -861,8 +942,15 @@ export class ACECodeSearchService {
|
|
|
861
942
|
: ['-r', '-n', '-H', '-E', '-i'];
|
|
862
943
|
// Add exclusion patterns
|
|
863
944
|
const excludeDirs = [
|
|
864
|
-
'node_modules',
|
|
865
|
-
'
|
|
945
|
+
'node_modules',
|
|
946
|
+
'.git',
|
|
947
|
+
'dist',
|
|
948
|
+
'build',
|
|
949
|
+
'__pycache__',
|
|
950
|
+
'target',
|
|
951
|
+
'.next',
|
|
952
|
+
'.nuxt',
|
|
953
|
+
'coverage',
|
|
866
954
|
];
|
|
867
955
|
if (isRipgrep) {
|
|
868
956
|
// Ripgrep uses --glob for filtering
|
|
@@ -885,8 +973,8 @@ export class ACECodeSearchService {
|
|
|
885
973
|
});
|
|
886
974
|
const stdoutChunks = [];
|
|
887
975
|
const stderrChunks = [];
|
|
888
|
-
child.stdout.on('data',
|
|
889
|
-
child.stderr.on('data',
|
|
976
|
+
child.stdout.on('data', chunk => stdoutChunks.push(chunk));
|
|
977
|
+
child.stderr.on('data', chunk => {
|
|
890
978
|
const stderrStr = chunk.toString();
|
|
891
979
|
// Suppress common harmless stderr messages
|
|
892
980
|
if (!stderrStr.includes('Permission denied') &&
|
|
@@ -894,10 +982,10 @@ export class ACECodeSearchService {
|
|
|
894
982
|
stderrChunks.push(chunk);
|
|
895
983
|
}
|
|
896
984
|
});
|
|
897
|
-
child.on('error',
|
|
985
|
+
child.on('error', err => {
|
|
898
986
|
reject(new Error(`Failed to start ${grepCommand}: ${err.message}`));
|
|
899
987
|
});
|
|
900
|
-
child.on('close',
|
|
988
|
+
child.on('close', code => {
|
|
901
989
|
const stdoutData = Buffer.concat(stdoutChunks).toString('utf8');
|
|
902
990
|
const stderrData = Buffer.concat(stderrChunks).toString('utf8').trim();
|
|
903
991
|
if (code === 0) {
|
|
@@ -974,13 +1062,37 @@ export class ACECodeSearchService {
|
|
|
974
1062
|
// Skip binary files
|
|
975
1063
|
const ext = path.extname(entry.name).toLowerCase();
|
|
976
1064
|
const binaryExts = [
|
|
977
|
-
'.jpg',
|
|
978
|
-
'.
|
|
979
|
-
'.
|
|
980
|
-
'.
|
|
981
|
-
'.
|
|
982
|
-
'.
|
|
983
|
-
'.
|
|
1065
|
+
'.jpg',
|
|
1066
|
+
'.jpeg',
|
|
1067
|
+
'.png',
|
|
1068
|
+
'.gif',
|
|
1069
|
+
'.bmp',
|
|
1070
|
+
'.ico',
|
|
1071
|
+
'.svg',
|
|
1072
|
+
'.pdf',
|
|
1073
|
+
'.zip',
|
|
1074
|
+
'.tar',
|
|
1075
|
+
'.gz',
|
|
1076
|
+
'.rar',
|
|
1077
|
+
'.7z',
|
|
1078
|
+
'.exe',
|
|
1079
|
+
'.dll',
|
|
1080
|
+
'.so',
|
|
1081
|
+
'.dylib',
|
|
1082
|
+
'.mp3',
|
|
1083
|
+
'.mp4',
|
|
1084
|
+
'.avi',
|
|
1085
|
+
'.mov',
|
|
1086
|
+
'.woff',
|
|
1087
|
+
'.woff2',
|
|
1088
|
+
'.ttf',
|
|
1089
|
+
'.eot',
|
|
1090
|
+
'.class',
|
|
1091
|
+
'.jar',
|
|
1092
|
+
'.war',
|
|
1093
|
+
'.o',
|
|
1094
|
+
'.a',
|
|
1095
|
+
'.lib',
|
|
984
1096
|
];
|
|
985
1097
|
if (binaryExts.includes(ext)) {
|
|
986
1098
|
continue;
|
|
@@ -1048,8 +1160,8 @@ export class ACECodeSearchService {
|
|
|
1048
1160
|
}
|
|
1049
1161
|
// Strategy 2: Try system grep/ripgrep
|
|
1050
1162
|
try {
|
|
1051
|
-
const grepAvailable = await this.isCommandAvailable('rg') ||
|
|
1052
|
-
await this.isCommandAvailable('grep');
|
|
1163
|
+
const grepAvailable = (await this.isCommandAvailable('rg')) ||
|
|
1164
|
+
(await this.isCommandAvailable('grep'));
|
|
1053
1165
|
if (grepAvailable) {
|
|
1054
1166
|
const results = await this.systemGrepSearch(pattern, fileGlob, maxResults);
|
|
1055
1167
|
return await this.sortResultsByRecency(results);
|
|
@@ -1091,8 +1203,8 @@ export class ACECodeSearchService {
|
|
|
1091
1203
|
return results.sort((a, b) => {
|
|
1092
1204
|
const aMtime = fileModTimes.get(a.filePath) || 0;
|
|
1093
1205
|
const bMtime = fileModTimes.get(b.filePath) || 0;
|
|
1094
|
-
const aIsRecent =
|
|
1095
|
-
const bIsRecent =
|
|
1206
|
+
const aIsRecent = now - aMtime < recentThreshold;
|
|
1207
|
+
const bIsRecent = now - bMtime < recentThreshold;
|
|
1096
1208
|
// Recent files come first
|
|
1097
1209
|
if (aIsRecent && !bIsRecent)
|
|
1098
1210
|
return -1;
|
|
@@ -1181,7 +1293,8 @@ export class ACECodeSearchService {
|
|
|
1181
1293
|
clearCache() {
|
|
1182
1294
|
this.indexCache.clear();
|
|
1183
1295
|
this.fileModTimes.clear();
|
|
1184
|
-
this.allIndexedFiles
|
|
1296
|
+
this.allIndexedFiles.clear();
|
|
1297
|
+
this.fileContentCache.clear();
|
|
1185
1298
|
this.lastIndexTime = 0;
|
|
1186
1299
|
}
|
|
1187
1300
|
/**
|
|
@@ -1193,7 +1306,8 @@ export class ACECodeSearchService {
|
|
|
1193
1306
|
for (const symbols of this.indexCache.values()) {
|
|
1194
1307
|
totalSymbols += symbols.length;
|
|
1195
1308
|
for (const symbol of symbols) {
|
|
1196
|
-
languageBreakdown[symbol.language] =
|
|
1309
|
+
languageBreakdown[symbol.language] =
|
|
1310
|
+
(languageBreakdown[symbol.language] || 0) + 1;
|
|
1197
1311
|
}
|
|
1198
1312
|
}
|
|
1199
1313
|
return {
|
|
@@ -1220,12 +1334,31 @@ export const mcpTools = [
|
|
|
1220
1334
|
},
|
|
1221
1335
|
symbolType: {
|
|
1222
1336
|
type: 'string',
|
|
1223
|
-
enum: [
|
|
1337
|
+
enum: [
|
|
1338
|
+
'function',
|
|
1339
|
+
'class',
|
|
1340
|
+
'method',
|
|
1341
|
+
'variable',
|
|
1342
|
+
'constant',
|
|
1343
|
+
'interface',
|
|
1344
|
+
'type',
|
|
1345
|
+
'enum',
|
|
1346
|
+
'import',
|
|
1347
|
+
'export',
|
|
1348
|
+
],
|
|
1224
1349
|
description: 'Filter by specific symbol type (optional)',
|
|
1225
1350
|
},
|
|
1226
1351
|
language: {
|
|
1227
1352
|
type: 'string',
|
|
1228
|
-
enum: [
|
|
1353
|
+
enum: [
|
|
1354
|
+
'typescript',
|
|
1355
|
+
'javascript',
|
|
1356
|
+
'python',
|
|
1357
|
+
'go',
|
|
1358
|
+
'rust',
|
|
1359
|
+
'java',
|
|
1360
|
+
'csharp',
|
|
1361
|
+
],
|
|
1229
1362
|
description: 'Filter by programming language (optional)',
|
|
1230
1363
|
},
|
|
1231
1364
|
maxResults: {
|
|
@@ -1292,7 +1425,15 @@ export const mcpTools = [
|
|
|
1292
1425
|
},
|
|
1293
1426
|
language: {
|
|
1294
1427
|
type: 'string',
|
|
1295
|
-
enum: [
|
|
1428
|
+
enum: [
|
|
1429
|
+
'typescript',
|
|
1430
|
+
'javascript',
|
|
1431
|
+
'python',
|
|
1432
|
+
'go',
|
|
1433
|
+
'rust',
|
|
1434
|
+
'java',
|
|
1435
|
+
'csharp',
|
|
1436
|
+
],
|
|
1296
1437
|
description: 'Filter by programming language (optional)',
|
|
1297
1438
|
},
|
|
1298
1439
|
maxResults: {
|
|
@@ -1306,7 +1447,7 @@ export const mcpTools = [
|
|
|
1306
1447
|
},
|
|
1307
1448
|
{
|
|
1308
1449
|
name: 'ace_file_outline',
|
|
1309
|
-
description:
|
|
1450
|
+
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.",
|
|
1310
1451
|
inputSchema: {
|
|
1311
1452
|
type: 'object',
|
|
1312
1453
|
properties: {
|