@nclamvn/vibecode-cli 2.0.0 → 2.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.
- package/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/README.md +310 -49
- package/SESSION_NOTES.md +154 -0
- package/bin/vibecode.js +212 -2
- package/package.json +5 -2
- package/src/agent/decomposition.js +476 -0
- package/src/agent/index.js +391 -0
- package/src/agent/memory.js +542 -0
- package/src/agent/orchestrator.js +917 -0
- package/src/agent/self-healing.js +516 -0
- package/src/commands/agent.js +349 -0
- package/src/commands/ask.js +230 -0
- package/src/commands/assist.js +413 -0
- package/src/commands/build.js +345 -4
- package/src/commands/debug.js +565 -0
- package/src/commands/docs.js +167 -0
- package/src/commands/git.js +1024 -0
- package/src/commands/go.js +387 -0
- package/src/commands/learn.js +294 -0
- package/src/commands/migrate.js +341 -0
- package/src/commands/plan.js +8 -2
- package/src/commands/refactor.js +205 -0
- package/src/commands/review.js +126 -1
- package/src/commands/security.js +229 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/test.js +194 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/watch.js +556 -0
- package/src/commands/wizard.js +322 -0
- package/src/config/constants.js +5 -1
- package/src/config/templates.js +146 -15
- package/src/core/backup.js +325 -0
- package/src/core/error-analyzer.js +237 -0
- package/src/core/fix-generator.js +195 -0
- package/src/core/iteration.js +226 -0
- package/src/core/learning.js +295 -0
- package/src/core/session.js +18 -2
- package/src/core/test-runner.js +281 -0
- package/src/debug/analyzer.js +329 -0
- package/src/debug/evidence.js +228 -0
- package/src/debug/fixer.js +348 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/debug/index.js +378 -0
- package/src/debug/verifier.js +346 -0
- package/src/index.js +89 -0
- package/src/providers/claude-code.js +12 -7
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
- package/src/utils/image.js +222 -0
package/src/core/session.js
CHANGED
|
@@ -114,9 +114,25 @@ export async function createBlueprint(projectName, sessionId) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Create contract file
|
|
117
|
+
* Create contract file from intake and blueprint
|
|
118
118
|
*/
|
|
119
119
|
export async function createContract(projectName, sessionId) {
|
|
120
|
-
|
|
120
|
+
// Read intake and blueprint to extract real content
|
|
121
|
+
let intakeContent = '';
|
|
122
|
+
let blueprintContent = '';
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
intakeContent = await readSessionFile('intake.md');
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Intake not found, use empty
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
blueprintContent = await readSessionFile('blueprint.md');
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// Blueprint not found, use empty
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const template = getContractTemplate(projectName, sessionId, intakeContent, blueprintContent);
|
|
121
137
|
await writeSessionFile('contract.md', template);
|
|
122
138
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Test Runner
|
|
3
|
+
// Automated test execution for iterative builds
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { pathExists, readJson } from '../utils/files.js';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run all available tests for a project
|
|
15
|
+
* @param {string} projectPath - Path to the project
|
|
16
|
+
* @returns {Promise<TestResults>}
|
|
17
|
+
*/
|
|
18
|
+
export async function runTests(projectPath) {
|
|
19
|
+
const results = {
|
|
20
|
+
passed: true,
|
|
21
|
+
tests: [],
|
|
22
|
+
errors: [],
|
|
23
|
+
summary: {
|
|
24
|
+
total: 0,
|
|
25
|
+
passed: 0,
|
|
26
|
+
failed: 0
|
|
27
|
+
},
|
|
28
|
+
duration: 0
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
|
|
33
|
+
// 1. Check if package.json exists
|
|
34
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
35
|
+
const hasPackageJson = await pathExists(packageJsonPath);
|
|
36
|
+
|
|
37
|
+
if (hasPackageJson) {
|
|
38
|
+
const pkg = await readJson(packageJsonPath);
|
|
39
|
+
|
|
40
|
+
// 2. Run npm test (if script exists)
|
|
41
|
+
if (pkg.scripts?.test && !pkg.scripts.test.includes('no test specified')) {
|
|
42
|
+
const npmTest = await runCommand('npm test', projectPath, 'npm test');
|
|
43
|
+
results.tests.push(npmTest);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3. Run npm run lint (if script exists) - soft fail (warnings only)
|
|
47
|
+
if (pkg.scripts?.lint) {
|
|
48
|
+
const npmLint = await runCommand('npm run lint', projectPath, 'npm lint');
|
|
49
|
+
npmLint.softFail = true; // Lint errors are warnings, don't block build
|
|
50
|
+
results.tests.push(npmLint);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Run npm run build (if script exists) - check for build errors
|
|
54
|
+
if (pkg.scripts?.build) {
|
|
55
|
+
const npmBuild = await runCommand('npm run build', projectPath, 'npm build');
|
|
56
|
+
results.tests.push(npmBuild);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 5. Run TypeScript check if tsconfig exists
|
|
60
|
+
const tsconfigPath = path.join(projectPath, 'tsconfig.json');
|
|
61
|
+
if (await pathExists(tsconfigPath)) {
|
|
62
|
+
const tscCheck = await runCommand('npx tsc --noEmit', projectPath, 'typescript');
|
|
63
|
+
results.tests.push(tscCheck);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 6. Check for syntax errors in JS files
|
|
68
|
+
const syntaxCheck = await checkJsSyntax(projectPath);
|
|
69
|
+
if (syntaxCheck.ran) {
|
|
70
|
+
results.tests.push(syntaxCheck);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 7. Aggregate results
|
|
74
|
+
// Separate hard tests from soft-fail tests (like lint)
|
|
75
|
+
const hardTests = results.tests.filter(t => !t.softFail);
|
|
76
|
+
const softTests = results.tests.filter(t => t.softFail);
|
|
77
|
+
|
|
78
|
+
results.summary.total = results.tests.length;
|
|
79
|
+
results.summary.passed = results.tests.filter(t => t.passed).length;
|
|
80
|
+
results.summary.failed = results.tests.filter(t => !t.passed).length;
|
|
81
|
+
results.summary.warnings = softTests.filter(t => !t.passed).length;
|
|
82
|
+
|
|
83
|
+
// Only hard tests determine pass/fail
|
|
84
|
+
results.passed = hardTests.length === 0 || hardTests.every(t => t.passed);
|
|
85
|
+
|
|
86
|
+
// Collect errors, but mark soft-fail errors as warnings
|
|
87
|
+
results.errors = results.tests.filter(t => !t.passed && !t.softFail).flatMap(t => t.errors || []);
|
|
88
|
+
results.warnings = softTests.filter(t => !t.passed).flatMap(t => t.errors || []);
|
|
89
|
+
results.duration = Date.now() - startTime;
|
|
90
|
+
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run a single command and capture results
|
|
96
|
+
*/
|
|
97
|
+
async function runCommand(command, cwd, name) {
|
|
98
|
+
const result = {
|
|
99
|
+
name,
|
|
100
|
+
command,
|
|
101
|
+
passed: false,
|
|
102
|
+
ran: true,
|
|
103
|
+
output: '',
|
|
104
|
+
errors: [],
|
|
105
|
+
duration: 0
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
112
|
+
cwd,
|
|
113
|
+
timeout: 120000, // 2 minute timeout
|
|
114
|
+
maxBuffer: 10 * 1024 * 1024
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
result.passed = true;
|
|
118
|
+
result.output = stdout + stderr;
|
|
119
|
+
result.duration = Date.now() - startTime;
|
|
120
|
+
|
|
121
|
+
} catch (error) {
|
|
122
|
+
result.passed = false;
|
|
123
|
+
result.output = error.stdout || '';
|
|
124
|
+
result.error = error.stderr || error.message;
|
|
125
|
+
result.exitCode = error.code;
|
|
126
|
+
result.duration = Date.now() - startTime;
|
|
127
|
+
|
|
128
|
+
// Parse errors from output
|
|
129
|
+
result.errors = parseErrors(error.stderr || error.stdout || error.message, name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check JavaScript syntax errors
|
|
137
|
+
*/
|
|
138
|
+
async function checkJsSyntax(projectPath) {
|
|
139
|
+
const result = {
|
|
140
|
+
name: 'syntax-check',
|
|
141
|
+
passed: true,
|
|
142
|
+
ran: false,
|
|
143
|
+
errors: []
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Find JS/TS files (limited to src/ to avoid node_modules)
|
|
148
|
+
const { stdout } = await execAsync(
|
|
149
|
+
'find src -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | head -20',
|
|
150
|
+
{ cwd: projectPath }
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const files = stdout.trim().split('\n').filter(f => f);
|
|
154
|
+
if (files.length === 0) return result;
|
|
155
|
+
|
|
156
|
+
result.ran = true;
|
|
157
|
+
|
|
158
|
+
// Check each file for syntax errors using node --check
|
|
159
|
+
for (const file of files) {
|
|
160
|
+
if (file.endsWith('.ts') || file.endsWith('.tsx')) continue; // Skip TS files
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await execAsync(`node --check "${file}"`, { cwd: projectPath });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
result.passed = false;
|
|
166
|
+
result.errors.push({
|
|
167
|
+
file,
|
|
168
|
+
message: error.message,
|
|
169
|
+
type: 'syntax'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// find command failed, skip syntax check
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Parse error messages into structured format
|
|
182
|
+
*/
|
|
183
|
+
function parseErrors(errorOutput, source) {
|
|
184
|
+
const errors = [];
|
|
185
|
+
const lines = errorOutput.split('\n');
|
|
186
|
+
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
// Match common error patterns
|
|
189
|
+
// Pattern: file.js:10:5: error message
|
|
190
|
+
const fileLineMatch = line.match(/([^\s:]+):(\d+):(\d+)?:?\s*(.+)/);
|
|
191
|
+
if (fileLineMatch) {
|
|
192
|
+
errors.push({
|
|
193
|
+
source,
|
|
194
|
+
file: fileLineMatch[1],
|
|
195
|
+
line: parseInt(fileLineMatch[2]),
|
|
196
|
+
column: fileLineMatch[3] ? parseInt(fileLineMatch[3]) : null,
|
|
197
|
+
message: fileLineMatch[4].trim(),
|
|
198
|
+
raw: line
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Pattern: Error: message
|
|
204
|
+
const errorMatch = line.match(/^(Error|TypeError|SyntaxError|ReferenceError):\s*(.+)/);
|
|
205
|
+
if (errorMatch) {
|
|
206
|
+
errors.push({
|
|
207
|
+
source,
|
|
208
|
+
type: errorMatch[1],
|
|
209
|
+
message: errorMatch[2].trim(),
|
|
210
|
+
raw: line
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Pattern: ✖ or ✗ or FAIL
|
|
216
|
+
if (line.includes('✖') || line.includes('✗') || line.includes('FAIL')) {
|
|
217
|
+
errors.push({
|
|
218
|
+
source,
|
|
219
|
+
message: line.trim(),
|
|
220
|
+
raw: line
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If no structured errors found, add the whole output
|
|
226
|
+
if (errors.length === 0 && errorOutput.trim()) {
|
|
227
|
+
errors.push({
|
|
228
|
+
source,
|
|
229
|
+
message: errorOutput.substring(0, 500),
|
|
230
|
+
raw: errorOutput
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return errors;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Format test results for display
|
|
239
|
+
*/
|
|
240
|
+
export function formatTestResults(results) {
|
|
241
|
+
const lines = [];
|
|
242
|
+
|
|
243
|
+
lines.push(`Tests: ${results.summary.passed}/${results.summary.total} passed`);
|
|
244
|
+
if (results.summary.warnings > 0) {
|
|
245
|
+
lines.push(`Warnings: ${results.summary.warnings} (lint)`);
|
|
246
|
+
}
|
|
247
|
+
lines.push(`Duration: ${(results.duration / 1000).toFixed(1)}s`);
|
|
248
|
+
|
|
249
|
+
// Show hard failures
|
|
250
|
+
const hardFailures = results.tests.filter(t => !t.passed && !t.softFail);
|
|
251
|
+
if (hardFailures.length > 0) {
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push('Failed tests:');
|
|
254
|
+
for (const test of hardFailures) {
|
|
255
|
+
lines.push(` ❌ ${test.name}`);
|
|
256
|
+
for (const error of test.errors || []) {
|
|
257
|
+
const loc = error.file ? `${error.file}:${error.line || '?'}` : '';
|
|
258
|
+
lines.push(` ${loc} ${error.message?.substring(0, 80) || ''}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Show soft failures (warnings)
|
|
264
|
+
const softFailures = results.tests.filter(t => !t.passed && t.softFail);
|
|
265
|
+
if (softFailures.length > 0) {
|
|
266
|
+
lines.push('');
|
|
267
|
+
lines.push('Warnings (non-blocking):');
|
|
268
|
+
for (const test of softFailures) {
|
|
269
|
+
lines.push(` ⚠️ ${test.name}`);
|
|
270
|
+
for (const error of (test.errors || []).slice(0, 3)) {
|
|
271
|
+
const loc = error.file ? `${error.file}:${error.line || '?'}` : '';
|
|
272
|
+
lines.push(` ${loc} ${error.message?.substring(0, 80) || ''}`);
|
|
273
|
+
}
|
|
274
|
+
if ((test.errors?.length || 0) > 3) {
|
|
275
|
+
lines.push(` ... and ${test.errors.length - 3} more`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE DEBUG - Root Cause Analyzer
|
|
3
|
+
// Analyzes evidence to determine root cause and generate hypotheses
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Root Cause Analyzer Class
|
|
8
|
+
* Uses pattern matching and heuristics to identify bug sources
|
|
9
|
+
*/
|
|
10
|
+
export class RootCauseAnalyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.patterns = this.initPatterns();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Analyze evidence to find root cause
|
|
17
|
+
*/
|
|
18
|
+
async analyze(evidence) {
|
|
19
|
+
const analysis = {
|
|
20
|
+
category: evidence.category,
|
|
21
|
+
rootCause: null,
|
|
22
|
+
suggestedFix: null,
|
|
23
|
+
relatedFiles: evidence.files || [],
|
|
24
|
+
confidence: 0,
|
|
25
|
+
patterns: []
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Match against known patterns
|
|
29
|
+
for (const pattern of this.patterns) {
|
|
30
|
+
if (this.matchesPattern(evidence, pattern)) {
|
|
31
|
+
analysis.patterns.push(pattern.name);
|
|
32
|
+
|
|
33
|
+
if (pattern.confidence > analysis.confidence) {
|
|
34
|
+
analysis.rootCause = pattern.rootCause;
|
|
35
|
+
analysis.suggestedFix = pattern.fix;
|
|
36
|
+
analysis.confidence = pattern.confidence;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If no pattern matched, use generic analysis
|
|
42
|
+
if (!analysis.rootCause) {
|
|
43
|
+
analysis.rootCause = this.inferRootCause(evidence);
|
|
44
|
+
analysis.confidence = 0.3;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return analysis;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build hypotheses from analysis
|
|
52
|
+
*/
|
|
53
|
+
buildHypotheses(analysis) {
|
|
54
|
+
const hypotheses = [];
|
|
55
|
+
|
|
56
|
+
// Primary hypothesis from analysis
|
|
57
|
+
if (analysis.suggestedFix) {
|
|
58
|
+
hypotheses.push({
|
|
59
|
+
description: analysis.suggestedFix,
|
|
60
|
+
confidence: analysis.confidence,
|
|
61
|
+
category: analysis.category,
|
|
62
|
+
rootCause: analysis.rootCause
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add category-specific hypotheses
|
|
67
|
+
const categoryFixes = this.getCategoryFixes(analysis.category);
|
|
68
|
+
for (const fix of categoryFixes) {
|
|
69
|
+
if (!hypotheses.find(h => h.description === fix.description)) {
|
|
70
|
+
hypotheses.push({
|
|
71
|
+
...fix,
|
|
72
|
+
category: analysis.category
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Sort by confidence
|
|
78
|
+
return hypotheses.sort((a, b) => b.confidence - a.confidence);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Initialize pattern database
|
|
83
|
+
*/
|
|
84
|
+
initPatterns() {
|
|
85
|
+
return [
|
|
86
|
+
// Next.js specific
|
|
87
|
+
{
|
|
88
|
+
name: 'nextjs-server-client-boundary',
|
|
89
|
+
match: (e) => {
|
|
90
|
+
const msg = (e.message || e.description || '').toLowerCase();
|
|
91
|
+
return msg.includes('functions cannot be passed directly to client components') ||
|
|
92
|
+
msg.includes('client component') && msg.includes('server');
|
|
93
|
+
},
|
|
94
|
+
rootCause: 'Server/Client Component boundary violation in Next.js',
|
|
95
|
+
fix: 'Convert function props to serializable format (e.g., use formatType string instead of formatValue function). Or mark the function with "use server".',
|
|
96
|
+
confidence: 0.95
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'nextjs-hydration',
|
|
100
|
+
match: (e) => {
|
|
101
|
+
const msg = (e.message || e.description || '').toLowerCase();
|
|
102
|
+
return msg.includes('hydration') || msg.includes('text content does not match');
|
|
103
|
+
},
|
|
104
|
+
rootCause: 'Hydration mismatch between server and client render',
|
|
105
|
+
fix: 'Ensure server and client render identical content. Use useEffect for client-only code.',
|
|
106
|
+
confidence: 0.85
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Type errors
|
|
110
|
+
{
|
|
111
|
+
name: 'undefined-property-access',
|
|
112
|
+
match: (e) => {
|
|
113
|
+
const msg = (e.message || '').toLowerCase();
|
|
114
|
+
return msg.includes('cannot read properties of undefined') ||
|
|
115
|
+
msg.includes('cannot read property') && msg.includes('undefined');
|
|
116
|
+
},
|
|
117
|
+
rootCause: 'Accessing property on undefined object',
|
|
118
|
+
fix: 'Add null check or optional chaining (?.) before property access',
|
|
119
|
+
confidence: 0.85
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'null-property-access',
|
|
123
|
+
match: (e) => {
|
|
124
|
+
const msg = (e.message || '').toLowerCase();
|
|
125
|
+
return msg.includes('cannot read properties of null');
|
|
126
|
+
},
|
|
127
|
+
rootCause: 'Accessing property on null value',
|
|
128
|
+
fix: 'Add null check before property access. Check if data is loaded before accessing.',
|
|
129
|
+
confidence: 0.85
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Import errors
|
|
133
|
+
{
|
|
134
|
+
name: 'module-not-found',
|
|
135
|
+
match: (e) => {
|
|
136
|
+
const msg = (e.message || e.description || '').toLowerCase();
|
|
137
|
+
return msg.includes('cannot find module') || msg.includes('module not found');
|
|
138
|
+
},
|
|
139
|
+
rootCause: 'Missing import or incorrect module path',
|
|
140
|
+
fix: 'Install missing package with npm install, or fix import path',
|
|
141
|
+
confidence: 0.9
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'export-not-found',
|
|
145
|
+
match: (e) => {
|
|
146
|
+
const msg = (e.message || '').toLowerCase();
|
|
147
|
+
return msg.includes('does not provide an export named') ||
|
|
148
|
+
msg.includes('is not exported from');
|
|
149
|
+
},
|
|
150
|
+
rootCause: 'Importing non-existent export',
|
|
151
|
+
fix: 'Check export name in source module. Use correct named or default import.',
|
|
152
|
+
confidence: 0.9
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Syntax errors
|
|
156
|
+
{
|
|
157
|
+
name: 'unexpected-token',
|
|
158
|
+
match: (e) => {
|
|
159
|
+
const msg = (e.message || '').toLowerCase();
|
|
160
|
+
return msg.includes('unexpected token') || e.type === 'SyntaxError';
|
|
161
|
+
},
|
|
162
|
+
rootCause: 'Syntax error in JavaScript/TypeScript code',
|
|
163
|
+
fix: 'Fix syntax error at indicated line. Check for missing brackets, semicolons, or incorrect syntax.',
|
|
164
|
+
confidence: 0.9
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'jsx-syntax',
|
|
168
|
+
match: (e) => {
|
|
169
|
+
const msg = (e.message || '').toLowerCase();
|
|
170
|
+
return msg.includes('jsx') && (msg.includes('unexpected') || msg.includes('syntax'));
|
|
171
|
+
},
|
|
172
|
+
rootCause: 'JSX syntax error',
|
|
173
|
+
fix: 'Check JSX syntax. Ensure proper closing tags and valid JSX expressions.',
|
|
174
|
+
confidence: 0.85
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Reference errors
|
|
178
|
+
{
|
|
179
|
+
name: 'undefined-variable',
|
|
180
|
+
match: (e) => {
|
|
181
|
+
const msg = (e.message || '').toLowerCase();
|
|
182
|
+
return msg.includes('is not defined') || e.type === 'ReferenceError';
|
|
183
|
+
},
|
|
184
|
+
rootCause: 'Using undefined variable',
|
|
185
|
+
fix: 'Define the variable before use, or import it from the correct module',
|
|
186
|
+
confidence: 0.85
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Database errors
|
|
190
|
+
{
|
|
191
|
+
name: 'prisma-client',
|
|
192
|
+
match: (e) => {
|
|
193
|
+
const msg = (e.message || '').toLowerCase();
|
|
194
|
+
return msg.includes('prisma') || msg.includes('@prisma/client');
|
|
195
|
+
},
|
|
196
|
+
rootCause: 'Prisma database client error',
|
|
197
|
+
fix: 'Run npx prisma generate and npx prisma db push. Check DATABASE_URL.',
|
|
198
|
+
confidence: 0.8
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// Auth errors
|
|
202
|
+
{
|
|
203
|
+
name: 'nextauth-error',
|
|
204
|
+
match: (e) => {
|
|
205
|
+
const msg = (e.message || '').toLowerCase();
|
|
206
|
+
return msg.includes('next-auth') || msg.includes('nextauth');
|
|
207
|
+
},
|
|
208
|
+
rootCause: 'NextAuth configuration error',
|
|
209
|
+
fix: 'Check NEXTAUTH_URL and NEXTAUTH_SECRET in .env. Verify auth provider config.',
|
|
210
|
+
confidence: 0.8
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// ESLint/Lint errors
|
|
214
|
+
{
|
|
215
|
+
name: 'eslint-error',
|
|
216
|
+
match: (e) => e.category === 'LINT' || (e.message || '').toLowerCase().includes('eslint'),
|
|
217
|
+
rootCause: 'ESLint code style violation',
|
|
218
|
+
fix: 'Fix the linting error or add eslint-disable comment if intentional',
|
|
219
|
+
confidence: 0.75
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// Test failures
|
|
223
|
+
{
|
|
224
|
+
name: 'test-assertion',
|
|
225
|
+
match: (e) => {
|
|
226
|
+
const msg = (e.message || '').toLowerCase();
|
|
227
|
+
return msg.includes('expect') || msg.includes('assertion') || e.category === 'TEST';
|
|
228
|
+
},
|
|
229
|
+
rootCause: 'Test assertion failure',
|
|
230
|
+
fix: 'Review test expectations and implementation. Update test or fix code.',
|
|
231
|
+
confidence: 0.7
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if evidence matches a pattern
|
|
238
|
+
*/
|
|
239
|
+
matchesPattern(evidence, pattern) {
|
|
240
|
+
try {
|
|
241
|
+
return pattern.match(evidence);
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Infer root cause when no pattern matches
|
|
249
|
+
*/
|
|
250
|
+
inferRootCause(evidence) {
|
|
251
|
+
const parts = [];
|
|
252
|
+
|
|
253
|
+
if (evidence.type && evidence.type !== 'unknown') {
|
|
254
|
+
parts.push(evidence.type);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (evidence.files.length > 0) {
|
|
258
|
+
parts.push(`in ${evidence.files[0]}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (evidence.lines.length > 0) {
|
|
262
|
+
parts.push(`at line ${evidence.lines[0]}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (parts.length === 0) {
|
|
266
|
+
return 'Unknown error - manual investigation needed';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return parts.join(' ');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get category-specific fixes
|
|
274
|
+
*/
|
|
275
|
+
getCategoryFixes(category) {
|
|
276
|
+
const fixes = {
|
|
277
|
+
SYNTAX: [
|
|
278
|
+
{ description: 'Check for missing brackets, parentheses, or semicolons', confidence: 0.6 },
|
|
279
|
+
{ description: 'Verify JSX syntax and proper tag closing', confidence: 0.5 }
|
|
280
|
+
],
|
|
281
|
+
TYPE: [
|
|
282
|
+
{ description: 'Add null/undefined check before property access', confidence: 0.6 },
|
|
283
|
+
{ description: 'Use optional chaining (?.) for nested access', confidence: 0.6 },
|
|
284
|
+
{ description: 'Verify variable types match expected types', confidence: 0.5 }
|
|
285
|
+
],
|
|
286
|
+
REFERENCE: [
|
|
287
|
+
{ description: 'Import or define the missing variable', confidence: 0.6 },
|
|
288
|
+
{ description: 'Check for typos in variable names', confidence: 0.5 }
|
|
289
|
+
],
|
|
290
|
+
IMPORT: [
|
|
291
|
+
{ description: 'Install missing package: npm install <package>', confidence: 0.7 },
|
|
292
|
+
{ description: 'Fix import path to correct location', confidence: 0.6 },
|
|
293
|
+
{ description: 'Check if export exists in source module', confidence: 0.5 }
|
|
294
|
+
],
|
|
295
|
+
FILE: [
|
|
296
|
+
{ description: 'Create missing file or directory', confidence: 0.6 },
|
|
297
|
+
{ description: 'Fix file path in configuration', confidence: 0.5 }
|
|
298
|
+
],
|
|
299
|
+
LINT: [
|
|
300
|
+
{ description: 'Fix code style violation', confidence: 0.6 },
|
|
301
|
+
{ description: 'Add eslint-disable comment if intentional', confidence: 0.4 }
|
|
302
|
+
],
|
|
303
|
+
TEST: [
|
|
304
|
+
{ description: 'Update test expectations to match implementation', confidence: 0.5 },
|
|
305
|
+
{ description: 'Fix implementation to pass test', confidence: 0.5 }
|
|
306
|
+
],
|
|
307
|
+
NEXTJS: [
|
|
308
|
+
{ description: 'Check Server/Client Component boundaries', confidence: 0.7 },
|
|
309
|
+
{ description: 'Move client-only code to useEffect', confidence: 0.6 }
|
|
310
|
+
],
|
|
311
|
+
DATABASE: [
|
|
312
|
+
{ description: 'Run prisma generate and db push', confidence: 0.7 },
|
|
313
|
+
{ description: 'Check DATABASE_URL environment variable', confidence: 0.6 }
|
|
314
|
+
],
|
|
315
|
+
RUNTIME: [
|
|
316
|
+
{ description: 'Debug runtime error with console.log', confidence: 0.3 },
|
|
317
|
+
{ description: 'Check error stack trace for source', confidence: 0.4 }
|
|
318
|
+
]
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
return fixes[category] || [
|
|
322
|
+
{ description: 'Investigate error message and stack trace', confidence: 0.2 }
|
|
323
|
+
];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function createRootCauseAnalyzer() {
|
|
328
|
+
return new RootCauseAnalyzer();
|
|
329
|
+
}
|