@mrxkun/mcfast-mcp 4.1.13 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.1.13",
3
+ "version": "4.1.14",
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
@@ -29,11 +29,11 @@ console.log = function (...args) {
29
29
  // Suppressed in MCP mode
30
30
  };
31
31
 
32
- // Override console.error - suppress in MCP mode
33
- const originalConsoleError = console.error;
34
- console.error = function (...args) {
35
- // Suppressed in MCP mode
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";
@@ -112,9 +112,21 @@ async function acquireLock() {
112
112
  const [pid, timestamp] = lockContent.trim().split('\n');
113
113
  const lockAge = Date.now() - parseInt(timestamp);
114
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...`);
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...`);
118
130
  await fs.unlink(LOCK_FILE_PATH);
119
131
  return acquireLock(); // Retry
120
132
  }
@@ -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
- // Perform initial scan
256
- await this.performInitialScan();
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
- !memoryContent.includes('<!-- Add project context here -->')) {
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
  }