@mrxkun/mcfast-mcp 4.0.14 → 4.1.0

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.
@@ -1,34 +1,66 @@
1
1
  /**
2
- * Memory Engine
3
- * Central coordinator cho memory system
2
+ * Memory Engine v4.1.0
3
+ * Refactored with Markdown source of truth + SQLite index
4
+ * Two-tier memory: Daily logs + Curated memory
5
+ * Hybrid search: Vector 70% + BM25 30%
4
6
  */
5
7
 
6
- import { MemoryDatabase } from './stores/database.js';
8
+ import path from 'path';
9
+ import fs from 'fs/promises';
10
+ import crypto from 'crypto';
11
+
12
+ // Database stores
13
+ import { MemoryDatabase } from './stores/memory-database.js';
14
+ import { CodebaseDatabase } from './stores/codebase-database.js';
15
+
16
+ // Watchers
7
17
  import { FileWatcher } from './watchers/file-watcher.js';
18
+
19
+ // Indexers
8
20
  import { CodeIndexer } from './utils/indexer.js';
21
+
22
+ // Two-tier memory layers
23
+ import { DailyLogs } from './layers/daily-logs.js';
24
+ import { CuratedMemory } from './layers/curated-memory.js';
25
+
26
+ // Bootstrap
27
+ import { AgentsMdBootstrap } from './bootstrap/agents-md.js';
28
+
29
+ // Embedders
9
30
  import { UltraEnhancedEmbedder } from './utils/ultra-embedder.js';
10
31
  import { SmartRouter } from './utils/smart-router.js';
11
32
  import { DashboardClient } from './utils/dashboard-client.js';
12
- import { DailyLogs } from './utils/daily-logs.js';
13
33
  import { SyncEngine } from './utils/sync-engine.js';
34
+
35
+ // Intelligence
14
36
  import { PatternDetector, SuggestionEngine, StrategySelector } from '../intelligence/index.js';
15
- import path from 'path';
16
37
 
