@mrxkun/mcfast-mcp 4.1.13 → 4.1.15
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 +28 -10
- package/src/memory/memory-engine.js +119 -117
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.15",
|
|
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
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
// CRITICAL: Suppress ALL console output in MCP mode to prevent JSON parsing errors
|
|
9
9
|
// MCP protocol requires stdout to contain ONLY JSON-RPC messages
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
// ANSI Color Codes for Terminal Output
|
|
12
15
|
const colors = {
|
|
13
16
|
reset: '\x1b[0m',
|
|
14
17
|
bold: '\x1b[1m',
|
|
@@ -29,11 +32,11 @@ console.log = function (...args) {
|
|
|
29
32
|
// Suppressed in MCP mode
|
|
30
33
|
};
|
|
31
34
|
|
|
32
|
-
//
|
|
33
|
-
const originalConsoleError = console.error;
|
|
34
|
-
console.error = function
|
|
35
|
-
|
|
36
|
-
};
|
|
35
|
+
// Keep console.error enabled for debugging and MCP logging
|
|
36
|
+
// const originalConsoleError = console.error;
|
|
37
|
+
// console.error = function(...args) {
|
|
38
|
+
// // Suppressed in MCP mode
|
|
39
|
+
// };
|
|
37
40
|
|
|
38
41
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
39
42
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -81,8 +84,11 @@ const execAsync = promisify(exec);
|
|
|
81
84
|
|
|
82
85
|
// ============================================================================
|
|
83
86
|
// LOCK FILE MECHANISM - Prevent multiple instances running simultaneously
|
|
87
|
+
// Use home directory to avoid permission issues in project root or systems where CWD is /
|
|
84
88
|
// ============================================================================
|
|
85
|
-
const
|
|
89
|
+
const LOCK_DIR = path.join(os.homedir(), '.mcfast', 'locks');
|
|
90
|
+
const projectHash = crypto.createHash('md5').update(process.cwd()).digest('hex');
|
|
91
|
+
const LOCK_FILE_PATH = path.join(LOCK_DIR, `${projectHash}.lock`);
|
|
86
92
|
let lockFileHandle = null;
|
|
87
93
|
|
|
88
94
|
async function acquireLock() {
|
|
@@ -112,9 +118,21 @@ async function acquireLock() {
|
|
|
112
118
|
const [pid, timestamp] = lockContent.trim().split('\n');
|
|
113
119
|
const lockAge = Date.now() - parseInt(timestamp);
|
|
114
120
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// Robust stale check: stale if > 5 mins OR timestamp is in the future (prevent stuck lock)
|
|
122
|
+
// OR check if PID is actually running
|
|
123
|
+
let isStale = lockAge > 5 * 60 * 1000 || lockAge < -60 * 1000;
|
|
124
|
+
|
|
125
|
+
if (!isStale && pid) {
|
|
126
|
+
try {
|
|
127
|
+
process.kill(parseInt(pid), 0);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// PID not running
|
|
130
|
+
isStale = true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (isStale) {
|
|
135
|
+
console.error(`${colors.yellow}[Lock]${colors.reset} Stale or invalid lock detected, removing...`);
|
|
118
136
|
await fs.unlink(LOCK_FILE_PATH);
|
|
119
137
|
return acquireLock(); // Retry
|
|
120
138
|
}
|
|
@@ -42,7 +42,7 @@ export class MemoryEngine {
|
|
|
42
42
|
static instance = null;
|
|
43
43
|
static instancePromise = null;
|
|
44
44
|
static instancePid = null; // Track which process owns the instance
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
/**
|
|
47
47
|
* Get singleton instance of MemoryEngine
|
|
48
48
|
* @param {Object} options - Configuration options
|
|
@@ -50,7 +50,7 @@ export class MemoryEngine {
|
|
|
50
50
|
*/
|
|
51
51
|
static getInstance(options = {}) {
|
|
52
52
|
const currentPid = process.pid;
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
// Check if instance belongs to current process
|
|
55
55
|
if (MemoryEngine.instancePid !== currentPid) {
|
|
56
56
|
// Different process - reset instance
|
|
@@ -58,14 +58,14 @@ export class MemoryEngine {
|
|
|
58
58
|
MemoryEngine.instancePromise = null;
|
|
59
59
|
MemoryEngine.instancePid = null;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
if (!MemoryEngine.instance) {
|
|
63
63
|
MemoryEngine.instance = new MemoryEngine(options);
|
|
64
64
|
MemoryEngine.instancePid = currentPid;
|
|
65
65
|
}
|
|
66
66
|
return MemoryEngine.instance;
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
/**
|
|
70
70
|
* Get or create singleton instance with async initialization
|
|
71
71
|
* @param {string} projectPath - Project path
|
|
@@ -74,7 +74,7 @@ export class MemoryEngine {
|
|
|
74
74
|
*/
|
|
75
75
|
static async getOrCreate(projectPath, options = {}) {
|
|
76
76
|
const currentPid = process.pid;
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// Check if instance belongs to current process
|
|
79
79
|
if (MemoryEngine.instancePid !== currentPid) {
|
|
80
80
|
// Different process - reset instance
|
|
@@ -82,47 +82,47 @@ export class MemoryEngine {
|
|
|
82
82
|
MemoryEngine.instancePromise = null;
|
|
83
83
|
MemoryEngine.instancePid = null;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
if (MemoryEngine.instance && MemoryEngine.instance.isInitialized) {
|
|
87
87
|
return MemoryEngine.instance;
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
if (MemoryEngine.instancePromise) {
|
|
91
91
|
return MemoryEngine.instancePromise;
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
const engine = new MemoryEngine(options);
|
|
95
95
|
MemoryEngine.instance = engine;
|
|
96
96
|
MemoryEngine.instancePid = currentPid;
|
|
97
97
|
MemoryEngine.instancePromise = engine.initialize(projectPath).then(() => engine);
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
return MemoryEngine.instancePromise;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
constructor(options = {}) {
|
|
103
103
|
this.projectPath = null;
|
|
104
104
|
this.isInitialized = false;
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
// Paths
|
|
107
107
|
this.memoryPath = options.memoryPath || null;
|
|
108
108
|
this.indexPath = options.indexPath || null;
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
// Databases (separate concerns)
|
|
111
111
|
this.memoryDb = null; // For notes/memory (Markdown source)
|
|
112
112
|
this.codebaseDb = null; // For code indexing
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
// Two-tier memory
|
|
115
115
|
this.dailyLogs = null;
|
|
116
116
|
this.curatedMemory = null;
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
// Bootstrap
|
|
119
119
|
this.agentsBootstrap = null;
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
// Indexing
|
|
122
122
|
this.indexer = new CodeIndexer(options.indexer);
|
|
123
123
|
this.watcher = null;
|
|
124
124
|
this.isScanning = false;
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
// Sync Engine
|
|
127
127
|
this.syncEngine = null;
|
|
128
128
|
this.syncEngineOptions = {
|
|
@@ -130,14 +130,14 @@ export class MemoryEngine {
|
|
|
130
130
|
baseUrl: options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app',
|
|
131
131
|
syncInterval: options.syncInterval || 5 * 60 * 1000
|
|
132
132
|
};
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
// Embedder
|
|
135
135
|
this.embedder = new UltraEnhancedEmbedder({
|
|
136
136
|
dimension: options.dimension || 1024,
|
|
137
137
|
cacheSize: options.cacheSize || 2000,
|
|
138
138
|
useMercury: options.useMercury || false
|
|
139
139
|
});
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
// Smart routing
|
|
142
142
|
this.dashboardClient = options.dashboardClient || new DashboardClient({
|
|
143
143
|
apiKey: process.env.MCFAST_TOKEN
|
|
@@ -145,21 +145,21 @@ export class MemoryEngine {
|
|
|
145
145
|
this.smartRouter = new SmartRouter({
|
|
146
146
|
dashboardClient: this.dashboardClient
|
|
147
147
|
});
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
// Configuration
|
|
150
150
|
this.useUltraHybrid = options.useUltraHybrid !== false;
|
|
151
151
|
this.targetAccuracy = options.targetAccuracy || 90;
|
|
152
152
|
this.smartRoutingEnabled = options.smartRoutingEnabled !== false;
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
// Intelligence
|
|
155
155
|
this.intelligenceEnabled = options.intelligenceEnabled !== false;
|
|
156
156
|
this.patternDetector = null;
|
|
157
157
|
this.suggestionEngine = null;
|
|
158
158
|
this.strategySelector = null;
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
// Auto-analyze project on first use
|
|
161
161
|
this.autoAnalyze = options.autoAnalyze !== false;
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
// Search configuration
|
|
164
164
|
this.searchConfig = {
|
|
165
165
|
hybrid: {
|
|
@@ -178,7 +178,7 @@ export class MemoryEngine {
|
|
|
178
178
|
if (this.isInitialized) return;
|
|
179
179
|
|
|
180
180
|
this.projectPath = projectPath;
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
// Setup paths
|
|
183
183
|
if (!this.memoryPath) {
|
|
184
184
|
this.memoryPath = path.join(projectPath, '.mcfast');
|
|
@@ -186,19 +186,19 @@ export class MemoryEngine {
|
|
|
186
186
|
if (!this.indexPath) {
|
|
187
187
|
this.indexPath = path.join(this.memoryPath, 'index');
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
// Create directories
|
|
191
191
|
await fs.mkdir(this.memoryPath, { recursive: true });
|
|
192
192
|
await fs.mkdir(this.indexPath, { recursive: true });
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
console.error(`[MemoryEngine] Initializing...`);
|
|
195
195
|
console.error(`[MemoryEngine] Project: ${projectPath}`);
|
|
196
196
|
console.error(`[MemoryEngine] Memory path: ${this.memoryPath}`);
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
// Initialize bootstrap (AGENTS.md)
|
|
199
199
|
this.agentsBootstrap = new AgentsMdBootstrap({ projectPath });
|
|
200
200
|
await this.agentsBootstrap.bootstrap();
|
|
201
|
-
|
|
201
|
+
|
|
202
202
|
// Initialize databases
|
|
203
203
|
this.memoryDb = new MemoryDatabase(
|
|
204
204
|
path.join(this.indexPath, 'memory.sqlite')
|
|
@@ -206,15 +206,15 @@ export class MemoryEngine {
|
|
|
206
206
|
this.codebaseDb = new CodebaseDatabase(
|
|
207
207
|
path.join(this.indexPath, 'codebase.sqlite')
|
|
208
208
|
);
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
await this.memoryDb.initialize();
|
|
211
211
|
await this.codebaseDb.initialize();
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
// Initialize two-tier memory
|
|
214
214
|
this.dailyLogs = new DailyLogs({ memoryPath: this.memoryPath });
|
|
215
215
|
this.curatedMemory = new CuratedMemory({ memoryPath: this.memoryPath });
|
|
216
216
|
await this.curatedMemory.ensureFile(); // Create MEMORY.md if not exists
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
// Initialize sync engine
|
|
219
219
|
if (this.syncEngineOptions.apiKey && this.syncEngineOptions.enableSync !== false) {
|
|
220
220
|
this.syncEngine = new SyncEngine({
|
|
@@ -224,10 +224,10 @@ export class MemoryEngine {
|
|
|
224
224
|
syncInterval: this.syncEngineOptions.syncInterval
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
// Initialize file watcher (configurable via MCFAST_FILE_WATCHER env)
|
|
229
229
|
const ENABLE_FILE_WATCHER = process.env.MCFAST_FILE_WATCHER !== 'false';
|
|
230
|
-
|
|
230
|
+
|
|
231
231
|
if (ENABLE_FILE_WATCHER) {
|
|
232
232
|
this.watcher = new FileWatcher(projectPath, this, {
|
|
233
233
|
debounceMs: 1500,
|
|
@@ -240,7 +240,7 @@ export class MemoryEngine {
|
|
|
240
240
|
'**/*.log'
|
|
241
241
|
]
|
|
242
242
|
});
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
try {
|
|
245
245
|
await this.watcher.start();
|
|
246
246
|
} catch (watcherError) {
|
|
@@ -251,23 +251,25 @@ export class MemoryEngine {
|
|
|
251
251
|
console.error('[MemoryEngine] File watcher disabled (MCFAST_FILE_WATCHER=false)');
|
|
252
252
|
this.watcher = null;
|
|
253
253
|
}
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
|
|
255
|
+
// Start initial scan in background (don't block initialize)
|
|
256
|
+
this.performInitialScan().catch(error => {
|
|
257
|
+
console.error('[MemoryEngine] Background scan error:', error);
|
|
258
|
+
});
|
|
259
|
+
|
|
258
260
|
// Initialize intelligence
|
|
259
261
|
await this.initializeIntelligence();
|
|
260
|
-
|
|
262
|
+
|
|
261
263
|
this.isInitialized = true;
|
|
262
|
-
|
|
264
|
+
|
|
263
265
|
console.error(`[MemoryEngine] ✅ Initialized successfully`);
|
|
264
266
|
console.error(`[MemoryEngine] Hybrid Search: ${this.searchConfig.hybrid.enabled ? 'ENABLED' : 'DISABLED'}`);
|
|
265
267
|
console.error(`[MemoryEngine] Smart Routing: ${this.smartRoutingEnabled ? 'ENABLED' : 'DISABLED'}`);
|
|
266
268
|
console.error(`[MemoryEngine] Intelligence: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
|
|
267
|
-
|
|
269
|
+
|
|
268
270
|
// Auto-analyze project if MCFAST_TOKEN is set
|
|
269
271
|
await this.autoAnalyzeProject();
|
|
270
|
-
|
|
272
|
+
|
|
271
273
|
// Log initialization to daily logs
|
|
272
274
|
await this.dailyLogs.log('Memory Engine Initialized', `Project: ${projectPath}`, {
|
|
273
275
|
stats: this.getStats()
|
|
@@ -276,14 +278,14 @@ export class MemoryEngine {
|
|
|
276
278
|
|
|
277
279
|
async initializeIntelligence() {
|
|
278
280
|
if (!this.intelligenceEnabled) return;
|
|
279
|
-
|
|
281
|
+
|
|
280
282
|
try {
|
|
281
283
|
this.patternDetector = new PatternDetector({ memoryEngine: this });
|
|
282
284
|
this.suggestionEngine = new SuggestionEngine({ memoryEngine: this });
|
|
283
285
|
this.strategySelector = new StrategySelector({ memoryEngine: this });
|
|
284
|
-
|
|
286
|
+
|
|
285
287
|
console.error('[MemoryEngine] Intelligence engines initialized');
|
|
286
|
-
|
|
288
|
+
|
|
287
289
|
// Load models in background
|
|
288
290
|
(async () => {
|
|
289
291
|
try {
|
|
@@ -292,11 +294,11 @@ export class MemoryEngine {
|
|
|
292
294
|
this.suggestionEngine.loadModel?.(),
|
|
293
295
|
this.strategySelector.loadModel?.()
|
|
294
296
|
].filter(Boolean);
|
|
295
|
-
|
|
296
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
297
|
+
|
|
298
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
297
299
|
setTimeout(() => reject(new Error('Model loading timeout')), 5000)
|
|
298
300
|
);
|
|
299
|
-
|
|
301
|
+
|
|
300
302
|
await Promise.race([Promise.all(loadPromises), timeoutPromise]);
|
|
301
303
|
console.error('[MemoryEngine] ✅ Intelligence models loaded');
|
|
302
304
|
} catch (error) {
|
|
@@ -315,28 +317,28 @@ export class MemoryEngine {
|
|
|
315
317
|
*/
|
|
316
318
|
async autoAnalyzeProject() {
|
|
317
319
|
if (!this.autoAnalyze) return;
|
|
318
|
-
|
|
320
|
+
|
|
319
321
|
const apiKey = process.env.MCFAST_TOKEN;
|
|
320
322
|
if (!apiKey) {
|
|
321
323
|
console.error('[MemoryEngine] Skipping auto-analysis (no MCFAST_TOKEN)');
|
|
322
324
|
return;
|
|
323
325
|
}
|
|
324
|
-
|
|
326
|
+
|
|
325
327
|
// Check if already analyzed (MEMORY.md has Project Context section)
|
|
326
328
|
try {
|
|
327
329
|
const memoryContent = await this.curatedMemory.read();
|
|
328
|
-
if (memoryContent && memoryContent.includes('## Project Context') &&
|
|
329
|
-
|
|
330
|
+
if (memoryContent && memoryContent.includes('## Project Context') &&
|
|
331
|
+
!memoryContent.includes('<!-- Add project context here -->')) {
|
|
330
332
|
console.error('[MemoryEngine] Project already analyzed, skipping auto-analysis');
|
|
331
333
|
return;
|
|
332
334
|
}
|
|
333
335
|
} catch (e) {
|
|
334
336
|
// MEMORY.md doesn't exist yet, proceed with analysis
|
|
335
337
|
}
|
|
336
|
-
|
|
338
|
+
|
|
337
339
|
// Run analysis in background
|
|
338
340
|
console.error('[MemoryEngine] 🔄 Starting auto-analysis in background...');
|
|
339
|
-
|
|
341
|
+
|
|
340
342
|
projectAnalyzeExecute({ force: false, updateMemory: true })
|
|
341
343
|
.then(result => {
|
|
342
344
|
if (result.metadata?.memoryUpdated) {
|
|
@@ -353,22 +355,22 @@ export class MemoryEngine {
|
|
|
353
355
|
async performInitialScan() {
|
|
354
356
|
if (this.isScanning) return;
|
|
355
357
|
this.isScanning = true;
|
|
356
|
-
|
|
358
|
+
|
|
357
359
|
console.error('[MemoryEngine] 🔍 Performing initial codebase scan...');
|
|
358
|
-
|
|
360
|
+
|
|
359
361
|
try {
|
|
360
362
|
// Scan memory files first
|
|
361
363
|
await this.scanMemoryFiles();
|
|
362
|
-
|
|
364
|
+
|
|
363
365
|
// Scan codebase
|
|
364
366
|
await this.scanCodebase();
|
|
365
|
-
|
|
367
|
+
|
|
366
368
|
// Update indexing progress
|
|
367
369
|
this.codebaseDb?.completeIndexing?.();
|
|
368
|
-
|
|
370
|
+
|
|
369
371
|
console.error('[MemoryEngine] ✅ Initial scan complete');
|
|
370
372
|
console.error(`[MemoryEngine] Stats:`, this.getStats());
|
|
371
|
-
|
|
373
|
+
|
|
372
374
|
} catch (error) {
|
|
373
375
|
console.error('[MemoryEngine] Error during initial scan:', error);
|
|
374
376
|
} finally {
|
|
@@ -378,14 +380,14 @@ export class MemoryEngine {
|
|
|
378
380
|
|
|
379
381
|
async scanMemoryFiles() {
|
|
380
382
|
console.error('[MemoryEngine] Scanning memory files...');
|
|
381
|
-
|
|
383
|
+
|
|
382
384
|
try {
|
|
383
385
|
// Index MEMORY.md
|
|
384
386
|
const memoryFile = path.join(this.memoryPath, 'MEMORY.md');
|
|
385
387
|
if (await this.fileExists(memoryFile)) {
|
|
386
388
|
await this.indexMarkdownFile(memoryFile);
|
|
387
389
|
}
|
|
388
|
-
|
|
390
|
+
|
|
389
391
|
// Index daily logs
|
|
390
392
|
const memoryDir = path.join(this.memoryPath, 'memory');
|
|
391
393
|
if (await this.fileExists(memoryDir)) {
|
|
@@ -394,7 +396,7 @@ export class MemoryEngine {
|
|
|
394
396
|
await this.indexMarkdownFile(path.join(memoryDir, file));
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
|
-
|
|
399
|
+
|
|
398
400
|
console.error('[MemoryEngine] Memory files indexed');
|
|
399
401
|
} catch (error) {
|
|
400
402
|
console.error('[MemoryEngine] Error scanning memory files:', error);
|
|
@@ -403,37 +405,37 @@ export class MemoryEngine {
|
|
|
403
405
|
|
|
404
406
|
async scanCodebase() {
|
|
405
407
|
console.error('[MemoryEngine] Scanning codebase...');
|
|
406
|
-
|
|
408
|
+
|
|
407
409
|
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.go', '.rs', '.cpp', '.c', '.h'];
|
|
408
410
|
const files = await this.findFiles(this.projectPath, extensions);
|
|
409
|
-
|
|
411
|
+
|
|
410
412
|
if (files.length === 0) {
|
|
411
413
|
console.error('[MemoryEngine] No code files found to index');
|
|
412
414
|
return;
|
|
413
415
|
}
|
|
414
|
-
|
|
416
|
+
|
|
415
417
|
console.error(`[MemoryEngine] Found ${files.length} files to index`);
|
|
416
|
-
|
|
418
|
+
|
|
417
419
|
// Start indexing progress
|
|
418
420
|
this.codebaseDb?.startIndexing?.(files.length);
|
|
419
|
-
|
|
421
|
+
|
|
420
422
|
let indexed = 0;
|
|
421
423
|
let failed = 0;
|
|
422
|
-
|
|
424
|
+
|
|
423
425
|
for (const filePath of files) {
|
|
424
426
|
try {
|
|
425
427
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
426
428
|
const contentHash = crypto.createHash('md5').update(content).digest('hex');
|
|
427
|
-
|
|
429
|
+
|
|
428
430
|
// Skip if already indexed
|
|
429
431
|
if (this.codebaseDb?.isFileIndexed?.(filePath, contentHash)) {
|
|
430
432
|
continue;
|
|
431
433
|
}
|
|
432
|
-
|
|
434
|
+
|
|
433
435
|
// Index file
|
|
434
436
|
const indexedData = await this.indexer.indexFile(filePath, content);
|
|
435
437
|
await this.storeIndexed(indexedData);
|
|
436
|
-
|
|
438
|
+
|
|
437
439
|
indexed++;
|
|
438
440
|
if (indexed % 10 === 0) {
|
|
439
441
|
console.error(`[MemoryEngine] Indexed ${indexed}/${files.length} files...`);
|
|
@@ -443,7 +445,7 @@ export class MemoryEngine {
|
|
|
443
445
|
failed++;
|
|
444
446
|
}
|
|
445
447
|
}
|
|
446
|
-
|
|
448
|
+
|
|
447
449
|
console.error(`[MemoryEngine] Codebase scan complete: ${indexed} indexed, ${failed} failed`);
|
|
448
450
|
}
|
|
449
451
|
|
|
@@ -453,40 +455,40 @@ export class MemoryEngine {
|
|
|
453
455
|
const contentHash = crypto.createHash('md5').update(content).digest('hex');
|
|
454
456
|
const stats = await fs.stat(filePath);
|
|
455
457
|
const relativePath = path.relative(this.projectPath, filePath);
|
|
456
|
-
|
|
458
|
+
|
|
457
459
|
// Skip if unchanged
|
|
458
460
|
if (this.memoryDb?.isFileIndexed?.(relativePath, contentHash)) {
|
|
459
461
|
return;
|
|
460
462
|
}
|
|
461
|
-
|
|
463
|
+
|
|
462
464
|
// Import chunker dynamically
|
|
463
465
|
const { MarkdownChunker } = await import('./utils/markdown-chunker.js');
|
|
464
466
|
const chunker = new MarkdownChunker({
|
|
465
467
|
chunkSize: this.searchConfig.chunkSize * 4, // chars
|
|
466
468
|
overlap: this.searchConfig.chunkOverlap * 4
|
|
467
469
|
});
|
|
468
|
-
|
|
470
|
+
|
|
469
471
|
const chunks = chunker.chunk(content, relativePath);
|
|
470
|
-
|
|
472
|
+
|
|
471
473
|
// Track file
|
|
472
474
|
this.memoryDb?.upsertFile?.(relativePath, contentHash, stats.mtimeMs, stats.size);
|
|
473
|
-
|
|
475
|
+
|
|
474
476
|
// Delete old chunks
|
|
475
477
|
this.memoryDb?.deleteChunksByFile?.(relativePath);
|
|
476
|
-
|
|
478
|
+
|
|
477
479
|
// Process chunks
|
|
478
480
|
for (const chunk of chunks) {
|
|
479
481
|
// Check embedding cache
|
|
480
482
|
let embedding = null;
|
|
481
483
|
const cached = this.memoryDb?.getCachedEmbedding?.(chunk.contentHash);
|
|
482
|
-
|
|
484
|
+
|
|
483
485
|
if (cached) {
|
|
484
486
|
embedding = new Float32Array(cached.embedding.buffer, cached.embedding.byteOffset, cached.dimensions);
|
|
485
487
|
} else {
|
|
486
488
|
// Generate embedding
|
|
487
489
|
const result = this.embedder.embedCode(chunk.content);
|
|
488
490
|
embedding = result.vector;
|
|
489
|
-
|
|
491
|
+
|
|
490
492
|
// Cache it
|
|
491
493
|
this.memoryDb?.cacheEmbedding?.(
|
|
492
494
|
chunk.contentHash,
|
|
@@ -495,7 +497,7 @@ export class MemoryEngine {
|
|
|
495
497
|
embedding.length
|
|
496
498
|
);
|
|
497
499
|
}
|
|
498
|
-
|
|
500
|
+
|
|
499
501
|
// Insert chunk
|
|
500
502
|
this.memoryDb?.insertChunk?.({
|
|
501
503
|
id: chunk.id,
|
|
@@ -506,7 +508,7 @@ export class MemoryEngine {
|
|
|
506
508
|
content_hash: chunk.contentHash,
|
|
507
509
|
chunk_type: chunk.chunkType
|
|
508
510
|
});
|
|
509
|
-
|
|
511
|
+
|
|
510
512
|
// Insert embedding
|
|
511
513
|
this.memoryDb?.insertEmbedding?.({
|
|
512
514
|
chunk_id: chunk.id,
|
|
@@ -515,7 +517,7 @@ export class MemoryEngine {
|
|
|
515
517
|
dimensions: embedding.length
|
|
516
518
|
});
|
|
517
519
|
}
|
|
518
|
-
|
|
520
|
+
|
|
519
521
|
} catch (error) {
|
|
520
522
|
console.warn(`[MemoryEngine] Failed to index markdown ${filePath}:`, error.message);
|
|
521
523
|
}
|
|
@@ -524,13 +526,13 @@ export class MemoryEngine {
|
|
|
524
526
|
async findFiles(dir, extensions) {
|
|
525
527
|
const files = [];
|
|
526
528
|
const ignored = ['node_modules', '.git', 'dist', 'build', '.mcfast'];
|
|
527
|
-
|
|
529
|
+
|
|
528
530
|
try {
|
|
529
531
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
530
|
-
|
|
532
|
+
|
|
531
533
|
for (const entry of entries) {
|
|
532
534
|
const fullPath = path.join(dir, entry.name);
|
|
533
|
-
|
|
535
|
+
|
|
534
536
|
if (entry.isDirectory() && !ignored.includes(entry.name)) {
|
|
535
537
|
const subFiles = await this.findFiles(fullPath, extensions);
|
|
536
538
|
files.push(...subFiles);
|
|
@@ -541,7 +543,7 @@ export class MemoryEngine {
|
|
|
541
543
|
} catch (error) {
|
|
542
544
|
// Permission denied or other error, skip
|
|
543
545
|
}
|
|
544
|
-
|
|
546
|
+
|
|
545
547
|
return files;
|
|
546
548
|
}
|
|
547
549
|
|
|
@@ -602,26 +604,26 @@ export class MemoryEngine {
|
|
|
602
604
|
|
|
603
605
|
async searchVector(query, limit = 20) {
|
|
604
606
|
const startTime = performance.now();
|
|
605
|
-
|
|
607
|
+
|
|
606
608
|
const queryResult = this.embedder.embedCode(query);
|
|
607
609
|
const queryEmbedding = queryResult.vector;
|
|
608
|
-
|
|
610
|
+
|
|
609
611
|
const allEmbeddings = this.codebaseDb?.getAllEmbeddings?.() || [];
|
|
610
|
-
|
|
612
|
+
|
|
611
613
|
const scored = allEmbeddings.map(item => {
|
|
612
614
|
const vector = Array.from(new Float32Array(item.embedding.buffer, item.embedding.byteOffset, item.embedding.byteLength / 4));
|
|
613
615
|
const similarity = this.embedder.cosineSimilarity(queryEmbedding, vector);
|
|
614
|
-
return {
|
|
615
|
-
...item,
|
|
616
|
+
return {
|
|
617
|
+
...item,
|
|
616
618
|
similarity,
|
|
617
619
|
queryEmbedding: queryResult.metadata
|
|
618
620
|
};
|
|
619
621
|
});
|
|
620
|
-
|
|
622
|
+
|
|
621
623
|
scored.sort((a, b) => b.similarity - a.similarity);
|
|
622
|
-
|
|
624
|
+
|
|
623
625
|
const duration = performance.now() - startTime;
|
|
624
|
-
|
|
626
|
+
|
|
625
627
|
return {
|
|
626
628
|
results: scored.slice(0, limit),
|
|
627
629
|
metadata: {
|
|
@@ -640,15 +642,15 @@ export class MemoryEngine {
|
|
|
640
642
|
async searchHybrid(query, options = {}) {
|
|
641
643
|
const startTime = performance.now();
|
|
642
644
|
const limit = options.limit || 10;
|
|
643
|
-
|
|
645
|
+
|
|
644
646
|
console.error(`[MemoryEngine] 🔍 Hybrid search: "${query}"`);
|
|
645
|
-
|
|
647
|
+
|
|
646
648
|
// Get vector results first
|
|
647
649
|
const vectorResult = await this.searchVector(query, limit * 4);
|
|
648
|
-
|
|
650
|
+
|
|
649
651
|
// Use database hybrid search
|
|
650
652
|
const dbResults = this.memoryDb?.searchHybrid?.(query, vectorResult.results, limit);
|
|
651
|
-
|
|
653
|
+
|
|
652
654
|
if (dbResults) {
|
|
653
655
|
const totalDuration = performance.now() - startTime;
|
|
654
656
|
return {
|
|
@@ -659,7 +661,7 @@ export class MemoryEngine {
|
|
|
659
661
|
}
|
|
660
662
|
};
|
|
661
663
|
}
|
|
662
|
-
|
|
664
|
+
|
|
663
665
|
// Fallback to vector-only if hybrid fails
|
|
664
666
|
return vectorResult;
|
|
665
667
|
}
|
|
@@ -670,11 +672,11 @@ export class MemoryEngine {
|
|
|
670
672
|
useHybrid = this.searchConfig.hybrid.enabled,
|
|
671
673
|
minScore = this.searchConfig.hybrid.minScore
|
|
672
674
|
} = options;
|
|
673
|
-
|
|
675
|
+
|
|
674
676
|
if (useHybrid) {
|
|
675
677
|
return this.searchHybrid(query, { limit, minScore });
|
|
676
678
|
}
|
|
677
|
-
|
|
679
|
+
|
|
678
680
|
return this.searchVector(query, limit);
|
|
679
681
|
}
|
|
680
682
|
|
|
@@ -756,18 +758,18 @@ export class MemoryEngine {
|
|
|
756
758
|
// Search codebase using existing methods
|
|
757
759
|
const vectorResults = await this.searchVector(query, limit * 2);
|
|
758
760
|
const ftsResults = this.codebaseDb?.searchFTS?.(query, limit * 2);
|
|
759
|
-
|
|
761
|
+
|
|
760
762
|
// Combine and deduplicate
|
|
761
763
|
const seen = new Set();
|
|
762
764
|
const combined = [];
|
|
763
|
-
|
|
765
|
+
|
|
764
766
|
for (const r of vectorResults.results) {
|
|
765
767
|
if (!seen.has(r.chunk_id)) {
|
|
766
768
|
seen.add(r.chunk_id);
|
|
767
769
|
combined.push({ ...r, source: 'vector' });
|
|
768
770
|
}
|
|
769
771
|
}
|
|
770
|
-
|
|
772
|
+
|
|
771
773
|
if (ftsResults?.results) {
|
|
772
774
|
for (const r of ftsResults.results) {
|
|
773
775
|
if (!seen.has(r.chunk_id)) {
|
|
@@ -776,10 +778,10 @@ export class MemoryEngine {
|
|
|
776
778
|
}
|
|
777
779
|
}
|
|
778
780
|
}
|
|
779
|
-
|
|
781
|
+
|
|
780
782
|
// Sort by score and limit
|
|
781
783
|
combined.sort((a, b) => (b.similarity || b.score) - (a.similarity || a.score));
|
|
782
|
-
|
|
784
|
+
|
|
783
785
|
return {
|
|
784
786
|
results: combined.slice(0, limit),
|
|
785
787
|
metadata: {
|
|
@@ -845,7 +847,7 @@ export class MemoryEngine {
|
|
|
845
847
|
async logEdit(edit) {
|
|
846
848
|
// Log to daily
|
|
847
849
|
await this.dailyLogs?.logEdit?.(edit);
|
|
848
|
-
|
|
850
|
+
|
|
849
851
|
// Record to database
|
|
850
852
|
this.codebaseDb?.recordEdit?.({
|
|
851
853
|
id: Math.random().toString(36).substring(2, 15),
|
|
@@ -876,7 +878,7 @@ export class MemoryEngine {
|
|
|
876
878
|
if (!this.intelligenceEnabled || !this.patternDetector) {
|
|
877
879
|
throw new Error('Intelligence layer not enabled');
|
|
878
880
|
}
|
|
879
|
-
|
|
881
|
+
|
|
880
882
|
const editHistory = this.codebaseDb?.getRecentEdits?.(100) || [];
|
|
881
883
|
return this.patternDetector.analyze(editHistory);
|
|
882
884
|
}
|
|
@@ -885,7 +887,7 @@ export class MemoryEngine {
|
|
|
885
887
|
if (!this.intelligenceEnabled || !this.suggestionEngine) {
|
|
886
888
|
throw new Error('Intelligence layer not enabled');
|
|
887
889
|
}
|
|
888
|
-
|
|
890
|
+
|
|
889
891
|
return this.suggestionEngine.getSuggestions(context);
|
|
890
892
|
}
|
|
891
893
|
|
|
@@ -893,21 +895,21 @@ export class MemoryEngine {
|
|
|
893
895
|
if (!this.intelligenceEnabled || !this.strategySelector) {
|
|
894
896
|
return this.fallbackStrategySelection(instruction, context);
|
|
895
897
|
}
|
|
896
|
-
|
|
898
|
+
|
|
897
899
|
return this.strategySelector.selectStrategy(instruction, context);
|
|
898
900
|
}
|
|
899
901
|
|
|
900
902
|
fallbackStrategySelection(instruction, context) {
|
|
901
903
|
const lower = instruction.toLowerCase();
|
|
902
|
-
|
|
904
|
+
|
|
903
905
|
if (lower.includes('replace') || lower.includes('change line') || lower.includes('exact')) {
|
|
904
906
|
return { strategy: 'deterministic', confidence: 0.8, reason: 'Simple replacement detected' };
|
|
905
907
|
}
|
|
906
|
-
|
|
908
|
+
|
|
907
909
|
if (lower.includes('refactor') || lower.includes('improve') || lower.includes('multiple')) {
|
|
908
910
|
return { strategy: 'llm', confidence: 0.8, reason: 'Complex operation detected' };
|
|
909
911
|
}
|
|
910
|
-
|
|
912
|
+
|
|
911
913
|
return { strategy: 'cached', confidence: 0.6, reason: 'Default selection' };
|
|
912
914
|
}
|
|
913
915
|
|
|
@@ -947,17 +949,17 @@ export class MemoryEngine {
|
|
|
947
949
|
|
|
948
950
|
async shutdown() {
|
|
949
951
|
console.error('[MemoryEngine] Shutting down...');
|
|
950
|
-
|
|
952
|
+
|
|
951
953
|
if (this.intelligenceEnabled) {
|
|
952
954
|
await this.strategySelector?.saveModel?.();
|
|
953
955
|
}
|
|
954
|
-
|
|
956
|
+
|
|
955
957
|
await this.watcher?.stop?.();
|
|
956
958
|
this.syncEngine?.shutdown?.();
|
|
957
|
-
|
|
959
|
+
|
|
958
960
|
this.memoryDb?.close?.();
|
|
959
961
|
this.codebaseDb?.close?.();
|
|
960
|
-
|
|
962
|
+
|
|
961
963
|
this.isInitialized = false;
|
|
962
964
|
console.error('[MemoryEngine] Shutdown complete');
|
|
963
965
|
}
|