@mrxkun/mcfast-mcp 4.0.1 → 4.0.3

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.
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Suggestion Engine
3
+ * Provides intelligent code suggestions based on context and patterns
4
+ * Phase 4: Intelligence Layer
5
+ * Version: 4.0.2
6
+ */
7
+
8
+ import { PatternDetector } from './pattern-detector.js';
9
+
10
+ export class SuggestionEngine {
11
+ constructor(options = {}) {
12
+ this.db = options.db || null;
13
+ this.detector = new PatternDetector(options);
14
+ this.memoryEngine = options.memoryEngine || null;
15
+ this.minConfidence = options.minConfidence || 0.70;
16
+ this.suggestionCache = new Map();
17
+ this.cacheTTL = 5 * 60 * 1000; // 5 minutes
18
+ }
19
+
20
+ /**
21
+ * Get intelligent suggestions based on current context
22
+ * @param {Object} context - Current editing context
23
+ * @returns {Array} Suggestions with confidence scores
24
+ */
25
+ async getSuggestions(context) {
26
+ const suggestions = [];
27
+
28
+ // 1. Naming inconsistency suggestions
29
+ const namingSuggestions = await this.suggestNamingFixes(context);
30
+ suggestions.push(...namingSuggestions);
31
+
32
+ // 2. Long function detection
33
+ const functionSuggestions = await this.suggestFunctionRefactoring(context);
34
+ suggestions.push(...functionSuggestions);
35
+
36
+ // 3. Test coverage suggestions
37
+ const testSuggestions = await this.suggestTestAdditions(context);
38
+ suggestions.push(...testSuggestions);
39
+
40
+ // 4. Import/organization suggestions
41
+ const importSuggestions = await this.suggestImportOrganization(context);
42
+ suggestions.push(...importSuggestions);
43
+
44
+ // 5. Duplicate code detection
45
+ const duplicateSuggestions = await this.suggestDuplicateCodeElimination(context);
46
+ suggestions.push(...duplicateSuggestions);
47
+
48
+ // 6. Performance optimization suggestions
49
+ const perfSuggestions = await this.suggestPerformanceImprovements(context);
50
+ suggestions.push(...perfSuggestions);
51
+
52
+ // Filter by confidence and sort
53
+ return suggestions
54
+ .filter(s => s.confidence >= this.minConfidence)
55
+ .sort((a, b) => b.confidence - a.confidence)
56
+ .slice(0, 10); // Max 10 suggestions
57
+ }
58
+
59
+ /**
60
+ * Suggest naming convention fixes
61
+ */
62
+ async suggestNamingFixes(context) {
63
+ const suggestions = [];
64
+ const editHistory = await this.getEditHistory();
65
+
66
+ // Find inconsistent naming patterns
67
+ const namingPatterns = this.analyzeNamingPatterns(editHistory);
68
+
69
+ for (const pattern of namingPatterns) {
70
+ if (pattern.inconsistency > 0.3) { // More than 30% inconsistency
71
+ suggestions.push({
72
+ type: 'rename',
73
+ category: 'naming',
74
+ description: `Inconsistent naming: "${pattern.current}" doesn't match convention "${pattern.convention}"`,
75
+ current: pattern.current,
76
+ suggested: pattern.suggested,
77
+ files: pattern.files,
78
+ confidence: pattern.confidence,
79
+ autoApply: false,
80
+ reason: `Found ${pattern.count} similar names using "${pattern.convention}" convention`,
81
+ action: {
82
+ type: 'refactor',
83
+ operation: 'rename_symbol',
84
+ symbol: pattern.current,
85
+ newName: pattern.suggested
86
+ }
87
+ });
88
+ }
89
+ }
90
+
91
+ return suggestions;
92
+ }
93
+
94
+ /**
95
+ * Suggest function extraction for long functions
96
+ */
97
+ async suggestFunctionRefactoring(context) {
98
+ const suggestions = [];
99
+
100
+ if (!context || !context.currentFile) return suggestions;
101
+
102
+ // Get facts about current file
103
+ const facts = await this.memoryEngine?.searchFacts(context.currentFile, 50);
104
+ if (!facts) return suggestions;
105
+
106
+ for (const fact of facts) {
107
+ if (fact.type === 'function' || fact.type === 'method') {
108
+ const lineCount = (fact.line_end || 0) - (fact.line_start || 0);
109
+
110
+ if (lineCount > 50) {
111
+ suggestions.push({
112
+ type: 'extract_function',
113
+ category: 'refactoring',
114
+ description: `Function "${fact.name}" is ${lineCount} lines long. Consider extracting parts into smaller functions.`,
115
+ target: fact.name,
116
+ file: context.currentFile,
117
+ lineCount,
118
+ confidence: Math.min(0.75 + (lineCount - 50) / 100, 0.95),
119
+ autoApply: false,
120
+ reason: `Functions longer than 50 lines are harder to maintain and test`,
121
+ action: {
122
+ type: 'refactor',
123
+ operation: 'extract_function',
124
+ function: fact.name,
125
+ file: context.currentFile,
126
+ lines: [fact.line_start, fact.line_end]
127
+ }
128
+ });
129
+ }
130
+ }
131
+ }
132
+
133
+ return suggestions;
134
+ }
135
+
136
+ /**
137
+ * Suggest adding tests for uncovered code
138
+ */
139
+ async suggestTestAdditions(context) {
140
+ const suggestions = [];
141
+
142
+ // Get recently modified files
143
+ const recentEdits = await this.getRecentEdits(20);
144
+ const filesToCheck = new Set();
145
+
146
+ for (const edit of recentEdits) {
147
+ if (edit.files) {
148
+ edit.files.forEach(f => {
149
+ if (!f.includes('.test.') && !f.includes('.spec.') && !f.includes('__tests__')) {
150
+ filesToCheck.add(f);
151
+ }
152
+ });
153
+ }
154
+ }
155
+
156
+ // Check for test files
157
+ for (const file of filesToCheck) {
158
+ const hasTest = await this.checkHasTestFile(file);
159
+
160
+ if (!hasTest) {
161
+ suggestions.push({
162
+ type: 'add_tests',
163
+ category: 'testing',
164
+ description: `No tests found for "${file}". Consider adding test coverage.`,
165
+ target: file,
166
+ confidence: 0.80,
167
+ autoApply: false,
168
+ reason: 'Recently modified file without test coverage',
169
+ action: {
170
+ type: 'create',
171
+ operation: 'generate_tests',
172
+ target: file
173
+ }
174
+ });
175
+ }
176
+ }
177
+
178
+ return suggestions;
179
+ }
180
+
181
+ /**
182
+ * Suggest import organization
183
+ */
184
+ async suggestImportOrganization(context) {
185
+ const suggestions = [];
186
+
187
+ if (!context || !context.currentFile) return suggestions;
188
+
189
+ // Check for unused imports by looking at facts
190
+ const facts = await this.memoryEngine?.searchFacts(context.currentFile, 100);
191
+ if (!facts) return suggestions;
192
+
193
+ const imports = facts.filter(f => f.type === 'import');
194
+ const exports = facts.filter(f => f.type === 'export' || f.type === 'function' || f.type === 'class');
195
+
196
+ for (const imp of imports) {
197
+ // Check if import is used in exports
198
+ const isUsed = exports.some(exp =>
199
+ exp.name === imp.name ||
200
+ exp.content?.includes(imp.name)
201
+ );
202
+
203
+ if (!isUsed) {
204
+ suggestions.push({
205
+ type: 'remove_unused_import',
206
+ category: 'organization',
207
+ description: `Import "${imp.name}" appears unused in "${context.currentFile}"`,
208
+ target: imp.name,
209
+ file: context.currentFile,
210
+ confidence: 0.85,
211
+ autoApply: false,
212
+ reason: 'Import is not referenced in any exports or function bodies',
213
+ action: {
214
+ type: 'remove',
215
+ operation: 'remove_import',
216
+ import: imp.name,
217
+ file: context.currentFile
218
+ }
219
+ });
220
+ }
221
+ }
222
+
223
+ return suggestions;
224
+ }
225
+
226
+ /**
227
+ * Suggest eliminating duplicate code
228
+ */
229
+ async suggestDuplicateCodeElimination(context) {
230
+ const suggestions = [];
231
+
232
+ // Get recent code chunks
233
+ const chunks = await this.memoryEngine?.getRecentChunks(100);
234
+ if (!chunks || chunks.length < 2) return suggestions;
235
+
236
+ // Find similar chunks (simple similarity check)
237
+ const duplicates = this.findDuplicateChunks(chunks);
238
+
239
+ for (const dup of duplicates) {
240
+ suggestions.push({
241
+ type: 'extract_duplicate',
242
+ category: 'refactoring',
243
+ description: `Duplicate code detected: Similar code found in ${dup.files.length} locations`,
244
+ files: dup.files,
245
+ similarity: dup.similarity,
246
+ confidence: dup.similarity,
247
+ autoApply: false,
248
+ reason: `Code similarity: ${(dup.similarity * 100).toFixed(0)}%`,
249
+ action: {
250
+ type: 'refactor',
251
+ operation: 'extract_common',
252
+ files: dup.files,
253
+ similarity: dup.similarity
254
+ }
255
+ });
256
+ }
257
+
258
+ return suggestions;
259
+ }
260
+
261
+ /**
262
+ * Suggest performance improvements
263
+ */
264
+ async suggestPerformanceImprovements(context) {
265
+ const suggestions = [];
266
+
267
+ if (!context || !context.currentFile) return suggestions;
268
+
269
+ // Check for known anti-patterns
270
+ const facts = await this.memoryEngine?.searchFacts(context.currentFile, 50);
271
+ if (!facts) return suggestions;
272
+
273
+ for (const fact of facts) {
274
+ // Check for console.log in production code
275
+ if (fact.content?.includes('console.log')) {
276
+ suggestions.push({
277
+ type: 'remove_debug_code',
278
+ category: 'performance',
279
+ description: `console.log found in "${fact.name}". Consider removing for production.`,
280
+ target: fact.name,
281
+ file: context.currentFile,
282
+ confidence: 0.75,
283
+ autoApply: false,
284
+ reason: 'Debug code should not be in production',
285
+ action: {
286
+ type: 'remove',
287
+ operation: 'remove_console_logs',
288
+ file: context.currentFile
289
+ }
290
+ });
291
+ }
292
+
293
+ // Check for nested loops (potential O(n²))
294
+ if (fact.content?.includes('for') && fact.content?.split('for').length > 2) {
295
+ suggestions.push({
296
+ type: 'optimize_complexity',
297
+ category: 'performance',
298
+ description: `Nested loops detected in "${fact.name}". Consider optimizing algorithm.`,
299
+ target: fact.name,
300
+ file: context.currentFile,
301
+ confidence: 0.70,
302
+ autoApply: false,
303
+ reason: 'Nested loops may cause performance issues with large datasets',
304
+ action: {
305
+ type: 'refactor',
306
+ operation: 'optimize_loops',
307
+ function: fact.name,
308
+ file: context.currentFile
309
+ }
310
+ });
311
+ }
312
+ }
313
+
314
+ return suggestions;
315
+ }
316
+
317
+ /**
318
+ * Analyze naming patterns from edit history
319
+ */
320
+ analyzeNamingPatterns(editHistory) {
321
+ const patterns = [];
322
+ const namingConventions = {
323
+ camelCase: /^[a-z][a-zA-Z0-9]*$/,
324
+ PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
325
+ snake_case: /^[a-z][a-z0-9_]*$/,
326
+ SCREAMING_SNAKE: /^[A-Z][A-Z0-9_]*$/
327
+ };
328
+
329
+ // Collect all identifiers
330
+ const identifiers = [];
331
+ for (const edit of editHistory) {
332
+ if (edit.instruction) {
333
+ const words = edit.instruction.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g) || [];
334
+ identifiers.push(...words);
335
+ }
336
+ }
337
+
338
+ // Count by convention
339
+ const conventionCounts = {};
340
+ for (const id of identifiers) {
341
+ for (const [name, regex] of Object.entries(namingConventions)) {
342
+ if (regex.test(id)) {
343
+ conventionCounts[name] = (conventionCounts[name] || 0) + 1;
344
+ break;
345
+ }
346
+ }
347
+ }
348
+
349
+ // Find dominant convention
350
+ const dominant = Object.entries(conventionCounts)
351
+ .sort((a, b) => b[1] - a[1])[0];
352
+
353
+ if (dominant) {
354
+ // Find violations
355
+ for (const id of identifiers) {
356
+ const matchesDominant = namingConventions[dominant[0]].test(id);
357
+ if (!matchesDominant) {
358
+ const currentConvention = Object.entries(namingConventions)
359
+ .find(([_, regex]) => regex.test(id))?.[0] || 'unknown';
360
+
361
+ patterns.push({
362
+ current: id,
363
+ convention: dominant[0],
364
+ currentConvention,
365
+ suggested: this.convertNaming(id, dominant[0]),
366
+ count: dominant[1],
367
+ inconsistency: 1 - (conventionCounts[currentConvention] || 0) / identifiers.length,
368
+ confidence: Math.min(0.70 + dominant[1] / 100, 0.95)
369
+ });
370
+ }
371
+ }
372
+ }
373
+
374
+ return patterns;
375
+ }
376
+
377
+ /**
378
+ * Convert naming convention
379
+ */
380
+ convertNaming(name, targetConvention) {
381
+ // Simple conversion logic
382
+ switch (targetConvention) {
383
+ case 'camelCase':
384
+ return name.toLowerCase().replace(/[_-](.)/g, (_, c) => c.toUpperCase());
385
+ case 'PascalCase':
386
+ return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase().replace(/[_-](.)/g, (_, c) => c.toUpperCase());
387
+ case 'snake_case':
388
+ return name.replace(/[A-Z]/g, c => '_' + c.toLowerCase()).replace(/^_/, '');
389
+ default:
390
+ return name;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Find duplicate code chunks
396
+ */
397
+ findDuplicateChunks(chunks) {
398
+ const duplicates = [];
399
+ const seen = new Map();
400
+
401
+ for (const chunk of chunks) {
402
+ const normalized = this.normalizeCode(chunk.content);
403
+
404
+ if (seen.has(normalized)) {
405
+ const existing = seen.get(normalized);
406
+ existing.files.push(chunk.file_id);
407
+ existing.count++;
408
+ } else {
409
+ seen.set(normalized, {
410
+ content: chunk.content,
411
+ files: [chunk.file_id],
412
+ count: 1
413
+ });
414
+ }
415
+ }
416
+
417
+ for (const [_, data] of seen) {
418
+ if (data.count >= 2) {
419
+ duplicates.push({
420
+ files: data.files,
421
+ similarity: 1.0, // Exact match for now
422
+ count: data.count
423
+ });
424
+ }
425
+ }
426
+
427
+ return duplicates;
428
+ }
429
+
430
+ /**
431
+ * Normalize code for comparison
432
+ */
433
+ normalizeCode(code) {
434
+ return code
435
+ .replace(/\s+/g, ' ')
436
+ .replace(/\n/g, ' ')
437
+ .replace(/\/\/.*$/gm, '')
438
+ .replace(/\/\*[\s\S]*?\*\//g, '')
439
+ .trim()
440
+ .toLowerCase();
441
+ }
442
+
443
+ /**
444
+ * Check if file has corresponding test file
445
+ */
446
+ async checkHasTestFile(file) {
447
+ const testPatterns = [
448
+ file.replace(/\.(js|ts|jsx|tsx)$/, '.test.$1'),
449
+ file.replace(/\.(js|ts|jsx|tsx)$/, '.spec.$1'),
450
+ file.replace(/\/([^/]+)\.(js|ts|jsx|tsx)$/, '/__tests__/$1.test.$2')
451
+ ];
452
+
453
+ for (const pattern of testPatterns) {
454
+ // Check in memory database
455
+ const exists = await this.memoryEngine?.db?.getFileByPath(pattern);
456
+ if (exists) return true;
457
+ }
458
+
459
+ return false;
460
+ }
461
+
462
+ /**
463
+ * Get edit history from database
464
+ */
465
+ async getEditHistory(limit = 100) {
466
+ if (!this.memoryEngine?.db) return [];
467
+ return this.memoryEngine.db.getRecentEdits(limit);
468
+ }
469
+
470
+ /**
471
+ * Get recent edits
472
+ */
473
+ async getRecentEdits(limit = 20) {
474
+ return this.getEditHistory(limit);
475
+ }
476
+
477
+ /**
478
+ * Get engine stats
479
+ */
480
+ getStats() {
481
+ return {
482
+ minConfidence: this.minConfidence,
483
+ cacheSize: this.suggestionCache.size,
484
+ detectorStats: this.detector.getStats()
485
+ };
486
+ }
487
+ }
488
+
489
+ export default SuggestionEngine;
@@ -12,3 +12,6 @@ export { DailyLogs } from './utils/daily-logs.js';
12
12
  export { SyncEngine } from './utils/sync-engine.js';
13
13
  export { MemoryEngine } from './memory-engine.js';
14
14
  export { MemoryEngine as default } from './memory-engine.js';
15
+
16
+ // Phase 4: Intelligence Layer
17
+ export { PatternDetector, SuggestionEngine, StrategySelector } from '../intelligence/index.js';
@@ -11,6 +11,7 @@ import { SmartRouter } from './utils/smart-router.js';
11
11
  import { DashboardClient } from './utils/dashboard-client.js';
12
12
  import { DailyLogs } from './utils/daily-logs.js';
13
13
  import { SyncEngine } from './utils/sync-engine.js';
14
+ import { PatternDetector, SuggestionEngine, StrategySelector } from '../intelligence/index.js';
14
15
  import path from 'path';
15
16
  import os from 'os';
16
17
 
@@ -30,13 +31,14 @@ export class MemoryEngine {
30
31
  memoryPath: this.memoryPath
31
32
  });
32
33
 
33
- // Sync Engine - local ↔ cloud (optional)
34
+ // Sync Engine - local ↔ cloud (optional, auto-detect from MCFAST_TOKEN)
34
35
  this.syncEngine = null;
35
- if (options.cloudEndpoint && options.apiKey) {
36
+ const hasToken = options.apiKey || process.env.MCFAST_TOKEN;
37
+ if (hasToken && options.enableSync !== false) {
36
38
  this.syncEngine = new SyncEngine({
37
39
  memoryPath: this.memoryPath,
38
- cloudEndpoint: options.cloudEndpoint,
39
- apiKey: options.apiKey,
40
+ apiKey: hasToken,
41
+ baseUrl: options.baseUrl || process.env.MCFAST_DASHBOARD_URL || 'https://mcfast.vercel.app',
40
42
  syncInterval: options.syncInterval || 5 * 60 * 1000
41
43
  });
42
44
  }
@@ -61,6 +63,21 @@ export class MemoryEngine {
61
63
  this.useUltraHybrid = options.useUltraHybrid !== false;
62
64
  this.targetAccuracy = options.targetAccuracy || 90;
63
65
  this.smartRoutingEnabled = options.smartRoutingEnabled !== false;
66
+
67
+ // Phase 4: Intelligence Layer
68
+ this.intelligenceEnabled = options.intelligenceEnabled !== false;
69
+ this.patternDetector = null;
70
+ this.suggestionEngine = null;
71
+ this.strategySelector = null;
72
+
73
+ if (this.intelligenceEnabled) {
74
+ this.patternDetector = new PatternDetector({ db: this.db });
75
+ this.suggestionEngine = new SuggestionEngine({
76
+ db: this.db,
77
+ memoryEngine: this
78
+ });
79
+ this.strategySelector = new StrategySelector({ db: this.db });
80
+ }
64
81
  }
65
82
 
66
83
  async initialize(projectPath) {
@@ -95,6 +112,15 @@ export class MemoryEngine {
95
112
  this.isInitialized = true;
96
113
  console.log(`[MemoryEngine] Initialized for: ${projectPath}`);
97
114
  console.log(`[MemoryEngine] Smart Routing: ${this.smartRoutingEnabled ? 'ENABLED' : 'DISABLED'}`);
115
+ console.log(`[MemoryEngine] Intelligence Layer: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
116
+
117
+ // Load intelligence models
118
+ if (this.intelligenceEnabled) {
119
+ await this.patternDetector.loadModel?.();
120
+ await this.suggestionEngine.loadModel?.();
121
+ await this.strategySelector.loadModel?.();
122
+ console.log('[MemoryEngine] ✅ Intelligence models loaded');
123
+ }
98
124
  }
99
125
 
100
126
  async indexFile(filePath, content) {
@@ -517,9 +543,129 @@ export class MemoryEngine {
517
543
  }
518
544
  }
519
545
 
546
+ // ==================== INTELLIGENCE LAYER (Phase 4) ====================
547
+
548
+ /**
549
+ * Detect patterns from edit history
550
+ * @returns {Array} Detected patterns with confidence scores
551
+ */
552
+ async detectPatterns() {
553
+ if (!this.intelligenceEnabled || !this.patternDetector) {
554
+ throw new Error('Intelligence layer not enabled');
555
+ }
556
+
557
+ console.log('[MemoryEngine] 🔍 Detecting patterns...');
558
+
559
+ const editHistory = await this.db.getRecentEdits(100);
560
+ const patterns = await this.patternDetector.analyze(editHistory);
561
+
562
+ console.log(`[MemoryEngine] ✅ Found ${patterns.length} patterns`);
563
+ return patterns;
564
+ }
565
+
566
+ /**
567
+ * Get intelligent suggestions based on current context
568
+ * @param {Object} context - Current editing context
569
+ * @returns {Array} Suggestions with confidence scores
570
+ */
571
+ async getSuggestions(context = {}) {
572
+ if (!this.intelligenceEnabled || !this.suggestionEngine) {
573
+ throw new Error('Intelligence layer not enabled');
574
+ }
575
+
576
+ console.log('[MemoryEngine] 💡 Generating suggestions...');
577
+
578
+ const suggestions = await this.suggestionEngine.getSuggestions(context);
579
+
580
+ console.log(`[MemoryEngine] ✅ Generated ${suggestions.length} suggestions`);
581
+ return suggestions;
582
+ }
583
+
584
+ /**
585
+ * Select best strategy for edit operation
586
+ * @param {string} instruction - Edit instruction
587
+ * @param {Object} context - Edit context
588
+ * @returns {Object} Selected strategy with confidence
589
+ */
590
+ async selectStrategy(instruction, context = {}) {
591
+ if (!this.intelligenceEnabled || !this.strategySelector) {
592
+ // Fallback: use simple heuristics
593
+ return this.fallbackStrategySelection(instruction, context);
594
+ }
595
+
596
+ console.log('[MemoryEngine] 🎯 Selecting strategy...');
597
+
598
+ const result = await this.strategySelector.selectStrategy(instruction, context);
599
+
600
+ console.log(`[MemoryEngine] ✅ Selected: ${result.strategy} (confidence: ${(result.confidence * 100).toFixed(1)}%)`);
601
+ return result;
602
+ }
603
+
604
+ /**
605
+ * Learn from edit result (improve future selections)
606
+ * @param {Object} edit - Edit data
607
+ * @param {Object} result - Edit result
608
+ */
609
+ async learnFromEdit(edit, result) {
610
+ if (!this.intelligenceEnabled || !this.strategySelector) {
611
+ return;
612
+ }
613
+
614
+ await this.strategySelector.learn(edit, result);
615
+ console.log('[MemoryEngine] 🧠 Learned from edit result');
616
+ }
617
+
618
+ /**
619
+ * Get intelligence layer statistics
620
+ */
621
+ getIntelligenceStats() {
622
+ if (!this.intelligenceEnabled) {
623
+ return { enabled: false };
624
+ }
625
+
626
+ return {
627
+ enabled: true,
628
+ patternDetector: this.patternDetector?.getStats() || null,
629
+ suggestionEngine: this.suggestionEngine?.getStats() || null,
630
+ strategySelector: this.strategySelector?.getStats() || null
631
+ };
632
+ }
633
+
634
+ /**
635
+ * Enable/disable intelligence layer
636
+ */
637
+ setIntelligenceEnabled(enabled) {
638
+ this.intelligenceEnabled = enabled;
639
+ console.log(`[MemoryEngine] Intelligence layer ${enabled ? 'ENABLED' : 'DISABLED'}`);
640
+ }
641
+
642
+ /**
643
+ * Fallback strategy selection when ML is disabled
644
+ */
645
+ fallbackStrategySelection(instruction, context) {
646
+ const lower = instruction.toLowerCase();
647
+
648
+ // Simple keyword-based selection
649
+ if (lower.includes('replace') || lower.includes('change line') || lower.includes('exact')) {
650
+ return { strategy: 'deterministic', confidence: 0.8, reason: 'Simple replacement detected' };
651
+ }
652
+
653
+ if (lower.includes('refactor') || lower.includes('improve') || lower.includes('multiple')) {
654
+ return { strategy: 'llm', confidence: 0.8, reason: 'Complex operation detected' };
655
+ }
656
+
657
+ return { strategy: 'cached', confidence: 0.6, reason: 'Default selection' };
658
+ }
659
+
520
660
  // ==================== LIFECYCLE ====================
521
661
 
522
662
  async shutdown() {
663
+ // Save intelligence models
664
+ if (this.intelligenceEnabled) {
665
+ await this.strategySelector?.saveModel?.();
666
+ console.log('[MemoryEngine] 💾 Intelligence models saved');
667
+ }
668
+
523
669
  if (this.watcher) await this.watcher.stop();
524
670
  if (this.syncEngine) this.syncEngine.shutdown();
525
671
  this.db.close();
@@ -69,10 +69,13 @@ export class Chunker {
69
69
  createChunk(lines, startLine, endLine, metadata) {
70
70
  const content = lines.join('\n');
71
71
  const tokens = this.estimateTokens(content);
72
+
73
+ // Handle both string (filePath) and object metadata
74
+ const filePath = typeof metadata === 'string' ? metadata : (metadata.filePath || '');
72
75
 
73
76
  return {
74
- id: this.generateChunkId(metadata.filePath, startLine),
75
- file_id: this.generateFileId(metadata.filePath),
77
+ id: this.generateChunkId(filePath, startLine),
78
+ file_id: this.generateFileId(filePath),
76
79
  content: content,
77
80
  start_line: startLine,
78
81
  end_line: endLine,