@mrxkun/mcfast-mcp 4.0.0 ā 4.0.3
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/README.md +159 -3
- package/package.json +4 -2
- package/src/index.js +544 -200
- package/src/intelligence/index.js +12 -0
- package/src/intelligence/pattern-detector.js +338 -0
- package/src/intelligence/strategy-selector.js +362 -0
- package/src/intelligence/suggestion-engine.js +489 -0
- package/src/memory/index.js +17 -0
- package/src/memory/memory-engine.js +676 -0
- package/src/memory/stores/database.js +104 -0
- package/src/memory/utils/chunker.js +97 -0
- package/src/memory/utils/daily-logs.js +263 -0
- package/src/memory/utils/dashboard-client.js +141 -0
- package/src/memory/utils/embedder.js +217 -0
- package/src/memory/utils/enhanced-embedder.js +717 -0
- package/src/memory/utils/indexer.js +118 -0
- package/src/memory/utils/simple-embedder.js +234 -0
- package/src/memory/utils/smart-router.js +344 -0
- package/src/memory/utils/sync-engine.js +388 -0
- package/src/memory/utils/ultra-embedder.js +1448 -0
- package/src/memory/watchers/file-watcher.js +61 -0
- package/src/tools/memory_get.js +342 -0
- package/src/tools/memory_search.js +215 -0
package/src/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from './strategies/tree-sitter/refactor.js';
|
|
41
41
|
import { safeEdit } from './utils/backup.js';
|
|
42
42
|
import { formatError } from './utils/error-formatter.js';
|
|
43
|
+
import { MemoryEngine } from './memory/index.js';
|
|
43
44
|
|
|
44
45
|
const execAsync = promisify(exec);
|
|
45
46
|
|
|
@@ -47,6 +48,132 @@ const API_URL = "https://mcfast.vercel.app/api/v1";
|
|
|
47
48
|
const TOKEN = process.env.MCFAST_TOKEN;
|
|
48
49
|
const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
|
|
49
50
|
|
|
51
|
+
// Memory Engine (initialized lazily)
|
|
52
|
+
let memoryEngine = null;
|
|
53
|
+
|
|
54
|
+
async function getMemoryEngine() {
|
|
55
|
+
if (!memoryEngine) {
|
|
56
|
+
memoryEngine = new MemoryEngine({
|
|
57
|
+
apiKey: TOKEN,
|
|
58
|
+
enableSync: true
|
|
59
|
+
});
|
|
60
|
+
await memoryEngine.initialize(process.cwd());
|
|
61
|
+
console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized`);
|
|
62
|
+
}
|
|
63
|
+
return memoryEngine;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Search memory for context related to instruction using UltraHybrid search
|
|
68
|
+
*/
|
|
69
|
+
async function searchMemoryContext(instruction, maxResults = 5) {
|
|
70
|
+
try {
|
|
71
|
+
const engine = await getMemoryEngine();
|
|
72
|
+
|
|
73
|
+
// Use intelligent search for best context
|
|
74
|
+
const searchResult = await engine.intelligentSearch(instruction, {
|
|
75
|
+
limit: maxResults
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!searchResult.results || searchResult.results.length === 0) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let context = '';
|
|
83
|
+
|
|
84
|
+
// Add code context from search results
|
|
85
|
+
context += '\n\n--- MEMORY CONTEXT (Relevant Code) ---\n';
|
|
86
|
+
searchResult.results.forEach((result, idx) => {
|
|
87
|
+
const fileName = result.filePath || result.path || 'unknown';
|
|
88
|
+
const score = result.finalScore || result.score || 0;
|
|
89
|
+
context += `\n[${idx + 1}] ${fileName} (score: ${score.toFixed(2)})\n`;
|
|
90
|
+
if (result.code) {
|
|
91
|
+
context += `${result.code.substring(0, 300)}${result.code.length > 300 ? '...' : ''}\n`;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Add facts if available
|
|
96
|
+
const facts = await engine.searchFacts(instruction, 5);
|
|
97
|
+
if (facts && facts.length > 0) {
|
|
98
|
+
context += '\n--- MEMORY CONTEXT (Facts) ---\n';
|
|
99
|
+
facts.forEach(fact => {
|
|
100
|
+
context += `⢠${fact.type}: ${fact.name} (${fact.file_id})\n`;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return context;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Context search failed: ${error.message}`);
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Log edit to memory after successful edit
|
|
113
|
+
*/
|
|
114
|
+
async function logEditToMemory({ instruction, files, strategy, success, errorMessage, durationMs }) {
|
|
115
|
+
try {
|
|
116
|
+
const engine = await getMemoryEngine();
|
|
117
|
+
const diffSize = Object.keys(files).reduce((total, fp) => total + (files[fp]?.length || 0), 0);
|
|
118
|
+
|
|
119
|
+
engine.logEditToMemory({
|
|
120
|
+
instruction: instruction.substring(0, 500),
|
|
121
|
+
files: Object.keys(files),
|
|
122
|
+
strategy,
|
|
123
|
+
success,
|
|
124
|
+
diffSize,
|
|
125
|
+
durationMs,
|
|
126
|
+
errorMessage
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.error(`${colors.cyan}[Memory]${colors.reset} Edit logged to history`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Log failed: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Impact Analysis for rename operations using semantic search
|
|
137
|
+
*/
|
|
138
|
+
async function analyzeRenameImpact(symbolName, currentFile = null) {
|
|
139
|
+
try {
|
|
140
|
+
const engine = await getMemoryEngine();
|
|
141
|
+
|
|
142
|
+
// Search for symbol references across codebase
|
|
143
|
+
const searchResult = await engine.intelligentSearch(symbolName, {
|
|
144
|
+
limit: 20,
|
|
145
|
+
currentFile
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const impactedFiles = new Set();
|
|
149
|
+
const references = [];
|
|
150
|
+
|
|
151
|
+
searchResult.results.forEach(result => {
|
|
152
|
+
const filePath = result.filePath || result.path;
|
|
153
|
+
if (filePath && filePath !== currentFile) {
|
|
154
|
+
impactedFiles.add(filePath);
|
|
155
|
+
references.push({
|
|
156
|
+
file: filePath,
|
|
157
|
+
score: result.finalScore || result.score,
|
|
158
|
+
context: result.code?.substring(0, 150)
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
impactedFiles: Array.from(impactedFiles),
|
|
165
|
+
references: references.slice(0, 10),
|
|
166
|
+
totalReferences: searchResult.results.length,
|
|
167
|
+
suggestion: impactedFiles.size > 0
|
|
168
|
+
? `ā ļø Rename will affect ${impactedFiles.size} file(s) with ${references.length} reference(s). Consider using multi-file edit.`
|
|
169
|
+
: null
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Impact analysis failed: ${error.message}`);
|
|
173
|
+
return { impactedFiles: [], references: [], totalReferences: 0, suggestion: null };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
50
177
|
// ANSI Color Codes for Terminal Output
|
|
51
178
|
const colors = {
|
|
52
179
|
reset: '\x1b[0m',
|
|
@@ -69,6 +196,8 @@ const toolIcons = {
|
|
|
69
196
|
read: 'š',
|
|
70
197
|
list_files: 'š',
|
|
71
198
|
reapply: 'š',
|
|
199
|
+
memory_search: 'š§ ',
|
|
200
|
+
memory_get: 'š',
|
|
72
201
|
// Legacy aliases (backward compatibility)
|
|
73
202
|
apply_fast: 'ā”',
|
|
74
203
|
apply_search_replace: 'š',
|
|
@@ -957,11 +1086,41 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
|
|
|
957
1086
|
}
|
|
958
1087
|
|
|
959
1088
|
/**
|
|
960
|
-
* UNIFIED HANDLER 1: handleEdit (
|
|
1089
|
+
* UNIFIED HANDLER 1: handleEdit (v3.0 - Enhanced with Memory)
|
|
961
1090
|
* Consolidates: apply_fast + edit_file + apply_search_replace
|
|
962
1091
|
* Auto-detects best strategy based on input
|
|
1092
|
+
* NEW: Context retrieval, impact analysis, auto-fix references
|
|
963
1093
|
*/
|
|
964
1094
|
async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
1095
|
+
const editStartTime = Date.now();
|
|
1096
|
+
const firstFile = Object.keys(files)[0];
|
|
1097
|
+
|
|
1098
|
+
// 1. Retrieve memory context for enhanced understanding
|
|
1099
|
+
let memoryContext = '';
|
|
1100
|
+
try {
|
|
1101
|
+
memoryContext = await searchMemoryContext(instruction, 5);
|
|
1102
|
+
if (memoryContext) {
|
|
1103
|
+
console.error(`${colors.cyan}[MEMORY]${colors.reset} Retrieved context for edit`);
|
|
1104
|
+
}
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
console.error(`${colors.yellow}[MEMORY]${colors.reset} Context retrieval failed: ${error.message}`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// 2. Analyze impact for rename operations
|
|
1110
|
+
let impactAnalysis = null;
|
|
1111
|
+
const renamePattern = instruction.match(/(?:rename|change)\s+(?:function|class|const|let|var|method)\s+(\w+)\s+to\s+(\w+)/i);
|
|
1112
|
+
if (renamePattern) {
|
|
1113
|
+
const symbolName = renamePattern[1];
|
|
1114
|
+
try {
|
|
1115
|
+
impactAnalysis = await analyzeRenameImpact(symbolName, firstFile);
|
|
1116
|
+
if (impactAnalysis.suggestion) {
|
|
1117
|
+
console.error(`${colors.yellow}[IMPACT]${colors.reset} ${impactAnalysis.suggestion}`);
|
|
1118
|
+
}
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
console.error(`${colors.yellow}[IMPACT]${colors.reset} Analysis failed: ${error.message}`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
965
1124
|
// Check for multi-file edits first
|
|
966
1125
|
const multiFileEdit = detectCrossFileEdit(instruction, files);
|
|
967
1126
|
|
|
@@ -986,6 +1145,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
986
1145
|
}
|
|
987
1146
|
|
|
988
1147
|
if (!result.success) {
|
|
1148
|
+
// Log failed edit to memory
|
|
1149
|
+
await logEditToMemory({
|
|
1150
|
+
instruction,
|
|
1151
|
+
files,
|
|
1152
|
+
strategy: multiFileEdit.type,
|
|
1153
|
+
success: false,
|
|
1154
|
+
errorMessage: result.message || result.error,
|
|
1155
|
+
durationMs: Date.now() - editStartTime
|
|
1156
|
+
});
|
|
1157
|
+
|
|
989
1158
|
return {
|
|
990
1159
|
content: [{
|
|
991
1160
|
type: "text",
|
|
@@ -997,6 +1166,15 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
997
1166
|
};
|
|
998
1167
|
}
|
|
999
1168
|
|
|
1169
|
+
// Log successful edit to memory
|
|
1170
|
+
await logEditToMemory({
|
|
1171
|
+
instruction,
|
|
1172
|
+
files: result.files || Object.keys(files),
|
|
1173
|
+
strategy: multiFileEdit.type,
|
|
1174
|
+
success: true,
|
|
1175
|
+
durationMs: Date.now() - editStartTime
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1000
1178
|
return {
|
|
1001
1179
|
content: [{
|
|
1002
1180
|
type: "text",
|
|
@@ -1005,6 +1183,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1005
1183
|
};
|
|
1006
1184
|
|
|
1007
1185
|
} catch (error) {
|
|
1186
|
+
// Log failed edit to memory
|
|
1187
|
+
await logEditToMemory({
|
|
1188
|
+
instruction,
|
|
1189
|
+
files,
|
|
1190
|
+
strategy: 'multi_file_error',
|
|
1191
|
+
success: false,
|
|
1192
|
+
errorMessage: error.message,
|
|
1193
|
+
durationMs: Date.now() - editStartTime
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1008
1196
|
return {
|
|
1009
1197
|
content: [{
|
|
1010
1198
|
type: "text",
|
|
@@ -1018,13 +1206,12 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1018
1206
|
}
|
|
1019
1207
|
|
|
1020
1208
|
const strategy = detectEditStrategy({ instruction, code_edit, files });
|
|
1021
|
-
|
|
1022
1209
|
console.error(`${colors.cyan}[EDIT STRATEGY]${colors.reset} ${strategy}`);
|
|
1023
1210
|
|
|
1024
1211
|
// Strategy 1: Fuzzy Patch (unified diff format)
|
|
1025
1212
|
if (strategy === 'fuzzy_patch') {
|
|
1026
1213
|
const diffText = code_edit || instruction;
|
|
1027
|
-
const filePath = Object.keys(files)[0];
|
|
1214
|
+
const filePath = Object.keys(files)[0];
|
|
1028
1215
|
const fileContent = files[filePath];
|
|
1029
1216
|
|
|
1030
1217
|
try {
|
|
@@ -1056,6 +1243,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1056
1243
|
await fs.writeFile(filePath, patchResult.content, 'utf8');
|
|
1057
1244
|
});
|
|
1058
1245
|
|
|
1246
|
+
// Log edit to memory
|
|
1247
|
+
await logEditToMemory({
|
|
1248
|
+
instruction,
|
|
1249
|
+
files: { [filePath]: fileContent },
|
|
1250
|
+
strategy: 'fuzzy_patch',
|
|
1251
|
+
success: editResult.success,
|
|
1252
|
+
errorMessage: editResult.error,
|
|
1253
|
+
durationMs: Date.now() - editStartTime
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1059
1256
|
if (!editResult.success) {
|
|
1060
1257
|
// Determine error type based on result
|
|
1061
1258
|
let errorType = 'generic';
|
|
@@ -1091,6 +1288,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1091
1288
|
};
|
|
1092
1289
|
|
|
1093
1290
|
} catch (error) {
|
|
1291
|
+
// Log failed edit
|
|
1292
|
+
await logEditToMemory({
|
|
1293
|
+
instruction,
|
|
1294
|
+
files,
|
|
1295
|
+
strategy: 'fuzzy_patch',
|
|
1296
|
+
success: false,
|
|
1297
|
+
errorMessage: error.message,
|
|
1298
|
+
durationMs: Date.now() - editStartTime
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1094
1301
|
return {
|
|
1095
1302
|
content: [{
|
|
1096
1303
|
type: "text",
|
|
@@ -1191,6 +1398,15 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1191
1398
|
await fs.writeFile(filePath, transformResult.code, 'utf8');
|
|
1192
1399
|
});
|
|
1193
1400
|
|
|
1401
|
+
// Log successful AST refactor
|
|
1402
|
+
await logEditToMemory({
|
|
1403
|
+
instruction,
|
|
1404
|
+
files: { [filePath]: fileContent },
|
|
1405
|
+
strategy: `ast_refactor:${pattern.type}`,
|
|
1406
|
+
success: editResult.success,
|
|
1407
|
+
durationMs: Date.now() - editStartTime
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1194
1410
|
if (!editResult.success) {
|
|
1195
1411
|
let errorType = 'generic';
|
|
1196
1412
|
if (editResult.error.includes('ENOENT')) errorType = 'file_not_found';
|
|
@@ -1210,14 +1426,40 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1210
1426
|
};
|
|
1211
1427
|
}
|
|
1212
1428
|
|
|
1429
|
+
// Include impact analysis in result if available
|
|
1430
|
+
let resultText = `ā
AST Refactor Applied Successfully\n\nOperation: ${pattern.type}\nChanges: ${transformResult.count || transformResult.replacements || 0} locations\n\nBackup: ${editResult.backupPath}`;
|
|
1431
|
+
|
|
1432
|
+
if (impactAnalysis && impactAnalysis.suggestion) {
|
|
1433
|
+
resultText += `\n\n${impactAnalysis.suggestion}`;
|
|
1434
|
+
if (impactAnalysis.impactedFiles.length > 0) {
|
|
1435
|
+
resultText += `\n\nš Affected files:`;
|
|
1436
|
+
impactAnalysis.impactedFiles.slice(0, 5).forEach(f => {
|
|
1437
|
+
resultText += `\n ⢠${f}`;
|
|
1438
|
+
});
|
|
1439
|
+
if (impactAnalysis.impactedFiles.length > 5) {
|
|
1440
|
+
resultText += `\n ... and ${impactAnalysis.impactedFiles.length - 5} more`;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1213
1445
|
return {
|
|
1214
1446
|
content: [{
|
|
1215
1447
|
type: "text",
|
|
1216
|
-
text:
|
|
1448
|
+
text: resultText
|
|
1217
1449
|
}]
|
|
1218
1450
|
};
|
|
1219
1451
|
|
|
1220
1452
|
} catch (error) {
|
|
1453
|
+
// Log failed edit
|
|
1454
|
+
await logEditToMemory({
|
|
1455
|
+
instruction,
|
|
1456
|
+
files,
|
|
1457
|
+
strategy: `ast_refactor:${pattern?.type || 'unknown'}`,
|
|
1458
|
+
success: false,
|
|
1459
|
+
errorMessage: error.message,
|
|
1460
|
+
durationMs: Date.now() - editStartTime
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1221
1463
|
return {
|
|
1222
1464
|
content: [{
|
|
1223
1465
|
type: "text",
|
|
@@ -1234,40 +1476,85 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1234
1476
|
if (strategy === 'search_replace') {
|
|
1235
1477
|
const extracted = extractSearchReplace(instruction);
|
|
1236
1478
|
if (extracted) {
|
|
1237
|
-
|
|
1479
|
+
const result = await handleApplyFast({
|
|
1238
1480
|
instruction: `Replace checking for exact match:\nSEARCH:\n${extracted.search}\n\nREPLACE WITH:\n${extracted.replace}`,
|
|
1239
1481
|
files,
|
|
1240
1482
|
dryRun,
|
|
1241
1483
|
toolName: 'edit'
|
|
1242
1484
|
});
|
|
1485
|
+
|
|
1486
|
+
// Log to memory
|
|
1487
|
+
await logEditToMemory({
|
|
1488
|
+
instruction,
|
|
1489
|
+
files,
|
|
1490
|
+
strategy: 'search_replace',
|
|
1491
|
+
success: !result.isError,
|
|
1492
|
+
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1493
|
+
durationMs: Date.now() - editStartTime
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
return result;
|
|
1243
1497
|
}
|
|
1244
1498
|
}
|
|
1245
1499
|
|
|
1246
1500
|
// Strategy 3: Placeholder Merge (token-efficient)
|
|
1247
1501
|
if (strategy === 'placeholder_merge' && code_edit) {
|
|
1248
|
-
|
|
1502
|
+
const result = await handleApplyFast({
|
|
1249
1503
|
instruction: `${instruction}\n\nUSE PLACEHOLDER MERGE STRATEGY. Code snippet:\n${code_edit}`,
|
|
1250
1504
|
files,
|
|
1251
1505
|
dryRun,
|
|
1252
1506
|
toolName: 'edit'
|
|
1253
1507
|
});
|
|
1508
|
+
|
|
1509
|
+
// Log to memory
|
|
1510
|
+
await logEditToMemory({
|
|
1511
|
+
instruction,
|
|
1512
|
+
files,
|
|
1513
|
+
strategy: 'placeholder_merge',
|
|
1514
|
+
success: !result.isError,
|
|
1515
|
+
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1516
|
+
durationMs: Date.now() - editStartTime
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
return result;
|
|
1254
1520
|
}
|
|
1255
1521
|
|
|
1256
1522
|
// Strategy 4: Mercury Intelligent (most flexible, default)
|
|
1257
|
-
|
|
1258
|
-
|
|
1523
|
+
// Enhance instruction with memory context
|
|
1524
|
+
let enhancedInstruction = instruction;
|
|
1525
|
+
if (memoryContext) {
|
|
1526
|
+
enhancedInstruction = `${instruction}\n\n--- CONTEXT FROM CODEBASE ---${memoryContext}`;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const result = await handleApplyFast({
|
|
1530
|
+
instruction: enhancedInstruction,
|
|
1259
1531
|
files,
|
|
1260
1532
|
dryRun,
|
|
1261
1533
|
toolName: 'edit'
|
|
1262
1534
|
});
|
|
1535
|
+
|
|
1536
|
+
// Log to memory
|
|
1537
|
+
await logEditToMemory({
|
|
1538
|
+
instruction,
|
|
1539
|
+
files,
|
|
1540
|
+
strategy: 'mercury_intelligent',
|
|
1541
|
+
success: !result.isError,
|
|
1542
|
+
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1543
|
+
durationMs: Date.now() - editStartTime
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
return result;
|
|
1263
1547
|
}
|
|
1264
1548
|
|
|
1265
1549
|
/**
|
|
1266
|
-
* UNIFIED HANDLER 2: handleSearch (
|
|
1550
|
+
* UNIFIED HANDLER 2: handleSearch (v3.0 - Enhanced with Memory)
|
|
1267
1551
|
* Consolidates: search_code + search_code_ai + search_filesystem
|
|
1268
1552
|
* Auto-detects best strategy based on input
|
|
1553
|
+
* Memory System Integration: Intelligent local-first hybrid search
|
|
1269
1554
|
*/
|
|
1270
|
-
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2 }) {
|
|
1555
|
+
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2, maxResults = 10 }) {
|
|
1556
|
+
const searchStartTime = Date.now();
|
|
1557
|
+
|
|
1271
1558
|
// For regex mode without files, we need to do content-based regex search
|
|
1272
1559
|
// since fast-glob doesn't support full regex patterns
|
|
1273
1560
|
if (regex && !files && mode === 'auto') {
|
|
@@ -1278,6 +1565,50 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
|
|
|
1278
1565
|
|
|
1279
1566
|
console.error(`${colors.cyan}[SEARCH STRATEGY]${colors.reset} ${detectedMode}`);
|
|
1280
1567
|
|
|
1568
|
+
// Memory System Integration: Try intelligent search for auto/local modes
|
|
1569
|
+
if ((detectedMode === 'auto' || detectedMode === 'local') && !regex) {
|
|
1570
|
+
try {
|
|
1571
|
+
const engine = await getMemoryEngine();
|
|
1572
|
+
|
|
1573
|
+
// Use intelligent search (UltraHybrid + Smart Routing)
|
|
1574
|
+
const searchResult = await engine.intelligentSearch(query, {
|
|
1575
|
+
limit: maxResults,
|
|
1576
|
+
currentFile: files ? Object.keys(files)[0] : null
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
if (searchResult.results && searchResult.results.length > 0) {
|
|
1580
|
+
console.error(`${colors.cyan}[MEMORY]${colors.reset} Found ${searchResult.results.length} results via intelligent search`);
|
|
1581
|
+
|
|
1582
|
+
// Format results
|
|
1583
|
+
let output = `š Intelligent Search Results for "${query}"\n`;
|
|
1584
|
+
output += `${colors.dim}Method: ${searchResult.metadata?.method || 'intelligent'} | `;
|
|
1585
|
+
output += `Accuracy: ${searchResult.metadata?.accuracy || '90%'} | `;
|
|
1586
|
+
output += `Duration: ${searchResult.metadata?.totalDuration || 'N/A'}${colors.reset}\n\n`;
|
|
1587
|
+
|
|
1588
|
+
searchResult.results.slice(0, maxResults).forEach((result, i) => {
|
|
1589
|
+
const filePath = result.filePath || result.path || 'unknown';
|
|
1590
|
+
const score = result.finalScore || result.score || 0;
|
|
1591
|
+
output += `[${i + 1}] ${colors.yellow}${filePath}${colors.reset}\n`;
|
|
1592
|
+
if (result.code) {
|
|
1593
|
+
output += ` ${result.code.slice(0, 200)}${result.code.length > 200 ? '...' : ''}\n`;
|
|
1594
|
+
}
|
|
1595
|
+
output += ` ${colors.dim}Score: ${score.toFixed(2)}${colors.reset}\n\n`;
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
if (searchResult.results.length > maxResults) {
|
|
1599
|
+
output += `${colors.dim}... and ${searchResult.results.length - maxResults} more results${colors.reset}\n`;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
return { content: [{ type: "text", text: output }] };
|
|
1603
|
+
} else {
|
|
1604
|
+
console.error(`${colors.yellow}[MEMORY]${colors.reset} No results from intelligent search, falling back...`);
|
|
1605
|
+
}
|
|
1606
|
+
} catch (memoryError) {
|
|
1607
|
+
console.error(`${colors.yellow}[MEMORY]${colors.reset} Intelligent search failed: ${memoryError.message}`);
|
|
1608
|
+
// Continue with normal search strategies
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1281
1612
|
// Strategy 1: Local search (if files provided)
|
|
1282
1613
|
if (detectedMode === 'local' && files) {
|
|
1283
1614
|
return await handleSearchCode({ query, files, regex, caseSensitive, contextLines });
|
|
@@ -1550,6 +1881,12 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
|
|
|
1550
1881
|
});
|
|
1551
1882
|
return { content: [{ type: "text", text: msg }] };
|
|
1552
1883
|
}
|
|
1884
|
+
|
|
1885
|
+
// Handle regex syntax errors
|
|
1886
|
+
if (execErr.stderr && execErr.stderr.includes('Invalid')) {
|
|
1887
|
+
throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1553
1890
|
throw execErr;
|
|
1554
1891
|
}
|
|
1555
1892
|
} catch (error) {
|
|
@@ -1569,6 +1906,11 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
|
|
|
1569
1906
|
}
|
|
1570
1907
|
}
|
|
1571
1908
|
|
|
1909
|
+
/**
|
|
1910
|
+
* UNIFIED HANDLER 4: handleSearchCode (v2.0)
|
|
1911
|
+
* Renamed from handleSearchCode for consistency
|
|
1912
|
+
* Now supports both single file and batch search modes
|
|
1913
|
+
*/
|
|
1572
1914
|
async function handleSearchCode({ query, files, regex = false, caseSensitive = false, contextLines = 2 }) {
|
|
1573
1915
|
const start = Date.now();
|
|
1574
1916
|
try {
|
|
@@ -1730,104 +2072,161 @@ async function handleSearchCode({ query, files, regex = false, caseSensitive = f
|
|
|
1730
2072
|
}
|
|
1731
2073
|
}
|
|
1732
2074
|
|
|
1733
|
-
|
|
1734
|
-
|
|
2075
|
+
/**
|
|
2076
|
+
* UNIFIED HANDLER 5: handleSearchCodeAI (v2.0)
|
|
2077
|
+
* Renamed from handleSearchCodeAI for consistency
|
|
2078
|
+
* Now supports both single file and batch search modes
|
|
2079
|
+
*/
|
|
2080
|
+
async function handleSearchCodeAI({ query, files, contextLines = 2 }) {
|
|
2081
|
+
if (!TOKEN) {
|
|
2082
|
+
return {
|
|
2083
|
+
content: [{ type: "text", text: "ā Error: MCFAST_TOKEN is missing. Please set it in your MCP config." }],
|
|
2084
|
+
isError: true
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
1735
2087
|
try {
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2088
|
+
const response = await fetch(`${API_URL}/search-ai`, {
|
|
2089
|
+
method: "POST",
|
|
2090
|
+
headers: {
|
|
2091
|
+
"Content-Type": "application/json",
|
|
2092
|
+
"Authorization": `Bearer ${TOKEN}`,
|
|
2093
|
+
},
|
|
2094
|
+
body: JSON.stringify({ query, files, contextLines }),
|
|
2095
|
+
});
|
|
1742
2096
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2097
|
+
if (!response.ok) {
|
|
2098
|
+
const errorText = await response.text();
|
|
2099
|
+
return {
|
|
2100
|
+
content: [{ type: "text", text: `Search Error (${response.status}): ${errorText}` }],
|
|
2101
|
+
isError: true,
|
|
2102
|
+
};
|
|
1748
2103
|
}
|
|
1749
|
-
output += `\n\n${paginatedFiles.join('\n')}`;
|
|
1750
2104
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2105
|
+
const data = await response.json();
|
|
2106
|
+
|
|
2107
|
+
// Check for warning (empty file content)
|
|
2108
|
+
if (data.warning) {
|
|
2109
|
+
return {
|
|
2110
|
+
content: [{ type: "text", text: `ā ļø ${data.warning}\n\nThis usually means the AI agent didn't load file contents before searching. The files parameter should contain actual file content, not empty strings.` }],
|
|
2111
|
+
};
|
|
1755
2112
|
}
|
|
1756
2113
|
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2114
|
+
// Format results nicely
|
|
2115
|
+
let output = `š Found ${data.totalMatches} matches for "${query}"\n\n`;
|
|
2116
|
+
|
|
2117
|
+
if (data.results.length === 0) {
|
|
2118
|
+
output += "No matches found.";
|
|
2119
|
+
} else {
|
|
2120
|
+
data.results.forEach((result, i) => {
|
|
2121
|
+
output += `š ${result.file}:${result.lineNumber}\n`;
|
|
2122
|
+
result.context.forEach(ctx => {
|
|
2123
|
+
const prefix = ctx.isMatch ? "ā " : " ";
|
|
2124
|
+
output += `${prefix}${ctx.lineNumber}: ${ctx.content}\n`;
|
|
2125
|
+
});
|
|
2126
|
+
if (i < data.results.length - 1) output += "\n";
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
1767
2129
|
|
|
1768
2130
|
return {
|
|
1769
|
-
content: [{ type: "text", text: output }]
|
|
2131
|
+
content: [{ type: "text", text: output }],
|
|
1770
2132
|
};
|
|
1771
2133
|
|
|
1772
2134
|
} catch (error) {
|
|
1773
|
-
// Error recovery: suggest using depth parameter
|
|
1774
|
-
let errorMessage = `ā Error listing files: ${error.message}`;
|
|
1775
|
-
if (error.message.includes('too many files') || error.message.includes('EMFILE') || error.message.includes('max')) {
|
|
1776
|
-
errorMessage += `\n\nš” Tip: Try reducing the depth (e.g., depth: 2 or 3) to limit the search scope.`;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
reportAudit({
|
|
1780
|
-
tool: 'list_files_fast',
|
|
1781
|
-
instruction: dirPath,
|
|
1782
|
-
status: 'error',
|
|
1783
|
-
error_message: error.message,
|
|
1784
|
-
latency_ms: Date.now() - start
|
|
1785
|
-
});
|
|
1786
2135
|
return {
|
|
1787
|
-
content: [{ type: "text", text:
|
|
1788
|
-
isError: true
|
|
2136
|
+
content: [{ type: "text", text: `Search Connection Error: ${error.message}` }],
|
|
2137
|
+
isError: true,
|
|
1789
2138
|
};
|
|
1790
2139
|
}
|
|
1791
2140
|
}
|
|
1792
2141
|
|
|
1793
|
-
|
|
1794
|
-
|
|
2142
|
+
/**
|
|
2143
|
+
* UNIFIED HANDLER 6: handleGetDefinition (v2.0)
|
|
2144
|
+
* Renamed from handleGetDefinition for consistency
|
|
2145
|
+
* Now supports both single file and batch search modes
|
|
2146
|
+
*/
|
|
2147
|
+
async function handleGetDefinition({ path: filePath, symbol }) {
|
|
2148
|
+
if (!filePath || !symbol) throw new Error("Missing path or symbol");
|
|
2149
|
+
|
|
2150
|
+
const absolutePath = path.resolve(filePath);
|
|
2151
|
+
|
|
2152
|
+
// Check if path is a directory - search across files in directory
|
|
2153
|
+
let stats;
|
|
1795
2154
|
try {
|
|
1796
|
-
await fs.
|
|
2155
|
+
stats = await fs.stat(absolutePath);
|
|
2156
|
+
} catch (e) {
|
|
2157
|
+
throw new Error(`Path does not exist: ${filePath}`);
|
|
2158
|
+
}
|
|
1797
2159
|
|
|
1798
|
-
|
|
2160
|
+
if (!stats.isFile()) {
|
|
2161
|
+
// It's a directory - search for symbol definition using grep
|
|
2162
|
+
return await handleDefinitionInDirectory({ path: absolutePath, symbol });
|
|
2163
|
+
}
|
|
1799
2164
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
status: 'success',
|
|
1804
|
-
latency_ms: Date.now() - start,
|
|
1805
|
-
files_count: 1,
|
|
1806
|
-
input_tokens: estimatedTokens,
|
|
1807
|
-
output_tokens: 10 // Minimal output (success message)
|
|
1808
|
-
});
|
|
2165
|
+
// It's a file - use tree-sitter to find definition
|
|
2166
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
2167
|
+
const definitions = await findDefinition(content, filePath, symbol);
|
|
1809
2168
|
|
|
2169
|
+
if (definitions.length === 0) {
|
|
1810
2170
|
return {
|
|
1811
|
-
content: [{ type: "text", text:
|
|
2171
|
+
content: [{ type: "text", text: `No definition found for '${symbol}' in ${filePath}` }]
|
|
1812
2172
|
};
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
const def = definitions[0]; // Take first
|
|
2176
|
+
|
|
2177
|
+
return {
|
|
2178
|
+
content: [{ type: "text", text: `Definition of '${symbol}':\n${filePath}:${def.line}:${def.column} (${def.type || 'unknown'})` }]
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* UNIFIED HANDLER 7: handleFindReferences (v2.0)
|
|
2184
|
+
* Renamed from handleFindReferences for consistency
|
|
2185
|
+
* Now supports both single file and batch search modes
|
|
2186
|
+
*/
|
|
2187
|
+
async function handleFindReferences({ path: filePath, symbol }) {
|
|
2188
|
+
if (!filePath || !symbol) throw new Error("Missing path or symbol");
|
|
2189
|
+
|
|
2190
|
+
const absolutePath = path.resolve(filePath);
|
|
2191
|
+
|
|
2192
|
+
// Check if path is a directory - search across files in directory
|
|
2193
|
+
let stats;
|
|
2194
|
+
try {
|
|
2195
|
+
stats = await fs.stat(absolutePath);
|
|
2196
|
+
} catch (e) {
|
|
2197
|
+
throw new Error(`Path does not exist: ${filePath}`);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
if (!stats.isFile()) {
|
|
2201
|
+
// It's a directory - search for references using grep
|
|
2202
|
+
return await handleReferencesInDirectory({ path: absolutePath, symbol });
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// It's a file - use tree-sitter to find references
|
|
2206
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
2207
|
+
const references = await findReferences(content, filePath, symbol);
|
|
2208
|
+
|
|
2209
|
+
if (references.length === 0) {
|
|
1823
2210
|
return {
|
|
1824
|
-
content: [{ type: "text", text:
|
|
1825
|
-
isError: true
|
|
2211
|
+
content: [{ type: "text", text: `No references found for '${symbol}' in ${filePath}` }]
|
|
1826
2212
|
};
|
|
1827
2213
|
}
|
|
1828
|
-
}
|
|
1829
2214
|
|
|
2215
|
+
let output = `References for '${symbol}' in ${filePath} (${references.length}):\n`;
|
|
2216
|
+
references.forEach(ref => {
|
|
2217
|
+
output += `${ref.line}:${ref.column}\n`;
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2220
|
+
return {
|
|
2221
|
+
content: [{ type: "text", text: output }]
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
1830
2224
|
|
|
2225
|
+
/**
|
|
2226
|
+
* UNIFIED HANDLER 8: handleReadFile (v2.0)
|
|
2227
|
+
* Renamed from handleReadFile for consistency
|
|
2228
|
+
* Now supports both single file and batch search modes
|
|
2229
|
+
*/
|
|
1831
2230
|
async function handleReadFile({ path: filePath, start_line, end_line }) {
|
|
1832
2231
|
const start = Date.now();
|
|
1833
2232
|
try {
|
|
@@ -1880,7 +2279,7 @@ async function handleReadFile({ path: filePath, start_line, end_line }) {
|
|
|
1880
2279
|
if (startLine && endLine) {
|
|
1881
2280
|
lineRangeInfo = `(Lines ${startLine}-${endLine} of ${totalLines})`;
|
|
1882
2281
|
} else if (startLine) {
|
|
1883
|
-
lineRangeInfo = `(Lines ${startLine}-${currentLine} of ${totalLines})`;
|
|
2282
|
+
lineRangeInfo = `(Lines ${startLine}-${currentLine} of ${totalLines} - truncated)`;
|
|
1884
2283
|
} else {
|
|
1885
2284
|
lineRangeInfo = `(Lines 1-${lines.length} of ${totalLines} - truncated)`;
|
|
1886
2285
|
}
|
|
@@ -1935,6 +2334,11 @@ async function handleReadFile({ path: filePath, start_line, end_line }) {
|
|
|
1935
2334
|
}
|
|
1936
2335
|
}
|
|
1937
2336
|
|
|
2337
|
+
/**
|
|
2338
|
+
* UNIFIED HANDLER 9: handleApplyFast (v2.0)
|
|
2339
|
+
* Renamed from handleApplyFast for consistency
|
|
2340
|
+
* Now supports both single file and batch search modes
|
|
2341
|
+
*/
|
|
1938
2342
|
async function handleApplyFast({ instruction, files, dryRun, toolName }) {
|
|
1939
2343
|
if (!TOKEN) {
|
|
1940
2344
|
return {
|
|
@@ -2008,103 +2412,76 @@ async function handleApplyFast({ instruction, files, dryRun, toolName }) {
|
|
|
2008
2412
|
}
|
|
2009
2413
|
}
|
|
2010
2414
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2415
|
+
/**
|
|
2416
|
+
* UNIFIED HANDLER 10: handleListFiles (v2.0)
|
|
2417
|
+
* Renamed from handleListFiles for consistency
|
|
2418
|
+
* Now supports both single file and batch search modes
|
|
2419
|
+
*/
|
|
2420
|
+
async function handleListFiles({ path: dirPath = process.cwd(), depth = 5, offset = 0, limit = 500 }) {
|
|
2421
|
+
const start = Date.now();
|
|
2018
2422
|
try {
|
|
2019
|
-
const
|
|
2020
|
-
method: "POST",
|
|
2021
|
-
headers: {
|
|
2022
|
-
"Content-Type": "application/json",
|
|
2023
|
-
"Authorization": `Bearer ${TOKEN}`,
|
|
2024
|
-
},
|
|
2025
|
-
body: JSON.stringify({ query, files, contextLines }),
|
|
2026
|
-
});
|
|
2027
|
-
|
|
2028
|
-
if (!response.ok) {
|
|
2029
|
-
const errorText = await response.text();
|
|
2030
|
-
return {
|
|
2031
|
-
content: [{ type: "text", text: `Search Error (${response.status}): ${errorText}` }],
|
|
2032
|
-
isError: true,
|
|
2033
|
-
};
|
|
2034
|
-
}
|
|
2423
|
+
const files = await getFiles(dirPath, depth);
|
|
2035
2424
|
|
|
2036
|
-
|
|
2425
|
+
// Apply pagination
|
|
2426
|
+
const totalFiles = files.length;
|
|
2427
|
+
const paginatedFiles = files.slice(offset, offset + limit);
|
|
2428
|
+
const hasMore = offset + limit < totalFiles;
|
|
2037
2429
|
|
|
2038
|
-
//
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
}
|
|
2430
|
+
// Build output with pagination info
|
|
2431
|
+
let output = `š Files in ${dirPath} (depth: ${depth}):\n`;
|
|
2432
|
+
output += `Showing ${offset + 1}-${Math.min(offset + limit, totalFiles)} of ${totalFiles} files`;
|
|
2433
|
+
if (hasMore) {
|
|
2434
|
+
output += `. Use offset: ${offset + limit} to see more.`;
|
|
2043
2435
|
}
|
|
2436
|
+
output += `\n\n${paginatedFiles.join('\n')}`;
|
|
2044
2437
|
|
|
2045
|
-
//
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
output += "No matches found.";
|
|
2050
|
-
} else {
|
|
2051
|
-
data.results.forEach((result, i) => {
|
|
2052
|
-
output += `š ${result.file}:${result.lineNumber}\n`;
|
|
2053
|
-
result.context.forEach(ctx => {
|
|
2054
|
-
const prefix = ctx.isMatch ? "ā " : " ";
|
|
2055
|
-
output += `${prefix}${ctx.lineNumber}: ${ctx.content}\n`;
|
|
2056
|
-
});
|
|
2057
|
-
if (i < data.results.length - 1) output += "\n";
|
|
2058
|
-
});
|
|
2438
|
+
// Check for large output and add helpful message
|
|
2439
|
+
const outputLength = output.length;
|
|
2440
|
+
if (outputLength > 50000) {
|
|
2441
|
+
output += `\n\nš” Tip: Output is large. Use depth parameter to limit depth (e.g., depth: 2 or 3) to limit the search scope.`;
|
|
2059
2442
|
}
|
|
2060
2443
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2444
|
+
reportAudit({
|
|
2445
|
+
tool: 'list_files_fast',
|
|
2446
|
+
instruction: dirPath,
|
|
2447
|
+
status: 'success',
|
|
2448
|
+
latency_ms: Date.now() - start,
|
|
2449
|
+
files_count: totalFiles,
|
|
2450
|
+
result_summary: JSON.stringify(paginatedFiles.slice(0, 500)),
|
|
2451
|
+
input_tokens: Math.ceil(dirPath.length / 4),
|
|
2452
|
+
output_tokens: Math.ceil(output.length / 4)
|
|
2453
|
+
});
|
|
2064
2454
|
|
|
2065
|
-
} catch (error) {
|
|
2066
2455
|
return {
|
|
2067
|
-
content: [{ type: "text", text:
|
|
2068
|
-
isError: true,
|
|
2456
|
+
content: [{ type: "text", text: output }]
|
|
2069
2457
|
};
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
async function handleGetDefinition({ path: filePath, symbol }) {
|
|
2074
|
-
if (!filePath || !symbol) throw new Error("Missing path or symbol");
|
|
2075
|
-
|
|
2076
|
-
const absolutePath = path.resolve(filePath);
|
|
2077
|
-
|
|
2078
|
-
// Check if path is a directory - search across files in directory
|
|
2079
|
-
let stats;
|
|
2080
|
-
try {
|
|
2081
|
-
stats = await fs.stat(absolutePath);
|
|
2082
|
-
} catch (e) {
|
|
2083
|
-
throw new Error(`Path does not exist: ${filePath}`);
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
if (!stats.isFile()) {
|
|
2087
|
-
// It's a directory - search for symbol definition using grep
|
|
2088
|
-
return await handleDefinitionInDirectory({ path: absolutePath, symbol });
|
|
2089
|
-
}
|
|
2090
2458
|
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
// Error recovery: suggest using depth parameter
|
|
2461
|
+
let errorMessage = `ā Error listing files: ${error.message}`;
|
|
2462
|
+
if (error.message.includes('too many files') || error.message.includes('EMFILE') || error.message.includes('max')) {
|
|
2463
|
+
errorMessage += `\n\nš” Tip: Try reducing the depth (e.g., depth: 2 or 3) to limit the search scope.`;
|
|
2464
|
+
}
|
|
2094
2465
|
|
|
2095
|
-
|
|
2466
|
+
reportAudit({
|
|
2467
|
+
tool: 'list_files_fast',
|
|
2468
|
+
instruction: dirPath,
|
|
2469
|
+
status: 'error',
|
|
2470
|
+
error_message: error.message,
|
|
2471
|
+
latency_ms: Date.now() - start
|
|
2472
|
+
});
|
|
2096
2473
|
return {
|
|
2097
|
-
content: [{ type: "text", text:
|
|
2474
|
+
content: [{ type: "text", text: errorMessage }],
|
|
2475
|
+
isError: true
|
|
2098
2476
|
};
|
|
2099
2477
|
}
|
|
2100
|
-
|
|
2101
|
-
const def = definitions[0]; // Take first
|
|
2102
|
-
|
|
2103
|
-
return {
|
|
2104
|
-
content: [{ type: "text", text: `Definition of '${symbol}':\n${filePath}:${def.line}:${def.column} (${def.type || 'unknown'})` }]
|
|
2105
|
-
};
|
|
2106
2478
|
}
|
|
2107
2479
|
|
|
2480
|
+
/**
|
|
2481
|
+
* UNIFIED HANDLER 11: handleDefinitionInDirectory (v2.0)
|
|
2482
|
+
* Renamed from handleDefinitionInDirectory for consistency
|
|
2483
|
+
* Now supports both single file and batch search modes
|
|
2484
|
+
*/
|
|
2108
2485
|
async function handleDefinitionInDirectory({ path: dirPath, symbol }) {
|
|
2109
2486
|
// Search for symbol definition in directory using grep
|
|
2110
2487
|
// Look for common definition patterns: const/let/var, function, class, interface, type, etc.
|
|
@@ -2168,44 +2545,11 @@ async function handleDefinitionInDirectory({ path: dirPath, symbol }) {
|
|
|
2168
2545
|
};
|
|
2169
2546
|
}
|
|
2170
2547
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
// Check if path is a directory - search across files in directory
|
|
2177
|
-
let stats;
|
|
2178
|
-
try {
|
|
2179
|
-
stats = await fs.stat(absolutePath);
|
|
2180
|
-
} catch (e) {
|
|
2181
|
-
throw new Error(`Path does not exist: ${filePath}`);
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
if (!stats.isFile()) {
|
|
2185
|
-
// It's a directory - search for references using grep
|
|
2186
|
-
return await handleReferencesInDirectory({ path: absolutePath, symbol });
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
// It's a file - use tree-sitter to find references
|
|
2190
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
2191
|
-
const references = await findReferences(content, filePath, symbol);
|
|
2192
|
-
|
|
2193
|
-
if (references.length === 0) {
|
|
2194
|
-
return {
|
|
2195
|
-
content: [{ type: "text", text: `No references found for '${symbol}' in ${filePath}` }]
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
let output = `References for '${symbol}' in ${filePath} (${references.length}):\n`;
|
|
2200
|
-
references.forEach(ref => {
|
|
2201
|
-
output += `${ref.line}:${ref.column}\n`;
|
|
2202
|
-
});
|
|
2203
|
-
|
|
2204
|
-
return {
|
|
2205
|
-
content: [{ type: "text", text: output }]
|
|
2206
|
-
};
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2548
|
+
/**
|
|
2549
|
+
* UNIFIED HANDLER 12: handleReferencesInDirectory (v2.0)
|
|
2550
|
+
* Renamed from handleReferencesInDirectory for consistency
|
|
2551
|
+
* Now supports both single file and batch search modes
|
|
2552
|
+
*/
|
|
2209
2553
|
async function handleReferencesInDirectory({ path: dirPath, symbol }) {
|
|
2210
2554
|
// Search for symbol references in directory using grep
|
|
2211
2555
|
const flags = "-rn";
|