@nclamvn/vibecode-cli 1.2.0 → 1.5.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.
@@ -0,0 +1,516 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE AGENT - Self-Healing Engine
3
+ // Automatic error analysis, fix generation, and recovery
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import { analyzeErrors } from '../core/error-analyzer.js';
7
+ import { generateFixPrompt } from '../core/fix-generator.js';
8
+
9
+ /**
10
+ * Error categories for intelligent handling
11
+ */
12
+ const ERROR_CATEGORIES = {
13
+ SYNTAX: {
14
+ patterns: [/SyntaxError/, /Unexpected token/, /Parse error/],
15
+ strategy: 'syntax_fix',
16
+ canAutoFix: true
17
+ },
18
+ IMPORT: {
19
+ patterns: [/Cannot find module/, /Module not found/, /import.*from/],
20
+ strategy: 'import_fix',
21
+ canAutoFix: true
22
+ },
23
+ TYPE: {
24
+ patterns: [/TypeError/, /is not a function/, /undefined is not/],
25
+ strategy: 'type_fix',
26
+ canAutoFix: true
27
+ },
28
+ REFERENCE: {
29
+ patterns: [/ReferenceError/, /is not defined/],
30
+ strategy: 'reference_fix',
31
+ canAutoFix: true
32
+ },
33
+ DEPENDENCY: {
34
+ patterns: [/peer dep/, /ERESOLVE/, /npm ERR!/, /dependency/],
35
+ strategy: 'dependency_fix',
36
+ canAutoFix: true
37
+ },
38
+ BUILD: {
39
+ patterns: [/Build failed/, /Compilation error/, /webpack/],
40
+ strategy: 'build_fix',
41
+ canAutoFix: true
42
+ },
43
+ TEST: {
44
+ patterns: [/test failed/i, /Tests failed/i, /expect\(/, /AssertionError/, /FAIL/i],
45
+ strategy: 'test_fix',
46
+ canAutoFix: true
47
+ },
48
+ RUNTIME: {
49
+ patterns: [/at runtime/, /ENOENT/, /EACCES/, /ECONNREFUSED/],
50
+ strategy: 'runtime_fix',
51
+ canAutoFix: false
52
+ },
53
+ UNKNOWN: {
54
+ patterns: [],
55
+ strategy: 'generic_fix',
56
+ canAutoFix: false
57
+ }
58
+ };
59
+
60
+ /**
61
+ * Self-Healing Engine Class
62
+ * Analyzes errors and generates fixes automatically
63
+ */
64
+ export class SelfHealingEngine {
65
+ constructor(memoryEngine = null) {
66
+ this.memory = memoryEngine;
67
+ this.healingHistory = [];
68
+ this.maxRetries = 3;
69
+ }
70
+
71
+ /**
72
+ * Set memory engine for learning
73
+ */
74
+ setMemoryEngine(memoryEngine) {
75
+ this.memory = memoryEngine;
76
+ }
77
+
78
+ /**
79
+ * Analyze an error and determine fix strategy
80
+ */
81
+ analyzeError(error) {
82
+ const errorStr = typeof error === 'string' ? error : error.message || String(error);
83
+ const errorLower = errorStr.toLowerCase();
84
+
85
+ // Determine category
86
+ let category = 'UNKNOWN';
87
+ for (const [cat, config] of Object.entries(ERROR_CATEGORIES)) {
88
+ if (config.patterns.some(p => p.test(errorStr))) {
89
+ category = cat;
90
+ break;
91
+ }
92
+ }
93
+
94
+ const config = ERROR_CATEGORIES[category];
95
+
96
+ // Extract file and line info if present
97
+ const fileMatch = errorStr.match(/(?:at |in |file:?\s*)([^\s:]+):(\d+)/i);
98
+ const file = fileMatch ? fileMatch[1] : null;
99
+ const line = fileMatch ? parseInt(fileMatch[2]) : null;
100
+
101
+ return {
102
+ original: errorStr,
103
+ category,
104
+ strategy: config.strategy,
105
+ canAutoFix: config.canAutoFix,
106
+ file,
107
+ line,
108
+ severity: this.calculateSeverity(category, errorStr),
109
+ suggestedActions: this.suggestActions(category, errorStr)
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Calculate error severity
115
+ */
116
+ calculateSeverity(category, errorStr) {
117
+ // Critical: blocks all builds
118
+ if (['SYNTAX', 'IMPORT', 'DEPENDENCY'].includes(category)) {
119
+ return 'critical';
120
+ }
121
+
122
+ // High: blocks module build
123
+ if (['TYPE', 'REFERENCE', 'BUILD'].includes(category)) {
124
+ return 'high';
125
+ }
126
+
127
+ // Medium: can continue but needs fixing
128
+ if (category === 'TEST') {
129
+ return 'medium';
130
+ }
131
+
132
+ // Low: warning-level
133
+ return 'low';
134
+ }
135
+
136
+ /**
137
+ * Suggest actions based on error type
138
+ */
139
+ suggestActions(category, errorStr) {
140
+ const actions = [];
141
+
142
+ switch (category) {
143
+ case 'SYNTAX':
144
+ actions.push('Check for missing brackets, semicolons, or quotes');
145
+ actions.push('Verify JSX syntax if using React');
146
+ break;
147
+
148
+ case 'IMPORT':
149
+ actions.push('Install missing package with npm install');
150
+ actions.push('Check import path is correct');
151
+ actions.push('Verify file exists at specified path');
152
+ break;
153
+
154
+ case 'TYPE':
155
+ actions.push('Check if variable is properly initialized');
156
+ actions.push('Verify function exists before calling');
157
+ actions.push('Add null/undefined checks');
158
+ break;
159
+
160
+ case 'REFERENCE':
161
+ actions.push('Declare variable before using');
162
+ actions.push('Check for typos in variable names');
163
+ actions.push('Verify import statement');
164
+ break;
165
+
166
+ case 'DEPENDENCY':
167
+ actions.push('Run npm install to install dependencies');
168
+ actions.push('Check for version conflicts');
169
+ actions.push('Try npm install --legacy-peer-deps');
170
+ break;
171
+
172
+ case 'BUILD':
173
+ actions.push('Check build configuration');
174
+ actions.push('Verify all source files are valid');
175
+ actions.push('Check for circular dependencies');
176
+ break;
177
+
178
+ case 'TEST':
179
+ actions.push('Review test expectations');
180
+ actions.push('Check mock data and fixtures');
181
+ actions.push('Verify component behavior');
182
+ break;
183
+
184
+ default:
185
+ actions.push('Review error message for details');
186
+ actions.push('Check logs for more context');
187
+ }
188
+
189
+ return actions;
190
+ }
191
+
192
+ /**
193
+ * Generate a fix for the error
194
+ */
195
+ async generateFix(error, context = {}) {
196
+ const analysis = this.analyzeError(error);
197
+
198
+ // Check memory for similar past fixes
199
+ let historicalFix = null;
200
+ if (this.memory) {
201
+ const similar = this.memory.findSimilarErrors(analysis.original);
202
+ if (similar.length > 0 && similar[0].fix) {
203
+ historicalFix = similar[0];
204
+ }
205
+ }
206
+
207
+ // Use existing fix-generator for detailed prompt
208
+ const fixPrompt = generateFixPrompt([{
209
+ type: (analysis?.category || 'unknown').toLowerCase(),
210
+ message: analysis?.original || String(error),
211
+ file: analysis?.file,
212
+ line: analysis?.line,
213
+ priority: analysis?.severity || 'medium'
214
+ }], context.files || []);
215
+
216
+ return {
217
+ analysis,
218
+ historicalFix,
219
+ prompt: this.enhanceFixPrompt(fixPrompt, analysis, context),
220
+ canAutoFix: analysis.canAutoFix,
221
+ estimatedDifficulty: this.estimateDifficulty(analysis)
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Enhance fix prompt with additional context
227
+ */
228
+ enhanceFixPrompt(basePrompt, analysis, context) {
229
+ let enhanced = basePrompt;
230
+
231
+ // Add category-specific instructions
232
+ switch (analysis?.category) {
233
+ case 'IMPORT':
234
+ enhanced += `\n\n## Import Fix Guidelines
235
+ - If package is missing, create a minimal implementation or use a different approach
236
+ - If path is wrong, check the actual file structure
237
+ - Consider if the import should be a devDependency`;
238
+ break;
239
+
240
+ case 'TYPE':
241
+ enhanced += `\n\n## Type Fix Guidelines
242
+ - Add proper type checking before operations
243
+ - Initialize variables with appropriate default values
244
+ - Consider using optional chaining (?.) for nested access`;
245
+ break;
246
+
247
+ case 'TEST':
248
+ enhanced += `\n\n## Test Fix Guidelines
249
+ - Match implementation behavior, not the other way around
250
+ - Update test expectations if implementation is correct
251
+ - Ensure mocks return appropriate data`;
252
+ break;
253
+ }
254
+
255
+ // Add context from previous modules
256
+ if (context.completedModules && context.completedModules.length > 0) {
257
+ enhanced += `\n\n## Context from Previous Modules
258
+ The following modules are already built and should be referenced:
259
+ ${context.completedModules.map(m => `- ${m}`).join('\n')}`;
260
+ }
261
+
262
+ // Add historical fix info if available
263
+ if (context.historicalFix) {
264
+ enhanced += `\n\n## Similar Error Fixed Before
265
+ Previous fix: ${context.historicalFix}
266
+ Apply similar approach if applicable.`;
267
+ }
268
+
269
+ return enhanced;
270
+ }
271
+
272
+ /**
273
+ * Estimate fix difficulty
274
+ */
275
+ estimateDifficulty(analysis) {
276
+ if (analysis.severity === 'critical') return 'complex';
277
+ if (analysis.severity === 'high') return 'moderate';
278
+ if (!analysis.canAutoFix) return 'complex';
279
+ return 'simple';
280
+ }
281
+
282
+ /**
283
+ * Attempt to heal (fix and retry)
284
+ */
285
+ async heal(error, moduleId, context = {}) {
286
+ const fix = await this.generateFix(error, context);
287
+
288
+ const healingRecord = {
289
+ id: `heal_${Date.now()}`,
290
+ moduleId,
291
+ error: fix.analysis,
292
+ fix,
293
+ attempt: (context.attempt || 0) + 1,
294
+ timestamp: new Date().toISOString(),
295
+ status: 'pending'
296
+ };
297
+
298
+ this.healingHistory.push(healingRecord);
299
+
300
+ // Record in memory
301
+ if (this.memory && fix?.analysis) {
302
+ await this.memory.recordError({
303
+ message: fix.analysis.original || String(error),
304
+ type: fix.analysis.category || 'UNKNOWN',
305
+ moduleId,
306
+ file: fix.analysis.file,
307
+ line: fix.analysis.line,
308
+ severity: fix.analysis.severity || 'medium'
309
+ });
310
+ }
311
+
312
+ return {
313
+ ...healingRecord,
314
+ shouldRetry: fix.canAutoFix && healingRecord.attempt <= this.maxRetries,
315
+ prompt: fix.prompt
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Report successful fix
321
+ */
322
+ async reportSuccess(healingId) {
323
+ const record = this.healingHistory.find(h => h.id === healingId);
324
+ if (record) {
325
+ record.status = 'success';
326
+ record.completedAt = new Date().toISOString();
327
+
328
+ // Record in memory
329
+ if (this.memory && record.error) {
330
+ await this.memory.recordLearning({
331
+ type: 'fix_success',
332
+ errorType: record.error?.category || 'UNKNOWN',
333
+ module: record.moduleId,
334
+ outcome: 'success',
335
+ details: `Fixed ${record.error?.category || 'unknown'} error in ${record.error?.file || 'unknown file'}`
336
+ });
337
+ }
338
+ }
339
+ return record;
340
+ }
341
+
342
+ /**
343
+ * Report failed fix
344
+ */
345
+ async reportFailure(healingId, reason) {
346
+ const record = this.healingHistory.find(h => h.id === healingId);
347
+ if (record) {
348
+ record.status = 'failed';
349
+ record.failureReason = reason;
350
+ record.completedAt = new Date().toISOString();
351
+
352
+ // Record in memory
353
+ if (this.memory) {
354
+ await this.memory.recordLearning({
355
+ type: 'fix_failure',
356
+ errorType: record.error?.category,
357
+ module: record.moduleId,
358
+ outcome: 'failure',
359
+ reason
360
+ });
361
+ }
362
+ }
363
+ return record;
364
+ }
365
+
366
+ /**
367
+ * Batch analyze multiple errors
368
+ */
369
+ batchAnalyze(errors) {
370
+ const analyzed = errors.map(e => this.analyzeError(e));
371
+
372
+ // Sort by severity and group by category
373
+ const grouped = {};
374
+ for (const error of analyzed) {
375
+ const cat = error?.category || 'UNKNOWN';
376
+ if (!grouped[cat]) {
377
+ grouped[cat] = [];
378
+ }
379
+ grouped[cat].push(error);
380
+ }
381
+
382
+ // Prioritize fixes
383
+ const priority = ['SYNTAX', 'IMPORT', 'DEPENDENCY', 'REFERENCE', 'TYPE', 'BUILD', 'TEST', 'RUNTIME', 'UNKNOWN'];
384
+ const ordered = [];
385
+ for (const cat of priority) {
386
+ if (grouped[cat]) {
387
+ ordered.push(...grouped[cat]);
388
+ }
389
+ }
390
+
391
+ return {
392
+ total: errors.length,
393
+ byCategory: grouped,
394
+ prioritized: ordered,
395
+ criticalCount: analyzed.filter(e => e.severity === 'critical').length,
396
+ canAutoFixCount: analyzed.filter(e => e.canAutoFix).length
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Generate combined fix prompt for multiple errors
402
+ */
403
+ generateCombinedFixPrompt(errors, context = {}) {
404
+ const batchAnalysis = this.batchAnalyze(errors);
405
+
406
+ let prompt = `# Fix Multiple Errors\n\n`;
407
+ prompt += `Found ${batchAnalysis.total} errors (${batchAnalysis.criticalCount} critical)\n\n`;
408
+
409
+ // Group by file for efficient fixing
410
+ const byFile = {};
411
+ for (const error of batchAnalysis.prioritized) {
412
+ const file = error.file || 'unknown';
413
+ if (!byFile[file]) {
414
+ byFile[file] = [];
415
+ }
416
+ byFile[file].push(error);
417
+ }
418
+
419
+ for (const [file, fileErrors] of Object.entries(byFile)) {
420
+ prompt += `## File: ${file}\n\n`;
421
+ for (const error of fileErrors) {
422
+ const cat = error?.category || 'UNKNOWN';
423
+ prompt += `### ${cat} Error${error?.line ? ` (line ${error.line})` : ''}\n`;
424
+ prompt += `${error?.original || 'Unknown error'}\n\n`;
425
+ prompt += `Actions:\n${(error?.suggestedActions || ['Review error']).map(a => `- ${a}`).join('\n')}\n\n`;
426
+ }
427
+ }
428
+
429
+ prompt += `## Fix Instructions\n`;
430
+ prompt += `1. Start with SYNTAX and IMPORT errors (they often cause other errors)\n`;
431
+ prompt += `2. Fix one file at a time, top to bottom\n`;
432
+ prompt += `3. Verify each fix before moving to the next\n`;
433
+ prompt += `4. Run tests after all fixes\n`;
434
+
435
+ return prompt;
436
+ }
437
+
438
+ /**
439
+ * Get healing statistics
440
+ */
441
+ getStats() {
442
+ const total = this.healingHistory.length;
443
+ const successful = this.healingHistory.filter(h => h.status === 'success').length;
444
+ const failed = this.healingHistory.filter(h => h.status === 'failed').length;
445
+
446
+ const byCategory = {};
447
+ for (const record of this.healingHistory) {
448
+ const cat = record.error?.category || 'UNKNOWN';
449
+ if (!byCategory[cat]) {
450
+ byCategory[cat] = { total: 0, success: 0, failed: 0 };
451
+ }
452
+ byCategory[cat].total++;
453
+ if (record.status === 'success') byCategory[cat].success++;
454
+ if (record.status === 'failed') byCategory[cat].failed++;
455
+ }
456
+
457
+ return {
458
+ total,
459
+ successful,
460
+ failed,
461
+ pending: total - successful - failed,
462
+ successRate: total > 0 ? (successful / total * 100).toFixed(1) + '%' : 'N/A',
463
+ byCategory
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Check if module should be skipped due to repeated failures
469
+ */
470
+ shouldSkipModule(moduleId) {
471
+ const moduleRecords = this.healingHistory.filter(h => h.moduleId === moduleId);
472
+ const failures = moduleRecords.filter(h => h.status === 'failed').length;
473
+ return failures >= this.maxRetries;
474
+ }
475
+
476
+ /**
477
+ * Get recovery suggestions for a stuck module
478
+ */
479
+ getRecoverySuggestions(moduleId) {
480
+ const moduleRecords = this.healingHistory.filter(h => h.moduleId === moduleId);
481
+
482
+ if (moduleRecords.length === 0) {
483
+ return ['No healing attempts recorded for this module'];
484
+ }
485
+
486
+ const suggestions = [];
487
+ const categories = [...new Set(moduleRecords.map(r => r.error?.category))];
488
+
489
+ if (categories.includes('DEPENDENCY')) {
490
+ suggestions.push('Try running: npm install --legacy-peer-deps');
491
+ suggestions.push('Check package.json for version conflicts');
492
+ }
493
+
494
+ if (categories.includes('IMPORT')) {
495
+ suggestions.push('Verify all import paths match actual file structure');
496
+ suggestions.push('Consider using relative imports instead of aliases');
497
+ }
498
+
499
+ if (moduleRecords.length >= this.maxRetries) {
500
+ suggestions.push('Consider simplifying the module requirements');
501
+ suggestions.push('Split the module into smaller, more focused components');
502
+ suggestions.push('Manual intervention may be required');
503
+ }
504
+
505
+ return suggestions;
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Create self-healing engine instance
511
+ */
512
+ export function createSelfHealingEngine(memoryEngine = null) {
513
+ return new SelfHealingEngine(memoryEngine);
514
+ }
515
+
516
+ export { ERROR_CATEGORIES };