erosolar-cli 1.7.172 → 1.7.174
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/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/validationCapability.d.ts +13 -0
- package/dist/capabilities/validationCapability.d.ts.map +1 -0
- package/dist/capabilities/validationCapability.js +24 -0
- package/dist/capabilities/validationCapability.js.map +1 -0
- package/dist/core/agent.d.ts +11 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +18 -2
- package/dist/core/agent.js.map +1 -1
- package/dist/core/contextManager.d.ts +36 -1
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +54 -0
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/errors/errorTypes.d.ts +20 -16
- package/dist/core/errors/errorTypes.d.ts.map +1 -1
- package/dist/core/errors/errorTypes.js +1 -1
- package/dist/core/errors/errorTypes.js.map +1 -1
- package/dist/core/performanceMonitor.d.ts +33 -20
- package/dist/core/performanceMonitor.d.ts.map +1 -1
- package/dist/core/performanceMonitor.js.map +1 -1
- package/dist/core/preferences.d.ts +1 -0
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +7 -0
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts +23 -4
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +90 -0
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts +27 -18
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +24 -1
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/validationRunner.d.ts +93 -0
- package/dist/core/validationRunner.d.ts.map +1 -0
- package/dist/core/validationRunner.js +740 -0
- package/dist/core/validationRunner.js.map +1 -0
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +2 -0
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/plugins/tools/validation/validationPlugin.d.ts +3 -0
- package/dist/plugins/tools/validation/validationPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/validation/validationPlugin.js +14 -0
- package/dist/plugins/tools/validation/validationPlugin.js.map +1 -0
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +11 -3
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/chatBox.d.ts +228 -0
- package/dist/shell/chatBox.d.ts.map +1 -0
- package/dist/shell/chatBox.js +811 -0
- package/dist/shell/chatBox.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +13 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +120 -11
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/tools/validationTools.d.ts +7 -0
- package/dist/tools/validationTools.d.ts.map +1 -0
- package/dist/tools/validationTools.js +278 -0
- package/dist/tools/validationTools.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive validation runner for AI software engineering.
|
|
3
|
+
* Validates all changes (TypeScript, tests, lint) and provides intelligent error parsing
|
|
4
|
+
* with actionable fix suggestions - similar to Claude Code's validation flow.
|
|
5
|
+
*/
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { readFile, access } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Error Parsers
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Parse TypeScript compiler errors into structured format
|
|
16
|
+
*/
|
|
17
|
+
export function parseTypeScriptErrors(output) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
// Match patterns like: src/file.ts(10,5): error TS2322: Type 'string' is not assignable...
|
|
20
|
+
// or: src/file.ts:10:5 - error TS2322: Type 'string' is not assignable...
|
|
21
|
+
const patterns = [
|
|
22
|
+
/^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+(TS\d+):\s*(.+)$/gm,
|
|
23
|
+
/^(.+?):(\d+):(\d+)\s*-\s*(error|warning)\s+(TS\d+):\s*(.+)$/gm,
|
|
24
|
+
];
|
|
25
|
+
for (const pattern of patterns) {
|
|
26
|
+
let match;
|
|
27
|
+
while ((match = pattern.exec(output)) !== null) {
|
|
28
|
+
const [, file, line, column, severity, code, message] = match;
|
|
29
|
+
const error = {
|
|
30
|
+
type: 'typescript',
|
|
31
|
+
file,
|
|
32
|
+
line: parseInt(line, 10),
|
|
33
|
+
column: parseInt(column, 10),
|
|
34
|
+
code,
|
|
35
|
+
message: message.trim(),
|
|
36
|
+
severity: severity === 'error' ? 'error' : 'warning',
|
|
37
|
+
suggestedFix: generateTypeScriptFix(code, message.trim(), file),
|
|
38
|
+
};
|
|
39
|
+
errors.push(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return errors;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse Jest/test runner errors into structured format
|
|
46
|
+
*/
|
|
47
|
+
export function parseTestErrors(output) {
|
|
48
|
+
const errors = [];
|
|
49
|
+
// Match Jest failure patterns
|
|
50
|
+
// FAIL src/tests/foo.test.ts
|
|
51
|
+
const failPattern = /FAIL\s+(.+\.(?:test|spec)\.[jt]sx?)/g;
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = failPattern.exec(output)) !== null) {
|
|
54
|
+
const file = match[1];
|
|
55
|
+
// Try to extract specific test failure details
|
|
56
|
+
const testNamePattern = /✕\s+(.+?)(?:\s+\((\d+)\s*ms\))?$/gm;
|
|
57
|
+
let testMatch;
|
|
58
|
+
while ((testMatch = testNamePattern.exec(output)) !== null) {
|
|
59
|
+
errors.push({
|
|
60
|
+
type: 'test',
|
|
61
|
+
file,
|
|
62
|
+
message: `Test failed: ${testMatch[1]}`,
|
|
63
|
+
severity: 'error',
|
|
64
|
+
suggestedFix: {
|
|
65
|
+
description: 'Review test assertion and fix the code or update the test',
|
|
66
|
+
autoFixable: false,
|
|
67
|
+
fixType: 'manual',
|
|
68
|
+
fixDetails: {
|
|
69
|
+
manualSteps: [
|
|
70
|
+
`Open ${file}`,
|
|
71
|
+
`Find test: "${testMatch[1]}"`,
|
|
72
|
+
'Review assertion failure',
|
|
73
|
+
'Fix the code or update the expected value',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Match assertion errors
|
|
81
|
+
const assertPattern = /expect\((.+?)\)\.(.+?)\((.+?)\)/g;
|
|
82
|
+
while ((match = assertPattern.exec(output)) !== null) {
|
|
83
|
+
errors.push({
|
|
84
|
+
type: 'test',
|
|
85
|
+
message: `Assertion failed: expect(${match[1]}).${match[2]}(${match[3]})`,
|
|
86
|
+
severity: 'error',
|
|
87
|
+
rawOutput: match[0],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return errors;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse ESLint errors into structured format
|
|
94
|
+
*/
|
|
95
|
+
export function parseLintErrors(output) {
|
|
96
|
+
const errors = [];
|
|
97
|
+
// Match ESLint output patterns
|
|
98
|
+
// /path/to/file.ts
|
|
99
|
+
// 10:5 error 'foo' is defined but never used @typescript-eslint/no-unused-vars
|
|
100
|
+
let currentFile = null;
|
|
101
|
+
const lines = output.split('\n');
|
|
102
|
+
for (const line of lines) {
|
|
103
|
+
const fileMatch = line.match(/^([^\s].+\.[jt]sx?)$/);
|
|
104
|
+
if (fileMatch) {
|
|
105
|
+
currentFile = fileMatch[1];
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const errorMatch = line.match(/^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(.+)$/);
|
|
109
|
+
if (errorMatch && currentFile) {
|
|
110
|
+
const [, lineNum, column, severity, message, rule] = errorMatch;
|
|
111
|
+
errors.push({
|
|
112
|
+
type: 'lint',
|
|
113
|
+
file: currentFile,
|
|
114
|
+
line: parseInt(lineNum, 10),
|
|
115
|
+
column: parseInt(column, 10),
|
|
116
|
+
code: rule,
|
|
117
|
+
message: message.trim(),
|
|
118
|
+
severity: severity === 'error' ? 'error' : 'warning',
|
|
119
|
+
suggestedFix: generateLintFix(rule, message.trim(), currentFile, parseInt(lineNum, 10)),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return errors;
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Fix Generators
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Generate suggested fix for TypeScript errors
|
|
130
|
+
*/
|
|
131
|
+
function generateTypeScriptFix(code, _message, file) {
|
|
132
|
+
const fixes = {
|
|
133
|
+
// Type assignment errors
|
|
134
|
+
'TS2322': () => ({
|
|
135
|
+
description: 'Type mismatch - update the type annotation or fix the value',
|
|
136
|
+
autoFixable: false,
|
|
137
|
+
fixType: 'manual',
|
|
138
|
+
fixDetails: {
|
|
139
|
+
file,
|
|
140
|
+
manualSteps: [
|
|
141
|
+
'Check if the assigned value is correct',
|
|
142
|
+
'Update the type annotation if the value is intentional',
|
|
143
|
+
'Or fix the value to match the expected type',
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
// Property does not exist
|
|
148
|
+
'TS2339': () => ({
|
|
149
|
+
description: 'Property does not exist on type',
|
|
150
|
+
autoFixable: false,
|
|
151
|
+
fixType: 'manual',
|
|
152
|
+
fixDetails: {
|
|
153
|
+
file,
|
|
154
|
+
manualSteps: [
|
|
155
|
+
'Check if the property name is spelled correctly',
|
|
156
|
+
'Add the property to the interface/type definition',
|
|
157
|
+
'Use optional chaining (?.) if property might not exist',
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
// Cannot find name
|
|
162
|
+
'TS2304': () => ({
|
|
163
|
+
description: 'Cannot find name - likely missing import',
|
|
164
|
+
autoFixable: true,
|
|
165
|
+
fixType: 'command',
|
|
166
|
+
fixDetails: {
|
|
167
|
+
command: 'Add the missing import statement at the top of the file',
|
|
168
|
+
manualSteps: [
|
|
169
|
+
'Identify what needs to be imported',
|
|
170
|
+
'Add import statement',
|
|
171
|
+
'Or declare the variable/type if it should be local',
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
// Module not found
|
|
176
|
+
'TS2307': () => ({
|
|
177
|
+
description: 'Cannot find module - check import path or install package',
|
|
178
|
+
autoFixable: false,
|
|
179
|
+
fixType: 'manual',
|
|
180
|
+
fixDetails: {
|
|
181
|
+
manualSteps: [
|
|
182
|
+
'Check if the import path is correct',
|
|
183
|
+
'If it\'s an npm package, run: npm install <package>',
|
|
184
|
+
'If it\'s a local file, verify the path exists',
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
// Argument type mismatch
|
|
189
|
+
'TS2345': () => ({
|
|
190
|
+
description: 'Argument type mismatch',
|
|
191
|
+
autoFixable: false,
|
|
192
|
+
fixType: 'manual',
|
|
193
|
+
fixDetails: {
|
|
194
|
+
file,
|
|
195
|
+
manualSteps: [
|
|
196
|
+
'Check the function signature for expected parameter types',
|
|
197
|
+
'Cast the argument if appropriate: (arg as ExpectedType)',
|
|
198
|
+
'Or fix the argument value to match expected type',
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
// Object comparison warning
|
|
203
|
+
'TS2839': () => ({
|
|
204
|
+
description: 'Object comparison by reference - use proper comparison',
|
|
205
|
+
autoFixable: true,
|
|
206
|
+
fixType: 'edit',
|
|
207
|
+
fixDetails: {
|
|
208
|
+
file,
|
|
209
|
+
manualSteps: [
|
|
210
|
+
'Extract one side to a variable first',
|
|
211
|
+
'Or use JSON.stringify for deep comparison',
|
|
212
|
+
'Or use a proper equality function',
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
// Unused variable
|
|
217
|
+
'TS6133': () => ({
|
|
218
|
+
description: 'Variable declared but never used',
|
|
219
|
+
autoFixable: true,
|
|
220
|
+
fixType: 'edit',
|
|
221
|
+
fixDetails: {
|
|
222
|
+
file,
|
|
223
|
+
manualSteps: [
|
|
224
|
+
'Remove the unused variable',
|
|
225
|
+
'Or prefix with underscore if intentionally unused: _variable',
|
|
226
|
+
'Or use the variable where intended',
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
// Implicit any
|
|
231
|
+
'TS7006': () => ({
|
|
232
|
+
description: 'Parameter implicitly has an any type',
|
|
233
|
+
autoFixable: true,
|
|
234
|
+
fixType: 'edit',
|
|
235
|
+
fixDetails: {
|
|
236
|
+
file,
|
|
237
|
+
manualSteps: [
|
|
238
|
+
'Add explicit type annotation to the parameter',
|
|
239
|
+
'Example: (param: string) instead of (param)',
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
const fixGenerator = fixes[code];
|
|
245
|
+
return fixGenerator?.();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Generate suggested fix for ESLint errors
|
|
249
|
+
*/
|
|
250
|
+
function generateLintFix(rule, _message, file, _line) {
|
|
251
|
+
const fixes = {
|
|
252
|
+
// Unused variables
|
|
253
|
+
'@typescript-eslint/no-unused-vars': () => ({
|
|
254
|
+
description: 'Remove unused variable or prefix with underscore',
|
|
255
|
+
autoFixable: true,
|
|
256
|
+
fixType: 'command',
|
|
257
|
+
fixDetails: {
|
|
258
|
+
command: `eslint --fix ${file}`,
|
|
259
|
+
manualSteps: [
|
|
260
|
+
'Remove the unused variable declaration',
|
|
261
|
+
'Or prefix with underscore: const _unused = ...',
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
}),
|
|
265
|
+
// Missing return type
|
|
266
|
+
'@typescript-eslint/explicit-function-return-type': () => ({
|
|
267
|
+
description: 'Add explicit return type to function',
|
|
268
|
+
autoFixable: false,
|
|
269
|
+
fixType: 'manual',
|
|
270
|
+
fixDetails: {
|
|
271
|
+
file,
|
|
272
|
+
manualSteps: [
|
|
273
|
+
'Add return type annotation after function parameters',
|
|
274
|
+
'Example: function foo(): ReturnType { ... }',
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
278
|
+
// Prefer const
|
|
279
|
+
'prefer-const': () => ({
|
|
280
|
+
description: 'Use const instead of let for variables that are never reassigned',
|
|
281
|
+
autoFixable: true,
|
|
282
|
+
fixType: 'command',
|
|
283
|
+
fixDetails: {
|
|
284
|
+
command: `eslint --fix ${file}`,
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
// No explicit any
|
|
288
|
+
'@typescript-eslint/no-explicit-any': () => ({
|
|
289
|
+
description: 'Replace any with a specific type',
|
|
290
|
+
autoFixable: false,
|
|
291
|
+
fixType: 'manual',
|
|
292
|
+
fixDetails: {
|
|
293
|
+
file,
|
|
294
|
+
manualSteps: [
|
|
295
|
+
'Replace `any` with the actual expected type',
|
|
296
|
+
'Use `unknown` if type is truly unknown and add type guards',
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
};
|
|
301
|
+
const fixGenerator = fixes[rule];
|
|
302
|
+
return fixGenerator?.();
|
|
303
|
+
}
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// Validation Runner
|
|
306
|
+
// ============================================================================
|
|
307
|
+
export class ValidationRunner {
|
|
308
|
+
config;
|
|
309
|
+
constructor(config) {
|
|
310
|
+
this.config = {
|
|
311
|
+
workingDir: config.workingDir,
|
|
312
|
+
phases: config.phases ?? ['typescript', 'build', 'test'],
|
|
313
|
+
stopOnFirstFailure: config.stopOnFirstFailure ?? false,
|
|
314
|
+
timeout: config.timeout ?? 300000,
|
|
315
|
+
verbose: config.verbose ?? false,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Run all validation phases
|
|
320
|
+
*/
|
|
321
|
+
async runAll() {
|
|
322
|
+
const startTime = Date.now();
|
|
323
|
+
const allErrors = [];
|
|
324
|
+
const allWarnings = [];
|
|
325
|
+
const summaryParts = [];
|
|
326
|
+
for (const phase of this.config.phases) {
|
|
327
|
+
const result = await this.runPhase(phase);
|
|
328
|
+
allErrors.push(...result.errors);
|
|
329
|
+
allWarnings.push(...result.warnings);
|
|
330
|
+
summaryParts.push(`${phase}: ${result.success ? '✓' : '✗'}`);
|
|
331
|
+
if (!result.success && this.config.stopOnFirstFailure) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const autoFixableCount = allErrors.filter(e => e.suggestedFix?.autoFixable).length;
|
|
336
|
+
return {
|
|
337
|
+
success: allErrors.length === 0,
|
|
338
|
+
phase: 'all',
|
|
339
|
+
errors: allErrors,
|
|
340
|
+
warnings: allWarnings,
|
|
341
|
+
summary: summaryParts.join(' | '),
|
|
342
|
+
durationMs: Date.now() - startTime,
|
|
343
|
+
autoFixableCount,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Run a specific validation phase
|
|
348
|
+
*/
|
|
349
|
+
async runPhase(phase) {
|
|
350
|
+
switch (phase) {
|
|
351
|
+
case 'typescript':
|
|
352
|
+
return this.runTypeScriptValidation();
|
|
353
|
+
case 'build':
|
|
354
|
+
return this.runBuildValidation();
|
|
355
|
+
case 'test':
|
|
356
|
+
return this.runTestValidation();
|
|
357
|
+
case 'lint':
|
|
358
|
+
return this.runLintValidation();
|
|
359
|
+
default:
|
|
360
|
+
throw new Error(`Unknown validation phase: ${phase}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Run TypeScript type checking
|
|
365
|
+
*/
|
|
366
|
+
async runTypeScriptValidation() {
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
try {
|
|
369
|
+
// Check if tsconfig.json exists
|
|
370
|
+
await access(join(this.config.workingDir, 'tsconfig.json'));
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
phase: 'typescript',
|
|
376
|
+
errors: [],
|
|
377
|
+
warnings: [],
|
|
378
|
+
summary: 'TypeScript: skipped (no tsconfig.json)',
|
|
379
|
+
durationMs: Date.now() - startTime,
|
|
380
|
+
autoFixableCount: 0,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const { stdout, stderr } = await execAsync('npx tsc --noEmit', {
|
|
385
|
+
cwd: this.config.workingDir,
|
|
386
|
+
timeout: this.config.timeout,
|
|
387
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
388
|
+
});
|
|
389
|
+
const output = stdout + stderr;
|
|
390
|
+
const errors = parseTypeScriptErrors(output);
|
|
391
|
+
const warnings = errors.filter(e => e.severity === 'warning');
|
|
392
|
+
const actualErrors = errors.filter(e => e.severity === 'error');
|
|
393
|
+
return {
|
|
394
|
+
success: actualErrors.length === 0,
|
|
395
|
+
phase: 'typescript',
|
|
396
|
+
errors: actualErrors,
|
|
397
|
+
warnings,
|
|
398
|
+
summary: `TypeScript: ${actualErrors.length} error(s), ${warnings.length} warning(s)`,
|
|
399
|
+
durationMs: Date.now() - startTime,
|
|
400
|
+
autoFixableCount: errors.filter(e => e.suggestedFix?.autoFixable).length,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
405
|
+
const errors = parseTypeScriptErrors(output);
|
|
406
|
+
if (errors.length === 0) {
|
|
407
|
+
errors.push({
|
|
408
|
+
type: 'typescript',
|
|
409
|
+
message: error.message || 'TypeScript compilation failed',
|
|
410
|
+
severity: 'error',
|
|
411
|
+
rawOutput: output,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const actualErrors = errors.filter(e => e.severity === 'error');
|
|
415
|
+
const warnings = errors.filter(e => e.severity === 'warning');
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
phase: 'typescript',
|
|
419
|
+
errors: actualErrors,
|
|
420
|
+
warnings,
|
|
421
|
+
summary: `TypeScript: ${actualErrors.length} error(s)`,
|
|
422
|
+
durationMs: Date.now() - startTime,
|
|
423
|
+
autoFixableCount: errors.filter(e => e.suggestedFix?.autoFixable).length,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Run build validation
|
|
429
|
+
*/
|
|
430
|
+
async runBuildValidation() {
|
|
431
|
+
const startTime = Date.now();
|
|
432
|
+
try {
|
|
433
|
+
// Check if build script exists
|
|
434
|
+
const packageJson = await readFile(join(this.config.workingDir, 'package.json'), 'utf-8');
|
|
435
|
+
const pkg = JSON.parse(packageJson);
|
|
436
|
+
if (!pkg.scripts?.build) {
|
|
437
|
+
return {
|
|
438
|
+
success: true,
|
|
439
|
+
phase: 'build',
|
|
440
|
+
errors: [],
|
|
441
|
+
warnings: [],
|
|
442
|
+
summary: 'Build: skipped (no build script)',
|
|
443
|
+
durationMs: Date.now() - startTime,
|
|
444
|
+
autoFixableCount: 0,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
phase: 'build',
|
|
452
|
+
errors: [],
|
|
453
|
+
warnings: [],
|
|
454
|
+
summary: 'Build: skipped (no package.json)',
|
|
455
|
+
durationMs: Date.now() - startTime,
|
|
456
|
+
autoFixableCount: 0,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
await execAsync('npm run build', {
|
|
461
|
+
cwd: this.config.workingDir,
|
|
462
|
+
timeout: this.config.timeout,
|
|
463
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
464
|
+
});
|
|
465
|
+
return {
|
|
466
|
+
success: true,
|
|
467
|
+
phase: 'build',
|
|
468
|
+
errors: [],
|
|
469
|
+
warnings: [],
|
|
470
|
+
summary: 'Build: ✓ passed',
|
|
471
|
+
durationMs: Date.now() - startTime,
|
|
472
|
+
autoFixableCount: 0,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
477
|
+
// Try to parse as TypeScript errors first
|
|
478
|
+
let errors = parseTypeScriptErrors(output);
|
|
479
|
+
if (errors.length === 0) {
|
|
480
|
+
errors = [{
|
|
481
|
+
type: 'build',
|
|
482
|
+
message: 'Build failed',
|
|
483
|
+
severity: 'error',
|
|
484
|
+
rawOutput: output.slice(0, 2000),
|
|
485
|
+
}];
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
success: false,
|
|
489
|
+
phase: 'build',
|
|
490
|
+
errors,
|
|
491
|
+
warnings: [],
|
|
492
|
+
summary: `Build: ✗ failed (${errors.length} error(s))`,
|
|
493
|
+
durationMs: Date.now() - startTime,
|
|
494
|
+
autoFixableCount: errors.filter(e => e.suggestedFix?.autoFixable).length,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Run test validation
|
|
500
|
+
*/
|
|
501
|
+
async runTestValidation() {
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
try {
|
|
504
|
+
// Check if test script exists
|
|
505
|
+
const packageJson = await readFile(join(this.config.workingDir, 'package.json'), 'utf-8');
|
|
506
|
+
const pkg = JSON.parse(packageJson);
|
|
507
|
+
if (!pkg.scripts?.test) {
|
|
508
|
+
return {
|
|
509
|
+
success: true,
|
|
510
|
+
phase: 'test',
|
|
511
|
+
errors: [],
|
|
512
|
+
warnings: [],
|
|
513
|
+
summary: 'Tests: skipped (no test script)',
|
|
514
|
+
durationMs: Date.now() - startTime,
|
|
515
|
+
autoFixableCount: 0,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
return {
|
|
521
|
+
success: true,
|
|
522
|
+
phase: 'test',
|
|
523
|
+
errors: [],
|
|
524
|
+
warnings: [],
|
|
525
|
+
summary: 'Tests: skipped (no package.json)',
|
|
526
|
+
durationMs: Date.now() - startTime,
|
|
527
|
+
autoFixableCount: 0,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
const { stdout, stderr } = await execAsync('npm test', {
|
|
532
|
+
cwd: this.config.workingDir,
|
|
533
|
+
timeout: this.config.timeout,
|
|
534
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
535
|
+
});
|
|
536
|
+
const output = stdout + stderr;
|
|
537
|
+
// Check for test failures even in "successful" exit
|
|
538
|
+
const errors = parseTestErrors(output);
|
|
539
|
+
const actualErrors = errors.filter(e => e.severity === 'error');
|
|
540
|
+
return {
|
|
541
|
+
success: actualErrors.length === 0,
|
|
542
|
+
phase: 'test',
|
|
543
|
+
errors: actualErrors,
|
|
544
|
+
warnings: [],
|
|
545
|
+
summary: `Tests: ${actualErrors.length === 0 ? '✓ passed' : `✗ ${actualErrors.length} failure(s)`}`,
|
|
546
|
+
durationMs: Date.now() - startTime,
|
|
547
|
+
autoFixableCount: 0,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
552
|
+
const errors = parseTestErrors(output);
|
|
553
|
+
if (errors.length === 0) {
|
|
554
|
+
errors.push({
|
|
555
|
+
type: 'test',
|
|
556
|
+
message: 'Tests failed',
|
|
557
|
+
severity: 'error',
|
|
558
|
+
rawOutput: output.slice(0, 2000),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
success: false,
|
|
563
|
+
phase: 'test',
|
|
564
|
+
errors,
|
|
565
|
+
warnings: [],
|
|
566
|
+
summary: `Tests: ✗ failed (${errors.length} failure(s))`,
|
|
567
|
+
durationMs: Date.now() - startTime,
|
|
568
|
+
autoFixableCount: 0,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Run lint validation
|
|
574
|
+
*/
|
|
575
|
+
async runLintValidation() {
|
|
576
|
+
const startTime = Date.now();
|
|
577
|
+
try {
|
|
578
|
+
// Check if lint script exists
|
|
579
|
+
const packageJson = await readFile(join(this.config.workingDir, 'package.json'), 'utf-8');
|
|
580
|
+
const pkg = JSON.parse(packageJson);
|
|
581
|
+
if (!pkg.scripts?.lint) {
|
|
582
|
+
return {
|
|
583
|
+
success: true,
|
|
584
|
+
phase: 'lint',
|
|
585
|
+
errors: [],
|
|
586
|
+
warnings: [],
|
|
587
|
+
summary: 'Lint: skipped (no lint script)',
|
|
588
|
+
durationMs: Date.now() - startTime,
|
|
589
|
+
autoFixableCount: 0,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return {
|
|
595
|
+
success: true,
|
|
596
|
+
phase: 'lint',
|
|
597
|
+
errors: [],
|
|
598
|
+
warnings: [],
|
|
599
|
+
summary: 'Lint: skipped (no package.json)',
|
|
600
|
+
durationMs: Date.now() - startTime,
|
|
601
|
+
autoFixableCount: 0,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const { stdout, stderr } = await execAsync('npm run lint', {
|
|
606
|
+
cwd: this.config.workingDir,
|
|
607
|
+
timeout: this.config.timeout,
|
|
608
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
609
|
+
});
|
|
610
|
+
const output = stdout + stderr;
|
|
611
|
+
const errors = parseLintErrors(output);
|
|
612
|
+
const actualErrors = errors.filter(e => e.severity === 'error');
|
|
613
|
+
const warnings = errors.filter(e => e.severity === 'warning');
|
|
614
|
+
return {
|
|
615
|
+
success: actualErrors.length === 0,
|
|
616
|
+
phase: 'lint',
|
|
617
|
+
errors: actualErrors,
|
|
618
|
+
warnings,
|
|
619
|
+
summary: `Lint: ${actualErrors.length} error(s), ${warnings.length} warning(s)`,
|
|
620
|
+
durationMs: Date.now() - startTime,
|
|
621
|
+
autoFixableCount: errors.filter(e => e.suggestedFix?.autoFixable).length,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
626
|
+
const errors = parseLintErrors(output);
|
|
627
|
+
if (errors.length === 0) {
|
|
628
|
+
errors.push({
|
|
629
|
+
type: 'lint',
|
|
630
|
+
message: 'Lint check failed',
|
|
631
|
+
severity: 'error',
|
|
632
|
+
rawOutput: output.slice(0, 2000),
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
const actualErrors = errors.filter(e => e.severity === 'error');
|
|
636
|
+
const warnings = errors.filter(e => e.severity === 'warning');
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
phase: 'lint',
|
|
640
|
+
errors: actualErrors,
|
|
641
|
+
warnings,
|
|
642
|
+
summary: `Lint: ✗ ${actualErrors.length} error(s)`,
|
|
643
|
+
durationMs: Date.now() - startTime,
|
|
644
|
+
autoFixableCount: errors.filter(e => e.suggestedFix?.autoFixable).length,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// ============================================================================
|
|
650
|
+
// Formatting Helpers
|
|
651
|
+
// ============================================================================
|
|
652
|
+
/**
|
|
653
|
+
* Format validation result for display
|
|
654
|
+
*/
|
|
655
|
+
export function formatValidationResult(result) {
|
|
656
|
+
const lines = [];
|
|
657
|
+
lines.push(`## Validation ${result.success ? 'Passed ✓' : 'Failed ✗'}`);
|
|
658
|
+
lines.push(`Duration: ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
659
|
+
lines.push('');
|
|
660
|
+
lines.push(`Summary: ${result.summary}`);
|
|
661
|
+
if (result.errors.length > 0) {
|
|
662
|
+
lines.push('');
|
|
663
|
+
lines.push(`### Errors (${result.errors.length})`);
|
|
664
|
+
for (const error of result.errors.slice(0, 20)) {
|
|
665
|
+
const location = error.file
|
|
666
|
+
? `${error.file}${error.line ? `:${error.line}` : ''}${error.column ? `:${error.column}` : ''}`
|
|
667
|
+
: '';
|
|
668
|
+
const code = error.code ? `[${error.code}]` : '';
|
|
669
|
+
lines.push(`- ${location} ${code} ${error.message}`);
|
|
670
|
+
if (error.suggestedFix) {
|
|
671
|
+
lines.push(` Fix: ${error.suggestedFix.description}`);
|
|
672
|
+
if (error.suggestedFix.autoFixable) {
|
|
673
|
+
lines.push(` (Auto-fixable)`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (result.errors.length > 20) {
|
|
678
|
+
lines.push(`... and ${result.errors.length - 20} more errors`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (result.warnings.length > 0) {
|
|
682
|
+
lines.push('');
|
|
683
|
+
lines.push(`### Warnings (${result.warnings.length})`);
|
|
684
|
+
for (const warning of result.warnings.slice(0, 10)) {
|
|
685
|
+
const location = warning.file ? `${warning.file}:${warning.line || '?'}` : '';
|
|
686
|
+
lines.push(`- ${location} ${warning.message}`);
|
|
687
|
+
}
|
|
688
|
+
if (result.warnings.length > 10) {
|
|
689
|
+
lines.push(`... and ${result.warnings.length - 10} more warnings`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (result.autoFixableCount > 0) {
|
|
693
|
+
lines.push('');
|
|
694
|
+
lines.push(`### Auto-fixable: ${result.autoFixableCount} issue(s)`);
|
|
695
|
+
lines.push('Use the auto-fix feature to automatically resolve these issues.');
|
|
696
|
+
}
|
|
697
|
+
return lines.join('\n');
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Format errors for AI consumption (structured for intelligent fixing)
|
|
701
|
+
*/
|
|
702
|
+
export function formatErrorsForAI(errors) {
|
|
703
|
+
const lines = [];
|
|
704
|
+
lines.push('# Validation Errors for AI Analysis');
|
|
705
|
+
lines.push('');
|
|
706
|
+
lines.push('The following errors need to be fixed. For each error:');
|
|
707
|
+
lines.push('1. Read the file at the specified location');
|
|
708
|
+
lines.push('2. Understand the context around the error');
|
|
709
|
+
lines.push('3. Apply the suggested fix or determine the appropriate correction');
|
|
710
|
+
lines.push('');
|
|
711
|
+
const groupedByFile = new Map();
|
|
712
|
+
for (const error of errors) {
|
|
713
|
+
const key = error.file || 'unknown';
|
|
714
|
+
if (!groupedByFile.has(key)) {
|
|
715
|
+
groupedByFile.set(key, []);
|
|
716
|
+
}
|
|
717
|
+
groupedByFile.get(key).push(error);
|
|
718
|
+
}
|
|
719
|
+
for (const [file, fileErrors] of groupedByFile) {
|
|
720
|
+
lines.push(`## ${file}`);
|
|
721
|
+
lines.push('');
|
|
722
|
+
for (const error of fileErrors) {
|
|
723
|
+
lines.push(`### Line ${error.line || '?'}: ${error.code || error.type}`);
|
|
724
|
+
lines.push(`Message: ${error.message}`);
|
|
725
|
+
if (error.suggestedFix) {
|
|
726
|
+
lines.push(`Suggested fix: ${error.suggestedFix.description}`);
|
|
727
|
+
lines.push(`Auto-fixable: ${error.suggestedFix.autoFixable ? 'Yes' : 'No'}`);
|
|
728
|
+
if (error.suggestedFix.fixDetails.manualSteps) {
|
|
729
|
+
lines.push('Steps:');
|
|
730
|
+
for (const step of error.suggestedFix.fixDetails.manualSteps) {
|
|
731
|
+
lines.push(` - ${step}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
lines.push('');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return lines.join('\n');
|
|
739
|
+
}
|
|
740
|
+
//# sourceMappingURL=validationRunner.js.map
|