@mrxkun/mcfast-mcp 4.1.12 → 4.1.13
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 +231 -235
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.13",
|
|
4
4
|
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.12: Implement proper MCP stdio transport lifecycle and cleanup to prevent zombie processes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -25,13 +25,13 @@ const colors = {
|
|
|
25
25
|
|
|
26
26
|
// Override console.log - suppress in MCP mode
|
|
27
27
|
const originalConsoleLog = console.log;
|
|
28
|
-
console.log = function(...args) {
|
|
28
|
+
console.log = function (...args) {
|
|
29
29
|
// Suppressed in MCP mode
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
// Override console.error - suppress in MCP mode
|
|
33
33
|
const originalConsoleError = console.error;
|
|
34
|
-
console.error = function(...args) {
|
|
34
|
+
console.error = function (...args) {
|
|
35
35
|
// Suppressed in MCP mode
|
|
36
36
|
};
|
|
37
37
|
|
|
@@ -86,70 +86,70 @@ const LOCK_FILE_PATH = path.join(process.cwd(), '.mcfast', '.mcp-instance.lock')
|
|
|
86
86
|
let lockFileHandle = null;
|
|
87
87
|
|
|
88
88
|
async function acquireLock() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
89
|
+
try {
|
|
90
|
+
const lockDir = path.dirname(LOCK_FILE_PATH);
|
|
91
|
+
await fs.mkdir(lockDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
lockFileHandle = await fs.open(LOCK_FILE_PATH, 'wx');
|
|
94
|
+
|
|
95
|
+
// Write current PID
|
|
96
|
+
await lockFileHandle.write(`${process.pid}\n${Date.now()}\n`, 0);
|
|
97
|
+
await lockFileHandle.sync();
|
|
98
|
+
|
|
99
|
+
// Cleanup on exit
|
|
100
|
+
process.on('beforeExit', releaseLock);
|
|
101
|
+
process.on('SIGINT', releaseLock);
|
|
102
|
+
process.on('SIGTERM', releaseLock);
|
|
103
|
+
process.on('uncaughtException', releaseLock);
|
|
104
|
+
|
|
105
|
+
console.error(`${colors.cyan}[Lock]${colors.reset} Acquired lock file: ${LOCK_FILE_PATH}`);
|
|
106
|
+
return true;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code === 'EEXIST') {
|
|
109
|
+
// Lock file exists, check if it's stale
|
|
110
|
+
try {
|
|
111
|
+
const lockContent = await fs.readFile(LOCK_FILE_PATH, 'utf-8');
|
|
112
|
+
const [pid, timestamp] = lockContent.trim().split('\n');
|
|
113
|
+
const lockAge = Date.now() - parseInt(timestamp);
|
|
114
|
+
|
|
115
|
+
// Check if process is still running (stale lock if > 5 minutes)
|
|
116
|
+
if (lockAge > 5 * 60 * 1000) {
|
|
117
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Stale lock detected (age: ${Math.round(lockAge / 1000)}s), removing...`);
|
|
118
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
119
|
+
return acquireLock(); // Retry
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.error(`${colors.red}[Lock]${colors.reset} Another instance is already running (PID: ${pid})`);
|
|
123
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} To force start, delete: ${LOCK_FILE_PATH}`);
|
|
124
|
+
return false;
|
|
125
|
+
} catch (readError) {
|
|
126
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to read lock file: ${readError.message}`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
|
|
127
131
|
return false;
|
|
128
|
-
}
|
|
129
132
|
}
|
|
130
|
-
console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
async function releaseLock() {
|
|
136
|
-
|
|
136
|
+
if (lockFileHandle) {
|
|
137
|
+
try {
|
|
138
|
+
await lockFileHandle.close();
|
|
139
|
+
lockFileHandle = null;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore close errors
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
137
145
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} catch (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
await fs.unlink(LOCK_FILE_PATH);
|
|
147
|
-
console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
if (error.code !== 'ENOENT') {
|
|
150
|
-
console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
146
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
147
|
+
console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code !== 'ENOENT') {
|
|
150
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// ============================================================================
|
|
@@ -171,36 +171,36 @@ let memoryEngineReady = false;
|
|
|
171
171
|
*/
|
|
172
172
|
async function backgroundInitializeMemoryEngine() {
|
|
173
173
|
if (memoryEngineInitPromise) return memoryEngineInitPromise;
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
memoryEngineInitPromise = (async () => {
|
|
176
176
|
const maxRetries = 2;
|
|
177
177
|
let lastError = null;
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
180
180
|
try {
|
|
181
181
|
memoryEngine = new MemoryEngine({
|
|
182
182
|
apiKey: TOKEN,
|
|
183
183
|
enableSync: true
|
|
184
184
|
});
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
// Use timeout for initialization
|
|
187
187
|
const initTimeout = new Promise((_, reject) => {
|
|
188
188
|
setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
|
|
189
189
|
});
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
await Promise.race([
|
|
192
192
|
memoryEngine.initialize(process.cwd()),
|
|
193
193
|
initTimeout
|
|
194
194
|
]);
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
memoryEngineReady = true;
|
|
197
197
|
console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
|
|
198
198
|
return memoryEngine;
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
} catch (error) {
|
|
201
201
|
lastError = error;
|
|
202
202
|
console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
if (attempt < maxRetries) {
|
|
205
205
|
// Exponential backoff before retry
|
|
206
206
|
const backoffMs = 1000 * Math.pow(2, attempt - 1);
|
|
@@ -209,7 +209,7 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
// All retries failed - graceful degradation
|
|
214
214
|
console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
|
|
215
215
|
console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
|
|
@@ -217,7 +217,7 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
217
217
|
memoryEngineReady = false;
|
|
218
218
|
return null;
|
|
219
219
|
})();
|
|
220
|
-
|
|
220
|
+
|
|
221
221
|
return memoryEngineInitPromise;
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -228,7 +228,7 @@ async function getMemoryEngine() {
|
|
|
228
228
|
if (memoryEngine && memoryEngineReady) {
|
|
229
229
|
return memoryEngine;
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
// Wait for initialization with timeout
|
|
233
233
|
if (memoryEngineInitPromise) {
|
|
234
234
|
try {
|
|
@@ -240,12 +240,12 @@ async function getMemoryEngine() {
|
|
|
240
240
|
console.error(`${colors.yellow}[Memory]${colors.reset} Waiting for engine: ${error.message}`);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
// If still not ready, try to initialize now
|
|
245
245
|
if (!memoryEngine) {
|
|
246
246
|
await backgroundInitializeMemoryEngine();
|
|
247
247
|
}
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
return memoryEngine;
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -255,18 +255,18 @@ async function getMemoryEngine() {
|
|
|
255
255
|
async function searchMemoryContext(instruction, maxResults = 5) {
|
|
256
256
|
try {
|
|
257
257
|
const engine = await getMemoryEngine();
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
// Use intelligent search for best context
|
|
260
|
-
const searchResult = await engine.intelligentSearch(instruction, {
|
|
261
|
-
limit: maxResults
|
|
260
|
+
const searchResult = await engine.intelligentSearch(instruction, {
|
|
261
|
+
limit: maxResults
|
|
262
262
|
});
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
if (!searchResult.results || searchResult.results.length === 0) {
|
|
265
265
|
return '';
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
let context = '';
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
// Add code context from search results
|
|
271
271
|
context += '\n\n--- MEMORY CONTEXT (Relevant Code) ---\n';
|
|
272
272
|
searchResult.results.forEach((result, idx) => {
|
|
@@ -277,7 +277,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
|
|
|
277
277
|
context += `${result.code.substring(0, 300)}${result.code.length > 300 ? '...' : ''}\n`;
|
|
278
278
|
}
|
|
279
279
|
});
|
|
280
|
-
|
|
280
|
+
|
|
281
281
|
// Add facts if available
|
|
282
282
|
const facts = await engine.searchFacts(instruction, 5);
|
|
283
283
|
if (facts && facts.length > 0) {
|
|
@@ -286,7 +286,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
|
|
|
286
286
|
context += `• ${fact.type}: ${fact.name} (${fact.file_id})\n`;
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
|
-
|
|
289
|
+
|
|
290
290
|
return context;
|
|
291
291
|
} catch (error) {
|
|
292
292
|
console.error(`${colors.yellow}[Memory]${colors.reset} Context search failed: ${error.message}`);
|
|
@@ -304,44 +304,44 @@ async function autoTriggerIntelligence(filePath, instruction) {
|
|
|
304
304
|
if (process.env.MCFAST_INTELLIGENCE_AUTO !== 'true') {
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
const engine = await getMemoryEngine();
|
|
309
309
|
const cache = getIntelligenceCache();
|
|
310
|
-
|
|
310
|
+
|
|
311
311
|
// 1. Detect patterns (cached for 5 minutes)
|
|
312
312
|
const patternCacheKey = { file: filePath, type: 'patterns' };
|
|
313
313
|
let patterns = cache.get('patterns', patternCacheKey);
|
|
314
|
-
|
|
314
|
+
|
|
315
315
|
if (!patterns) {
|
|
316
316
|
patterns = await engine.detectPatterns();
|
|
317
317
|
if (patterns && patterns.length > 0) {
|
|
318
318
|
cache.set('patterns', patternCacheKey, patterns);
|
|
319
319
|
console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 Detected ${patterns.length} patterns`);
|
|
320
|
-
|
|
320
|
+
|
|
321
321
|
// Log high-confidence patterns
|
|
322
322
|
patterns.filter(p => p.confidence > 0.8).forEach(p => {
|
|
323
323
|
console.error(`${colors.yellow}[Pattern]${colors.reset} ${p.type}: ${p.message}`);
|
|
324
324
|
});
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
|
|
328
328
|
// 2. Get suggestions (cached for 5 minutes)
|
|
329
329
|
const suggestionCacheKey = { file: filePath, type: 'suggestions' };
|
|
330
330
|
let suggestions = cache.get('suggestions', suggestionCacheKey);
|
|
331
|
-
|
|
331
|
+
|
|
332
332
|
if (!suggestions) {
|
|
333
333
|
suggestions = await engine.getSuggestions({ currentFile: filePath });
|
|
334
334
|
if (suggestions && suggestions.length > 0) {
|
|
335
335
|
cache.set('suggestions', suggestionCacheKey, suggestions);
|
|
336
336
|
console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 ${suggestions.length} suggestions available`);
|
|
337
|
-
|
|
337
|
+
|
|
338
338
|
// Show top suggestions
|
|
339
339
|
suggestions.slice(0, 3).forEach(s => {
|
|
340
340
|
console.error(`${colors.yellow}[Suggestion]${colors.reset} ${s.type}: ${s.message}`);
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
|
-
|
|
344
|
+
|
|
345
345
|
} catch (error) {
|
|
346
346
|
// Silent fail - intelligence should not break the tool
|
|
347
347
|
if (VERBOSE) {
|
|
@@ -357,7 +357,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
357
357
|
try {
|
|
358
358
|
const engine = await getMemoryEngine();
|
|
359
359
|
const diffSize = Object.keys(files).reduce((total, fp) => total + (files[fp]?.length || 0), 0);
|
|
360
|
-
|
|
360
|
+
|
|
361
361
|
engine.logEditToMemory({
|
|
362
362
|
instruction: instruction.substring(0, 500),
|
|
363
363
|
files: Object.keys(files),
|
|
@@ -367,7 +367,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
367
367
|
durationMs,
|
|
368
368
|
errorMessage
|
|
369
369
|
});
|
|
370
|
-
|
|
370
|
+
|
|
371
371
|
console.error(`${colors.cyan}[Memory]${colors.reset} Edit logged to history`);
|
|
372
372
|
} catch (error) {
|
|
373
373
|
console.error(`${colors.yellow}[Memory]${colors.reset} Log failed: ${error.message}`);
|
|
@@ -380,13 +380,13 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
380
380
|
async function analyzeRenameImpact(symbolName, currentFile = null) {
|
|
381
381
|
try {
|
|
382
382
|
const engine = await getMemoryEngine();
|
|
383
|
-
|
|
383
|
+
|
|
384
384
|
// Search for symbol references across codebase
|
|
385
|
-
const searchResult = await engine.intelligentSearch(symbolName, {
|
|
385
|
+
const searchResult = await engine.intelligentSearch(symbolName, {
|
|
386
386
|
limit: 20,
|
|
387
387
|
currentFile
|
|
388
388
|
});
|
|
389
|
-
|
|
389
|
+
|
|
390
390
|
const impactedFiles = new Set();
|
|
391
391
|
const references = [];
|
|
392
392
|
|
|
@@ -1056,9 +1056,9 @@ async function reportAudit(data) {
|
|
|
1056
1056
|
flushInterval: 5000,
|
|
1057
1057
|
verbose: VERBOSE
|
|
1058
1058
|
});
|
|
1059
|
-
|
|
1059
|
+
|
|
1060
1060
|
queue.add(auditData);
|
|
1061
|
-
|
|
1061
|
+
|
|
1062
1062
|
if (VERBOSE) {
|
|
1063
1063
|
const stats = queue.getStats();
|
|
1064
1064
|
console.error(`${colors.dim}[Audit] Queued for batching. Queue size: ${stats.queued}${colors.reset}`);
|
|
@@ -1097,9 +1097,9 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1097
1097
|
if (!validation.valid) {
|
|
1098
1098
|
const latency = Date.now() - start;
|
|
1099
1099
|
return {
|
|
1100
|
-
content: [{
|
|
1101
|
-
type: "text",
|
|
1102
|
-
text: `⚠️ Invalid regex pattern: ${validation.error}\n\nQuery: "${query}"\n\n💡 Tips:\n• Basic regex works: \\bword\\b, [a-z]+, (foo|bar)\n• Avoid lookbehind (?<=...) and lookahead (?=...) - not supported by grep\n• Use word boundaries \\b instead of lookarounds\n• Escape special chars: \\\\, \\+, \\*`
|
|
1100
|
+
content: [{
|
|
1101
|
+
type: "text",
|
|
1102
|
+
text: `⚠️ Invalid regex pattern: ${validation.error}\n\nQuery: "${query}"\n\n💡 Tips:\n• Basic regex works: \\bword\\b, [a-z]+, (foo|bar)\n• Avoid lookbehind (?<=...) and lookahead (?=...) - not supported by grep\n• Use word boundaries \\b instead of lookarounds\n• Escape special chars: \\\\, \\+, \\*`
|
|
1103
1103
|
}],
|
|
1104
1104
|
isError: true
|
|
1105
1105
|
};
|
|
@@ -1135,12 +1135,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1135
1135
|
|
|
1136
1136
|
try {
|
|
1137
1137
|
const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 }); // 10MB buffer
|
|
1138
|
-
|
|
1138
|
+
|
|
1139
1139
|
// Check for grep regex errors in stderr
|
|
1140
1140
|
if (stderr && stderr.includes('Invalid regular expression')) {
|
|
1141
1141
|
throw new Error(`grep: ${stderr}`);
|
|
1142
1142
|
}
|
|
1143
|
-
|
|
1143
|
+
|
|
1144
1144
|
const results = stdout.trim().split('\n').filter(Boolean);
|
|
1145
1145
|
|
|
1146
1146
|
const latency = Date.now() - start;
|
|
@@ -1205,12 +1205,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1205
1205
|
content: [{ type: "text", text: `🔍 Regex Search: No matches found for "${query}" in ${searchPath} (${latency}ms)` }]
|
|
1206
1206
|
};
|
|
1207
1207
|
}
|
|
1208
|
-
|
|
1208
|
+
|
|
1209
1209
|
// Handle regex syntax errors
|
|
1210
1210
|
if (execErr.stderr && execErr.stderr.includes('Invalid')) {
|
|
1211
1211
|
throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
|
|
1212
1212
|
}
|
|
1213
|
-
|
|
1213
|
+
|
|
1214
1214
|
throw execErr;
|
|
1215
1215
|
}
|
|
1216
1216
|
} catch (error) {
|
|
@@ -1237,14 +1237,14 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1237
1237
|
*/
|
|
1238
1238
|
function validateRegex(pattern) {
|
|
1239
1239
|
const warnings = [];
|
|
1240
|
-
|
|
1240
|
+
|
|
1241
1241
|
try {
|
|
1242
1242
|
// Test if it's valid JavaScript regex
|
|
1243
1243
|
new RegExp(pattern);
|
|
1244
1244
|
} catch (e) {
|
|
1245
1245
|
return { valid: false, error: e.message, warnings };
|
|
1246
1246
|
}
|
|
1247
|
-
|
|
1247
|
+
|
|
1248
1248
|
// Check for patterns not supported by basic grep (PCRE-only features)
|
|
1249
1249
|
const unsupportedPatterns = [
|
|
1250
1250
|
{ regex: /\(\?<[=!]/, name: 'Lookbehind (?<=...) or negative lookbehind (?<!...)' },
|
|
@@ -1256,18 +1256,18 @@ function validateRegex(pattern) {
|
|
|
1256
1256
|
{ regex: /\(\?\d*\)/, name: 'Subroutine calls (?1)' },
|
|
1257
1257
|
{ regex: /\(\?&\w+\)/, name: 'Subroutine calls (?&name)' }
|
|
1258
1258
|
];
|
|
1259
|
-
|
|
1259
|
+
|
|
1260
1260
|
for (const { regex, name } of unsupportedPatterns) {
|
|
1261
1261
|
if (regex.test(pattern)) {
|
|
1262
1262
|
warnings.push(`Pattern uses ${name} which may not be supported by grep. Results may be incomplete.`);
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
-
|
|
1265
|
+
|
|
1266
1266
|
// Check for potentially dangerous patterns (catastrophic backtracking)
|
|
1267
1267
|
if (/\(\w+\+\+?\)|\(\w+\*\*?\)\*\*?/.test(pattern)) {
|
|
1268
1268
|
warnings.push('Pattern may cause performance issues (nested quantifiers)');
|
|
1269
1269
|
}
|
|
1270
|
-
|
|
1270
|
+
|
|
1271
1271
|
return { valid: true, warnings };
|
|
1272
1272
|
}
|
|
1273
1273
|
|
|
@@ -1297,7 +1297,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1297
1297
|
// Convert literal query to glob pattern for file search
|
|
1298
1298
|
pattern = `**/*${query}*`;
|
|
1299
1299
|
}
|
|
1300
|
-
|
|
1300
|
+
|
|
1301
1301
|
// Use fast-glob to find files
|
|
1302
1302
|
const files = await fg([pattern], {
|
|
1303
1303
|
cwd,
|
|
@@ -1309,7 +1309,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1309
1309
|
|
|
1310
1310
|
// Also search file contents if it's not a file pattern
|
|
1311
1311
|
const contentMatches = [];
|
|
1312
|
-
|
|
1312
|
+
|
|
1313
1313
|
// For simple queries, also search in common code files
|
|
1314
1314
|
if (!isRegex && query.length > 1) {
|
|
1315
1315
|
const codeFiles = await fg(['**/*.{js,ts,jsx,tsx,py,go,rs,java,rb,php}'], {
|
|
@@ -1317,7 +1317,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1317
1317
|
ignore: ['node_modules/**', '.git/**'],
|
|
1318
1318
|
absolute: false
|
|
1319
1319
|
});
|
|
1320
|
-
|
|
1320
|
+
|
|
1321
1321
|
// Search in first 50 files to avoid performance issues
|
|
1322
1322
|
for (const file of codeFiles.slice(0, 50)) {
|
|
1323
1323
|
try {
|
|
@@ -1325,7 +1325,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1325
1325
|
if (content.includes(query)) {
|
|
1326
1326
|
const lines = content.split('\n');
|
|
1327
1327
|
const matches = [];
|
|
1328
|
-
|
|
1328
|
+
|
|
1329
1329
|
lines.forEach((line, idx) => {
|
|
1330
1330
|
if (line.includes(query)) {
|
|
1331
1331
|
matches.push({
|
|
@@ -1335,7 +1335,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1335
1335
|
});
|
|
1336
1336
|
}
|
|
1337
1337
|
});
|
|
1338
|
-
|
|
1338
|
+
|
|
1339
1339
|
if (matches.length > 0) {
|
|
1340
1340
|
contentMatches.push({
|
|
1341
1341
|
file,
|
|
@@ -1350,7 +1350,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1350
1350
|
}
|
|
1351
1351
|
|
|
1352
1352
|
const latency = Date.now() - start;
|
|
1353
|
-
|
|
1353
|
+
|
|
1354
1354
|
// Format output
|
|
1355
1355
|
let output = `${colors.bold}Filesystem Search Results${colors.reset}\n\n`;
|
|
1356
1356
|
output += `Query: "${query}"\n`;
|
|
@@ -1396,7 +1396,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1396
1396
|
};
|
|
1397
1397
|
} catch (error) {
|
|
1398
1398
|
const latency = Date.now() - start;
|
|
1399
|
-
|
|
1399
|
+
|
|
1400
1400
|
reportAudit({
|
|
1401
1401
|
tool: 'search_filesystem',
|
|
1402
1402
|
instruction: `Search: ${query}`,
|
|
@@ -1456,7 +1456,7 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
|
|
|
1456
1456
|
*/
|
|
1457
1457
|
async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
1458
1458
|
const editStartTime = Date.now();
|
|
1459
|
-
|
|
1459
|
+
|
|
1460
1460
|
// Validate required parameters
|
|
1461
1461
|
if (!instruction) {
|
|
1462
1462
|
return {
|
|
@@ -1464,16 +1464,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1464
1464
|
isError: true
|
|
1465
1465
|
};
|
|
1466
1466
|
}
|
|
1467
|
-
|
|
1467
|
+
|
|
1468
1468
|
if (!files || Object.keys(files).length === 0) {
|
|
1469
1469
|
return {
|
|
1470
1470
|
content: [{ type: "text", text: "❌ Error: 'files' parameter is required and must contain at least one file" }],
|
|
1471
1471
|
isError: true
|
|
1472
1472
|
};
|
|
1473
1473
|
}
|
|
1474
|
-
|
|
1474
|
+
|
|
1475
1475
|
const firstFile = Object.keys(files)[0];
|
|
1476
|
-
|
|
1476
|
+
|
|
1477
1477
|
// 1. Retrieve memory context for enhanced understanding
|
|
1478
1478
|
let memoryContext = '';
|
|
1479
1479
|
try {
|
|
@@ -1484,7 +1484,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1484
1484
|
} catch (error) {
|
|
1485
1485
|
console.error(`${colors.yellow}[Memory]${colors.reset} Context retrieval failed: ${error.message}`);
|
|
1486
1486
|
}
|
|
1487
|
-
|
|
1487
|
+
|
|
1488
1488
|
// 2. Analyze impact for rename operations
|
|
1489
1489
|
let impactAnalysis = null;
|
|
1490
1490
|
const renamePattern = instruction.match(/(?:rename|change)\s+(?:function|class|const|let|var|method)\s+(\w+)\s+to\s+(\w+)/i);
|
|
@@ -1499,7 +1499,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1499
1499
|
console.error(`${colors.yellow}[IMPACT]${colors.reset} Analysis failed: ${error.message}`);
|
|
1500
1500
|
}
|
|
1501
1501
|
}
|
|
1502
|
-
|
|
1502
|
+
|
|
1503
1503
|
// Check for multi-file edits first
|
|
1504
1504
|
const multiFileEdit = detectCrossFileEdit(instruction, files);
|
|
1505
1505
|
|
|
@@ -1533,7 +1533,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1533
1533
|
errorMessage: result.message || result.error,
|
|
1534
1534
|
durationMs: Date.now() - editStartTime
|
|
1535
1535
|
});
|
|
1536
|
-
|
|
1536
|
+
|
|
1537
1537
|
return {
|
|
1538
1538
|
content: [{
|
|
1539
1539
|
type: "text",
|
|
@@ -1571,7 +1571,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1571
1571
|
errorMessage: error.message,
|
|
1572
1572
|
durationMs: Date.now() - editStartTime
|
|
1573
1573
|
});
|
|
1574
|
-
|
|
1574
|
+
|
|
1575
1575
|
return {
|
|
1576
1576
|
content: [{
|
|
1577
1577
|
type: "text",
|
|
@@ -1676,7 +1676,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1676
1676
|
errorMessage: error.message,
|
|
1677
1677
|
durationMs: Date.now() - editStartTime
|
|
1678
1678
|
});
|
|
1679
|
-
|
|
1679
|
+
|
|
1680
1680
|
return {
|
|
1681
1681
|
content: [{
|
|
1682
1682
|
type: "text",
|
|
@@ -1807,7 +1807,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1807
1807
|
|
|
1808
1808
|
// Include impact analysis in result if available
|
|
1809
1809
|
let resultText = `✅ AST Refactor Applied Successfully\n\nOperation: ${pattern.type}\nChanges: ${transformResult.count || transformResult.replacements || 0} locations\n\nBackup: ${editResult.backupPath}`;
|
|
1810
|
-
|
|
1810
|
+
|
|
1811
1811
|
if (impactAnalysis && impactAnalysis.suggestion) {
|
|
1812
1812
|
resultText += `\n\n${impactAnalysis.suggestion}`;
|
|
1813
1813
|
if (impactAnalysis.impactedFiles.length > 0) {
|
|
@@ -1838,7 +1838,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1838
1838
|
errorMessage: error.message,
|
|
1839
1839
|
durationMs: Date.now() - editStartTime
|
|
1840
1840
|
});
|
|
1841
|
-
|
|
1841
|
+
|
|
1842
1842
|
return {
|
|
1843
1843
|
content: [{
|
|
1844
1844
|
type: "text",
|
|
@@ -1861,7 +1861,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1861
1861
|
dryRun,
|
|
1862
1862
|
toolName: 'edit'
|
|
1863
1863
|
});
|
|
1864
|
-
|
|
1864
|
+
|
|
1865
1865
|
// Log to memory
|
|
1866
1866
|
await logEditToMemory({
|
|
1867
1867
|
instruction,
|
|
@@ -1871,7 +1871,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1871
1871
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1872
1872
|
durationMs: Date.now() - editStartTime
|
|
1873
1873
|
});
|
|
1874
|
-
|
|
1874
|
+
|
|
1875
1875
|
return result;
|
|
1876
1876
|
}
|
|
1877
1877
|
}
|
|
@@ -1884,7 +1884,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1884
1884
|
dryRun,
|
|
1885
1885
|
toolName: 'edit'
|
|
1886
1886
|
});
|
|
1887
|
-
|
|
1887
|
+
|
|
1888
1888
|
// Log to memory
|
|
1889
1889
|
await logEditToMemory({
|
|
1890
1890
|
instruction,
|
|
@@ -1894,7 +1894,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1894
1894
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1895
1895
|
durationMs: Date.now() - editStartTime
|
|
1896
1896
|
});
|
|
1897
|
-
|
|
1897
|
+
|
|
1898
1898
|
return result;
|
|
1899
1899
|
}
|
|
1900
1900
|
|
|
@@ -1904,14 +1904,14 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1904
1904
|
if (memoryContext) {
|
|
1905
1905
|
enhancedInstruction = `${instruction}\n\n--- CONTEXT FROM CODEBASE ---${memoryContext}`;
|
|
1906
1906
|
}
|
|
1907
|
-
|
|
1907
|
+
|
|
1908
1908
|
const result = await handleApplyFast({
|
|
1909
1909
|
instruction: enhancedInstruction,
|
|
1910
1910
|
files,
|
|
1911
1911
|
dryRun,
|
|
1912
1912
|
toolName: 'edit'
|
|
1913
1913
|
});
|
|
1914
|
-
|
|
1914
|
+
|
|
1915
1915
|
// Log to memory
|
|
1916
1916
|
await logEditToMemory({
|
|
1917
1917
|
instruction,
|
|
@@ -1921,13 +1921,13 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1921
1921
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1922
1922
|
durationMs: Date.now() - editStartTime
|
|
1923
1923
|
});
|
|
1924
|
-
|
|
1924
|
+
|
|
1925
1925
|
// Auto-trigger intelligence tools in background (non-blocking)
|
|
1926
1926
|
if (!result.isError) {
|
|
1927
1927
|
const firstFile = Object.keys(files)[0];
|
|
1928
|
-
autoTriggerIntelligence(firstFile, instruction).catch(() => {});
|
|
1928
|
+
autoTriggerIntelligence(firstFile, instruction).catch(() => { });
|
|
1929
1929
|
}
|
|
1930
|
-
|
|
1930
|
+
|
|
1931
1931
|
return result;
|
|
1932
1932
|
}
|
|
1933
1933
|
|
|
@@ -1939,7 +1939,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1939
1939
|
*/
|
|
1940
1940
|
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2, maxResults = 10 }) {
|
|
1941
1941
|
const searchStartTime = Date.now();
|
|
1942
|
-
|
|
1942
|
+
|
|
1943
1943
|
// For regex mode without files, we need to do content-based regex search
|
|
1944
1944
|
// since fast-glob doesn't support full regex patterns
|
|
1945
1945
|
if (regex && !files && mode === 'auto') {
|
|
@@ -1954,9 +1954,9 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
|
|
|
1954
1954
|
if ((detectedMode === 'auto' || detectedMode === 'local') && !regex) {
|
|
1955
1955
|
try {
|
|
1956
1956
|
const engine = await getMemoryEngine();
|
|
1957
|
-
|
|
1957
|
+
|
|
1958
1958
|
// Use intelligent search (UltraHybrid + Smart Routing)
|
|
1959
|
-
const searchResult = await engine.intelligentSearch(query, {
|
|
1959
|
+
const searchResult = await engine.intelligentSearch(query, {
|
|
1960
1960
|
limit: maxResults,
|
|
1961
1961
|
currentFile: files ? Object.keys(files)[0] : null
|
|
1962
1962
|
});
|
|
@@ -2032,7 +2032,7 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
|
|
|
2032
2032
|
if (filePaths && Array.isArray(filePaths) && filePaths.length > 0) {
|
|
2033
2033
|
return await handleBatchRead(filePaths, start_line, end_line, max_lines_per_file);
|
|
2034
2034
|
}
|
|
2035
|
-
|
|
2035
|
+
|
|
2036
2036
|
// Single file mode (backward compatible)
|
|
2037
2037
|
return await handleReadFile({ path: filePath, start_line, end_line });
|
|
2038
2038
|
}
|
|
@@ -2043,50 +2043,50 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
|
|
|
2043
2043
|
async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_file) {
|
|
2044
2044
|
const start = Date.now();
|
|
2045
2045
|
const MAX_BATCH_SIZE = 50;
|
|
2046
|
-
|
|
2046
|
+
|
|
2047
2047
|
if (filePaths.length > MAX_BATCH_SIZE) {
|
|
2048
2048
|
return {
|
|
2049
2049
|
content: [{ type: "text", text: `❌ Batch read limit exceeded: ${filePaths.length} files (max ${MAX_BATCH_SIZE})` }],
|
|
2050
2050
|
isError: true
|
|
2051
2051
|
};
|
|
2052
2052
|
}
|
|
2053
|
-
|
|
2053
|
+
|
|
2054
2054
|
console.error(`${colors.cyan}[BATCH READ]${colors.reset} ${filePaths.length} files`);
|
|
2055
|
-
|
|
2055
|
+
|
|
2056
2056
|
// Read all files in parallel with Promise.all
|
|
2057
2057
|
const results = await Promise.all(
|
|
2058
2058
|
filePaths.map(async (filePath) => {
|
|
2059
2059
|
try {
|
|
2060
|
-
const result = await handleReadFileInternal({
|
|
2061
|
-
path: filePath,
|
|
2062
|
-
start_line,
|
|
2060
|
+
const result = await handleReadFileInternal({
|
|
2061
|
+
path: filePath,
|
|
2062
|
+
start_line,
|
|
2063
2063
|
end_line,
|
|
2064
|
-
max_lines: max_lines_per_file
|
|
2064
|
+
max_lines: max_lines_per_file
|
|
2065
2065
|
});
|
|
2066
2066
|
return { path: filePath, ...result, success: true };
|
|
2067
2067
|
} catch (error) {
|
|
2068
|
-
return {
|
|
2069
|
-
path: filePath,
|
|
2070
|
-
success: false,
|
|
2068
|
+
return {
|
|
2069
|
+
path: filePath,
|
|
2070
|
+
success: false,
|
|
2071
2071
|
error: error.message,
|
|
2072
2072
|
content: `❌ Error reading ${filePath}: ${error.message}`
|
|
2073
2073
|
};
|
|
2074
2074
|
}
|
|
2075
2075
|
})
|
|
2076
2076
|
);
|
|
2077
|
-
|
|
2077
|
+
|
|
2078
2078
|
// Calculate stats
|
|
2079
2079
|
const successful = results.filter(r => r.success).length;
|
|
2080
2080
|
const failed = results.filter(r => !r.success).length;
|
|
2081
2081
|
const totalLines = results.reduce((sum, r) => sum + (r.lines || 0), 0);
|
|
2082
2082
|
const totalSize = results.reduce((sum, r) => sum + (r.size || 0), 0);
|
|
2083
|
-
|
|
2083
|
+
|
|
2084
2084
|
// Format output
|
|
2085
2085
|
let output = `📚 Batch Read Complete (${filePaths.length} files)\n`;
|
|
2086
2086
|
output += `✅ Successful: ${successful} ❌ Failed: ${failed}\n`;
|
|
2087
2087
|
output += `📊 Total: ${totalLines} lines, ${(totalSize / 1024).toFixed(1)} KB\n`;
|
|
2088
2088
|
output += `⏱️ Latency: ${Date.now() - start}ms\n\n`;
|
|
2089
|
-
|
|
2089
|
+
|
|
2090
2090
|
// Add each file's content
|
|
2091
2091
|
results.forEach((result, index) => {
|
|
2092
2092
|
output += `[${index + 1}/${filePaths.length}] `;
|
|
@@ -2105,7 +2105,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
|
|
|
2105
2105
|
}
|
|
2106
2106
|
output += `\n\n`;
|
|
2107
2107
|
});
|
|
2108
|
-
|
|
2108
|
+
|
|
2109
2109
|
// Report audit for batch operation
|
|
2110
2110
|
reportAudit({
|
|
2111
2111
|
tool: 'read_file',
|
|
@@ -2116,7 +2116,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
|
|
|
2116
2116
|
input_tokens: Math.ceil(filePaths.join(',').length / 4),
|
|
2117
2117
|
output_tokens: Math.ceil(output.length / 4)
|
|
2118
2118
|
});
|
|
2119
|
-
|
|
2119
|
+
|
|
2120
2120
|
return {
|
|
2121
2121
|
content: [{ type: "text", text: output }]
|
|
2122
2122
|
};
|
|
@@ -2134,7 +2134,7 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
|
|
|
2134
2134
|
}
|
|
2135
2135
|
|
|
2136
2136
|
const STREAM_THRESHOLD = 1024 * 1024; // 1MB
|
|
2137
|
-
|
|
2137
|
+
|
|
2138
2138
|
let startLine = start_line ? parseInt(start_line) : 1;
|
|
2139
2139
|
let endLine = end_line ? parseInt(end_line) : -1;
|
|
2140
2140
|
let outputContent;
|
|
@@ -2262,12 +2262,12 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
|
|
|
2262
2262
|
});
|
|
2263
2263
|
return { content: [{ type: "text", text: msg }] };
|
|
2264
2264
|
}
|
|
2265
|
-
|
|
2265
|
+
|
|
2266
2266
|
// Handle regex syntax errors
|
|
2267
2267
|
if (execErr.stderr && execErr.stderr.includes('Invalid')) {
|
|
2268
2268
|
throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
|
|
2269
2269
|
}
|
|
2270
|
-
|
|
2270
|
+
|
|
2271
2271
|
throw execErr;
|
|
2272
2272
|
}
|
|
2273
2273
|
} catch (error) {
|
|
@@ -3093,18 +3093,18 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
|
|
|
3093
3093
|
*/
|
|
3094
3094
|
async function handleMemoryGet({ type }) {
|
|
3095
3095
|
const start = Date.now();
|
|
3096
|
-
|
|
3096
|
+
|
|
3097
3097
|
// Early return if memory engine not ready
|
|
3098
3098
|
if (!memoryEngineReady) {
|
|
3099
3099
|
return {
|
|
3100
|
-
content: [{
|
|
3101
|
-
type: "text",
|
|
3102
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3100
|
+
content: [{
|
|
3101
|
+
type: "text",
|
|
3102
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3103
3103
|
}],
|
|
3104
3104
|
isError: true
|
|
3105
3105
|
};
|
|
3106
3106
|
}
|
|
3107
|
-
|
|
3107
|
+
|
|
3108
3108
|
try {
|
|
3109
3109
|
const engine = await getMemoryEngine();
|
|
3110
3110
|
let output = '';
|
|
@@ -3138,12 +3138,12 @@ async function handleMemoryGet({ type }) {
|
|
|
3138
3138
|
} else if (type === 'reindex') {
|
|
3139
3139
|
output = `🔄 Re-indexing Codebase\n\n`;
|
|
3140
3140
|
output += `Starting re-index in background...\n`;
|
|
3141
|
-
|
|
3141
|
+
|
|
3142
3142
|
// Trigger re-index in background
|
|
3143
3143
|
engine.performInitialScan().catch(err => {
|
|
3144
3144
|
console.error('[Reindex] Error:', err.message);
|
|
3145
3145
|
});
|
|
3146
|
-
|
|
3146
|
+
|
|
3147
3147
|
output += `✅ Re-index started. Check stats in a few seconds.\n`;
|
|
3148
3148
|
output += `Use 'memory_get stats' to check progress.`;
|
|
3149
3149
|
} else if (type === 'intelligence') {
|
|
@@ -3191,18 +3191,18 @@ async function handleMemoryGet({ type }) {
|
|
|
3191
3191
|
*/
|
|
3192
3192
|
async function handleDetectPatterns() {
|
|
3193
3193
|
const start = Date.now();
|
|
3194
|
-
|
|
3194
|
+
|
|
3195
3195
|
// Early return if memory engine not ready
|
|
3196
3196
|
if (!memoryEngineReady) {
|
|
3197
3197
|
return {
|
|
3198
|
-
content: [{
|
|
3199
|
-
type: "text",
|
|
3200
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3198
|
+
content: [{
|
|
3199
|
+
type: "text",
|
|
3200
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3201
3201
|
}],
|
|
3202
3202
|
isError: true
|
|
3203
3203
|
};
|
|
3204
3204
|
}
|
|
3205
|
-
|
|
3205
|
+
|
|
3206
3206
|
try {
|
|
3207
3207
|
const engine = await getMemoryEngine();
|
|
3208
3208
|
const patterns = await engine.detectPatterns();
|
|
@@ -3250,18 +3250,18 @@ async function handleDetectPatterns() {
|
|
|
3250
3250
|
*/
|
|
3251
3251
|
async function handleGetSuggestions({ currentFile, currentLine }) {
|
|
3252
3252
|
const start = Date.now();
|
|
3253
|
-
|
|
3253
|
+
|
|
3254
3254
|
// Early return if memory engine not ready
|
|
3255
3255
|
if (!memoryEngineReady) {
|
|
3256
3256
|
return {
|
|
3257
|
-
content: [{
|
|
3258
|
-
type: "text",
|
|
3259
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3257
|
+
content: [{
|
|
3258
|
+
type: "text",
|
|
3259
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3260
3260
|
}],
|
|
3261
3261
|
isError: true
|
|
3262
3262
|
};
|
|
3263
3263
|
}
|
|
3264
|
-
|
|
3264
|
+
|
|
3265
3265
|
try {
|
|
3266
3266
|
const engine = await getMemoryEngine();
|
|
3267
3267
|
const suggestions = await engine.getSuggestions({
|
|
@@ -3315,18 +3315,18 @@ async function handleGetSuggestions({ currentFile, currentLine }) {
|
|
|
3315
3315
|
*/
|
|
3316
3316
|
async function handleSelectStrategy({ instruction, files = [] }) {
|
|
3317
3317
|
const start = Date.now();
|
|
3318
|
-
|
|
3318
|
+
|
|
3319
3319
|
// Early return if memory engine not ready
|
|
3320
3320
|
if (!memoryEngineReady) {
|
|
3321
3321
|
return {
|
|
3322
|
-
content: [{
|
|
3323
|
-
type: "text",
|
|
3324
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3322
|
+
content: [{
|
|
3323
|
+
type: "text",
|
|
3324
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3325
3325
|
}],
|
|
3326
3326
|
isError: true
|
|
3327
3327
|
};
|
|
3328
3328
|
}
|
|
3329
|
-
|
|
3329
|
+
|
|
3330
3330
|
try {
|
|
3331
3331
|
const engine = await getMemoryEngine();
|
|
3332
3332
|
const result = await engine.selectStrategy(instruction, { files });
|
|
@@ -3372,11 +3372,11 @@ async function handleSelectStrategy({ instruction, files = [] }) {
|
|
|
3372
3372
|
*/
|
|
3373
3373
|
async function handleHealthCheck() {
|
|
3374
3374
|
const start = Date.now();
|
|
3375
|
-
|
|
3375
|
+
|
|
3376
3376
|
try {
|
|
3377
3377
|
const memoryStats = memoryEngine ? await memoryEngine.getStats() : null;
|
|
3378
3378
|
const watcherStats = memoryEngine?.watcher ? memoryEngine.watcher.getStats() : null;
|
|
3379
|
-
|
|
3379
|
+
|
|
3380
3380
|
const health = {
|
|
3381
3381
|
status: 'healthy',
|
|
3382
3382
|
timestamp: Date.now(),
|
|
@@ -3399,20 +3399,20 @@ async function handleHealthCheck() {
|
|
|
3399
3399
|
},
|
|
3400
3400
|
latencyMs: Date.now() - start
|
|
3401
3401
|
};
|
|
3402
|
-
|
|
3402
|
+
|
|
3403
3403
|
reportAudit({
|
|
3404
3404
|
tool: 'health_check',
|
|
3405
3405
|
status: 'success',
|
|
3406
3406
|
latency_ms: health.latencyMs
|
|
3407
3407
|
});
|
|
3408
|
-
|
|
3408
|
+
|
|
3409
3409
|
return {
|
|
3410
3410
|
content: [{
|
|
3411
3411
|
type: 'text',
|
|
3412
3412
|
text: JSON.stringify(health, null, 2)
|
|
3413
3413
|
}]
|
|
3414
3414
|
};
|
|
3415
|
-
|
|
3415
|
+
|
|
3416
3416
|
} catch (error) {
|
|
3417
3417
|
reportAudit({
|
|
3418
3418
|
tool: 'health_check',
|
|
@@ -3420,7 +3420,7 @@ async function handleHealthCheck() {
|
|
|
3420
3420
|
error_message: error.message,
|
|
3421
3421
|
latency_ms: Date.now() - start
|
|
3422
3422
|
});
|
|
3423
|
-
|
|
3423
|
+
|
|
3424
3424
|
return {
|
|
3425
3425
|
content: [{
|
|
3426
3426
|
type: 'text',
|
|
@@ -3442,30 +3442,26 @@ async function handleHealthCheck() {
|
|
|
3442
3442
|
// Acquire lock before starting - prevents multiple instances
|
|
3443
3443
|
const lockAcquired = await acquireLock();
|
|
3444
3444
|
if (!lockAcquired) {
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3445
|
+
console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
|
|
3446
|
+
console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
|
|
3447
|
+
process.exit(1);
|
|
3448
3448
|
}
|
|
3449
3449
|
|
|
3450
3450
|
const transport = new StdioServerTransport();
|
|
3451
3451
|
|
|
3452
|
-
//
|
|
3453
|
-
//
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
if (err.code === 'EPIPE' || err.code === 'ECONNRESET') {
|
|
3461
|
-
console.error('[MCP] Client disconnected (broken pipe)');
|
|
3462
|
-
gracefulShutdown('stdin.error');
|
|
3463
|
-
}
|
|
3464
|
-
});
|
|
3465
|
-
|
|
3452
|
+
// NOTE: Do NOT add process.stdin.on('end') handler here.
|
|
3453
|
+
// VSCode and some MCP clients send ephemeral EOF / half-close signals during
|
|
3454
|
+
// the initialize handshake, which would cause premature gracefulShutdown
|
|
3455
|
+
// before the server has a chance to respond, resulting in the
|
|
3456
|
+
// "Error: calling 'initialize': EOF" error.
|
|
3457
|
+
//
|
|
3458
|
+
// The MCP SDK's StdioServerTransport already manages transport lifecycle.
|
|
3459
|
+
// Only handle unrecoverable stdout errors.
|
|
3466
3460
|
process.stdout.on('error', (err) => {
|
|
3467
|
-
|
|
3468
|
-
|
|
3461
|
+
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
|
|
3462
|
+
console.error('[MCP] stdout closed, shutting down:', err.code);
|
|
3463
|
+
gracefulShutdown('stdout.error');
|
|
3464
|
+
}
|
|
3469
3465
|
});
|
|
3470
3466
|
|
|
3471
3467
|
// Pre-initialize memory engine in background
|
|
@@ -3476,35 +3472,35 @@ backgroundInitializeMemoryEngine().catch(err => {
|
|
|
3476
3472
|
|
|
3477
3473
|
// Graceful shutdown handler
|
|
3478
3474
|
async function gracefulShutdown(signal) {
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3475
|
+
console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
|
|
3476
|
+
|
|
3477
|
+
const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
|
|
3478
|
+
|
|
3479
|
+
try {
|
|
3480
|
+
await Promise.race([
|
|
3481
|
+
(async () => {
|
|
3482
|
+
// Stop memory engine
|
|
3483
|
+
if (memoryEngine && memoryEngineReady) {
|
|
3484
|
+
await memoryEngine.cleanup();
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// Flush audit queue
|
|
3488
|
+
const auditQ = getAuditQueue();
|
|
3489
|
+
await auditQ.destroy();
|
|
3490
|
+
|
|
3491
|
+
// Release lock
|
|
3492
|
+
await releaseLock();
|
|
3493
|
+
})(),
|
|
3494
|
+
new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT))
|
|
3495
|
+
]);
|
|
3496
|
+
|
|
3497
|
+
console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
|
|
3498
|
+
} catch (error) {
|
|
3499
|
+
console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// Always exit (even if cleanup fails) to prevent zombie processes
|
|
3503
|
+
process.exit(0);
|
|
3508
3504
|
}
|
|
3509
3505
|
|
|
3510
3506
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
@@ -3512,13 +3508,13 @@ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
|
3512
3508
|
|
|
3513
3509
|
// Protect against zombie processes - handle unexpected errors
|
|
3514
3510
|
process.on('uncaughtException', (err) => {
|
|
3515
|
-
|
|
3516
|
-
|
|
3511
|
+
console.error('[MCP] Uncaught exception:', err);
|
|
3512
|
+
gracefulShutdown('uncaughtException');
|
|
3517
3513
|
});
|
|
3518
3514
|
|
|
3519
3515
|
process.on('unhandledRejection', (reason, promise) => {
|
|
3520
|
-
|
|
3521
|
-
|
|
3516
|
+
console.error('[MCP] Unhandled rejection:', reason);
|
|
3517
|
+
gracefulShutdown('unhandledRejection');
|
|
3522
3518
|
});
|
|
3523
3519
|
|
|
3524
3520
|
await server.connect(transport);
|