@mrxkun/mcfast-mcp 4.1.10 → 4.1.11
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 +2 -2
- package/src/index.js +176 -21
- package/src/memory/memory-engine.js +213 -13
- package/src/memory/stores/base-database.js +223 -0
- package/src/memory/utils/chunker.js +1 -0
- package/src/memory/utils/indexer.js +110 -4
- package/src/memory/utils/logger.js +162 -0
- package/src/memory/utils/vector-index.js +241 -0
- package/src/memory/watchers/file-watcher.js +255 -103
- package/src/tools/project_analyze.js +491 -0
- package/src/utils/audit-queue.js +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "4.1.
|
|
4
|
-
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.
|
|
3
|
+
"version": "4.1.11",
|
|
4
|
+
"description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.11: Suppress console output to prevent JSON-RPC stream corruption.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcfast-mcp": "src/index.js"
|
package/src/index.js
CHANGED
|
@@ -75,9 +75,87 @@ import { getAuditQueue } from './utils/audit-queue.js';
|
|
|
75
75
|
import { getIntelligenceCache } from './utils/intelligence-cache.js';
|
|
76
76
|
import { getContextPrefetcher } from './utils/context-prefetcher.js';
|
|
77
77
|
import { parallelMemorySearch } from './utils/parallel-search.js';
|
|
78
|
+
import { execute as projectAnalyzeExecute } from './tools/project_analyze.js';
|
|
78
79
|
|
|
79
80
|
const execAsync = promisify(exec);
|
|
80
81
|
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// LOCK FILE MECHANISM - Prevent multiple instances running simultaneously
|
|
84
|
+
// ============================================================================
|
|
85
|
+
const LOCK_FILE_PATH = path.join(process.cwd(), '.mcfast', '.mcp-instance.lock');
|
|
86
|
+
let lockFileHandle = null;
|
|
87
|
+
|
|
88
|
+
async function acquireLock() {
|
|
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}`);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function releaseLock() {
|
|
136
|
+
if (lockFileHandle) {
|
|
137
|
+
try {
|
|
138
|
+
await lockFileHandle.close();
|
|
139
|
+
lockFileHandle = null;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// Ignore close errors
|
|
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
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// END LOCK FILE MECHANISM
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
81
159
|
const API_URL = "https://mcfast.vercel.app/api/v1";
|
|
82
160
|
const TOKEN = process.env.MCFAST_TOKEN;
|
|
83
161
|
const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
|
|
@@ -95,28 +173,49 @@ async function backgroundInitializeMemoryEngine() {
|
|
|
95
173
|
if (memoryEngineInitPromise) return memoryEngineInitPromise;
|
|
96
174
|
|
|
97
175
|
memoryEngineInitPromise = (async () => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
176
|
+
const maxRetries = 2;
|
|
177
|
+
let lastError = null;
|
|
178
|
+
|
|
179
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
180
|
+
try {
|
|
181
|
+
memoryEngine = new MemoryEngine({
|
|
182
|
+
apiKey: TOKEN,
|
|
183
|
+
enableSync: true
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Use timeout for initialization
|
|
187
|
+
const initTimeout = new Promise((_, reject) => {
|
|
188
|
+
setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await Promise.race([
|
|
192
|
+
memoryEngine.initialize(process.cwd()),
|
|
193
|
+
initTimeout
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
memoryEngineReady = true;
|
|
197
|
+
console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
|
|
198
|
+
return memoryEngine;
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
lastError = error;
|
|
202
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
|
|
203
|
+
|
|
204
|
+
if (attempt < maxRetries) {
|
|
205
|
+
// Exponential backoff before retry
|
|
206
|
+
const backoffMs = 1000 * Math.pow(2, attempt - 1);
|
|
207
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Retrying in ${backoffMs}ms...`);
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, backoffMs));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
119
211
|
}
|
|
212
|
+
|
|
213
|
+
// All retries failed - graceful degradation
|
|
214
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
|
|
215
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
|
|
216
|
+
console.error(`${colors.yellow}[Memory]${colors.reset} Tools requiring memory will be unavailable.`);
|
|
217
|
+
memoryEngineReady = false;
|
|
218
|
+
return null;
|
|
120
219
|
})();
|
|
121
220
|
|
|
122
221
|
return memoryEngineInitPromise;
|
|
@@ -645,6 +744,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
645
744
|
},
|
|
646
745
|
required: ["instruction"]
|
|
647
746
|
}
|
|
747
|
+
},
|
|
748
|
+
// PROJECT ANALYSIS TOOL
|
|
749
|
+
{
|
|
750
|
+
name: "project_analyze",
|
|
751
|
+
description: "🔍 Analyze project structure using AI (Mercury). Returns: Project Overview, Technologies, API Endpoints, Main Features. Auto-updates MEMORY.md. Requires MCFAST_TOKEN.",
|
|
752
|
+
inputSchema: {
|
|
753
|
+
type: "object",
|
|
754
|
+
properties: {
|
|
755
|
+
force: { type: "boolean", description: "Force re-analysis (ignore cache)" },
|
|
756
|
+
updateMemory: { type: "boolean", description: "Update MEMORY.md with results (default: true)" }
|
|
757
|
+
},
|
|
758
|
+
required: []
|
|
759
|
+
}
|
|
648
760
|
}
|
|
649
761
|
],
|
|
650
762
|
};
|
|
@@ -847,6 +959,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
847
959
|
result = await handleSelectStrategy(args);
|
|
848
960
|
summary = 'Strategy selected';
|
|
849
961
|
}
|
|
962
|
+
// PROJECT ANALYSIS TOOL
|
|
963
|
+
else if (name === "project_analyze") {
|
|
964
|
+
result = await projectAnalyzeExecute(args);
|
|
965
|
+
summary = 'Project analyzed';
|
|
966
|
+
}
|
|
850
967
|
else {
|
|
851
968
|
throw new Error(`Tool not found: ${name}`);
|
|
852
969
|
}
|
|
@@ -3238,12 +3355,50 @@ async function handleSelectStrategy({ instruction, files = [] }) {
|
|
|
3238
3355
|
/**
|
|
3239
3356
|
* Start Server
|
|
3240
3357
|
*/
|
|
3358
|
+
|
|
3359
|
+
// Acquire lock before starting - prevents multiple instances
|
|
3360
|
+
const lockAcquired = await acquireLock();
|
|
3361
|
+
if (!lockAcquired) {
|
|
3362
|
+
console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
|
|
3363
|
+
console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
|
|
3364
|
+
process.exit(1);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3241
3367
|
const transport = new StdioServerTransport();
|
|
3242
3368
|
|
|
3243
3369
|
// Pre-initialize memory engine in background
|
|
3244
3370
|
backgroundInitializeMemoryEngine().catch(err => {
|
|
3245
3371
|
console.error(`${colors.yellow}[Memory]${colors.reset} Background init error: ${err.message}`);
|
|
3372
|
+
// Don't exit - continue without memory
|
|
3246
3373
|
});
|
|
3247
3374
|
|
|
3375
|
+
// Graceful shutdown handler
|
|
3376
|
+
async function gracefulShutdown(signal) {
|
|
3377
|
+
console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
|
|
3378
|
+
|
|
3379
|
+
try {
|
|
3380
|
+
// Stop memory engine
|
|
3381
|
+
if (memoryEngine && memoryEngineReady) {
|
|
3382
|
+
await memoryEngine.cleanup();
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// Flush audit queue
|
|
3386
|
+
const auditQ = getAuditQueue();
|
|
3387
|
+
await auditQ.destroy();
|
|
3388
|
+
|
|
3389
|
+
// Release lock
|
|
3390
|
+
await releaseLock();
|
|
3391
|
+
|
|
3392
|
+
console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
process.exit(0);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3401
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
3402
|
+
|
|
3248
3403
|
await server.connect(transport);
|
|
3249
3404
|
console.error("mcfast MCP v1.0.0 running on stdio");
|
|
@@ -26,6 +26,9 @@ import { CuratedMemory } from './layers/curated-memory.js';
|
|
|
26
26
|
// Bootstrap
|
|
27
27
|
import { AgentsMdBootstrap } from './bootstrap/agents-md.js';
|
|
28
28
|
|
|
29
|
+
// Project Analysis (AI-powered)
|
|
30
|
+
import { execute as projectAnalyzeExecute } from '../tools/project_analyze.js';
|
|
31
|
+
|
|
29
32
|
// Embedders
|
|
30
33
|
import { UltraEnhancedEmbedder } from './utils/ultra-embedder.js';
|
|
31
34
|
import { SmartRouter } from './utils/smart-router.js';
|
|
@@ -38,6 +41,7 @@ import { PatternDetector, SuggestionEngine, StrategySelector } from '../intellig
|
|
|
38
41
|
export class MemoryEngine {
|
|
39
42
|
static instance = null;
|
|
40
43
|
static instancePromise = null;
|
|
44
|
+
static instancePid = null; // Track which process owns the instance
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
47
|
* Get singleton instance of MemoryEngine
|
|
@@ -45,8 +49,19 @@ export class MemoryEngine {
|
|
|
45
49
|
* @returns {MemoryEngine} Singleton instance
|
|
46
50
|
*/
|
|
47
51
|
static getInstance(options = {}) {
|
|
52
|
+
const currentPid = process.pid;
|
|
53
|
+
|
|
54
|
+
// Check if instance belongs to current process
|
|
55
|
+
if (MemoryEngine.instancePid !== currentPid) {
|
|
56
|
+
// Different process - reset instance
|
|
57
|
+
MemoryEngine.instance = null;
|
|
58
|
+
MemoryEngine.instancePromise = null;
|
|
59
|
+
MemoryEngine.instancePid = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
if (!MemoryEngine.instance) {
|
|
49
63
|
MemoryEngine.instance = new MemoryEngine(options);
|
|
64
|
+
MemoryEngine.instancePid = currentPid;
|
|
50
65
|
}
|
|
51
66
|
return MemoryEngine.instance;
|
|
52
67
|
}
|
|
@@ -58,6 +73,16 @@ export class MemoryEngine {
|
|
|
58
73
|
* @returns {Promise<MemoryEngine>} Initialized singleton instance
|
|
59
74
|
*/
|
|
60
75
|
static async getOrCreate(projectPath, options = {}) {
|
|
76
|
+
const currentPid = process.pid;
|
|
77
|
+
|
|
78
|
+
// Check if instance belongs to current process
|
|
79
|
+
if (MemoryEngine.instancePid !== currentPid) {
|
|
80
|
+
// Different process - reset instance
|
|
81
|
+
MemoryEngine.instance = null;
|
|
82
|
+
MemoryEngine.instancePromise = null;
|
|
83
|
+
MemoryEngine.instancePid = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
if (MemoryEngine.instance && MemoryEngine.instance.isInitialized) {
|
|
62
87
|
return MemoryEngine.instance;
|
|
63
88
|
}
|
|
@@ -68,6 +93,7 @@ export class MemoryEngine {
|
|
|
68
93
|
|
|
69
94
|
const engine = new MemoryEngine(options);
|
|
70
95
|
MemoryEngine.instance = engine;
|
|
96
|
+
MemoryEngine.instancePid = currentPid;
|
|
71
97
|
MemoryEngine.instancePromise = engine.initialize(projectPath).then(() => engine);
|
|
72
98
|
|
|
73
99
|
return MemoryEngine.instancePromise;
|
|
@@ -131,6 +157,9 @@ export class MemoryEngine {
|
|
|
131
157
|
this.suggestionEngine = null;
|
|
132
158
|
this.strategySelector = null;
|
|
133
159
|
|
|
160
|
+
// Auto-analyze project on first use
|
|
161
|
+
this.autoAnalyze = options.autoAnalyze !== false;
|
|
162
|
+
|
|
134
163
|
// Search configuration
|
|
135
164
|
this.searchConfig = {
|
|
136
165
|
hybrid: {
|
|
@@ -208,7 +237,13 @@ export class MemoryEngine {
|
|
|
208
237
|
'**/*.log'
|
|
209
238
|
]
|
|
210
239
|
});
|
|
211
|
-
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await this.watcher.start();
|
|
243
|
+
} catch (watcherError) {
|
|
244
|
+
console.error('[MemoryEngine] ⚠️ File watcher failed to start (continuing without file watching):', watcherError.message);
|
|
245
|
+
this.watcher = null;
|
|
246
|
+
}
|
|
212
247
|
|
|
213
248
|
// Perform initial scan
|
|
214
249
|
await this.performInitialScan();
|
|
@@ -223,6 +258,9 @@ export class MemoryEngine {
|
|
|
223
258
|
console.error(`[MemoryEngine] Smart Routing: ${this.smartRoutingEnabled ? 'ENABLED' : 'DISABLED'}`);
|
|
224
259
|
console.error(`[MemoryEngine] Intelligence: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
|
|
225
260
|
|
|
261
|
+
// Auto-analyze project if MCFAST_TOKEN is set
|
|
262
|
+
await this.autoAnalyzeProject();
|
|
263
|
+
|
|
226
264
|
// Log initialization to daily logs
|
|
227
265
|
await this.dailyLogs.log('Memory Engine Initialized', `Project: ${projectPath}`, {
|
|
228
266
|
stats: this.getStats()
|
|
@@ -264,6 +302,47 @@ export class MemoryEngine {
|
|
|
264
302
|
}
|
|
265
303
|
}
|
|
266
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Auto-analyze project using AI if MCFAST_TOKEN is set
|
|
307
|
+
* Runs in background to not block initialization
|
|
308
|
+
*/
|
|
309
|
+
async autoAnalyzeProject() {
|
|
310
|
+
if (!this.autoAnalyze) return;
|
|
311
|
+
|
|
312
|
+
const apiKey = process.env.MCFAST_TOKEN;
|
|
313
|
+
if (!apiKey) {
|
|
314
|
+
console.error('[MemoryEngine] Skipping auto-analysis (no MCFAST_TOKEN)');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check if already analyzed (MEMORY.md has Project Context section)
|
|
319
|
+
try {
|
|
320
|
+
const memoryContent = await this.curatedMemory.read();
|
|
321
|
+
if (memoryContent && memoryContent.includes('## Project Context') &&
|
|
322
|
+
!memoryContent.includes('<!-- Add project context here -->')) {
|
|
323
|
+
console.error('[MemoryEngine] Project already analyzed, skipping auto-analysis');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
} catch (e) {
|
|
327
|
+
// MEMORY.md doesn't exist yet, proceed with analysis
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Run analysis in background
|
|
331
|
+
console.error('[MemoryEngine] 🔄 Starting auto-analysis in background...');
|
|
332
|
+
|
|
333
|
+
projectAnalyzeExecute({ force: false, updateMemory: true })
|
|
334
|
+
.then(result => {
|
|
335
|
+
if (result.metadata?.memoryUpdated) {
|
|
336
|
+
console.error('[MemoryEngine] ✅ Auto-analysis complete, MEMORY.md updated');
|
|
337
|
+
} else if (result.isError) {
|
|
338
|
+
console.error('[MemoryEngine] ⚠️ Auto-analysis failed:', result.content?.[0]?.text?.substring(0, 100));
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
.catch(err => {
|
|
342
|
+
console.error('[MemoryEngine] ⚠️ Auto-analysis error:', err.message);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
267
346
|
async performInitialScan() {
|
|
268
347
|
if (this.isScanning) return;
|
|
269
348
|
this.isScanning = true;
|
|
@@ -470,24 +549,44 @@ export class MemoryEngine {
|
|
|
470
549
|
|
|
471
550
|
// ========== Storage Operations ==========
|
|
472
551
|
|
|
473
|
-
async storeIndexed(indexed) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
552
|
+
async storeIndexed(indexed, maxRetries = 3) {
|
|
553
|
+
// Retry logic with exponential backoff
|
|
554
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
555
|
+
try {
|
|
556
|
+
this.codebaseDb?.upsertFile?.(indexed.file);
|
|
557
|
+
this.codebaseDb?.deleteFactsByFile?.(indexed.file.id);
|
|
558
|
+
this.codebaseDb?.deleteChunksByFile?.(indexed.file.id);
|
|
477
559
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
560
|
+
for (const fact of indexed.facts) {
|
|
561
|
+
this.codebaseDb?.insertFact?.(fact);
|
|
562
|
+
}
|
|
481
563
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
564
|
+
for (const chunk of indexed.chunks) {
|
|
565
|
+
this.codebaseDb?.insertChunk?.(chunk);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
for (const embedding of indexed.embeddings) {
|
|
569
|
+
this.codebaseDb?.insertEmbedding?.(embedding);
|
|
570
|
+
}
|
|
485
571
|
|
|
486
|
-
|
|
487
|
-
|
|
572
|
+
return true; // Success
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (attempt === maxRetries) {
|
|
575
|
+
console.error(`[MemoryEngine] Failed to store indexed data after ${maxRetries} attempts:`, error.message);
|
|
576
|
+
this.codebaseDb?.recordFailedIndex?.(indexed.file.path, error.message);
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
// Exponential backoff: 100ms, 200ms, 400ms
|
|
580
|
+
await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, attempt - 1)));
|
|
581
|
+
}
|
|
488
582
|
}
|
|
489
583
|
}
|
|
490
584
|
|
|
585
|
+
// Record failed indexing for tracking
|
|
586
|
+
recordFailedIndex(filePath, errorMessage) {
|
|
587
|
+
console.error(`[MemoryEngine] Indexing failed for ${filePath}: ${errorMessage}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
491
590
|
// ========== Search Operations ==========
|
|
492
591
|
|
|
493
592
|
async searchFacts(query, limit = 20) {
|
|
@@ -573,6 +672,80 @@ export class MemoryEngine {
|
|
|
573
672
|
}
|
|
574
673
|
|
|
575
674
|
async searchCodebase(query, limit = 20) {
|
|
675
|
+
const startTime = performance.now();
|
|
676
|
+
|
|
677
|
+
// FIX: Get embeddings from both codebaseDb AND memoryDb
|
|
678
|
+
const [codebaseEmbeddings, memoryEmbeddings] = await Promise.all([
|
|
679
|
+
this.codebaseDb?.getAllEmbeddings?.() || [],
|
|
680
|
+
this.memoryDb?.getAllEmbeddings?.() || []
|
|
681
|
+
]);
|
|
682
|
+
|
|
683
|
+
// Generate query embedding
|
|
684
|
+
const queryResult = this.embedder.embedCode(query);
|
|
685
|
+
const queryEmbedding = queryResult.vector;
|
|
686
|
+
|
|
687
|
+
// Score and tag results from each source
|
|
688
|
+
const scoreFn = (item) => {
|
|
689
|
+
const buf = item.embedding?.buffer || item.embedding;
|
|
690
|
+
const arr = buf ? new Uint8Array(buf) : new Uint8Array(0);
|
|
691
|
+
return this.embedder.cosineSimilarity(queryEmbedding, Array.from(arr));
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const codebaseResults = (codebaseEmbeddings || []).map(item => ({
|
|
695
|
+
...item, similarity: scoreFn(item), source: 'codebase', type: 'code'
|
|
696
|
+
}));
|
|
697
|
+
|
|
698
|
+
const memoryResults = (memoryEmbeddings || []).map(item => ({
|
|
699
|
+
...item, similarity: scoreFn(item), source: 'memory', type: 'memory'
|
|
700
|
+
}));
|
|
701
|
+
|
|
702
|
+
// Combine results
|
|
703
|
+
let combined = [...codebaseResults, ...memoryResults];
|
|
704
|
+
|
|
705
|
+
// Also get FTS results from both databases
|
|
706
|
+
const [codebaseFts, memoryFts] = await Promise.all([
|
|
707
|
+
this.codebaseDb?.searchFTS?.(query, limit * 2) || { results: [] },
|
|
708
|
+
this.memoryDb?.searchFTS?.(query, limit * 2) || { results: [] }
|
|
709
|
+
]);
|
|
710
|
+
|
|
711
|
+
// Add FTS results
|
|
712
|
+
for (const r of codebaseFts.results || []) {
|
|
713
|
+
combined.push({ ...r, source: 'fts', type: 'code', score: r.score });
|
|
714
|
+
}
|
|
715
|
+
for (const r of memoryFts.results || []) {
|
|
716
|
+
combined.push({ ...r, source: 'fts', type: 'memory', score: r.score });
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Deduplicate and sort
|
|
720
|
+
const seen = new Map();
|
|
721
|
+
for (const r of combined) {
|
|
722
|
+
const key = r.chunk_id || r.id;
|
|
723
|
+
if (!seen.has(key) || (r.similarity || r.score || 0) > (seen.get(key).similarity || seen.get(key).score || 0)) {
|
|
724
|
+
seen.set(key, r);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const finalResults = Array.from(seen.values())
|
|
729
|
+
.sort((a, b) => (b.similarity || b.score || 0) - (a.similarity || a.score || 0))
|
|
730
|
+
.slice(0, limit);
|
|
731
|
+
|
|
732
|
+
const duration = performance.now() - startTime;
|
|
733
|
+
|
|
734
|
+
return {
|
|
735
|
+
results: finalResults,
|
|
736
|
+
metadata: {
|
|
737
|
+
vectorCandidates: codebaseResults.length + memoryResults.length,
|
|
738
|
+
ftsCandidates: (codebaseFts.results?.length || 0) + (memoryFts.results?.length || 0),
|
|
739
|
+
totalCandidates: combined.length,
|
|
740
|
+
codebaseResults: codebaseResults.length,
|
|
741
|
+
memoryResults: memoryResults.length,
|
|
742
|
+
duration: duration.toFixed(2) + 'ms'
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Legacy search - kept for backward compatibility
|
|
748
|
+
async _searchCodebaseLegacy(query, limit = 20) {
|
|
576
749
|
// Search codebase using existing methods
|
|
577
750
|
const vectorResults = await this.searchVector(query, limit * 2);
|
|
578
751
|
const ftsResults = this.codebaseDb?.searchFTS?.(query, limit * 2);
|
|
@@ -781,6 +954,33 @@ export class MemoryEngine {
|
|
|
781
954
|
this.isInitialized = false;
|
|
782
955
|
console.error('[MemoryEngine] Shutdown complete');
|
|
783
956
|
}
|
|
957
|
+
|
|
958
|
+
async cleanup() {
|
|
959
|
+
// Alias for shutdown for consistency
|
|
960
|
+
return this.shutdown();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Auto-register cleanup handlers for graceful shutdown
|
|
965
|
+
if (typeof process !== 'undefined') {
|
|
966
|
+
const cleanupHandler = async (signal) => {
|
|
967
|
+
console.error(`[MemoryEngine] Received ${signal}, performing cleanup...`);
|
|
968
|
+
if (MemoryEngine.instance) {
|
|
969
|
+
try {
|
|
970
|
+
await MemoryEngine.instance.shutdown();
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error(`[MemoryEngine] Error during shutdown: ${error.message}`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// Register handlers only once
|
|
978
|
+
if (!process._memoryEngineCleanupRegistered) {
|
|
979
|
+
process.on('beforeExit', () => cleanupHandler('beforeExit'));
|
|
980
|
+
process.on('SIGINT', () => cleanupHandler('SIGINT'));
|
|
981
|
+
process.on('SIGTERM', () => cleanupHandler('SIGTERM'));
|
|
982
|
+
process._memoryEngineCleanupRegistered = true;
|
|
983
|
+
}
|
|
784
984
|
}
|
|
785
985
|
|
|
786
986
|
export default MemoryEngine;
|