@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.
- package/README.md +59 -3
- package/package.json +2 -2
- package/src/index.js +476 -322
- package/src/intelligence/index.js +12 -0
- package/src/intelligence/pattern-detector.js +338 -0
- package/src/intelligence/strategy-selector.js +362 -0
- package/src/intelligence/suggestion-engine.js +489 -0
- package/src/memory/index.js +3 -0
- package/src/memory/memory-engine.js +150 -4
- package/src/memory/utils/chunker.js +5 -2
- package/src/memory/utils/sync-engine.js +32 -17
- package/src/tools/memory_get.js +311 -204
- package/src/tools/memory_search.js +170 -251
|
@@ -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;
|
package/src/memory/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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(
|
|
75
|
-
file_id: this.generateFileId(
|
|
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,
|