@mrxkun/mcfast-mcp 3.5.7 → 3.5.8
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/package.json +1 -1
- package/src/index.js +332 -21
- package/src/strategies/search-strategy.js +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.8",
|
|
4
4
|
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. Optimized for AI code assistants with 80-98% latency reduction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -203,7 +203,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
203
203
|
// CORE TOOL 1: edit (consolidates apply_fast + edit_file + apply_search_replace)
|
|
204
204
|
{
|
|
205
205
|
name: "edit",
|
|
206
|
-
description: "**PRIMARY TOOL FOR EDITING FILES** - Intelligent auto-switching strategies: (1) Search & Replace (Fastest) - use 'Replace X with Y'
|
|
206
|
+
description: "**PRIMARY TOOL FOR EDITING FILES** - Intelligent auto-switching strategies: (1) Search & Replace (Fastest) - use 'Replace X with Y'. (2) Placeholder (Efficient) - use '// ... existing code ...' placeholders to save tokens. (3) Mercury AI (Intelligent) - for complex refactoring. Includes Auto-Rollback for syntax errors.",
|
|
207
207
|
inputSchema: {
|
|
208
208
|
type: "object",
|
|
209
209
|
properties: {
|
|
@@ -283,7 +283,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
283
283
|
type: "object",
|
|
284
284
|
properties: {
|
|
285
285
|
path: { type: "string", description: "Root directory path (default: current dir)" },
|
|
286
|
-
depth: { type: "number", description: "Max depth to traverse (default: 5)" }
|
|
286
|
+
depth: { type: "number", description: "Max depth to traverse (default: 5)" },
|
|
287
|
+
offset: { type: "number", description: "Number of files to skip (for pagination, default: 0)" },
|
|
288
|
+
limit: { type: "number", description: "Max files to return (for pagination, default: 500)" }
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
},
|
|
@@ -589,16 +591,140 @@ async function reportAudit(data) {
|
|
|
589
591
|
}
|
|
590
592
|
|
|
591
593
|
// ============================================
|
|
592
|
-
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Handle regex-based filesystem search using grep
|
|
597
|
+
* Uses native grep for high-performance regex searching
|
|
598
|
+
*/
|
|
599
|
+
async function handleRegexSearch({ query, path: searchPath, caseSensitive = false }) {
|
|
600
|
+
const start = Date.now();
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
const flags = [
|
|
604
|
+
"-r", // Recursive
|
|
605
|
+
"-n", // Line number
|
|
606
|
+
"-I", // Ignore binary files
|
|
607
|
+
caseSensitive ? "" : "-i" // Case sensitivity
|
|
608
|
+
].filter(Boolean).join(" ");
|
|
609
|
+
|
|
610
|
+
// Exclude common noise directories
|
|
611
|
+
const excludes = [
|
|
612
|
+
"--exclude-dir=node_modules",
|
|
613
|
+
"--exclude-dir=.git",
|
|
614
|
+
"--exclude-dir=.next",
|
|
615
|
+
"--exclude-dir=dist",
|
|
616
|
+
"--exclude-dir=build",
|
|
617
|
+
"--exclude-dir=coverage",
|
|
618
|
+
"--exclude-dir=.cache"
|
|
619
|
+
].join(" ");
|
|
620
|
+
|
|
621
|
+
const command = `grep ${flags} ${excludes} -E "${query.replace(/"/g, '\\"')}" "${searchPath}"`;
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
const { stdout } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 }); // 10MB buffer
|
|
625
|
+
const results = stdout.trim().split('\n').filter(Boolean);
|
|
626
|
+
|
|
627
|
+
const latency = Date.now() - start;
|
|
628
|
+
|
|
629
|
+
let output = `🔍 Regex Search Results for "${query}"\n`;
|
|
630
|
+
output += `Path: ${searchPath}\n`;
|
|
631
|
+
output += `Matches: ${results.length} lines\n`;
|
|
632
|
+
output += `Latency: ${latency}ms\n\n`;
|
|
633
|
+
|
|
634
|
+
if (results.length === 0) {
|
|
635
|
+
output += "No matches found.";
|
|
636
|
+
} else {
|
|
637
|
+
// Parse grep output format: file:line:content
|
|
638
|
+
const parsedResults = results.map(line => {
|
|
639
|
+
const match = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
640
|
+
if (match) {
|
|
641
|
+
return { file: match[1], line: parseInt(match[2]), content: match[3] };
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
644
|
+
}).filter(Boolean);
|
|
645
|
+
|
|
646
|
+
// Group by file
|
|
647
|
+
const byFile = {};
|
|
648
|
+
parsedResults.forEach(r => {
|
|
649
|
+
if (!byFile[r.file]) byFile[r.file] = [];
|
|
650
|
+
byFile[r.file].push(r);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Output grouped by file
|
|
654
|
+
Object.entries(byFile).slice(0, 20).forEach(([file, matches]) => {
|
|
655
|
+
output += `${colors.yellow}${file}${colors.reset}\n`;
|
|
656
|
+
matches.slice(0, 10).forEach(m => {
|
|
657
|
+
output += ` ${colors.dim}${m.line}:${colors.reset} ${m.content}\n`;
|
|
658
|
+
});
|
|
659
|
+
if (matches.length > 10) {
|
|
660
|
+
output += ` ${colors.dim}... and ${matches.length - 10} more matches${colors.reset}\n`;
|
|
661
|
+
}
|
|
662
|
+
output += "\n";
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
if (parsedResults.length > 200) {
|
|
666
|
+
output += `${colors.dim}... and ${parsedResults.length - 200} more results${colors.reset}\n`;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
reportAudit({
|
|
671
|
+
tool: 'search_regex',
|
|
672
|
+
instruction: `Regex search: ${query}`,
|
|
673
|
+
status: 'success',
|
|
674
|
+
latency_ms: latency,
|
|
675
|
+
files_count: 0,
|
|
676
|
+
result_summary: JSON.stringify(results.slice(0, 100)),
|
|
677
|
+
input_tokens: Math.ceil(query.length / 4),
|
|
678
|
+
output_tokens: Math.ceil(output.length / 4)
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
return { content: [{ type: "text", text: output }] };
|
|
682
|
+
} catch (execErr) {
|
|
683
|
+
if (execErr.code === 1) { // grep returns 1 when no matches found
|
|
684
|
+
const latency = Date.now() - start;
|
|
685
|
+
return {
|
|
686
|
+
content: [{ type: "text", text: `🔍 Regex Search: No matches found for "${query}" in ${searchPath} (${latency}ms)` }]
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
throw execErr;
|
|
690
|
+
}
|
|
691
|
+
} catch (error) {
|
|
692
|
+
const latency = Date.now() - start;
|
|
693
|
+
|
|
694
|
+
reportAudit({
|
|
695
|
+
tool: 'search_regex',
|
|
696
|
+
instruction: `Regex search: ${query}`,
|
|
697
|
+
status: 'error',
|
|
698
|
+
latency_ms: latency,
|
|
699
|
+
error: error.message
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
content: [{ type: "text", text: `❌ Regex search error: ${error.message}` }],
|
|
704
|
+
isError: true
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
593
709
|
// ============================================
|
|
594
710
|
|
|
595
711
|
/**
|
|
596
712
|
* Handle filesystem search using fast-glob and pattern matching
|
|
597
713
|
* Fast alternative to API-based search for local files
|
|
714
|
+
* Now supports regex search using grep
|
|
598
715
|
*/
|
|
599
716
|
async function handleSearchFilesystem({ query, path: searchPath, isRegex = false, caseSensitive = false }) {
|
|
600
717
|
const start = Date.now();
|
|
601
|
-
|
|
718
|
+
|
|
719
|
+
// Default to current directory if no path specified
|
|
720
|
+
const cwd = searchPath || process.cwd();
|
|
721
|
+
|
|
722
|
+
// For regex searches, use grep instead of fast-glob
|
|
723
|
+
if (isRegex) {
|
|
724
|
+
return await handleRegexSearch({ query, path: cwd, caseSensitive });
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Original fast-glob based search for non-regex queries
|
|
602
728
|
try {
|
|
603
729
|
// Determine search pattern
|
|
604
730
|
let pattern = query;
|
|
@@ -607,9 +733,6 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
607
733
|
pattern = `**/*${query}*`;
|
|
608
734
|
}
|
|
609
735
|
|
|
610
|
-
// Default to current directory if no path specified
|
|
611
|
-
const cwd = searchPath || process.cwd();
|
|
612
|
-
|
|
613
736
|
// Use fast-glob to find files
|
|
614
737
|
const files = await fg([pattern], {
|
|
615
738
|
cwd,
|
|
@@ -1071,6 +1194,12 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1071
1194
|
* Auto-detects best strategy based on input
|
|
1072
1195
|
*/
|
|
1073
1196
|
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2 }) {
|
|
1197
|
+
// For regex mode without files, we need to do content-based regex search
|
|
1198
|
+
// since fast-glob doesn't support full regex patterns
|
|
1199
|
+
if (regex && !files && mode === 'auto') {
|
|
1200
|
+
mode = 'filesystem'; // Will use grep-based search below
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1074
1203
|
const detectedMode = mode === 'auto' ? detectSearchStrategy({ query, files, path, mode }) : mode;
|
|
1075
1204
|
|
|
1076
1205
|
console.error(`${colors.cyan}[SEARCH STRATEGY]${colors.reset} ${detectedMode}`);
|
|
@@ -1087,12 +1216,16 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
|
|
|
1087
1216
|
|
|
1088
1217
|
// Strategy 3: LSP Definition
|
|
1089
1218
|
if (detectedMode === 'definition') {
|
|
1090
|
-
|
|
1219
|
+
// If no path provided, use current working directory
|
|
1220
|
+
const searchPath = path || process.cwd();
|
|
1221
|
+
return await handleGetDefinition({ path: searchPath, symbol: query });
|
|
1091
1222
|
}
|
|
1092
1223
|
|
|
1093
1224
|
// Strategy 4: LSP References
|
|
1094
1225
|
if (detectedMode === 'references') {
|
|
1095
|
-
|
|
1226
|
+
// If no path provided, use current working directory
|
|
1227
|
+
const searchPath = path || process.cwd();
|
|
1228
|
+
return await handleFindReferences({ path: searchPath, symbol: query });
|
|
1096
1229
|
}
|
|
1097
1230
|
|
|
1098
1231
|
// Strategy 5: Filesystem search (fast grep-based)
|
|
@@ -1523,20 +1656,37 @@ async function handleSearchCode({ query, files, regex = false, caseSensitive = f
|
|
|
1523
1656
|
}
|
|
1524
1657
|
}
|
|
1525
1658
|
|
|
1526
|
-
async function handleListFiles({ path: dirPath = process.cwd(), depth = 5 }) {
|
|
1659
|
+
async function handleListFiles({ path: dirPath = process.cwd(), depth = 5, offset = 0, limit = 500 }) {
|
|
1527
1660
|
const start = Date.now();
|
|
1528
1661
|
try {
|
|
1529
1662
|
const files = await getFiles(dirPath, depth);
|
|
1530
1663
|
|
|
1531
|
-
|
|
1664
|
+
// Apply pagination
|
|
1665
|
+
const totalFiles = files.length;
|
|
1666
|
+
const paginatedFiles = files.slice(offset, offset + limit);
|
|
1667
|
+
const hasMore = offset + limit < totalFiles;
|
|
1668
|
+
|
|
1669
|
+
// Build output with pagination info
|
|
1670
|
+
let output = `📁 Files in ${dirPath} (depth: ${depth}):\n`;
|
|
1671
|
+
output += `Showing ${offset + 1}-${Math.min(offset + limit, totalFiles)} of ${totalFiles} files`;
|
|
1672
|
+
if (hasMore) {
|
|
1673
|
+
output += `. Use offset: ${offset + limit} to see more.`;
|
|
1674
|
+
}
|
|
1675
|
+
output += `\n\n${paginatedFiles.join('\n')}`;
|
|
1676
|
+
|
|
1677
|
+
// Check for large output and add helpful message
|
|
1678
|
+
const outputLength = output.length;
|
|
1679
|
+
if (outputLength > 50000) {
|
|
1680
|
+
output += `\n\n💡 Tip: Output is large. Use depth parameter to limit depth (e.g., depth: 2 or 3) to limit the search scope.`;
|
|
1681
|
+
}
|
|
1532
1682
|
|
|
1533
1683
|
reportAudit({
|
|
1534
1684
|
tool: 'list_files_fast',
|
|
1535
1685
|
instruction: dirPath,
|
|
1536
1686
|
status: 'success',
|
|
1537
1687
|
latency_ms: Date.now() - start,
|
|
1538
|
-
files_count:
|
|
1539
|
-
result_summary: JSON.stringify(
|
|
1688
|
+
files_count: totalFiles,
|
|
1689
|
+
result_summary: JSON.stringify(paginatedFiles.slice(0, 500)),
|
|
1540
1690
|
input_tokens: Math.ceil(dirPath.length / 4),
|
|
1541
1691
|
output_tokens: Math.ceil(output.length / 4)
|
|
1542
1692
|
});
|
|
@@ -1546,6 +1696,12 @@ async function handleListFiles({ path: dirPath = process.cwd(), depth = 5 }) {
|
|
|
1546
1696
|
};
|
|
1547
1697
|
|
|
1548
1698
|
} catch (error) {
|
|
1699
|
+
// Error recovery: suggest using depth parameter
|
|
1700
|
+
let errorMessage = `❌ Error listing files: ${error.message}`;
|
|
1701
|
+
if (error.message.includes('too many files') || error.message.includes('EMFILE') || error.message.includes('max')) {
|
|
1702
|
+
errorMessage += `\n\n💡 Tip: Try reducing the depth (e.g., depth: 2 or 3) to limit the search scope.`;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1549
1705
|
reportAudit({
|
|
1550
1706
|
tool: 'list_files_fast',
|
|
1551
1707
|
instruction: dirPath,
|
|
@@ -1554,7 +1710,7 @@ async function handleListFiles({ path: dirPath = process.cwd(), depth = 5 }) {
|
|
|
1554
1710
|
latency_ms: Date.now() - start
|
|
1555
1711
|
});
|
|
1556
1712
|
return {
|
|
1557
|
-
content: [{ type: "text", text:
|
|
1713
|
+
content: [{ type: "text", text: errorMessage }],
|
|
1558
1714
|
isError: true
|
|
1559
1715
|
};
|
|
1560
1716
|
}
|
|
@@ -1843,14 +1999,22 @@ async function handleSearchCodeAI({ query, files, contextLines = 2 }) {
|
|
|
1843
1999
|
async function handleGetDefinition({ path: filePath, symbol }) {
|
|
1844
2000
|
if (!filePath || !symbol) throw new Error("Missing path or symbol");
|
|
1845
2001
|
|
|
1846
|
-
// Check if path is a directory
|
|
1847
2002
|
const absolutePath = path.resolve(filePath);
|
|
1848
|
-
|
|
2003
|
+
|
|
2004
|
+
// Check if path is a directory - search across files in directory
|
|
2005
|
+
let stats;
|
|
2006
|
+
try {
|
|
2007
|
+
stats = await fs.stat(absolutePath);
|
|
2008
|
+
} catch (e) {
|
|
2009
|
+
throw new Error(`Path does not exist: ${filePath}`);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1849
2012
|
if (!stats.isFile()) {
|
|
1850
|
-
|
|
2013
|
+
// It's a directory - search for symbol definition using grep
|
|
2014
|
+
return await handleDefinitionInDirectory({ path: absolutePath, symbol });
|
|
1851
2015
|
}
|
|
1852
2016
|
|
|
1853
|
-
//
|
|
2017
|
+
// It's a file - use tree-sitter to find definition
|
|
1854
2018
|
const content = await fs.readFile(filePath, 'utf8');
|
|
1855
2019
|
const definitions = await findDefinition(content, filePath, symbol);
|
|
1856
2020
|
|
|
@@ -1867,16 +2031,88 @@ async function handleGetDefinition({ path: filePath, symbol }) {
|
|
|
1867
2031
|
};
|
|
1868
2032
|
}
|
|
1869
2033
|
|
|
2034
|
+
async function handleDefinitionInDirectory({ path: dirPath, symbol }) {
|
|
2035
|
+
// Search for symbol definition in directory using grep
|
|
2036
|
+
// Look for common definition patterns: const/let/var, function, class, interface, type, etc.
|
|
2037
|
+
const patterns = [
|
|
2038
|
+
`^\\s*(const|let|var)\\s+${symbol}\\s*=`,
|
|
2039
|
+
`^\\s*function\\s+${symbol}\\s*\\(`,
|
|
2040
|
+
`^\\s*class\\s+${symbol}\\s*[\\{\\s]`,
|
|
2041
|
+
`^\\s*interface\\s+${symbol}\\s*[\\{\\s]`,
|
|
2042
|
+
`^\\s*type\\s+${symbol}\\s*=`,
|
|
2043
|
+
`^\\s*enum\\s+${symbol}\\s*[\\{\\s]`,
|
|
2044
|
+
`^\\s*export\\s+(const|let|var|function|class|interface|type|enum)\\s+${symbol}`,
|
|
2045
|
+
`^\\s*async\\s+function\\s+${symbol}\\s*\\(`,
|
|
2046
|
+
];
|
|
2047
|
+
|
|
2048
|
+
const results = [];
|
|
2049
|
+
for (const pattern of patterns) {
|
|
2050
|
+
const flags = "-rn";
|
|
2051
|
+
const excludes = [
|
|
2052
|
+
"--exclude-dir=node_modules",
|
|
2053
|
+
"--exclude-dir=.git",
|
|
2054
|
+
"--exclude-dir=dist",
|
|
2055
|
+
"--exclude-dir=build"
|
|
2056
|
+
].join(" ");
|
|
2057
|
+
|
|
2058
|
+
try {
|
|
2059
|
+
const { stdout } = await execAsync(
|
|
2060
|
+
`grep ${flags} ${excludes} -E "${pattern}" "${dirPath}" 2>/dev/null`,
|
|
2061
|
+
{ maxBuffer: 5 * 1024 * 1024 }
|
|
2062
|
+
);
|
|
2063
|
+
if (stdout.trim()) {
|
|
2064
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
2065
|
+
lines.forEach(line => {
|
|
2066
|
+
const match = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
2067
|
+
if (match) {
|
|
2068
|
+
results.push({
|
|
2069
|
+
file: match[1],
|
|
2070
|
+
line: parseInt(match[2]),
|
|
2071
|
+
content: match[3].trim()
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
} catch (e) {
|
|
2077
|
+
// Ignore grep errors - no matches
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
if (results.length === 0) {
|
|
2082
|
+
return {
|
|
2083
|
+
content: [{ type: "text", text: `No definition found for '${symbol}' in ${dirPath}` }]
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
const output = `Definition of '${symbol}' (${results.length} found):\n`;
|
|
2088
|
+
const formatted = results.slice(0, 10).map(r =>
|
|
2089
|
+
`${r.file}:${r.line}\n ${r.content}`
|
|
2090
|
+
).join('\n');
|
|
2091
|
+
|
|
2092
|
+
return {
|
|
2093
|
+
content: [{ type: "text", text: output + formatted + (results.length > 10 ? `\n... and ${results.length - 10} more` : '') }]
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1870
2097
|
async function handleFindReferences({ path: filePath, symbol }) {
|
|
1871
2098
|
if (!filePath || !symbol) throw new Error("Missing path or symbol");
|
|
1872
2099
|
|
|
1873
|
-
// Check if path is a directory
|
|
1874
2100
|
const absolutePath = path.resolve(filePath);
|
|
1875
|
-
|
|
2101
|
+
|
|
2102
|
+
// Check if path is a directory - search across files in directory
|
|
2103
|
+
let stats;
|
|
2104
|
+
try {
|
|
2105
|
+
stats = await fs.stat(absolutePath);
|
|
2106
|
+
} catch (e) {
|
|
2107
|
+
throw new Error(`Path does not exist: ${filePath}`);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1876
2110
|
if (!stats.isFile()) {
|
|
1877
|
-
|
|
2111
|
+
// It's a directory - search for references using grep
|
|
2112
|
+
return await handleReferencesInDirectory({ path: absolutePath, symbol });
|
|
1878
2113
|
}
|
|
1879
2114
|
|
|
2115
|
+
// It's a file - use tree-sitter to find references
|
|
1880
2116
|
const content = await fs.readFile(filePath, 'utf8');
|
|
1881
2117
|
const references = await findReferences(content, filePath, symbol);
|
|
1882
2118
|
|
|
@@ -1896,6 +2132,81 @@ async function handleFindReferences({ path: filePath, symbol }) {
|
|
|
1896
2132
|
};
|
|
1897
2133
|
}
|
|
1898
2134
|
|
|
2135
|
+
async function handleReferencesInDirectory({ path: dirPath, symbol }) {
|
|
2136
|
+
// Search for symbol references in directory using grep
|
|
2137
|
+
const flags = "-rn";
|
|
2138
|
+
const excludes = [
|
|
2139
|
+
"--exclude-dir=node_modules",
|
|
2140
|
+
"--exclude-dir=.git",
|
|
2141
|
+
"--exclude-dir=dist",
|
|
2142
|
+
"--exclude-dir=build"
|
|
2143
|
+
].join(" ");
|
|
2144
|
+
|
|
2145
|
+
try {
|
|
2146
|
+
const { stdout } = await execAsync(
|
|
2147
|
+
`grep ${flags} ${excludes} -E "\\b${symbol}\\b" "${dirPath}" 2>/dev/null`,
|
|
2148
|
+
{ maxBuffer: 10 * 1024 * 1024 }
|
|
2149
|
+
);
|
|
2150
|
+
|
|
2151
|
+
if (!stdout.trim()) {
|
|
2152
|
+
return {
|
|
2153
|
+
content: [{ type: "text", text: `No references found for '${symbol}' in ${dirPath}` }]
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
const lines = stdout.trim().split('\n').filter(Boolean);
|
|
2158
|
+
const results = lines.map(line => {
|
|
2159
|
+
const match = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
2160
|
+
if (match) {
|
|
2161
|
+
return {
|
|
2162
|
+
file: match[1],
|
|
2163
|
+
line: parseInt(match[2]),
|
|
2164
|
+
content: match[3].trim()
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
return null;
|
|
2168
|
+
}).filter(Boolean);
|
|
2169
|
+
|
|
2170
|
+
if (results.length === 0) {
|
|
2171
|
+
return {
|
|
2172
|
+
content: [{ type: "text", text: `No references found for '${symbol}' in ${dirPath}` }]
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
let output = `References for '${symbol}' in ${dirPath} (${results.length} found):\n\n`;
|
|
2177
|
+
|
|
2178
|
+
// Group by file
|
|
2179
|
+
const byFile = {};
|
|
2180
|
+
results.forEach(r => {
|
|
2181
|
+
if (!byFile[r.file]) byFile[r.file] = [];
|
|
2182
|
+
byFile[r.file].push(r);
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
Object.entries(byFile).slice(0, 10).forEach(([file, matches]) => {
|
|
2186
|
+
output += `${colors.yellow}${file}${colors.reset}\n`;
|
|
2187
|
+
matches.slice(0, 5).forEach(m => {
|
|
2188
|
+
output += ` ${colors.dim}${m.line}:${colors.reset} ${m.content}\n`;
|
|
2189
|
+
});
|
|
2190
|
+
if (matches.length > 5) {
|
|
2191
|
+
output += ` ${colors.dim}... and ${matches.length - 5} more${colors.reset}\n`;
|
|
2192
|
+
}
|
|
2193
|
+
output += "\n";
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
if (results.length > 50) {
|
|
2197
|
+
output += `${colors.dim}... and ${results.length - 50} more results${colors.reset}\n`;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
return {
|
|
2201
|
+
content: [{ type: "text", text: output }]
|
|
2202
|
+
};
|
|
2203
|
+
} catch (e) {
|
|
2204
|
+
return {
|
|
2205
|
+
content: [{ type: "text", text: `No references found for '${symbol}' in ${dirPath}` }]
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
|
|
1899
2210
|
/**
|
|
1900
2211
|
* Start Server
|
|
1901
2212
|
*/
|
|
@@ -29,14 +29,19 @@ export function isSemanticQuery(query) {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Determine the best search strategy
|
|
32
|
-
* @returns {'local' | 'ai' | 'filesystem'}
|
|
32
|
+
* @returns {'local' | 'ai' | 'filesystem' | 'definition' | 'references'}
|
|
33
33
|
*/
|
|
34
|
-
export function detectSearchStrategy({ query, files, path, mode }) {
|
|
34
|
+
export function detectSearchStrategy({ query, files, path, mode, regex = false }) {
|
|
35
35
|
// If mode is explicitly set, use it
|
|
36
36
|
if (mode && mode !== 'auto') {
|
|
37
37
|
return mode;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// For regex searches without files, use filesystem (grep-based)
|
|
41
|
+
if (regex && !files) {
|
|
42
|
+
return 'filesystem';
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
// Priority 1: Local search (if files provided in context)
|
|
41
46
|
if (files && Object.keys(files).length > 0) {
|
|
42
47
|
return 'local';
|