17
38
  export class MemoryEngine {
18
39
  constructor(options = {}) {
19
- this.db = new MemoryDatabase(options.dbPath);
20
- this.indexer = new CodeIndexer(options.indexer);
21
- this.watcher = null;
22
40
  this.projectPath = null;
23
41
  this.isInitialized = false;
24
42
 
25
- // Memory path - now stored in project directory
26
- this.memoryPath = options.memoryPath || null; // Will be set on initialize
43
+ // Paths
44
+ this.memoryPath = options.memoryPath || null;
45
+ this.indexPath = options.indexPath || null;
46
+
47
+ // Databases (separate concerns)
48
+ this.memoryDb = null; // For notes/memory (Markdown source)
49
+ this.codebaseDb = null; // For code indexing
27
50
 
28
- // Daily Logs - auto-generated markdown notes
29
- this.dailyLogs = null; // Will be initialized on initialize
51
+ // Two-tier memory
52
+ this.dailyLogs = null;
53
+ this.curatedMemory = null;
30
54
 
31
- // Sync Engine - local ↔ cloud (optional, auto-detect from MCFAST_TOKEN)
55
+ // Bootstrap
56
+ this.agentsBootstrap = null;
57
+
58
+ // Indexing
59
+ this.indexer = new CodeIndexer(options.indexer);
60
+ this.watcher = null;
61
+ this.isScanning = false;
62
+
63
+ // Sync Engine
32
64
  this.syncEngine = null;
33
65
  this.syncEngineOptions = {
34
66
  apiKey: options.apiKey || process.env.MCFAST_TOKEN,
@@ -36,32 +68,44 @@ export class MemoryEngine {
36
68
  syncInterval: options.syncInterval || 5 * 60 * 1000
37
69
  };
38
70
 
39
- // Sử dụng UltraEnhancedEmbedder cho 90% accuracy
71
+ // Embedder
40
72
  this.embedder = new UltraEnhancedEmbedder({
41
73
  dimension: options.dimension || 1024,
42
74
  cacheSize: options.cacheSize || 2000,
43
75
  useMercury: options.useMercury || false
44
76
  });
45
77
 
46
- // Dashboard client for smart routing
78
+ // Smart routing
47
79
  this.dashboardClient = options.dashboardClient || new DashboardClient({
48
80
  apiKey: process.env.MCFAST_TOKEN
49
81
  });
50
-
51
- // Smart router - intelligent local vs Mercury decision
52
82
  this.smartRouter = new SmartRouter({
53
83
  dashboardClient: this.dashboardClient
54
84
  });
55
85
 
86
+ // Configuration
56
87
  this.useUltraHybrid = options.useUltraHybrid !== false;
57
88
  this.targetAccuracy = options.targetAccuracy || 90;
58
89
  this.smartRoutingEnabled = options.smartRoutingEnabled !== false;
59
90
 
60
- // Phase 4: Intelligence Layer
91
+ // Intelligence
61
92
  this.intelligenceEnabled = options.intelligenceEnabled !== false;
62
93
  this.patternDetector = null;
63
94
  this.suggestionEngine = null;
64
95
  this.strategySelector = null;
96
+
97
+ // Search configuration
98
+ this.searchConfig = {
99
+ hybrid: {
100
+ enabled: options.hybridSearch !== false,
101
+ vectorWeight: options.vectorWeight || 0.7,
102
+ textWeight: options.textWeight || 0.3,
103
+ minScore: options.minScore || 0.35,
104
+ candidateMultiplier: options.candidateMultiplier || 4
105
+ },
106
+ chunkSize: options.chunkSize || 400,
107
+ chunkOverlap: options.chunkOverlap || 80
108
+ };
65
109
  }
66
110
 
67
111
  async initialize(projectPath) {
@@ -69,24 +113,43 @@ export class MemoryEngine {
69
113
 
70
114
  this.projectPath = projectPath;
71
115
 
72
- // Set memory path to project directory
116
+ // Setup paths
73
117
  if (!this.memoryPath) {
74
- this.memoryPath = path.join(projectPath, '.mcfast', 'memory');
118
+ this.memoryPath = path.join(projectPath, '.mcfast');
75
119
  }
76
-
77
- // Update database path if not explicitly set
78
- if (!this.db.dbPath) {
79
- this.db.dbPath = path.join(this.memoryPath, 'index.db');
120
+ if (!this.indexPath) {
121
+ this.indexPath = path.join(this.memoryPath, 'index');
80
122
  }
81
123
 
82
- await this.db.initialize();
124
+ // Create directories
125
+ await fs.mkdir(this.memoryPath, { recursive: true });
126
+ await fs.mkdir(this.indexPath, { recursive: true });
83
127
 
84
- // Initialize Daily Logs with project-specific path
85
- this.dailyLogs = new DailyLogs({
86
- memoryPath: this.memoryPath
87
- });
128
+ console.log(`[MemoryEngine] Initializing...`);
129
+ console.log(`[MemoryEngine] Project: ${projectPath}`);
130
+ console.log(`[MemoryEngine] Memory path: ${this.memoryPath}`);
131
+
132
+ // Initialize bootstrap (AGENTS.md)
133
+ this.agentsBootstrap = new AgentsMdBootstrap({ projectPath });
134
+ await this.agentsBootstrap.bootstrap();
135
+
136
+ // Initialize databases
137
+ this.memoryDb = new MemoryDatabase(
138
+ path.join(this.indexPath, 'memory.sqlite')
139
+ );
140
+ this.codebaseDb = new CodebaseDatabase(
141
+ path.join(this.indexPath, 'codebase.sqlite')
142
+ );
88
143
 
89
- // Initialize Sync Engine if configured
144
+ await this.memoryDb.initialize();
145
+ await this.codebaseDb.initialize();
146
+
147
+ // Initialize two-tier memory
148
+ this.dailyLogs = new DailyLogs({ memoryPath: this.memoryPath });
149
+ this.curatedMemory = new CuratedMemory({ memoryPath: this.memoryPath });
150
+ await this.curatedMemory.ensureFile(); // Create MEMORY.md if not exists
151
+
152
+ // Initialize sync engine
90
153
  if (this.syncEngineOptions.apiKey && this.syncEngineOptions.enableSync !== false) {
91
154
  this.syncEngine = new SyncEngine({
92
155
  memoryPath: this.memoryPath,
@@ -96,31 +159,50 @@ export class MemoryEngine {
96
159
  });
97
160
  }
98
161
 
99
- this.watcher = new FileWatcher(projectPath, this);
162
+ // Initialize file watcher
163
+ this.watcher = new FileWatcher(projectPath, this, {
164
+ debounceMs: 1500,
165
+ ignored: [
166
+ '**/node_modules/**',
167
+ '**/.git/**',
168
+ '**/dist/**',
169
+ '**/build/**',
170
+ '**/.mcfast/**',
171
+ '**/*.log'
172
+ ]
173
+ });
100
174
  await this.watcher.start();
101
-
175
+
176
+ // Perform initial scan
177
+ await this.performInitialScan();
178
+
179
+ // Initialize intelligence
180
+ await this.initializeIntelligence();
181
+
102
182
  this.isInitialized = true;
103
- console.log(`[MemoryEngine] Initialized for: ${projectPath}`);
183
+
184
+ console.log(`[MemoryEngine] ✅ Initialized successfully`);
185
+ console.log(`[MemoryEngine] Hybrid Search: ${this.searchConfig.hybrid.enabled ? 'ENABLED' : 'DISABLED'}`);
104
186
  console.log(`[MemoryEngine] Smart Routing: ${this.smartRoutingEnabled ? 'ENABLED' : 'DISABLED'}`);
105
- console.log(`[MemoryEngine] Intelligence Layer: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
187
+ console.log(`[MemoryEngine] Intelligence: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
106
188
 
107
- // Initialize intelligence engines if enabled
108
- if (this.intelligenceEnabled) {
109
- try {
110
- const { PatternDetector, SuggestionEngine, StrategySelector } = await import('../intelligence/index.js');
111
- this.patternDetector = new PatternDetector({ memoryEngine: this });
112
- this.suggestionEngine = new SuggestionEngine({ memoryEngine: this });
113
- this.strategySelector = new StrategySelector({ memoryEngine: this });
114
- console.log('[MemoryEngine] Intelligence engines initialized');
115
- } catch (error) {
116
- console.warn('[MemoryEngine] Failed to initialize intelligence engines:', error.message);
117
- this.intelligenceEnabled = false;
118
- }
119
- }
189
+ // Log initialization to daily logs
190
+ await this.dailyLogs.log('Memory Engine Initialized', `Project: ${projectPath}`, {
191
+ stats: this.getStats()
192
+ });
193
+ }
194
+
195
+ async initializeIntelligence() {
196
+ if (!this.intelligenceEnabled) return;
120
197
 
121
- // Load intelligence models - run in background to not block initialization
122
- if (this.intelligenceEnabled) {
123
- // Start model loading in background without awaiting
198
+ try {
199
+ this.patternDetector = new PatternDetector({ memoryEngine: this });
200
+ this.suggestionEngine = new SuggestionEngine({ memoryEngine: this });
201
+ this.strategySelector = new StrategySelector({ memoryEngine: this });
202
+
203
+ console.log('[MemoryEngine] Intelligence engines initialized');
204
+
205
+ // Load models in background
124
206
  (async () => {
125
207
  try {
126
208
  const loadPromises = [
@@ -129,63 +211,250 @@ export class MemoryEngine {
129
211
  this.strategySelector.loadModel?.()
130
212
  ].filter(Boolean);
131
213
 
132
- // Add timeout to prevent hanging
133
214
  const timeoutPromise = new Promise((_, reject) =>
134
215
  setTimeout(() => reject(new Error('Model loading timeout')), 5000)
135
216
  );
136
217
 
137
- await Promise.race([
138
- Promise.all(loadPromises),
139
- timeoutPromise
140
- ]);
141
-
218
+ await Promise.race([Promise.all(loadPromises), timeoutPromise]);
142
219
  console.log('[MemoryEngine] ✅ Intelligence models loaded');
143
220
  } catch (error) {
144
221
  console.warn('[MemoryEngine] Model loading failed or timed out:', error.message);
145
- // Don't disable intelligence - models are optional
146
222
  }
147
223
  })();
224
+ } catch (error) {
225
+ console.warn('[MemoryEngine] Failed to initialize intelligence engines:', error.message);
226
+ this.intelligenceEnabled = false;
227
+ }
228
+ }
229
+
230
+ async performInitialScan() {
231
+ if (this.isScanning) return;
232
+ this.isScanning = true;
233
+
234
+ console.log('[MemoryEngine] 🔍 Performing initial codebase scan...');
235
+
236
+ try {
237
+ // Scan memory files first
238
+ await this.scanMemoryFiles();
239
+
240
+ // Scan codebase
241
+ await this.scanCodebase();
242
+
243
+ // Update indexing progress
244
+ this.codebaseDb?.completeIndexing?.();
245
+
246
+ console.log('[MemoryEngine] ✅ Initial scan complete');
247
+ console.log(`[MemoryEngine] Stats:`, this.getStats());
248
+
249
+ } catch (error) {
250
+ console.error('[MemoryEngine] Error during initial scan:', error);
251
+ } finally {
252
+ this.isScanning = false;
148
253
  }
149
254
  }
150
255
 
151
- async indexFile(filePath, content) {
152
- const indexed = await this.indexer.indexFile(filePath, content);
153
- await this.storeIndexed(indexed);
154
- return indexed;
256
+ async scanMemoryFiles() {
257
+ console.log('[MemoryEngine] Scanning memory files...');
258
+
259
+ try {
260
+ // Index MEMORY.md
261
+ const memoryFile = path.join(this.memoryPath, 'MEMORY.md');
262
+ if (await this.fileExists(memoryFile)) {
263
+ await this.indexMarkdownFile(memoryFile);
264
+ }
265
+
266
+ // Index daily logs
267
+ const memoryDir = path.join(this.memoryPath, 'memory');
268
+ if (await this.fileExists(memoryDir)) {
269
+ const files = await fs.readdir(memoryDir);
270
+ for (const file of files.filter(f => f.endsWith('.md'))) {
271
+ await this.indexMarkdownFile(path.join(memoryDir, file));
272
+ }
273
+ }
274
+
275
+ console.log('[MemoryEngine] Memory files indexed');
276
+ } catch (error) {
277
+ console.error('[MemoryEngine] Error scanning memory files:', error);
278
+ }
155
279
  }
156
280
 
281
+ async scanCodebase() {
282
+ console.log('[MemoryEngine] Scanning codebase...');
283
+
284
+ const extensions = ['.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.go', '.rs', '.cpp', '.c', '.h'];
285
+ const files = await this.findFiles(this.projectPath, extensions);
286
+
287
+ if (files.length === 0) {
288
+ console.log('[MemoryEngine] No code files found to index');
289
+ return;
290
+ }
291
+
292
+ console.log(`[MemoryEngine] Found ${files.length} files to index`);
293
+
294
+ // Start indexing progress
295
+ this.codebaseDb?.startIndexing?.(files.length);
296
+
297
+ let indexed = 0;
298
+ let failed = 0;
299
+
300
+ for (const filePath of files) {
301
+ try {
302
+ const content = await fs.readFile(filePath, 'utf-8');
303
+ const contentHash = crypto.createHash('md5').update(content).digest('hex');
304
+
305
+ // Skip if already indexed
306
+ if (this.codebaseDb?.isFileIndexed?.(filePath, contentHash)) {
307
+ continue;
308
+ }
309
+
310
+ // Index file
311
+ const indexedData = await this.indexer.indexFile(filePath, content);
312
+ await this.storeIndexed(indexedData);
313
+
314
+ indexed++;
315
+ if (indexed % 10 === 0) {
316
+ console.log(`[MemoryEngine] Indexed ${indexed}/${files.length} files...`);
317
+ }
318
+ } catch (error) {
319
+ console.warn(`[MemoryEngine] Failed to index ${filePath}:`, error.message);
320
+ failed++;
321
+ }
322
+ }
323
+
324
+ console.log(`[MemoryEngine] Codebase scan complete: ${indexed} indexed, ${failed} failed`);
325
+ }
326
+
327
+ async indexMarkdownFile(filePath) {
328
+ try {
329
+ const content = await fs.readFile(filePath, 'utf-8');
330
+ const contentHash = crypto.createHash('md5').update(content).digest('hex');
331
+ const stats = await fs.stat(filePath);
332
+ const relativePath = path.relative(this.projectPath, filePath);
333
+
334
+ // Skip if unchanged
335
+ if (this.memoryDb?.isFileIndexed?.(relativePath, contentHash)) {
336
+ return;
337
+ }
338
+
339
+ // Import chunker dynamically
340
+ const { MarkdownChunker } = await import('./utils/markdown-chunker.js');
341
+ const chunker = new MarkdownChunker({
342
+ chunkSize: this.searchConfig.chunkSize * 4, // chars
343
+ overlap: this.searchConfig.chunkOverlap * 4
344
+ });
345
+
346
+ const chunks = chunker.chunk(content, relativePath);
347
+
348
+ // Track file
349
+ this.memoryDb?.upsertFile?.(relativePath, contentHash, stats.mtimeMs, stats.size);
350
+
351
+ // Delete old chunks
352
+ this.memoryDb?.deleteChunksByFile?.(relativePath);
353
+
354
+ // Process chunks
355
+ for (const chunk of chunks) {
356
+ // Check embedding cache
357
+ let embedding = null;
358
+ const cached = this.memoryDb?.getCachedEmbedding?.(chunk.contentHash);
359
+
360
+ if (cached) {
361
+ embedding = new Float32Array(cached.embedding.buffer, cached.embedding.byteOffset, cached.dimensions);
362
+ } else {
363
+ // Generate embedding
364
+ const result = this.embedder.embedCode(chunk.content);
365
+ embedding = result.vector;
366
+
367
+ // Cache it
368
+ this.memoryDb?.cacheEmbedding?.(
369
+ chunk.contentHash,
370
+ Buffer.from(embedding.buffer),
371
+ 'simple-embedder',
372
+ embedding.length
373
+ );
374
+ }
375
+
376
+ // Insert chunk
377
+ this.memoryDb?.insertChunk?.({
378
+ id: chunk.id,
379
+ file_path: chunk.filePath,
380
+ start_line: chunk.startLine,
381
+ end_line: chunk.endLine,
382
+ content: chunk.content,
383
+ content_hash: chunk.contentHash,
384
+ chunk_type: chunk.chunkType
385
+ });
386
+
387
+ // Insert embedding
388
+ this.memoryDb?.insertEmbedding?.({
389
+ chunk_id: chunk.id,
390
+ embedding: Buffer.from(embedding.buffer),
391
+ model: 'simple-embedder',
392
+ dimensions: embedding.length
393
+ });
394
+ }
395
+
396
+ } catch (error) {
397
+ console.warn(`[MemoryEngine] Failed to index markdown ${filePath}:`, error.message);
398
+ }
399
+ }
400
+
401
+ async findFiles(dir, extensions) {
402
+ const files = [];
403
+ const ignored = ['node_modules', '.git', 'dist', 'build', '.mcfast'];
404
+
405
+ try {
406
+ const entries = await fs.readdir(dir, { withFileTypes: true });
407
+
408
+ for (const entry of entries) {
409
+ const fullPath = path.join(dir, entry.name);
410
+
411
+ if (entry.isDirectory() && !ignored.includes(entry.name)) {
412
+ const subFiles = await this.findFiles(fullPath, extensions);
413
+ files.push(...subFiles);
414
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
415
+ files.push(fullPath);
416
+ }
417
+ }
418
+ } catch (error) {
419
+ // Permission denied or other error, skip
420
+ }
421
+
422
+ return files;
423
+ }
424
+
425
+ async fileExists(filePath) {
426
+ try {
427
+ await fs.access(filePath);
428
+ return true;
429
+ } catch {
430
+ return false;
431
+ }
432
+ }
433
+
434
+ // ========== Storage Operations ==========
435
+
157
436
  async storeIndexed(indexed) {
158
- this.db.upsertFile(indexed.file);
159
- this.db.deleteFactsByFile(indexed.file.id);
160
- this.db.deleteChunksByFile(indexed.file.id);
437
+ this.codebaseDb?.upsertFile?.(indexed.file);
438
+ this.codebaseDb?.deleteFactsByFile?.(indexed.file.id);
439
+ this.codebaseDb?.deleteChunksByFile?.(indexed.file.id);
161
440
 
162
441
  for (const fact of indexed.facts) {
163
- this.db.insertFact(fact);
442
+ this.codebaseDb?.insertFact?.(fact);
164
443
  }
165
444
 
166
445
  for (const chunk of indexed.chunks) {
167
- this.db.insertChunk(chunk);
446
+ this.codebaseDb?.insertChunk?.(chunk);
168
447
  }
169
448
 
170
449
  for (const embedding of indexed.embeddings) {
171
- this.db.insertEmbedding(embedding);
450
+ this.codebaseDb?.insertEmbedding?.(embedding);
172
451
  }
173
452
  }
174
453
 
175
- async getFileByPath(filePath) {
176
- return this.db.getFileByPath(filePath);
177
- }
178
-
179
- async deleteFile(fileId) {
180
- return this.db.deleteFile(fileId);
181
- }
454
+ // ========== Search Operations ==========
182
455
 
183
456
  async searchFacts(query, limit = 20) {
184
- return this.db.searchFacts(query, limit);
185
- }
186
-
187
- async searchFTS(query, limit = 20) {
188
- return this.db.searchFTS(query, limit);
457
+ return this.codebaseDb?.searchFacts?.(query, limit) || [];
189
458
  }
190
459
 
191
460
  async searchVector(query, limit = 20) {
@@ -194,7 +463,7 @@ export class MemoryEngine {
194
463
  const queryResult = this.embedder.embedCode(query);
195
464
  const queryEmbedding = queryResult.vector;
196
465
 
197
- const allEmbeddings = this.db.getAllEmbeddings();
466
+ const allEmbeddings = this.codebaseDb?.getAllEmbeddings?.() || [];
198
467
 
199
468
  const scored = allEmbeddings.map(item => {
200
469
  const vector = Array.from(new Float32Array(item.embedding.buffer, item.embedding.byteOffset, item.embedding.byteLength / 4));
@@ -221,465 +490,199 @@ export class MemoryEngine {
221
490
  };
222
491
  }
223
492
 
224
- async searchEnhanced(query, limit = 20) {
493
+ /**
494
+ * Hybrid search: Combine Vector + BM25
495
+ * Vector 70% + BM25 30%
496
+ */
497
+ async searchHybrid(query, options = {}) {
225
498
  const startTime = performance.now();
499
+ const limit = options.limit || 10;
226
500
 
227
- // Lấy tất cả candidates từ database
228
- const allChunks = this.db.getAllChunksWithContent();
229
-
230
- // Chuẩn bị candidates cho ultra-hybrid search
231
- const candidates = allChunks.map(chunk => ({
232
- ...chunk,
233
- embedding: chunk.embedding ? Array.from(new Float32Array(chunk.embedding.buffer, chunk.embedding.byteOffset, chunk.embedding.byteLength / 4)) : null,
234
- code: chunk.content,
235
- filePath: chunk.path
236
- }));
237
-
238
- // Sử dụng ultra-hybrid search (6 techniques)
239
- const searchResult = await this.embedder.ultraHybridSearch(query, candidates, { limit });
501
+ console.log(`[MemoryEngine] 🔍 Hybrid search: "${query}"`);
240
502
 
241
- const duration = performance.now() - startTime;
503
+ // Get vector results first
504
+ const vectorResult = await this.searchVector(query, limit * 4);
242
505
 
243
- return {
244
- ...searchResult,
245
- metadata: {
246
- ...searchResult.metadata,
247
- totalDuration: duration.toFixed(2) + 'ms',
248
- method: 'ultra-hybrid-v2',
249
- targetAccuracy: this.targetAccuracy + '%'
250
- }
251
- };
252
- }
253
-
254
- async ultraSearch(query, options = {}) {
255
- const startTime = performance.now();
506
+ // Use database hybrid search
507
+ const dbResults = this.memoryDb?.searchHybrid?.(query, vectorResult.results, limit);
256
508
 
257
- console.log(`[MemoryEngine] 🎯 Ultra Search: "${query}" (Target: ${this.targetAccuracy}%)`);
258
-
259
- // Lấy candidates
260
- const allChunks = this.db.getAllChunksWithContent();
261
- const candidates = allChunks.map(chunk => ({
262
- ...chunk,
263
- embedding: chunk.embedding ? Array.from(new Float32Array(chunk.embedding.buffer, chunk.embedding.byteOffset, chunk.embedding.byteLength / 4)) : null,
264
- code: chunk.content,
265
- filePath: chunk.path
266
- }));
267
-
268
- // Ultra-hybrid search với 6 techniques
269
- const result = await this.embedder.ultraHybridSearch(query, candidates, {
270
- limit: options.limit || 10,
271
- ...options
272
- });
273
-
274
- // Record edit history để học từ searches
275
- if (options.currentFile) {
276
- const topResult = result.results[0];
277
- if (topResult && topResult.filePath) {
278
- this.embedder.recordEdit(options.currentFile, [topResult.filePath]);
279
- }
509
+ if (dbResults) {
510
+ const totalDuration = performance.now() - startTime;
511
+ return {
512
+ ...dbResults,
513
+ metadata: {
514
+ ...dbResults.metadata,
515
+ totalDuration: totalDuration.toFixed(2) + 'ms'
516
+ }
517
+ };
280
518
  }
281
519
 
282
- const duration = performance.now() - startTime;
283
-
284
- return {
285
- ...result,
286
- metadata: {
287
- ...result.metadata,
288
- totalDuration: duration.toFixed(2) + 'ms',
289
- method: 'ultra-search',
290
- accuracy: this.targetAccuracy + '%',
291
- techniques: ['vector', 'keyword', 'synonym', 'context', 'pattern', 'history']
292
- }
293
- };
520
+ // Fallback to vector-only if hybrid fails
521
+ return vectorResult;
294
522
  }
295
523
 
296
- async smartSearch(query, options = {}) {
524
+ async searchMemory(query, options = {}) {
297
525
  const {
298
526
  limit = 10,
299
- useUltra = true,
300
- useMercury = false,
301
- context = null
527
+ useHybrid = this.searchConfig.hybrid.enabled,
528
+ minScore = this.searchConfig.hybrid.minScore
302
529
  } = options;
303
530
 
304
- console.log(`[MemoryEngine] 🧠 Smart search: "${query}" (Ultra: ${useUltra}, Mercury: ${useMercury})`);
305
-
306
- // Option B: Mercury re-ranking
307
- if (useMercury && this.embedder.isMercuryEnabled()) {
308
- const allChunks = this.db.getAllChunksWithContent();
309
- const candidates = allChunks.map(chunk => ({
310
- ...chunk,
311
- embedding: chunk.embedding ? Array.from(new Float32Array(chunk.embedding.buffer, chunk.embedding.byteOffset, chunk.embedding.byteLength / 4)) : null,
312
- code: chunk.content,
313
- filePath: chunk.path
314
- }));
315
-
316
- return this.embedder.searchWithOptionalReranking(query, candidates, {
317
- limit,
318
- useMercury: true
319
- });
531
+ if (useHybrid) {
532
+ return this.searchHybrid(query, { limit, minScore });
320
533
  }
321
534
 
322
- // Option A: Ultra-hybrid (90% accuracy)
323
- if (useUltra) {
324
- return this.ultraSearch(query, { limit, ...options });
325
- }
326
-
327
- // Fallback: Enhanced search
328
- return this.searchEnhanced(query, limit);
535
+ return this.searchVector(query, limit);
329
536
  }
330
537
 
331
- async intelligentSearch(query, options = {}) {
332
- const startTime = performance.now();
538
+ async searchCodebase(query, limit = 20) {
539
+ // Search codebase using existing methods
540
+ const vectorResults = await this.searchVector(query, limit * 2);
541
+ const ftsResults = this.codebaseDb?.searchFTS?.(query, limit * 2);
333
542
 
334
- console.log(`[MemoryEngine] 🔍 Intelligent Search: "${query}"`);
335
-
336
- // 1. Always do local search first (fast)
337
- const localStart = performance.now();
338
- const localResult = await this.ultraSearch(query, {
339
- limit: 20, // Get more for smart decision
340
- ...options
341
- });
342
- const localDuration = performance.now() - localStart;
543
+ // Combine and deduplicate
544
+ const seen = new Set();
545
+ const combined = [];
343
546
 
344
- // 2. Smart router decides: need Mercury?
345
- const decision = await this.smartRouter.shouldUseMercury(query, localResult.results);
346
-
347
- console.log(`[MemoryEngine] 🤖 Smart decision: ${decision.useMercury ? 'USE MERCURY' : 'LOCAL ONLY'}`);
348
- console.log(`[MemoryEngine] Reason: ${decision.reason}`);
349
-
350
- // 3. If Mercury needed and enabled
351
- if (decision.useMercury && this.smartRouter.config.enableMercury) {
352
- try {
353
- const mercuryStart = performance.now();
354
-
355
- // Call Dashboard for Mercury re-ranking
356
- const mercuryResult = await this.dashboardClient.requestMercuryRerank(
357
- query,
358
- localResult.results
359
- );
360
-
361
- const mercuryDuration = performance.now() - mercuryStart;
362
- const totalDuration = performance.now() - startTime;
363
-
364
- // Log usage
365
- this.smartRouter.logUsage(query, true, mercuryDuration);
366
-
367
- // Record for learning
368
- this.smartRouter.recordResult(query, true, true);
369
-
370
- return {
371
- results: mercuryResult.results,
372
- metadata: {
373
- ...mercuryResult.metadata,
374
- localDuration: `${localDuration.toFixed(1)}ms`,
375
- mercuryDuration: `${mercuryDuration.toFixed(1)}ms`,
376
- totalDuration: `${totalDuration.toFixed(1)}ms`,
377
- method: 'intelligent-hybrid',
378
- routingDecision: decision,
379
- accuracy: '95%'
380
- }
381
- };
382
-
383
- } catch (error) {
384
- console.warn('[MemoryEngine] Mercury failed, fallback to local:', error.message);
385
-
386
- // Fallback to local results
387
- this.smartRouter.recordResult(query, false, true);
388
-
389
- return {
390
- ...localResult,
391
- metadata: {
392
- ...localResult.metadata,
393
- fallback: true,
394
- fallbackReason: error.message,
395
- routingDecision: decision
396
- }
397
- };
547
+ for (const r of vectorResults.results) {
548
+ if (!seen.has(r.chunk_id)) {
549
+ seen.add(r.chunk_id);
550
+ combined.push({ ...r, source: 'vector' });
398
551
  }
399
552
  }
400
553
 
401
- // 4. Return local results
402
- const totalDuration = performance.now() - startTime;
554
+ if (ftsResults?.results) {
555
+ for (const r of ftsResults.results) {
556
+ if (!seen.has(r.chunk_id)) {
557
+ seen.add(r.chunk_id);
558
+ combined.push({ ...r, source: 'fts' });
559
+ }
560
+ }
561
+ }
403
562
 
404
- this.smartRouter.recordResult(query, false, true);
563
+ // Sort by score and limit
564
+ combined.sort((a, b) => (b.similarity || b.score) - (a.similarity || a.score));
405
565
 
406
566
  return {
407
- ...localResult,
567
+ results: combined.slice(0, limit),
408
568
  metadata: {
409
- ...localResult.metadata,
410
- totalDuration: `${totalDuration.toFixed(1)}ms`,
411
- method: 'intelligent-local',
412
- routingDecision: decision,
413
- accuracy: '90%'
569
+ vectorCandidates: vectorResults.results.length,
570
+ ftsCandidates: ftsResults?.results?.length || 0,
571
+ totalCandidates: combined.length
414
572
  }
415
573
  };
416
574
  }
417
575
 
418
- // ==================== OPTION B: MERCURY INTEGRATION ====================
419
-
420
- /**
421
- * Enable Mercury re-ranking (Option B)
422
- */
423
- enableMercuryReRanking(apiKey, options = {}) {
424
- this.embedder.enableMercuryReRanking(apiKey, options);
425
- this.targetAccuracy = 95; // Tăng target lên 95%
426
- console.log(`[MemoryEngine] ✨ Mercury re-ranking enabled! Target accuracy: ${this.targetAccuracy}%`);
427
- }
428
-
429
- /**
430
- * Disable Mercury re-ranking
431
- */
432
- disableMercuryReRanking() {
433
- this.embedder.disableMercuryReRanking();
434
- this.targetAccuracy = 90; // Quay lại 90%
435
- console.log(`[MemoryEngine] Mercury disabled. Target accuracy: ${this.targetAccuracy}%`);
436
- }
437
-
438
- /**
439
- * Check Mercury status
440
- */
441
- isMercuryEnabled() {
442
- return this.embedder.isMercuryEnabled();
443
- }
444
-
445
- async recordEdit(edit) {
446
- return this.db.recordEdit({
447
- id: Math.random().toString(36).substring(2, 15),
448
- ...edit
449
- });
450
- }
451
-
452
- getStats() {
453
- return {
454
- ...this.db.getStats(),
455
- projectPath: this.projectPath
456
- };
457
- }
458
-
459
- /**
460
- * Get recent chunks from database
461
- * @param {number} limit - Number of chunks to retrieve
462
- * @returns {Array} Recent chunks
463
- */
464
- getRecentChunks(limit = 100) {
465
- return this.db.getRecentChunks(limit);
466
- }
467
-
468
- // ==================== SMART ROUTING CONTROLS ====================
576
+ // ========== Two-Tier Memory Operations ==========
469
577
 
470
578
  /**
471
- * Enable/disable smart routing
579
+ * Log to daily logs (Layer 1)
472
580
  */
473
- setSmartRouting(enabled) {
474
- this.smartRoutingEnabled = enabled;
475
- console.log(`[MemoryEngine] Smart routing ${enabled ? 'ENABLED' : 'DISABLED'}`);
581
+ async logToDaily(title, content, metadata = {}) {
582
+ return this.dailyLogs?.log?.(title, content, metadata);
476
583
  }
477
584
 
478
585
  /**
479
- * Get smart routing stats
586
+ * Add to curated memory (Layer 2)
480
587
  */
481
- getSmartRoutingStats() {
482
- return this.smartRouter.getStats();
588
+ async addToCurated(type, title, content) {
589
+ return this.curatedMemory?.addEntry?.(type, title, content);
483
590
  }
484
591
 
485
592
  /**
486
- * Refresh config from Dashboard
593
+ * Get today's log
487
594
  */
488
- async refreshConfig() {
489
- await this.smartRouter.loadConfig();
490
- console.log('[MemoryEngine] Config refreshed from Dashboard');
595
+ async getTodayLog() {
596
+ return this.dailyLogs?.getTodayLog?.();
491
597
  }
492
598
 
493
- // ==================== DAILY LOGS ====================
494
-
495
599
  /**
496
- * Log file creation event
600
+ * Get yesterday's log
497
601
  */
498
- logFileCreated(filePath, facts = []) {
499
- this.dailyLogs.logFileCreated(filePath, facts);
602
+ async getYesterdayLog() {
603
+ return this.dailyLogs?.getYesterdayLog?.();
500
604
  }
501
605
 
502
606
  /**
503
- * Log file modification event
607
+ * Get curated memory content
504
608
  */
505
- logFileModified(filePath, changes = {}) {
506
- this.dailyLogs.logFileModified(filePath, changes);
609
+ async getCuratedMemory() {
610
+ return this.curatedMemory?.read?.();
507
611
  }
508
612
 
509
613
  /**
510
- * Log file deletion event
614
+ * Log file operations
511
615
  */
512
- logFileDeleted(filePath) {
513
- this.dailyLogs.logFileDeleted(filePath);
616
+ async logFileCreated(filePath, facts = []) {
617
+ return this.dailyLogs?.logFileCreated?.(filePath, facts);
514
618
  }
515
619
 
516
- /**
517
- * Log an edit session
518
- */
519
- logEditToMemory(edit) {
520
- this.dailyLogs.logEdit(edit);
521
- // Also record to database
522
- this.recordEdit(edit);
620
+ async logFileModified(filePath, changes = {}) {
621
+ return this.dailyLogs?.logFileModified?.(filePath, changes);
523
622
  }
524
623
 
525
- /**
526
- * Add curated memory
527
- */
528
- addCuratedMemory(title, content, tags = []) {
529
- this.dailyLogs.addCuratedMemory(title, content, tags);
624
+ async logFileDeleted(filePath) {
625
+ return this.dailyLogs?.logFileDeleted?.(filePath);
530
626
  }
531
627
 
532
- /**
533
- * Get today's log
534
- */
535
- getTodayLog() {
536
- return this.dailyLogs.getTodayLog();
537
- }
538
-
539
- /**
540
- * Get curated memories
541
- */
542
- getCuratedMemories() {
543
- return this.dailyLogs.getCuratedMemories();
628
+ async logEdit(edit) {
629
+ // Log to daily
630
+ await this.dailyLogs?.logEdit?.(edit);
631
+
632
+ // Record to database
633
+ this.codebaseDb?.recordEdit?.({
634
+ id: Math.random().toString(36).substring(2, 15),
635
+ ...edit,
636
+ timestamp: Date.now()
637
+ });
544
638
  }
545
639
 
546
- // ==================== SYNC ENGINE ====================
640
+ // ========== Stats & Info ==========
547
641
 
548
- /**
549
- * Get sync status
550
- */
551
- getSyncStatus() {
552
- if (!this.syncEngine) {
553
- return { enabled: false };
554
- }
642
+ getStats() {
555
643
  return {
556
- enabled: true,
557
- ...this.syncEngine.getStatus()
644
+ memory: this.memoryDb?.getStats?.() || {},
645
+ codebase: this.codebaseDb?.getStats?.() || {},
646
+ indexing: this.codebaseDb?.getIndexingProgress?.() || {},
647
+ watcher: this.watcher?.getStats?.() || {},
648
+ projectPath: this.projectPath
558
649
  };
559
650
  }
560
651
 
561
- /**
562
- * Trigger manual sync
563
- */
564
- async triggerSync() {
565
- if (!this.syncEngine) {
566
- throw new Error('Sync engine not configured');
567
- }
568
- return this.syncEngine.sync();
569
- }
570
-
571
- /**
572
- * Queue change for sync
573
- */
574
- queueSyncChange(change) {
575
- if (this.syncEngine) {
576
- this.syncEngine.queueChange(change);
577
- }
652
+ getRecentChunks(limit = 100) {
653
+ return this.codebaseDb?.getRecentChunks?.(limit) || [];
578
654
  }
579
655
 
580
- // ==================== INTELLIGENCE LAYER (Phase 4) ====================
656
+ // ========== Intelligence Layer ==========
581
657
 
582
- /**
583
- * Detect patterns from edit history
584
- * @returns {Array} Detected patterns with confidence scores
585
- */
586
658
  async detectPatterns() {
587
659
  if (!this.intelligenceEnabled || !this.patternDetector) {
588
660
  throw new Error('Intelligence layer not enabled');
589
661
  }
590
-
591
- console.log('[MemoryEngine] 🔍 Detecting patterns...');
592
-
593
- const editHistory = await this.db.getRecentEdits(100);
594
- const patterns = await this.patternDetector.analyze(editHistory);
595
662
 
596
- console.log(`[MemoryEngine] Found ${patterns.length} patterns`);
597
- return patterns;
663
+ const editHistory = this.codebaseDb?.getRecentEdits?.(100) || [];
664
+ return this.patternDetector.analyze(editHistory);
598
665
  }
599
666
 
600
- /**
601
- * Get intelligent suggestions based on current context
602
- * @param {Object} context - Current editing context
603
- * @returns {Array} Suggestions with confidence scores
604
- */
605
667
  async getSuggestions(context = {}) {
606
668
  if (!this.intelligenceEnabled || !this.suggestionEngine) {
607
669
  throw new Error('Intelligence layer not enabled');
608
670
  }
609
-
610
- console.log('[MemoryEngine] 💡 Generating suggestions...');
611
-
612
- const suggestions = await this.suggestionEngine.getSuggestions(context);
613
671
 
614
- console.log(`[MemoryEngine] ✅ Generated ${suggestions.length} suggestions`);
615
- return suggestions;
672
+ return this.suggestionEngine.getSuggestions(context);
616
673
  }
617
674
 
618
- /**
619
- * Select best strategy for edit operation
620
- * @param {string} instruction - Edit instruction
621
- * @param {Object} context - Edit context
622
- * @returns {Object} Selected strategy with confidence
623
- */
624
675
  async selectStrategy(instruction, context = {}) {
625
676
  if (!this.intelligenceEnabled || !this.strategySelector) {
626
- // Fallback: use simple heuristics
627
677
  return this.fallbackStrategySelection(instruction, context);
628
678
  }
629
-
630
- console.log('[MemoryEngine] 🎯 Selecting strategy...');
631
-
632
- const result = await this.strategySelector.selectStrategy(instruction, context);
633
679
 
634
- console.log(`[MemoryEngine] ✅ Selected: ${result.strategy} (confidence: ${(result.confidence * 100).toFixed(1)}%)`);
635
- return result;
636
- }
637
-
638
- /**
639
- * Learn from edit result (improve future selections)
640
- * @param {Object} edit - Edit data
641
- * @param {Object} result - Edit result
642
- */
643
- async learnFromEdit(edit, result) {
644
- if (!this.intelligenceEnabled || !this.strategySelector) {
645
- return;
646
- }
647
-
648
- await this.strategySelector.learn(edit, result);
649
- console.log('[MemoryEngine] 🧠 Learned from edit result');
650
- }
651
-
652
- /**
653
- * Get intelligence layer statistics
654
- */
655
- getIntelligenceStats() {
656
- if (!this.intelligenceEnabled) {
657
- return { enabled: false };
658
- }
659
-
660
- return {
661
- enabled: true,
662
- patternDetector: this.patternDetector?.getStats() || null,
663
- suggestionEngine: this.suggestionEngine?.getStats() || null,
664
- strategySelector: this.strategySelector?.getStats() || null
665
- };
666
- }
667
-
668
- /**
669
- * Enable/disable intelligence layer
670
- */
671
- setIntelligenceEnabled(enabled) {
672
- this.intelligenceEnabled = enabled;
673
- console.log(`[MemoryEngine] Intelligence layer ${enabled ? 'ENABLED' : 'DISABLED'}`);
680
+ return this.strategySelector.selectStrategy(instruction, context);
674
681
  }
675
682
 
676
- /**
677
- * Fallback strategy selection when ML is disabled
678
- */
679
683
  fallbackStrategySelection(instruction, context) {
680
684
  const lower = instruction.toLowerCase();
681
685
 
682
- // Simple keyword-based selection
683
686
  if (lower.includes('replace') || lower.includes('change line') || lower.includes('exact')) {
684
687
  return { strategy: 'deterministic', confidence: 0.8, reason: 'Simple replacement detected' };
685
688
  }
@@ -691,19 +694,36 @@ export class MemoryEngine {
691
694
  return { strategy: 'cached', confidence: 0.6, reason: 'Default selection' };
692
695
  }
693
696
 
694
- // ==================== LIFECYCLE ====================
697
+ // ========== Sync Engine ==========
698
+
699
+ getSyncStatus() {
700
+ return this.syncEngine?.getStatus?.() || { enabled: false };
701
+ }
702
+
703
+ async triggerSync() {
704
+ if (!this.syncEngine) {
705
+ throw new Error('Sync engine not configured');
706
+ }
707
+ return this.syncEngine.sync();
708
+ }
709
+
710
+ // ========== Lifecycle ==========
695
711
 
696
712
  async shutdown() {
697
- // Save intelligence models
713
+ console.log('[MemoryEngine] Shutting down...');
714
+
698
715
  if (this.intelligenceEnabled) {
699
716
  await this.strategySelector?.saveModel?.();
700
- console.log('[MemoryEngine] 💾 Intelligence models saved');
701
717
  }
702
718
 
703
- if (this.watcher) await this.watcher.stop();
704
- if (this.syncEngine) this.syncEngine.shutdown();
705
- this.db.close();
719
+ await this.watcher?.stop?.();
720
+ this.syncEngine?.shutdown?.();
721
+
722
+ this.memoryDb?.close?.();
723
+ this.codebaseDb?.close?.();
724
+
706
725
  this.isInitialized = false;
726
+ console.log('[MemoryEngine] Shutdown complete');
707
727
  }
708
728
  }
709
729