@mrxkun/mcfast-mcp 4.1.12 → 4.1.14
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 +247 -239
- package/src/memory/memory-engine.js +119 -117
package/src/index.js
CHANGED
|
@@ -25,15 +25,15 @@ 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
|
-
//
|
|
33
|
-
const originalConsoleError = console.error;
|
|
34
|
-
console.error = function(...args) {
|
|
35
|
-
|
|
36
|
-
};
|
|
32
|
+
// Keep console.error enabled for debugging and MCP logging
|
|
33
|
+
// const originalConsoleError = console.error;
|
|
34
|
+
// console.error = function(...args) {
|
|
35
|
+
// // Suppressed in MCP mode
|
|
36
|
+
// };
|
|
37
37
|
|
|
38
38
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
39
39
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -86,70 +86,82 @@ 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
|
+
// Robust stale check: stale if > 5 mins OR timestamp is in the future (prevent stuck lock)
|
|
116
|
+
// OR check if PID is actually running
|
|
117
|
+
let isStale = lockAge > 5 * 60 * 1000 || lockAge < -60 * 1000;
|
|
118
|
+
|
|
119
|
+
if (!isStale && pid) {
|
|
120
|
+
try {
|
|
121
|
+
process.kill(parseInt(pid), 0);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// PID not running
|
|
124
|
+
isStale = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (isStale) {
|
|
129
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Stale or invalid lock detected, removing...`);
|
|
130
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
131
|
+
return acquireLock(); // Retry
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.error(`${colors.red}[Lock]${colors.reset} Another instance is already running (PID: ${pid})`);
|
|
135
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} To force start, delete: ${LOCK_FILE_PATH}`);
|
|
136
|
+
return false;
|
|
137
|
+
} catch (readError) {
|
|
138
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to read lock file: ${readError.message}`);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
|
|
127
143
|
return false;
|
|
128
|
-
}
|
|
129
144
|
}
|
|
130
|
-
console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
async function releaseLock() {
|
|
136
|
-
|
|
148
|
+
if (lockFileHandle) {
|
|
149
|
+
try {
|
|
150
|
+
await lockFileHandle.close();
|
|
151
|
+
lockFileHandle = null;
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Ignore close errors
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
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
|
-
}
|
|
158
|
+
await fs.unlink(LOCK_FILE_PATH);
|
|
159
|
+
console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error.code !== 'ENOENT') {
|
|
162
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
// ============================================================================
|
|
@@ -171,36 +183,36 @@ let memoryEngineReady = false;
|
|
|
171
183
|
*/
|
|
172
184
|
async function backgroundInitializeMemoryEngine() {
|
|
173
185
|
if (memoryEngineInitPromise) return memoryEngineInitPromise;
|
|
174
|
-
|
|
186
|
+
|
|
175
187
|
memoryEngineInitPromise = (async () => {
|
|
176
188
|
const maxRetries = 2;
|
|
177
189
|
let lastError = null;
|
|
178
|
-
|
|
190
|
+
|
|
179
191
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
180
192
|
try {
|
|
181
193
|
memoryEngine = new MemoryEngine({
|
|
182
194
|
apiKey: TOKEN,
|
|
183
195
|
enableSync: true
|
|
184
196
|
});
|
|
185
|
-
|
|
197
|
+
|
|
186
198
|
// Use timeout for initialization
|
|
187
199
|
const initTimeout = new Promise((_, reject) => {
|
|
188
200
|
setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
|
|
189
201
|
});
|
|
190
|
-
|
|
202
|
+
|
|
191
203
|
await Promise.race([
|
|
192
204
|
memoryEngine.initialize(process.cwd()),
|
|
193
205
|
initTimeout
|
|
194
206
|
]);
|
|
195
|
-
|
|
207
|
+
|
|
196
208
|
memoryEngineReady = true;
|
|
197
209
|
console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
|
|
198
210
|
return memoryEngine;
|
|
199
|
-
|
|
211
|
+
|
|
200
212
|
} catch (error) {
|
|
201
213
|
lastError = error;
|
|
202
214
|
console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
203
|
-
|
|
215
|
+
|
|
204
216
|
if (attempt < maxRetries) {
|
|
205
217
|
// Exponential backoff before retry
|
|
206
218
|
const backoffMs = 1000 * Math.pow(2, attempt - 1);
|
|
@@ -209,7 +221,7 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
209
221
|
}
|
|
210
222
|
}
|
|
211
223
|
}
|
|
212
|
-
|
|
224
|
+
|
|
213
225
|
// All retries failed - graceful degradation
|
|
214
226
|
console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
|
|
215
227
|
console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
|
|
@@ -217,7 +229,7 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
217
229
|
memoryEngineReady = false;
|
|
218
230
|
return null;
|
|
219
231
|
})();
|
|
220
|
-
|
|
232
|
+
|
|
221
233
|
return memoryEngineInitPromise;
|
|
222
234
|
}
|
|
223
235
|
|
|
@@ -228,7 +240,7 @@ async function getMemoryEngine() {
|
|
|
228
240
|
if (memoryEngine && memoryEngineReady) {
|
|
229
241
|
return memoryEngine;
|
|
230
242
|
}
|
|
231
|
-
|
|
243
|
+
|
|
232
244
|
// Wait for initialization with timeout
|
|
233
245
|
if (memoryEngineInitPromise) {
|
|
234
246
|
try {
|
|
@@ -240,12 +252,12 @@ async function getMemoryEngine() {
|
|
|
240
252
|
console.error(`${colors.yellow}[Memory]${colors.reset} Waiting for engine: ${error.message}`);
|
|
241
253
|
}
|
|
242
254
|
}
|
|
243
|
-
|
|
255
|
+
|
|
244
256
|
// If still not ready, try to initialize now
|
|
245
257
|
if (!memoryEngine) {
|
|
246
258
|
await backgroundInitializeMemoryEngine();
|
|
247
259
|
}
|
|
248
|
-
|
|
260
|
+
|
|
249
261
|
return memoryEngine;
|
|
250
262
|
}
|
|
251
263
|
|
|
@@ -255,18 +267,18 @@ async function getMemoryEngine() {
|
|
|
255
267
|
async function searchMemoryContext(instruction, maxResults = 5) {
|
|
256
268
|
try {
|
|
257
269
|
const engine = await getMemoryEngine();
|
|
258
|
-
|
|
270
|
+
|
|
259
271
|
// Use intelligent search for best context
|
|
260
|
-
const searchResult = await engine.intelligentSearch(instruction, {
|
|
261
|
-
limit: maxResults
|
|
272
|
+
const searchResult = await engine.intelligentSearch(instruction, {
|
|
273
|
+
limit: maxResults
|
|
262
274
|
});
|
|
263
|
-
|
|
275
|
+
|
|
264
276
|
if (!searchResult.results || searchResult.results.length === 0) {
|
|
265
277
|
return '';
|
|
266
278
|
}
|
|
267
279
|
|
|
268
280
|
let context = '';
|
|
269
|
-
|
|
281
|
+
|
|
270
282
|
// Add code context from search results
|
|
271
283
|
context += '\n\n--- MEMORY CONTEXT (Relevant Code) ---\n';
|
|
272
284
|
searchResult.results.forEach((result, idx) => {
|
|
@@ -277,7 +289,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
|
|
|
277
289
|
context += `${result.code.substring(0, 300)}${result.code.length > 300 ? '...' : ''}\n`;
|
|
278
290
|
}
|
|
279
291
|
});
|
|
280
|
-
|
|
292
|
+
|
|
281
293
|
// Add facts if available
|
|
282
294
|
const facts = await engine.searchFacts(instruction, 5);
|
|
283
295
|
if (facts && facts.length > 0) {
|
|
@@ -286,7 +298,7 @@ async function searchMemoryContext(instruction, maxResults = 5) {
|
|
|
286
298
|
context += `• ${fact.type}: ${fact.name} (${fact.file_id})\n`;
|
|
287
299
|
});
|
|
288
300
|
}
|
|
289
|
-
|
|
301
|
+
|
|
290
302
|
return context;
|
|
291
303
|
} catch (error) {
|
|
292
304
|
console.error(`${colors.yellow}[Memory]${colors.reset} Context search failed: ${error.message}`);
|
|
@@ -304,44 +316,44 @@ async function autoTriggerIntelligence(filePath, instruction) {
|
|
|
304
316
|
if (process.env.MCFAST_INTELLIGENCE_AUTO !== 'true') {
|
|
305
317
|
return;
|
|
306
318
|
}
|
|
307
|
-
|
|
319
|
+
|
|
308
320
|
const engine = await getMemoryEngine();
|
|
309
321
|
const cache = getIntelligenceCache();
|
|
310
|
-
|
|
322
|
+
|
|
311
323
|
// 1. Detect patterns (cached for 5 minutes)
|
|
312
324
|
const patternCacheKey = { file: filePath, type: 'patterns' };
|
|
313
325
|
let patterns = cache.get('patterns', patternCacheKey);
|
|
314
|
-
|
|
326
|
+
|
|
315
327
|
if (!patterns) {
|
|
316
328
|
patterns = await engine.detectPatterns();
|
|
317
329
|
if (patterns && patterns.length > 0) {
|
|
318
330
|
cache.set('patterns', patternCacheKey, patterns);
|
|
319
331
|
console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 Detected ${patterns.length} patterns`);
|
|
320
|
-
|
|
332
|
+
|
|
321
333
|
// Log high-confidence patterns
|
|
322
334
|
patterns.filter(p => p.confidence > 0.8).forEach(p => {
|
|
323
335
|
console.error(`${colors.yellow}[Pattern]${colors.reset} ${p.type}: ${p.message}`);
|
|
324
336
|
});
|
|
325
337
|
}
|
|
326
338
|
}
|
|
327
|
-
|
|
339
|
+
|
|
328
340
|
// 2. Get suggestions (cached for 5 minutes)
|
|
329
341
|
const suggestionCacheKey = { file: filePath, type: 'suggestions' };
|
|
330
342
|
let suggestions = cache.get('suggestions', suggestionCacheKey);
|
|
331
|
-
|
|
343
|
+
|
|
332
344
|
if (!suggestions) {
|
|
333
345
|
suggestions = await engine.getSuggestions({ currentFile: filePath });
|
|
334
346
|
if (suggestions && suggestions.length > 0) {
|
|
335
347
|
cache.set('suggestions', suggestionCacheKey, suggestions);
|
|
336
348
|
console.error(`${colors.cyan}[Intelligence]${colors.reset} 💡 ${suggestions.length} suggestions available`);
|
|
337
|
-
|
|
349
|
+
|
|
338
350
|
// Show top suggestions
|
|
339
351
|
suggestions.slice(0, 3).forEach(s => {
|
|
340
352
|
console.error(`${colors.yellow}[Suggestion]${colors.reset} ${s.type}: ${s.message}`);
|
|
341
353
|
});
|
|
342
354
|
}
|
|
343
355
|
}
|
|
344
|
-
|
|
356
|
+
|
|
345
357
|
} catch (error) {
|
|
346
358
|
// Silent fail - intelligence should not break the tool
|
|
347
359
|
if (VERBOSE) {
|
|
@@ -357,7 +369,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
357
369
|
try {
|
|
358
370
|
const engine = await getMemoryEngine();
|
|
359
371
|
const diffSize = Object.keys(files).reduce((total, fp) => total + (files[fp]?.length || 0), 0);
|
|
360
|
-
|
|
372
|
+
|
|
361
373
|
engine.logEditToMemory({
|
|
362
374
|
instruction: instruction.substring(0, 500),
|
|
363
375
|
files: Object.keys(files),
|
|
@@ -367,7 +379,7 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
367
379
|
durationMs,
|
|
368
380
|
errorMessage
|
|
369
381
|
});
|
|
370
|
-
|
|
382
|
+
|
|
371
383
|
console.error(`${colors.cyan}[Memory]${colors.reset} Edit logged to history`);
|
|
372
384
|
} catch (error) {
|
|
373
385
|
console.error(`${colors.yellow}[Memory]${colors.reset} Log failed: ${error.message}`);
|
|
@@ -380,13 +392,13 @@ async function logEditToMemory({ instruction, files, strategy, success, errorMes
|
|
|
380
392
|
async function analyzeRenameImpact(symbolName, currentFile = null) {
|
|
381
393
|
try {
|
|
382
394
|
const engine = await getMemoryEngine();
|
|
383
|
-
|
|
395
|
+
|
|
384
396
|
// Search for symbol references across codebase
|
|
385
|
-
const searchResult = await engine.intelligentSearch(symbolName, {
|
|
397
|
+
const searchResult = await engine.intelligentSearch(symbolName, {
|
|
386
398
|
limit: 20,
|
|
387
399
|
currentFile
|
|
388
400
|
});
|
|
389
|
-
|
|
401
|
+
|
|
390
402
|
const impactedFiles = new Set();
|
|
391
403
|
const references = [];
|
|
392
404
|
|
|
@@ -1056,9 +1068,9 @@ async function reportAudit(data) {
|
|
|
1056
1068
|
flushInterval: 5000,
|
|
1057
1069
|
verbose: VERBOSE
|
|
1058
1070
|
});
|
|
1059
|
-
|
|
1071
|
+
|
|
1060
1072
|
queue.add(auditData);
|
|
1061
|
-
|
|
1073
|
+
|
|
1062
1074
|
if (VERBOSE) {
|
|
1063
1075
|
const stats = queue.getStats();
|
|
1064
1076
|
console.error(`${colors.dim}[Audit] Queued for batching. Queue size: ${stats.queued}${colors.reset}`);
|
|
@@ -1097,9 +1109,9 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1097
1109
|
if (!validation.valid) {
|
|
1098
1110
|
const latency = Date.now() - start;
|
|
1099
1111
|
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: \\\\, \\+, \\*`
|
|
1112
|
+
content: [{
|
|
1113
|
+
type: "text",
|
|
1114
|
+
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
1115
|
}],
|
|
1104
1116
|
isError: true
|
|
1105
1117
|
};
|
|
@@ -1135,12 +1147,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1135
1147
|
|
|
1136
1148
|
try {
|
|
1137
1149
|
const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 }); // 10MB buffer
|
|
1138
|
-
|
|
1150
|
+
|
|
1139
1151
|
// Check for grep regex errors in stderr
|
|
1140
1152
|
if (stderr && stderr.includes('Invalid regular expression')) {
|
|
1141
1153
|
throw new Error(`grep: ${stderr}`);
|
|
1142
1154
|
}
|
|
1143
|
-
|
|
1155
|
+
|
|
1144
1156
|
const results = stdout.trim().split('\n').filter(Boolean);
|
|
1145
1157
|
|
|
1146
1158
|
const latency = Date.now() - start;
|
|
@@ -1205,12 +1217,12 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1205
1217
|
content: [{ type: "text", text: `🔍 Regex Search: No matches found for "${query}" in ${searchPath} (${latency}ms)` }]
|
|
1206
1218
|
};
|
|
1207
1219
|
}
|
|
1208
|
-
|
|
1220
|
+
|
|
1209
1221
|
// Handle regex syntax errors
|
|
1210
1222
|
if (execErr.stderr && execErr.stderr.includes('Invalid')) {
|
|
1211
1223
|
throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
|
|
1212
1224
|
}
|
|
1213
|
-
|
|
1225
|
+
|
|
1214
1226
|
throw execErr;
|
|
1215
1227
|
}
|
|
1216
1228
|
} catch (error) {
|
|
@@ -1237,14 +1249,14 @@ async function handleRegexSearch({ query, path: searchPath, caseSensitive = fals
|
|
|
1237
1249
|
*/
|
|
1238
1250
|
function validateRegex(pattern) {
|
|
1239
1251
|
const warnings = [];
|
|
1240
|
-
|
|
1252
|
+
|
|
1241
1253
|
try {
|
|
1242
1254
|
// Test if it's valid JavaScript regex
|
|
1243
1255
|
new RegExp(pattern);
|
|
1244
1256
|
} catch (e) {
|
|
1245
1257
|
return { valid: false, error: e.message, warnings };
|
|
1246
1258
|
}
|
|
1247
|
-
|
|
1259
|
+
|
|
1248
1260
|
// Check for patterns not supported by basic grep (PCRE-only features)
|
|
1249
1261
|
const unsupportedPatterns = [
|
|
1250
1262
|
{ regex: /\(\?<[=!]/, name: 'Lookbehind (?<=...) or negative lookbehind (?<!...)' },
|
|
@@ -1256,18 +1268,18 @@ function validateRegex(pattern) {
|
|
|
1256
1268
|
{ regex: /\(\?\d*\)/, name: 'Subroutine calls (?1)' },
|
|
1257
1269
|
{ regex: /\(\?&\w+\)/, name: 'Subroutine calls (?&name)' }
|
|
1258
1270
|
];
|
|
1259
|
-
|
|
1271
|
+
|
|
1260
1272
|
for (const { regex, name } of unsupportedPatterns) {
|
|
1261
1273
|
if (regex.test(pattern)) {
|
|
1262
1274
|
warnings.push(`Pattern uses ${name} which may not be supported by grep. Results may be incomplete.`);
|
|
1263
1275
|
}
|
|
1264
1276
|
}
|
|
1265
|
-
|
|
1277
|
+
|
|
1266
1278
|
// Check for potentially dangerous patterns (catastrophic backtracking)
|
|
1267
1279
|
if (/\(\w+\+\+?\)|\(\w+\*\*?\)\*\*?/.test(pattern)) {
|
|
1268
1280
|
warnings.push('Pattern may cause performance issues (nested quantifiers)');
|
|
1269
1281
|
}
|
|
1270
|
-
|
|
1282
|
+
|
|
1271
1283
|
return { valid: true, warnings };
|
|
1272
1284
|
}
|
|
1273
1285
|
|
|
@@ -1297,7 +1309,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1297
1309
|
// Convert literal query to glob pattern for file search
|
|
1298
1310
|
pattern = `**/*${query}*`;
|
|
1299
1311
|
}
|
|
1300
|
-
|
|
1312
|
+
|
|
1301
1313
|
// Use fast-glob to find files
|
|
1302
1314
|
const files = await fg([pattern], {
|
|
1303
1315
|
cwd,
|
|
@@ -1309,7 +1321,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1309
1321
|
|
|
1310
1322
|
// Also search file contents if it's not a file pattern
|
|
1311
1323
|
const contentMatches = [];
|
|
1312
|
-
|
|
1324
|
+
|
|
1313
1325
|
// For simple queries, also search in common code files
|
|
1314
1326
|
if (!isRegex && query.length > 1) {
|
|
1315
1327
|
const codeFiles = await fg(['**/*.{js,ts,jsx,tsx,py,go,rs,java,rb,php}'], {
|
|
@@ -1317,7 +1329,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1317
1329
|
ignore: ['node_modules/**', '.git/**'],
|
|
1318
1330
|
absolute: false
|
|
1319
1331
|
});
|
|
1320
|
-
|
|
1332
|
+
|
|
1321
1333
|
// Search in first 50 files to avoid performance issues
|
|
1322
1334
|
for (const file of codeFiles.slice(0, 50)) {
|
|
1323
1335
|
try {
|
|
@@ -1325,7 +1337,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1325
1337
|
if (content.includes(query)) {
|
|
1326
1338
|
const lines = content.split('\n');
|
|
1327
1339
|
const matches = [];
|
|
1328
|
-
|
|
1340
|
+
|
|
1329
1341
|
lines.forEach((line, idx) => {
|
|
1330
1342
|
if (line.includes(query)) {
|
|
1331
1343
|
matches.push({
|
|
@@ -1335,7 +1347,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1335
1347
|
});
|
|
1336
1348
|
}
|
|
1337
1349
|
});
|
|
1338
|
-
|
|
1350
|
+
|
|
1339
1351
|
if (matches.length > 0) {
|
|
1340
1352
|
contentMatches.push({
|
|
1341
1353
|
file,
|
|
@@ -1350,7 +1362,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1350
1362
|
}
|
|
1351
1363
|
|
|
1352
1364
|
const latency = Date.now() - start;
|
|
1353
|
-
|
|
1365
|
+
|
|
1354
1366
|
// Format output
|
|
1355
1367
|
let output = `${colors.bold}Filesystem Search Results${colors.reset}\n\n`;
|
|
1356
1368
|
output += `Query: "${query}"\n`;
|
|
@@ -1396,7 +1408,7 @@ async function handleSearchFilesystem({ query, path: searchPath, isRegex = false
|
|
|
1396
1408
|
};
|
|
1397
1409
|
} catch (error) {
|
|
1398
1410
|
const latency = Date.now() - start;
|
|
1399
|
-
|
|
1411
|
+
|
|
1400
1412
|
reportAudit({
|
|
1401
1413
|
tool: 'search_filesystem',
|
|
1402
1414
|
instruction: `Search: ${query}`,
|
|
@@ -1456,7 +1468,7 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
|
|
|
1456
1468
|
*/
|
|
1457
1469
|
async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
1458
1470
|
const editStartTime = Date.now();
|
|
1459
|
-
|
|
1471
|
+
|
|
1460
1472
|
// Validate required parameters
|
|
1461
1473
|
if (!instruction) {
|
|
1462
1474
|
return {
|
|
@@ -1464,16 +1476,16 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1464
1476
|
isError: true
|
|
1465
1477
|
};
|
|
1466
1478
|
}
|
|
1467
|
-
|
|
1479
|
+
|
|
1468
1480
|
if (!files || Object.keys(files).length === 0) {
|
|
1469
1481
|
return {
|
|
1470
1482
|
content: [{ type: "text", text: "❌ Error: 'files' parameter is required and must contain at least one file" }],
|
|
1471
1483
|
isError: true
|
|
1472
1484
|
};
|
|
1473
1485
|
}
|
|
1474
|
-
|
|
1486
|
+
|
|
1475
1487
|
const firstFile = Object.keys(files)[0];
|
|
1476
|
-
|
|
1488
|
+
|
|
1477
1489
|
// 1. Retrieve memory context for enhanced understanding
|
|
1478
1490
|
let memoryContext = '';
|
|
1479
1491
|
try {
|
|
@@ -1484,7 +1496,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1484
1496
|
} catch (error) {
|
|
1485
1497
|
console.error(`${colors.yellow}[Memory]${colors.reset} Context retrieval failed: ${error.message}`);
|
|
1486
1498
|
}
|
|
1487
|
-
|
|
1499
|
+
|
|
1488
1500
|
// 2. Analyze impact for rename operations
|
|
1489
1501
|
let impactAnalysis = null;
|
|
1490
1502
|
const renamePattern = instruction.match(/(?:rename|change)\s+(?:function|class|const|let|var|method)\s+(\w+)\s+to\s+(\w+)/i);
|
|
@@ -1499,7 +1511,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1499
1511
|
console.error(`${colors.yellow}[IMPACT]${colors.reset} Analysis failed: ${error.message}`);
|
|
1500
1512
|
}
|
|
1501
1513
|
}
|
|
1502
|
-
|
|
1514
|
+
|
|
1503
1515
|
// Check for multi-file edits first
|
|
1504
1516
|
const multiFileEdit = detectCrossFileEdit(instruction, files);
|
|
1505
1517
|
|
|
@@ -1533,7 +1545,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1533
1545
|
errorMessage: result.message || result.error,
|
|
1534
1546
|
durationMs: Date.now() - editStartTime
|
|
1535
1547
|
});
|
|
1536
|
-
|
|
1548
|
+
|
|
1537
1549
|
return {
|
|
1538
1550
|
content: [{
|
|
1539
1551
|
type: "text",
|
|
@@ -1571,7 +1583,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1571
1583
|
errorMessage: error.message,
|
|
1572
1584
|
durationMs: Date.now() - editStartTime
|
|
1573
1585
|
});
|
|
1574
|
-
|
|
1586
|
+
|
|
1575
1587
|
return {
|
|
1576
1588
|
content: [{
|
|
1577
1589
|
type: "text",
|
|
@@ -1676,7 +1688,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1676
1688
|
errorMessage: error.message,
|
|
1677
1689
|
durationMs: Date.now() - editStartTime
|
|
1678
1690
|
});
|
|
1679
|
-
|
|
1691
|
+
|
|
1680
1692
|
return {
|
|
1681
1693
|
content: [{
|
|
1682
1694
|
type: "text",
|
|
@@ -1807,7 +1819,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1807
1819
|
|
|
1808
1820
|
// Include impact analysis in result if available
|
|
1809
1821
|
let resultText = `✅ AST Refactor Applied Successfully\n\nOperation: ${pattern.type}\nChanges: ${transformResult.count || transformResult.replacements || 0} locations\n\nBackup: ${editResult.backupPath}`;
|
|
1810
|
-
|
|
1822
|
+
|
|
1811
1823
|
if (impactAnalysis && impactAnalysis.suggestion) {
|
|
1812
1824
|
resultText += `\n\n${impactAnalysis.suggestion}`;
|
|
1813
1825
|
if (impactAnalysis.impactedFiles.length > 0) {
|
|
@@ -1838,7 +1850,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1838
1850
|
errorMessage: error.message,
|
|
1839
1851
|
durationMs: Date.now() - editStartTime
|
|
1840
1852
|
});
|
|
1841
|
-
|
|
1853
|
+
|
|
1842
1854
|
return {
|
|
1843
1855
|
content: [{
|
|
1844
1856
|
type: "text",
|
|
@@ -1861,7 +1873,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1861
1873
|
dryRun,
|
|
1862
1874
|
toolName: 'edit'
|
|
1863
1875
|
});
|
|
1864
|
-
|
|
1876
|
+
|
|
1865
1877
|
// Log to memory
|
|
1866
1878
|
await logEditToMemory({
|
|
1867
1879
|
instruction,
|
|
@@ -1871,7 +1883,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1871
1883
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1872
1884
|
durationMs: Date.now() - editStartTime
|
|
1873
1885
|
});
|
|
1874
|
-
|
|
1886
|
+
|
|
1875
1887
|
return result;
|
|
1876
1888
|
}
|
|
1877
1889
|
}
|
|
@@ -1884,7 +1896,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1884
1896
|
dryRun,
|
|
1885
1897
|
toolName: 'edit'
|
|
1886
1898
|
});
|
|
1887
|
-
|
|
1899
|
+
|
|
1888
1900
|
// Log to memory
|
|
1889
1901
|
await logEditToMemory({
|
|
1890
1902
|
instruction,
|
|
@@ -1894,7 +1906,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1894
1906
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1895
1907
|
durationMs: Date.now() - editStartTime
|
|
1896
1908
|
});
|
|
1897
|
-
|
|
1909
|
+
|
|
1898
1910
|
return result;
|
|
1899
1911
|
}
|
|
1900
1912
|
|
|
@@ -1904,14 +1916,14 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1904
1916
|
if (memoryContext) {
|
|
1905
1917
|
enhancedInstruction = `${instruction}\n\n--- CONTEXT FROM CODEBASE ---${memoryContext}`;
|
|
1906
1918
|
}
|
|
1907
|
-
|
|
1919
|
+
|
|
1908
1920
|
const result = await handleApplyFast({
|
|
1909
1921
|
instruction: enhancedInstruction,
|
|
1910
1922
|
files,
|
|
1911
1923
|
dryRun,
|
|
1912
1924
|
toolName: 'edit'
|
|
1913
1925
|
});
|
|
1914
|
-
|
|
1926
|
+
|
|
1915
1927
|
// Log to memory
|
|
1916
1928
|
await logEditToMemory({
|
|
1917
1929
|
instruction,
|
|
@@ -1921,13 +1933,13 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1921
1933
|
errorMessage: result.isError ? result.content?.[0]?.text : null,
|
|
1922
1934
|
durationMs: Date.now() - editStartTime
|
|
1923
1935
|
});
|
|
1924
|
-
|
|
1936
|
+
|
|
1925
1937
|
// Auto-trigger intelligence tools in background (non-blocking)
|
|
1926
1938
|
if (!result.isError) {
|
|
1927
1939
|
const firstFile = Object.keys(files)[0];
|
|
1928
|
-
autoTriggerIntelligence(firstFile, instruction).catch(() => {});
|
|
1940
|
+
autoTriggerIntelligence(firstFile, instruction).catch(() => { });
|
|
1929
1941
|
}
|
|
1930
|
-
|
|
1942
|
+
|
|
1931
1943
|
return result;
|
|
1932
1944
|
}
|
|
1933
1945
|
|
|
@@ -1939,7 +1951,7 @@ async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
|
1939
1951
|
*/
|
|
1940
1952
|
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2, maxResults = 10 }) {
|
|
1941
1953
|
const searchStartTime = Date.now();
|
|
1942
|
-
|
|
1954
|
+
|
|
1943
1955
|
// For regex mode without files, we need to do content-based regex search
|
|
1944
1956
|
// since fast-glob doesn't support full regex patterns
|
|
1945
1957
|
if (regex && !files && mode === 'auto') {
|
|
@@ -1954,9 +1966,9 @@ async function handleSearch({ query, files, path, mode = 'auto', regex = false,
|
|
|
1954
1966
|
if ((detectedMode === 'auto' || detectedMode === 'local') && !regex) {
|
|
1955
1967
|
try {
|
|
1956
1968
|
const engine = await getMemoryEngine();
|
|
1957
|
-
|
|
1969
|
+
|
|
1958
1970
|
// Use intelligent search (UltraHybrid + Smart Routing)
|
|
1959
|
-
const searchResult = await engine.intelligentSearch(query, {
|
|
1971
|
+
const searchResult = await engine.intelligentSearch(query, {
|
|
1960
1972
|
limit: maxResults,
|
|
1961
1973
|
currentFile: files ? Object.keys(files)[0] : null
|
|
1962
1974
|
});
|
|
@@ -2032,7 +2044,7 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
|
|
|
2032
2044
|
if (filePaths && Array.isArray(filePaths) && filePaths.length > 0) {
|
|
2033
2045
|
return await handleBatchRead(filePaths, start_line, end_line, max_lines_per_file);
|
|
2034
2046
|
}
|
|
2035
|
-
|
|
2047
|
+
|
|
2036
2048
|
// Single file mode (backward compatible)
|
|
2037
2049
|
return await handleReadFile({ path: filePath, start_line, end_line });
|
|
2038
2050
|
}
|
|
@@ -2043,50 +2055,50 @@ async function handleRead({ filePath, filePaths, start_line, end_line, max_lines
|
|
|
2043
2055
|
async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_file) {
|
|
2044
2056
|
const start = Date.now();
|
|
2045
2057
|
const MAX_BATCH_SIZE = 50;
|
|
2046
|
-
|
|
2058
|
+
|
|
2047
2059
|
if (filePaths.length > MAX_BATCH_SIZE) {
|
|
2048
2060
|
return {
|
|
2049
2061
|
content: [{ type: "text", text: `❌ Batch read limit exceeded: ${filePaths.length} files (max ${MAX_BATCH_SIZE})` }],
|
|
2050
2062
|
isError: true
|
|
2051
2063
|
};
|
|
2052
2064
|
}
|
|
2053
|
-
|
|
2065
|
+
|
|
2054
2066
|
console.error(`${colors.cyan}[BATCH READ]${colors.reset} ${filePaths.length} files`);
|
|
2055
|
-
|
|
2067
|
+
|
|
2056
2068
|
// Read all files in parallel with Promise.all
|
|
2057
2069
|
const results = await Promise.all(
|
|
2058
2070
|
filePaths.map(async (filePath) => {
|
|
2059
2071
|
try {
|
|
2060
|
-
const result = await handleReadFileInternal({
|
|
2061
|
-
path: filePath,
|
|
2062
|
-
start_line,
|
|
2072
|
+
const result = await handleReadFileInternal({
|
|
2073
|
+
path: filePath,
|
|
2074
|
+
start_line,
|
|
2063
2075
|
end_line,
|
|
2064
|
-
max_lines: max_lines_per_file
|
|
2076
|
+
max_lines: max_lines_per_file
|
|
2065
2077
|
});
|
|
2066
2078
|
return { path: filePath, ...result, success: true };
|
|
2067
2079
|
} catch (error) {
|
|
2068
|
-
return {
|
|
2069
|
-
path: filePath,
|
|
2070
|
-
success: false,
|
|
2080
|
+
return {
|
|
2081
|
+
path: filePath,
|
|
2082
|
+
success: false,
|
|
2071
2083
|
error: error.message,
|
|
2072
2084
|
content: `❌ Error reading ${filePath}: ${error.message}`
|
|
2073
2085
|
};
|
|
2074
2086
|
}
|
|
2075
2087
|
})
|
|
2076
2088
|
);
|
|
2077
|
-
|
|
2089
|
+
|
|
2078
2090
|
// Calculate stats
|
|
2079
2091
|
const successful = results.filter(r => r.success).length;
|
|
2080
2092
|
const failed = results.filter(r => !r.success).length;
|
|
2081
2093
|
const totalLines = results.reduce((sum, r) => sum + (r.lines || 0), 0);
|
|
2082
2094
|
const totalSize = results.reduce((sum, r) => sum + (r.size || 0), 0);
|
|
2083
|
-
|
|
2095
|
+
|
|
2084
2096
|
// Format output
|
|
2085
2097
|
let output = `📚 Batch Read Complete (${filePaths.length} files)\n`;
|
|
2086
2098
|
output += `✅ Successful: ${successful} ❌ Failed: ${failed}\n`;
|
|
2087
2099
|
output += `📊 Total: ${totalLines} lines, ${(totalSize / 1024).toFixed(1)} KB\n`;
|
|
2088
2100
|
output += `⏱️ Latency: ${Date.now() - start}ms\n\n`;
|
|
2089
|
-
|
|
2101
|
+
|
|
2090
2102
|
// Add each file's content
|
|
2091
2103
|
results.forEach((result, index) => {
|
|
2092
2104
|
output += `[${index + 1}/${filePaths.length}] `;
|
|
@@ -2105,7 +2117,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
|
|
|
2105
2117
|
}
|
|
2106
2118
|
output += `\n\n`;
|
|
2107
2119
|
});
|
|
2108
|
-
|
|
2120
|
+
|
|
2109
2121
|
// Report audit for batch operation
|
|
2110
2122
|
reportAudit({
|
|
2111
2123
|
tool: 'read_file',
|
|
@@ -2116,7 +2128,7 @@ async function handleBatchRead(filePaths, start_line, end_line, max_lines_per_fi
|
|
|
2116
2128
|
input_tokens: Math.ceil(filePaths.join(',').length / 4),
|
|
2117
2129
|
output_tokens: Math.ceil(output.length / 4)
|
|
2118
2130
|
});
|
|
2119
|
-
|
|
2131
|
+
|
|
2120
2132
|
return {
|
|
2121
2133
|
content: [{ type: "text", text: output }]
|
|
2122
2134
|
};
|
|
@@ -2134,7 +2146,7 @@ async function handleReadFileInternal({ path: filePath, start_line, end_line, ma
|
|
|
2134
2146
|
}
|
|
2135
2147
|
|
|
2136
2148
|
const STREAM_THRESHOLD = 1024 * 1024; // 1MB
|
|
2137
|
-
|
|
2149
|
+
|
|
2138
2150
|
let startLine = start_line ? parseInt(start_line) : 1;
|
|
2139
2151
|
let endLine = end_line ? parseInt(end_line) : -1;
|
|
2140
2152
|
let outputContent;
|
|
@@ -2262,12 +2274,12 @@ async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensi
|
|
|
2262
2274
|
});
|
|
2263
2275
|
return { content: [{ type: "text", text: msg }] };
|
|
2264
2276
|
}
|
|
2265
|
-
|
|
2277
|
+
|
|
2266
2278
|
// Handle regex syntax errors
|
|
2267
2279
|
if (execErr.stderr && execErr.stderr.includes('Invalid')) {
|
|
2268
2280
|
throw new Error(`Invalid regex syntax: ${execErr.stderr}. Try simplifying your pattern.`);
|
|
2269
2281
|
}
|
|
2270
|
-
|
|
2282
|
+
|
|
2271
2283
|
throw execErr;
|
|
2272
2284
|
}
|
|
2273
2285
|
} catch (error) {
|
|
@@ -3093,18 +3105,18 @@ async function handleMemorySearch({ query, type = 'all', maxResults = 6, minScor
|
|
|
3093
3105
|
*/
|
|
3094
3106
|
async function handleMemoryGet({ type }) {
|
|
3095
3107
|
const start = Date.now();
|
|
3096
|
-
|
|
3108
|
+
|
|
3097
3109
|
// Early return if memory engine not ready
|
|
3098
3110
|
if (!memoryEngineReady) {
|
|
3099
3111
|
return {
|
|
3100
|
-
content: [{
|
|
3101
|
-
type: "text",
|
|
3102
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3112
|
+
content: [{
|
|
3113
|
+
type: "text",
|
|
3114
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3103
3115
|
}],
|
|
3104
3116
|
isError: true
|
|
3105
3117
|
};
|
|
3106
3118
|
}
|
|
3107
|
-
|
|
3119
|
+
|
|
3108
3120
|
try {
|
|
3109
3121
|
const engine = await getMemoryEngine();
|
|
3110
3122
|
let output = '';
|
|
@@ -3138,12 +3150,12 @@ async function handleMemoryGet({ type }) {
|
|
|
3138
3150
|
} else if (type === 'reindex') {
|
|
3139
3151
|
output = `🔄 Re-indexing Codebase\n\n`;
|
|
3140
3152
|
output += `Starting re-index in background...\n`;
|
|
3141
|
-
|
|
3153
|
+
|
|
3142
3154
|
// Trigger re-index in background
|
|
3143
3155
|
engine.performInitialScan().catch(err => {
|
|
3144
3156
|
console.error('[Reindex] Error:', err.message);
|
|
3145
3157
|
});
|
|
3146
|
-
|
|
3158
|
+
|
|
3147
3159
|
output += `✅ Re-index started. Check stats in a few seconds.\n`;
|
|
3148
3160
|
output += `Use 'memory_get stats' to check progress.`;
|
|
3149
3161
|
} else if (type === 'intelligence') {
|
|
@@ -3191,18 +3203,18 @@ async function handleMemoryGet({ type }) {
|
|
|
3191
3203
|
*/
|
|
3192
3204
|
async function handleDetectPatterns() {
|
|
3193
3205
|
const start = Date.now();
|
|
3194
|
-
|
|
3206
|
+
|
|
3195
3207
|
// Early return if memory engine not ready
|
|
3196
3208
|
if (!memoryEngineReady) {
|
|
3197
3209
|
return {
|
|
3198
|
-
content: [{
|
|
3199
|
-
type: "text",
|
|
3200
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3210
|
+
content: [{
|
|
3211
|
+
type: "text",
|
|
3212
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3201
3213
|
}],
|
|
3202
3214
|
isError: true
|
|
3203
3215
|
};
|
|
3204
3216
|
}
|
|
3205
|
-
|
|
3217
|
+
|
|
3206
3218
|
try {
|
|
3207
3219
|
const engine = await getMemoryEngine();
|
|
3208
3220
|
const patterns = await engine.detectPatterns();
|
|
@@ -3250,18 +3262,18 @@ async function handleDetectPatterns() {
|
|
|
3250
3262
|
*/
|
|
3251
3263
|
async function handleGetSuggestions({ currentFile, currentLine }) {
|
|
3252
3264
|
const start = Date.now();
|
|
3253
|
-
|
|
3265
|
+
|
|
3254
3266
|
// Early return if memory engine not ready
|
|
3255
3267
|
if (!memoryEngineReady) {
|
|
3256
3268
|
return {
|
|
3257
|
-
content: [{
|
|
3258
|
-
type: "text",
|
|
3259
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3269
|
+
content: [{
|
|
3270
|
+
type: "text",
|
|
3271
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3260
3272
|
}],
|
|
3261
3273
|
isError: true
|
|
3262
3274
|
};
|
|
3263
3275
|
}
|
|
3264
|
-
|
|
3276
|
+
|
|
3265
3277
|
try {
|
|
3266
3278
|
const engine = await getMemoryEngine();
|
|
3267
3279
|
const suggestions = await engine.getSuggestions({
|
|
@@ -3315,18 +3327,18 @@ async function handleGetSuggestions({ currentFile, currentLine }) {
|
|
|
3315
3327
|
*/
|
|
3316
3328
|
async function handleSelectStrategy({ instruction, files = [] }) {
|
|
3317
3329
|
const start = Date.now();
|
|
3318
|
-
|
|
3330
|
+
|
|
3319
3331
|
// Early return if memory engine not ready
|
|
3320
3332
|
if (!memoryEngineReady) {
|
|
3321
3333
|
return {
|
|
3322
|
-
content: [{
|
|
3323
|
-
type: "text",
|
|
3324
|
-
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3334
|
+
content: [{
|
|
3335
|
+
type: "text",
|
|
3336
|
+
text: `⏳ Memory engine is still initializing. Please try again in a few seconds.`
|
|
3325
3337
|
}],
|
|
3326
3338
|
isError: true
|
|
3327
3339
|
};
|
|
3328
3340
|
}
|
|
3329
|
-
|
|
3341
|
+
|
|
3330
3342
|
try {
|
|
3331
3343
|
const engine = await getMemoryEngine();
|
|
3332
3344
|
const result = await engine.selectStrategy(instruction, { files });
|
|
@@ -3372,11 +3384,11 @@ async function handleSelectStrategy({ instruction, files = [] }) {
|
|
|
3372
3384
|
*/
|
|
3373
3385
|
async function handleHealthCheck() {
|
|
3374
3386
|
const start = Date.now();
|
|
3375
|
-
|
|
3387
|
+
|
|
3376
3388
|
try {
|
|
3377
3389
|
const memoryStats = memoryEngine ? await memoryEngine.getStats() : null;
|
|
3378
3390
|
const watcherStats = memoryEngine?.watcher ? memoryEngine.watcher.getStats() : null;
|
|
3379
|
-
|
|
3391
|
+
|
|
3380
3392
|
const health = {
|
|
3381
3393
|
status: 'healthy',
|
|
3382
3394
|
timestamp: Date.now(),
|
|
@@ -3399,20 +3411,20 @@ async function handleHealthCheck() {
|
|
|
3399
3411
|
},
|
|
3400
3412
|
latencyMs: Date.now() - start
|
|
3401
3413
|
};
|
|
3402
|
-
|
|
3414
|
+
|
|
3403
3415
|
reportAudit({
|
|
3404
3416
|
tool: 'health_check',
|
|
3405
3417
|
status: 'success',
|
|
3406
3418
|
latency_ms: health.latencyMs
|
|
3407
3419
|
});
|
|
3408
|
-
|
|
3420
|
+
|
|
3409
3421
|
return {
|
|
3410
3422
|
content: [{
|
|
3411
3423
|
type: 'text',
|
|
3412
3424
|
text: JSON.stringify(health, null, 2)
|
|
3413
3425
|
}]
|
|
3414
3426
|
};
|
|
3415
|
-
|
|
3427
|
+
|
|
3416
3428
|
} catch (error) {
|
|
3417
3429
|
reportAudit({
|
|
3418
3430
|
tool: 'health_check',
|
|
@@ -3420,7 +3432,7 @@ async function handleHealthCheck() {
|
|
|
3420
3432
|
error_message: error.message,
|
|
3421
3433
|
latency_ms: Date.now() - start
|
|
3422
3434
|
});
|
|
3423
|
-
|
|
3435
|
+
|
|
3424
3436
|
return {
|
|
3425
3437
|
content: [{
|
|
3426
3438
|
type: 'text',
|
|
@@ -3442,30 +3454,26 @@ async function handleHealthCheck() {
|
|
|
3442
3454
|
// Acquire lock before starting - prevents multiple instances
|
|
3443
3455
|
const lockAcquired = await acquireLock();
|
|
3444
3456
|
if (!lockAcquired) {
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3457
|
+
console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
|
|
3458
|
+
console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
|
|
3459
|
+
process.exit(1);
|
|
3448
3460
|
}
|
|
3449
3461
|
|
|
3450
3462
|
const transport = new StdioServerTransport();
|
|
3451
3463
|
|
|
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
|
-
|
|
3464
|
+
// NOTE: Do NOT add process.stdin.on('end') handler here.
|
|
3465
|
+
// VSCode and some MCP clients send ephemeral EOF / half-close signals during
|
|
3466
|
+
// the initialize handshake, which would cause premature gracefulShutdown
|
|
3467
|
+
// before the server has a chance to respond, resulting in the
|
|
3468
|
+
// "Error: calling 'initialize': EOF" error.
|
|
3469
|
+
//
|
|
3470
|
+
// The MCP SDK's StdioServerTransport already manages transport lifecycle.
|
|
3471
|
+
// Only handle unrecoverable stdout errors.
|
|
3466
3472
|
process.stdout.on('error', (err) => {
|
|
3467
|
-
|
|
3468
|
-
|
|
3473
|
+
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
|
|
3474
|
+
console.error('[MCP] stdout closed, shutting down:', err.code);
|
|
3475
|
+
gracefulShutdown('stdout.error');
|
|
3476
|
+
}
|
|
3469
3477
|
});
|
|
3470
3478
|
|
|
3471
3479
|
// Pre-initialize memory engine in background
|
|
@@ -3476,35 +3484,35 @@ backgroundInitializeMemoryEngine().catch(err => {
|
|
|
3476
3484
|
|
|
3477
3485
|
// Graceful shutdown handler
|
|
3478
3486
|
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
|
-
|
|
3487
|
+
console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
|
|
3488
|
+
|
|
3489
|
+
const SHUTDOWN_TIMEOUT = 5000; // 5 seconds per MCP best practices
|
|
3490
|
+
|
|
3491
|
+
try {
|
|
3492
|
+
await Promise.race([
|
|
3493
|
+
(async () => {
|
|
3494
|
+
// Stop memory engine
|
|
3495
|
+
if (memoryEngine && memoryEngineReady) {
|
|
3496
|
+
await memoryEngine.cleanup();
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
// Flush audit queue
|
|
3500
|
+
const auditQ = getAuditQueue();
|
|
3501
|
+
await auditQ.destroy();
|
|
3502
|
+
|
|
3503
|
+
// Release lock
|
|
3504
|
+
await releaseLock();
|
|
3505
|
+
})(),
|
|
3506
|
+
new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT))
|
|
3507
|
+
]);
|
|
3508
|
+
|
|
3509
|
+
console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
|
|
3510
|
+
} catch (error) {
|
|
3511
|
+
console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
// Always exit (even if cleanup fails) to prevent zombie processes
|
|
3515
|
+
process.exit(0);
|
|
3508
3516
|
}
|
|
3509
3517
|
|
|
3510
3518
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
@@ -3512,13 +3520,13 @@ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
|
3512
3520
|
|
|
3513
3521
|
// Protect against zombie processes - handle unexpected errors
|
|
3514
3522
|
process.on('uncaughtException', (err) => {
|
|
3515
|
-
|
|
3516
|
-
|
|
3523
|
+
console.error('[MCP] Uncaught exception:', err);
|
|
3524
|
+
gracefulShutdown('uncaughtException');
|
|
3517
3525
|
});
|
|
3518
3526
|
|
|
3519
3527
|
process.on('unhandledRejection', (reason, promise) => {
|
|
3520
|
-
|
|
3521
|
-
|
|
3528
|
+
console.error('[MCP] Unhandled rejection:', reason);
|
|
3529
|
+
gracefulShutdown('unhandledRejection');
|
|
3522
3530
|
});
|
|
3523
3531
|
|
|
3524
3532
|
await server.connect(transport);
|