@nclamvn/vibecode-cli 1.3.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.
- package/README.md +310 -49
- package/bin/vibecode.js +65 -0
- package/package.json +1 -1
- package/src/agent/decomposition.js +476 -0
- package/src/agent/index.js +325 -0
- package/src/agent/memory.js +542 -0
- package/src/agent/orchestrator.js +644 -0
- package/src/agent/self-healing.js +516 -0
- package/src/commands/agent.js +255 -0
- package/src/commands/assist.js +413 -0
- package/src/commands/debug.js +457 -0
- package/src/commands/go.js +380 -0
- package/src/core/test-runner.js +38 -5
- package/src/debug/analyzer.js +329 -0
- package/src/debug/evidence.js +228 -0
- package/src/debug/fixer.js +348 -0
- package/src/debug/index.js +349 -0
- package/src/debug/verifier.js +346 -0
- package/src/index.js +31 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE DEBUG - Evidence Collector
|
|
3
|
+
// Gathers error information from multiple sources
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Evidence Collector Class
|
|
15
|
+
* Gathers and parses error information from various sources
|
|
16
|
+
*/
|
|
17
|
+
export class EvidenceCollector {
|
|
18
|
+
constructor(projectPath) {
|
|
19
|
+
this.projectPath = projectPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collect evidence from all available sources
|
|
24
|
+
*/
|
|
25
|
+
async collect(input) {
|
|
26
|
+
const evidence = {
|
|
27
|
+
type: 'unknown',
|
|
28
|
+
category: 'RUNTIME',
|
|
29
|
+
description: input.description || '',
|
|
30
|
+
message: '',
|
|
31
|
+
stackTrace: [],
|
|
32
|
+
files: [],
|
|
33
|
+
lines: [],
|
|
34
|
+
logs: [],
|
|
35
|
+
hasImage: false,
|
|
36
|
+
imagePath: null,
|
|
37
|
+
timestamp: new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// From description
|
|
41
|
+
if (input.description) {
|
|
42
|
+
this.parseDescription(evidence, input.description);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// From log paste
|
|
46
|
+
if (input.log) {
|
|
47
|
+
this.parseLog(evidence, input.log);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// From image (note for future OCR)
|
|
51
|
+
if (input.image) {
|
|
52
|
+
await this.parseImage(evidence, input.image);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Auto-scan mode
|
|
56
|
+
if (input.auto) {
|
|
57
|
+
await this.autoScan(evidence);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Categorize error
|
|
61
|
+
evidence.category = this.categorizeError(evidence);
|
|
62
|
+
|
|
63
|
+
return evidence;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse error description text
|
|
68
|
+
*/
|
|
69
|
+
parseDescription(evidence, description) {
|
|
70
|
+
const desc = description.toLowerCase();
|
|
71
|
+
|
|
72
|
+
// Detect error type from description
|
|
73
|
+
if (desc.includes('typeerror')) evidence.type = 'TypeError';
|
|
74
|
+
else if (desc.includes('syntaxerror')) evidence.type = 'SyntaxError';
|
|
75
|
+
else if (desc.includes('referenceerror')) evidence.type = 'ReferenceError';
|
|
76
|
+
else if (desc.includes('cannot find module')) evidence.type = 'ImportError';
|
|
77
|
+
else if (desc.includes('undefined')) evidence.type = 'UndefinedError';
|
|
78
|
+
else if (desc.includes('cannot read properties')) evidence.type = 'TypeError';
|
|
79
|
+
|
|
80
|
+
// Extract file references
|
|
81
|
+
const fileMatches = description.match(/[\w\/\-\.]+\.(js|ts|tsx|jsx|json|mjs|cjs)/gi);
|
|
82
|
+
if (fileMatches) {
|
|
83
|
+
evidence.files = [...new Set(fileMatches)];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Extract line numbers
|
|
87
|
+
const lineMatches = description.match(/:(\d+)(?::\d+)?/g);
|
|
88
|
+
if (lineMatches) {
|
|
89
|
+
evidence.lines = lineMatches.map(m => parseInt(m.split(':')[1]));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Extract error message
|
|
93
|
+
const errorMatch = description.match(/(Error|TypeError|SyntaxError|ReferenceError):\s*(.+?)(?:\n|$)/i);
|
|
94
|
+
if (errorMatch) {
|
|
95
|
+
evidence.type = errorMatch[1];
|
|
96
|
+
evidence.message = errorMatch[2].trim();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse error log text
|
|
102
|
+
*/
|
|
103
|
+
parseLog(evidence, log) {
|
|
104
|
+
evidence.logs.push(log);
|
|
105
|
+
|
|
106
|
+
// Parse stack trace
|
|
107
|
+
const stackLines = log.split('\n').filter(line =>
|
|
108
|
+
line.trim().startsWith('at ') || line.includes('Error:')
|
|
109
|
+
);
|
|
110
|
+
evidence.stackTrace = stackLines.slice(0, 15);
|
|
111
|
+
|
|
112
|
+
// Extract error message
|
|
113
|
+
const errorMatch = log.match(/(Error|TypeError|SyntaxError|ReferenceError|RangeError):\s*(.+?)(?:\n|$)/i);
|
|
114
|
+
if (errorMatch) {
|
|
115
|
+
evidence.type = errorMatch[1];
|
|
116
|
+
evidence.message = errorMatch[2].trim();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Extract files from stack trace
|
|
120
|
+
const fileMatches = log.match(/(?:at\s+)?(?:\w+\s+)?\(?([^\s()]+\.(js|ts|tsx|jsx)):(\d+)(?::\d+)?\)?/gi);
|
|
121
|
+
if (fileMatches) {
|
|
122
|
+
for (const match of fileMatches) {
|
|
123
|
+
const fileMatch = match.match(/([^\s()]+\.(js|ts|tsx|jsx)):(\d+)/i);
|
|
124
|
+
if (fileMatch) {
|
|
125
|
+
evidence.files.push(fileMatch[1]);
|
|
126
|
+
evidence.lines.push(parseInt(fileMatch[3]));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
evidence.files = [...new Set(evidence.files)];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parse image evidence (placeholder for OCR/Vision)
|
|
135
|
+
*/
|
|
136
|
+
async parseImage(evidence, imagePath) {
|
|
137
|
+
if (await fs.pathExists(imagePath)) {
|
|
138
|
+
evidence.hasImage = true;
|
|
139
|
+
evidence.imagePath = imagePath;
|
|
140
|
+
// Future: OCR or Claude Vision API
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Auto-scan project for errors
|
|
146
|
+
*/
|
|
147
|
+
async autoScan(evidence) {
|
|
148
|
+
const checks = [
|
|
149
|
+
{ name: 'npm test', cmd: 'npm test', softFail: true },
|
|
150
|
+
{ name: 'npm build', cmd: 'npm run build', softFail: false },
|
|
151
|
+
{ name: 'npm lint', cmd: 'npm run lint', softFail: true },
|
|
152
|
+
{ name: 'tsc', cmd: 'npx tsc --noEmit', softFail: true }
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Check if package.json exists
|
|
156
|
+
const pkgPath = path.join(this.projectPath, 'package.json');
|
|
157
|
+
if (!await fs.pathExists(pkgPath)) {
|
|
158
|
+
evidence.logs.push({ source: 'auto-scan', error: 'No package.json found' });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const pkg = await fs.readJson(pkgPath);
|
|
163
|
+
|
|
164
|
+
for (const check of checks) {
|
|
165
|
+
// Skip if script doesn't exist
|
|
166
|
+
const scriptName = check.cmd.replace('npm run ', '').replace('npm ', '');
|
|
167
|
+
if (check.cmd.startsWith('npm') && scriptName !== 'test' && !pkg.scripts?.[scriptName]) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await execAsync(check.cmd, {
|
|
173
|
+
cwd: this.projectPath,
|
|
174
|
+
timeout: 60000,
|
|
175
|
+
maxBuffer: 10 * 1024 * 1024
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
const errorOutput = error.stderr || error.stdout || error.message;
|
|
179
|
+
evidence.logs.push({
|
|
180
|
+
source: check.name,
|
|
181
|
+
error: errorOutput.substring(0, 5000), // Limit size
|
|
182
|
+
exitCode: error.code
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Parse errors from output
|
|
186
|
+
this.parseLog(evidence, errorOutput);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Categorize error by type
|
|
193
|
+
*/
|
|
194
|
+
categorizeError(evidence) {
|
|
195
|
+
const type = (evidence.type || '').toLowerCase();
|
|
196
|
+
const msg = (evidence.message || evidence.description || '').toLowerCase();
|
|
197
|
+
|
|
198
|
+
if (type.includes('syntax') || msg.includes('unexpected token')) return 'SYNTAX';
|
|
199
|
+
if (type.includes('type') || msg.includes('cannot read properties')) return 'TYPE';
|
|
200
|
+
if (type.includes('reference') || msg.includes('is not defined')) return 'REFERENCE';
|
|
201
|
+
if (msg.includes('cannot find module') || msg.includes('module not found')) return 'IMPORT';
|
|
202
|
+
if (msg.includes('enoent') || msg.includes('no such file')) return 'FILE';
|
|
203
|
+
if (msg.includes('eslint') || msg.includes('lint')) return 'LINT';
|
|
204
|
+
if (msg.includes('test') || msg.includes('expect') || msg.includes('assert')) return 'TEST';
|
|
205
|
+
if (msg.includes('client component') || msg.includes('server component')) return 'NEXTJS';
|
|
206
|
+
if (msg.includes('prisma') || msg.includes('database')) return 'DATABASE';
|
|
207
|
+
|
|
208
|
+
return 'RUNTIME';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get evidence summary
|
|
213
|
+
*/
|
|
214
|
+
getSummary(evidence) {
|
|
215
|
+
return {
|
|
216
|
+
type: evidence.type,
|
|
217
|
+
category: evidence.category,
|
|
218
|
+
message: evidence.message || evidence.description?.substring(0, 100),
|
|
219
|
+
fileCount: evidence.files.length,
|
|
220
|
+
hasStackTrace: evidence.stackTrace.length > 0,
|
|
221
|
+
logSources: evidence.logs.length
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function createEvidenceCollector(projectPath) {
|
|
227
|
+
return new EvidenceCollector(projectPath);
|
|
228
|
+
}
|