erosolar-cli 1.7.172 → 1.7.175

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.
Files changed (67) hide show
  1. package/dist/capabilities/index.d.ts +1 -0
  2. package/dist/capabilities/index.d.ts.map +1 -1
  3. package/dist/capabilities/index.js +1 -0
  4. package/dist/capabilities/index.js.map +1 -1
  5. package/dist/capabilities/validationCapability.d.ts +13 -0
  6. package/dist/capabilities/validationCapability.d.ts.map +1 -0
  7. package/dist/capabilities/validationCapability.js +24 -0
  8. package/dist/capabilities/validationCapability.js.map +1 -0
  9. package/dist/core/agent.d.ts +11 -0
  10. package/dist/core/agent.d.ts.map +1 -1
  11. package/dist/core/agent.js +18 -2
  12. package/dist/core/agent.js.map +1 -1
  13. package/dist/core/contextManager.d.ts +36 -1
  14. package/dist/core/contextManager.d.ts.map +1 -1
  15. package/dist/core/contextManager.js +54 -0
  16. package/dist/core/contextManager.js.map +1 -1
  17. package/dist/core/errors/errorTypes.d.ts +20 -16
  18. package/dist/core/errors/errorTypes.d.ts.map +1 -1
  19. package/dist/core/errors/errorTypes.js +1 -1
  20. package/dist/core/errors/errorTypes.js.map +1 -1
  21. package/dist/core/performanceMonitor.d.ts +33 -20
  22. package/dist/core/performanceMonitor.d.ts.map +1 -1
  23. package/dist/core/performanceMonitor.js.map +1 -1
  24. package/dist/core/preferences.d.ts +1 -0
  25. package/dist/core/preferences.d.ts.map +1 -1
  26. package/dist/core/preferences.js +7 -0
  27. package/dist/core/preferences.js.map +1 -1
  28. package/dist/core/toolPreconditions.d.ts +23 -4
  29. package/dist/core/toolPreconditions.d.ts.map +1 -1
  30. package/dist/core/toolPreconditions.js +90 -0
  31. package/dist/core/toolPreconditions.js.map +1 -1
  32. package/dist/core/toolRuntime.d.ts +27 -18
  33. package/dist/core/toolRuntime.d.ts.map +1 -1
  34. package/dist/core/toolRuntime.js +24 -1
  35. package/dist/core/toolRuntime.js.map +1 -1
  36. package/dist/core/validationRunner.d.ts +93 -0
  37. package/dist/core/validationRunner.d.ts.map +1 -0
  38. package/dist/core/validationRunner.js +740 -0
  39. package/dist/core/validationRunner.js.map +1 -0
  40. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
  41. package/dist/plugins/tools/nodeDefaults.js +2 -0
  42. package/dist/plugins/tools/nodeDefaults.js.map +1 -1
  43. package/dist/plugins/tools/validation/validationPlugin.d.ts +3 -0
  44. package/dist/plugins/tools/validation/validationPlugin.d.ts.map +1 -0
  45. package/dist/plugins/tools/validation/validationPlugin.js +14 -0
  46. package/dist/plugins/tools/validation/validationPlugin.js.map +1 -0
  47. package/dist/runtime/agentSession.d.ts +2 -0
  48. package/dist/runtime/agentSession.d.ts.map +1 -1
  49. package/dist/runtime/agentSession.js +12 -3
  50. package/dist/runtime/agentSession.js.map +1 -1
  51. package/dist/shell/chatBox.d.ts +228 -0
  52. package/dist/shell/chatBox.d.ts.map +1 -0
  53. package/dist/shell/chatBox.js +811 -0
  54. package/dist/shell/chatBox.js.map +1 -0
  55. package/dist/shell/interactiveShell.d.ts +15 -0
  56. package/dist/shell/interactiveShell.d.ts.map +1 -1
  57. package/dist/shell/interactiveShell.js +150 -11
  58. package/dist/shell/interactiveShell.js.map +1 -1
  59. package/dist/tools/validationTools.d.ts +7 -0
  60. package/dist/tools/validationTools.d.ts.map +1 -0
  61. package/dist/tools/validationTools.js +278 -0
  62. package/dist/tools/validationTools.js.map +1 -0
  63. package/dist/ui/persistentPrompt.d.ts +5 -11
  64. package/dist/ui/persistentPrompt.d.ts.map +1 -1
  65. package/dist/ui/persistentPrompt.js +40 -22
  66. package/dist/ui/persistentPrompt.js.map +1 -1
  67. 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