@codebakers/cli 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,889 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { execSync, spawn } from 'child_process';
6
+
7
+ // ============================================================================
8
+ // TYPES
9
+ // ============================================================================
10
+
11
+ type ErrorSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
12
+
13
+ type ErrorCategory =
14
+ | 'typescript'
15
+ | 'runtime'
16
+ | 'build'
17
+ | 'dependency'
18
+ | 'database'
19
+ | 'auth'
20
+ | 'api'
21
+ | 'performance'
22
+ | 'security'
23
+ | 'configuration'
24
+ | 'network'
25
+ | 'unknown';
26
+
27
+ interface ClassifiedError {
28
+ id: string;
29
+ timestamp: Date;
30
+ category: ErrorCategory;
31
+ severity: ErrorSeverity;
32
+ message: string;
33
+ stack?: string;
34
+ file?: string;
35
+ line?: number;
36
+ column?: number;
37
+ autoFixable: boolean;
38
+ confidence: number;
39
+ suggestedFixes: SuggestedFix[];
40
+ fixed?: boolean;
41
+ }
42
+
43
+ interface SuggestedFix {
44
+ id: string;
45
+ description: string;
46
+ code?: string;
47
+ file?: string;
48
+ confidence: number;
49
+ risk: 'safe' | 'moderate' | 'risky';
50
+ requiresReview: boolean;
51
+ command?: string;
52
+ }
53
+
54
+ interface HealOptions {
55
+ auto?: boolean;
56
+ watch?: boolean;
57
+ severity?: string;
58
+ dryRun?: boolean;
59
+ }
60
+
61
+ interface HealResult {
62
+ errors: ClassifiedError[];
63
+ fixed: number;
64
+ remaining: number;
65
+ }
66
+
67
+ // ============================================================================
68
+ // ERROR CLASSIFICATION
69
+ // ============================================================================
70
+
71
+ interface ClassificationRule {
72
+ category: ErrorCategory;
73
+ severity: ErrorSeverity;
74
+ pattern: RegExp;
75
+ autoFixable: boolean;
76
+ confidence: number;
77
+ fixId: string;
78
+ fixDescription: string;
79
+ fixCommand?: string;
80
+ risk: 'safe' | 'moderate' | 'risky';
81
+ }
82
+
83
+ const ERROR_PATTERNS: ClassificationRule[] = [
84
+ // TypeScript Errors
85
+ {
86
+ category: 'typescript',
87
+ severity: 'high',
88
+ pattern: /TS2307.*Cannot find module '([^']+)'/,
89
+ autoFixable: true,
90
+ confidence: 90,
91
+ fixId: 'install_module',
92
+ fixDescription: 'Install missing module',
93
+ risk: 'safe'
94
+ },
95
+ {
96
+ category: 'typescript',
97
+ severity: 'medium',
98
+ pattern: /TS2322.*Type '([^']+)' is not assignable to type '([^']+)'/,
99
+ autoFixable: false,
100
+ confidence: 60,
101
+ fixId: 'fix_type',
102
+ fixDescription: 'Fix type mismatch',
103
+ risk: 'moderate'
104
+ },
105
+ {
106
+ category: 'typescript',
107
+ severity: 'medium',
108
+ pattern: /TS2339.*Property '([^']+)' does not exist on type '([^']+)'/,
109
+ autoFixable: false,
110
+ confidence: 70,
111
+ fixId: 'add_property',
112
+ fixDescription: 'Add missing property to type',
113
+ risk: 'moderate'
114
+ },
115
+ {
116
+ category: 'typescript',
117
+ severity: 'low',
118
+ pattern: /TS7006.*Parameter '([^']+)' implicitly has an 'any' type/,
119
+ autoFixable: false,
120
+ confidence: 80,
121
+ fixId: 'add_type',
122
+ fixDescription: 'Add explicit type annotation',
123
+ risk: 'safe'
124
+ },
125
+
126
+ // Dependency Errors
127
+ {
128
+ category: 'dependency',
129
+ severity: 'high',
130
+ pattern: /Module not found: Can't resolve '([^']+)'/,
131
+ autoFixable: true,
132
+ confidence: 90,
133
+ fixId: 'install_package',
134
+ fixDescription: 'Install missing package',
135
+ risk: 'safe'
136
+ },
137
+ {
138
+ category: 'dependency',
139
+ severity: 'high',
140
+ pattern: /Cannot find module '([^']+)'/,
141
+ autoFixable: true,
142
+ confidence: 85,
143
+ fixId: 'install_package',
144
+ fixDescription: 'Install missing package',
145
+ risk: 'safe'
146
+ },
147
+
148
+ // Database Errors
149
+ {
150
+ category: 'database',
151
+ severity: 'critical',
152
+ pattern: /connect ECONNREFUSED/,
153
+ autoFixable: false,
154
+ confidence: 95,
155
+ fixId: 'check_db',
156
+ fixDescription: 'Check database connection - ensure database is running',
157
+ risk: 'safe'
158
+ },
159
+ {
160
+ category: 'database',
161
+ severity: 'high',
162
+ pattern: /relation "([^"]+)" does not exist/,
163
+ autoFixable: true,
164
+ confidence: 90,
165
+ fixId: 'run_migrations',
166
+ fixDescription: 'Run database migrations',
167
+ fixCommand: 'npx drizzle-kit push',
168
+ risk: 'moderate'
169
+ },
170
+
171
+ // Auth Errors
172
+ {
173
+ category: 'auth',
174
+ severity: 'critical',
175
+ pattern: /AUTH_SECRET.*missing|undefined|AUTH_SECRET is not set/i,
176
+ autoFixable: true,
177
+ confidence: 95,
178
+ fixId: 'generate_auth_secret',
179
+ fixDescription: 'Generate AUTH_SECRET',
180
+ risk: 'safe'
181
+ },
182
+
183
+ // Configuration Errors
184
+ {
185
+ category: 'configuration',
186
+ severity: 'high',
187
+ pattern: /Missing required environment variable:?\s*([A-Z_]+)/i,
188
+ autoFixable: false,
189
+ confidence: 95,
190
+ fixId: 'add_env_var',
191
+ fixDescription: 'Add missing environment variable',
192
+ risk: 'safe'
193
+ },
194
+ {
195
+ category: 'configuration',
196
+ severity: 'high',
197
+ pattern: /NEXT_PUBLIC_([A-Z_]+).*undefined/,
198
+ autoFixable: false,
199
+ confidence: 90,
200
+ fixId: 'add_env_var',
201
+ fixDescription: 'Add missing public environment variable',
202
+ risk: 'safe'
203
+ },
204
+
205
+ // Security Errors
206
+ {
207
+ category: 'security',
208
+ severity: 'high',
209
+ pattern: /found (\d+) vulnerabilit(y|ies)/i,
210
+ autoFixable: true,
211
+ confidence: 80,
212
+ fixId: 'npm_audit_fix',
213
+ fixDescription: 'Run npm audit fix',
214
+ fixCommand: 'npm audit fix',
215
+ risk: 'moderate'
216
+ },
217
+ {
218
+ category: 'security',
219
+ severity: 'critical',
220
+ pattern: /(\d+) critical/i,
221
+ autoFixable: false,
222
+ confidence: 90,
223
+ fixId: 'npm_audit_fix_critical',
224
+ fixDescription: 'Review and fix critical vulnerabilities manually',
225
+ risk: 'risky'
226
+ },
227
+
228
+ // Build Errors
229
+ {
230
+ category: 'build',
231
+ severity: 'high',
232
+ pattern: /Build failed|Failed to compile/i,
233
+ autoFixable: false,
234
+ confidence: 50,
235
+ fixId: 'fix_build',
236
+ fixDescription: 'Review build errors',
237
+ risk: 'moderate'
238
+ },
239
+
240
+ // Runtime Errors
241
+ {
242
+ category: 'runtime',
243
+ severity: 'high',
244
+ pattern: /Cannot read propert(y|ies) of (undefined|null)/,
245
+ autoFixable: false,
246
+ confidence: 70,
247
+ fixId: 'add_null_check',
248
+ fixDescription: 'Add null/undefined check',
249
+ risk: 'safe'
250
+ },
251
+ {
252
+ category: 'runtime',
253
+ severity: 'high',
254
+ pattern: /([A-Za-z]+) is not defined/,
255
+ autoFixable: false,
256
+ confidence: 75,
257
+ fixId: 'add_import',
258
+ fixDescription: 'Add missing import or declaration',
259
+ risk: 'safe'
260
+ }
261
+ ];
262
+
263
+ function generateErrorId(): string {
264
+ return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
265
+ }
266
+
267
+ function classifyError(errorText: string, file?: string, line?: number): ClassifiedError {
268
+ for (const rule of ERROR_PATTERNS) {
269
+ const match = errorText.match(rule.pattern);
270
+ if (match) {
271
+ return {
272
+ id: generateErrorId(),
273
+ timestamp: new Date(),
274
+ category: rule.category,
275
+ severity: rule.severity,
276
+ message: errorText.trim(),
277
+ file,
278
+ line,
279
+ autoFixable: rule.autoFixable,
280
+ confidence: rule.confidence,
281
+ suggestedFixes: [{
282
+ id: rule.fixId,
283
+ description: rule.fixDescription,
284
+ confidence: rule.confidence,
285
+ risk: rule.risk,
286
+ requiresReview: rule.risk !== 'safe',
287
+ command: rule.fixCommand
288
+ }]
289
+ };
290
+ }
291
+ }
292
+
293
+ // Unknown error
294
+ return {
295
+ id: generateErrorId(),
296
+ timestamp: new Date(),
297
+ category: 'unknown',
298
+ severity: 'medium',
299
+ message: errorText.trim(),
300
+ file,
301
+ line,
302
+ autoFixable: false,
303
+ confidence: 0,
304
+ suggestedFixes: []
305
+ };
306
+ }
307
+
308
+ // ============================================================================
309
+ // ERROR SCANNING
310
+ // ============================================================================
311
+
312
+ async function scanTypeScriptErrors(): Promise<ClassifiedError[]> {
313
+ const errors: ClassifiedError[] = [];
314
+
315
+ try {
316
+ execSync('npx tsc --noEmit 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
317
+ } catch (error: any) {
318
+ const output = error.stdout || error.stderr || error.message || '';
319
+ const lines = output.split('\n');
320
+
321
+ let currentFile = '';
322
+ let currentLine = 0;
323
+
324
+ for (const line of lines) {
325
+ // Match file:line:col pattern
326
+ const fileMatch = line.match(/^([^:]+):(\d+):(\d+)/);
327
+ if (fileMatch) {
328
+ currentFile = fileMatch[1];
329
+ currentLine = parseInt(fileMatch[2], 10);
330
+ }
331
+
332
+ // Match TS error codes
333
+ const tsMatch = line.match(/error (TS\d+):/);
334
+ if (tsMatch) {
335
+ const classified = classifyError(line, currentFile, currentLine);
336
+ errors.push(classified);
337
+ }
338
+ }
339
+ }
340
+
341
+ return errors;
342
+ }
343
+
344
+ async function scanBuildErrors(): Promise<ClassifiedError[]> {
345
+ const errors: ClassifiedError[] = [];
346
+
347
+ try {
348
+ // Check if build script exists
349
+ const packageJson = JSON.parse(await fs.readFile('package.json', 'utf-8'));
350
+ if (!packageJson.scripts?.build) {
351
+ return errors;
352
+ }
353
+
354
+ execSync('npm run build 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
355
+ } catch (error: any) {
356
+ const output = error.stdout || error.stderr || error.message || '';
357
+ const lines = output.split('\n');
358
+
359
+ for (const line of lines) {
360
+ if (line.includes('error') || line.includes('Error') || line.includes('failed')) {
361
+ const classified = classifyError(line);
362
+ // Avoid duplicates from TS errors
363
+ if (classified.category !== 'typescript') {
364
+ errors.push(classified);
365
+ }
366
+ }
367
+ }
368
+ }
369
+
370
+ return errors;
371
+ }
372
+
373
+ async function scanEnvironmentIssues(): Promise<ClassifiedError[]> {
374
+ const errors: ClassifiedError[] = [];
375
+
376
+ // Check for .env files
377
+ const envFiles = ['.env', '.env.local', '.env.development'];
378
+ let hasEnvFile = false;
379
+
380
+ for (const file of envFiles) {
381
+ try {
382
+ await fs.access(file);
383
+ hasEnvFile = true;
384
+ break;
385
+ } catch {
386
+ // File doesn't exist
387
+ }
388
+ }
389
+
390
+ if (!hasEnvFile) {
391
+ errors.push({
392
+ id: generateErrorId(),
393
+ timestamp: new Date(),
394
+ category: 'configuration',
395
+ severity: 'high',
396
+ message: 'No .env file found - environment variables may not be configured',
397
+ autoFixable: false,
398
+ confidence: 90,
399
+ suggestedFixes: [{
400
+ id: 'create_env',
401
+ description: 'Create .env.local file with required variables',
402
+ confidence: 90,
403
+ risk: 'safe',
404
+ requiresReview: true
405
+ }]
406
+ });
407
+ }
408
+
409
+ // Check for AUTH_SECRET if using auth
410
+ try {
411
+ const envContent = await fs.readFile('.env.local', 'utf-8').catch(() => '');
412
+ const packageJson = JSON.parse(await fs.readFile('package.json', 'utf-8'));
413
+
414
+ const hasAuth = packageJson.dependencies?.['next-auth'] ||
415
+ packageJson.dependencies?.['@auth/core'] ||
416
+ packageJson.dependencies?.['@supabase/auth-helpers-nextjs'];
417
+
418
+ if (hasAuth && !envContent.includes('AUTH_SECRET')) {
419
+ errors.push({
420
+ id: generateErrorId(),
421
+ timestamp: new Date(),
422
+ category: 'auth',
423
+ severity: 'critical',
424
+ message: 'AUTH_SECRET not found in .env.local - authentication will not work',
425
+ autoFixable: true,
426
+ confidence: 95,
427
+ suggestedFixes: [{
428
+ id: 'generate_auth_secret',
429
+ description: 'Generate and add AUTH_SECRET to .env.local',
430
+ confidence: 95,
431
+ risk: 'safe',
432
+ requiresReview: false
433
+ }]
434
+ });
435
+ }
436
+ } catch {
437
+ // Skip if can't read files
438
+ }
439
+
440
+ return errors;
441
+ }
442
+
443
+ async function scanSecurityIssues(): Promise<ClassifiedError[]> {
444
+ const errors: ClassifiedError[] = [];
445
+
446
+ try {
447
+ const output = execSync('npm audit --json 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
448
+ const audit = JSON.parse(output);
449
+
450
+ if (audit.metadata?.vulnerabilities) {
451
+ const { critical, high, moderate, low } = audit.metadata.vulnerabilities;
452
+
453
+ if (critical > 0) {
454
+ errors.push({
455
+ id: generateErrorId(),
456
+ timestamp: new Date(),
457
+ category: 'security',
458
+ severity: 'critical',
459
+ message: `${critical} critical vulnerabilities found`,
460
+ autoFixable: false,
461
+ confidence: 95,
462
+ suggestedFixes: [{
463
+ id: 'npm_audit_fix',
464
+ description: 'Run npm audit fix --force (may have breaking changes)',
465
+ confidence: 70,
466
+ risk: 'risky',
467
+ requiresReview: true,
468
+ command: 'npm audit fix --force'
469
+ }]
470
+ });
471
+ }
472
+
473
+ if (high > 0) {
474
+ errors.push({
475
+ id: generateErrorId(),
476
+ timestamp: new Date(),
477
+ category: 'security',
478
+ severity: 'high',
479
+ message: `${high} high-severity vulnerabilities found`,
480
+ autoFixable: true,
481
+ confidence: 85,
482
+ suggestedFixes: [{
483
+ id: 'npm_audit_fix',
484
+ description: 'Run npm audit fix',
485
+ confidence: 85,
486
+ risk: 'moderate',
487
+ requiresReview: false,
488
+ command: 'npm audit fix'
489
+ }]
490
+ });
491
+ }
492
+
493
+ if (moderate > 0) {
494
+ errors.push({
495
+ id: generateErrorId(),
496
+ timestamp: new Date(),
497
+ category: 'security',
498
+ severity: 'medium',
499
+ message: `${moderate} moderate vulnerabilities found`,
500
+ autoFixable: true,
501
+ confidence: 90,
502
+ suggestedFixes: [{
503
+ id: 'npm_audit_fix',
504
+ description: 'Run npm audit fix',
505
+ confidence: 90,
506
+ risk: 'safe',
507
+ requiresReview: false,
508
+ command: 'npm audit fix'
509
+ }]
510
+ });
511
+ }
512
+ }
513
+ } catch {
514
+ // npm audit failed or no issues
515
+ }
516
+
517
+ return errors;
518
+ }
519
+
520
+ async function scanDatabaseIssues(): Promise<ClassifiedError[]> {
521
+ const errors: ClassifiedError[] = [];
522
+
523
+ try {
524
+ // Check if using Drizzle
525
+ const packageJson = JSON.parse(await fs.readFile('package.json', 'utf-8'));
526
+ if (!packageJson.dependencies?.['drizzle-orm']) {
527
+ return errors;
528
+ }
529
+
530
+ // Check for migrations folder
531
+ const migrationsPaths = ['drizzle', 'src/db/migrations', 'migrations'];
532
+ let hasMigrations = false;
533
+
534
+ for (const migPath of migrationsPaths) {
535
+ try {
536
+ const stat = await fs.stat(migPath);
537
+ if (stat.isDirectory()) {
538
+ const files = await fs.readdir(migPath);
539
+ if (files.some(f => f.endsWith('.sql'))) {
540
+ hasMigrations = true;
541
+ break;
542
+ }
543
+ }
544
+ } catch {
545
+ // Directory doesn't exist
546
+ }
547
+ }
548
+
549
+ if (!hasMigrations) {
550
+ errors.push({
551
+ id: generateErrorId(),
552
+ timestamp: new Date(),
553
+ category: 'database',
554
+ severity: 'medium',
555
+ message: 'No database migrations found - schema may not be pushed',
556
+ autoFixable: true,
557
+ confidence: 85,
558
+ suggestedFixes: [{
559
+ id: 'generate_migrations',
560
+ description: 'Generate and push migrations',
561
+ confidence: 85,
562
+ risk: 'moderate',
563
+ requiresReview: true,
564
+ command: 'npx drizzle-kit generate && npx drizzle-kit push'
565
+ }]
566
+ });
567
+ }
568
+ } catch {
569
+ // Skip if can't read package.json
570
+ }
571
+
572
+ return errors;
573
+ }
574
+
575
+ // ============================================================================
576
+ // FIX APPLICATION
577
+ // ============================================================================
578
+
579
+ async function applyFix(error: ClassifiedError, fix: SuggestedFix): Promise<boolean> {
580
+ switch (fix.id) {
581
+ case 'install_package':
582
+ case 'install_module':
583
+ return await installMissingPackage(error);
584
+
585
+ case 'generate_auth_secret':
586
+ return await generateAuthSecret();
587
+
588
+ case 'npm_audit_fix':
589
+ return await runCommand('npm audit fix');
590
+
591
+ case 'run_migrations':
592
+ return await runCommand('npx drizzle-kit push');
593
+
594
+ case 'generate_migrations':
595
+ return await runCommand('npx drizzle-kit generate && npx drizzle-kit push');
596
+
597
+ default:
598
+ if (fix.command) {
599
+ return await runCommand(fix.command);
600
+ }
601
+ return false;
602
+ }
603
+ }
604
+
605
+ async function installMissingPackage(error: ClassifiedError): Promise<boolean> {
606
+ // Extract package name from error message
607
+ const match = error.message.match(/(?:Cannot find module|Can't resolve) '([^']+)'/);
608
+ if (!match) return false;
609
+
610
+ let packageName = match[1];
611
+
612
+ // Skip relative imports
613
+ if (packageName.startsWith('.') || packageName.startsWith('@/')) {
614
+ return false;
615
+ }
616
+
617
+ // Extract base package name (handle scoped packages and subpaths)
618
+ if (packageName.startsWith('@')) {
619
+ packageName = packageName.split('/').slice(0, 2).join('/');
620
+ } else {
621
+ packageName = packageName.split('/')[0];
622
+ }
623
+
624
+ return await runCommand(`npm install ${packageName}`);
625
+ }
626
+
627
+ async function generateAuthSecret(): Promise<boolean> {
628
+ try {
629
+ const crypto = await import('crypto');
630
+ const secret = crypto.randomBytes(32).toString('base64');
631
+
632
+ let envContent = '';
633
+ try {
634
+ envContent = await fs.readFile('.env.local', 'utf-8');
635
+ } catch {
636
+ // File doesn't exist, create it
637
+ }
638
+
639
+ if (envContent.includes('AUTH_SECRET=')) {
640
+ envContent = envContent.replace(/AUTH_SECRET=.*/, `AUTH_SECRET=${secret}`);
641
+ } else {
642
+ envContent += `\nAUTH_SECRET=${secret}\n`;
643
+ }
644
+
645
+ await fs.writeFile('.env.local', envContent.trim() + '\n');
646
+ return true;
647
+ } catch {
648
+ return false;
649
+ }
650
+ }
651
+
652
+ async function runCommand(command: string): Promise<boolean> {
653
+ try {
654
+ execSync(command, { stdio: 'pipe' });
655
+ return true;
656
+ } catch {
657
+ return false;
658
+ }
659
+ }
660
+
661
+ // ============================================================================
662
+ // DISPLAY
663
+ // ============================================================================
664
+
665
+ function displayError(error: ClassifiedError): void {
666
+ const severityColors: Record<ErrorSeverity, typeof chalk.red> = {
667
+ critical: chalk.red,
668
+ high: chalk.red,
669
+ medium: chalk.yellow,
670
+ low: chalk.blue,
671
+ info: chalk.gray
672
+ };
673
+
674
+ const severityIcons: Record<ErrorSeverity, string> = {
675
+ critical: 'šŸ”“',
676
+ high: '🟠',
677
+ medium: '🟔',
678
+ low: 'šŸ”µ',
679
+ info: 'ā„¹ļø'
680
+ };
681
+
682
+ const color = severityColors[error.severity];
683
+ const icon = severityIcons[error.severity];
684
+
685
+ console.log(`\n${icon} ${color(chalk.bold(error.category.toUpperCase()))} (${error.severity})`);
686
+ console.log(chalk.white(` ${error.message}`));
687
+
688
+ if (error.file) {
689
+ console.log(chalk.gray(` šŸ“ ${error.file}${error.line ? `:${error.line}` : ''}`));
690
+ }
691
+
692
+ if (error.autoFixable) {
693
+ console.log(chalk.green(` āœ“ Auto-fixable (${error.confidence}% confidence)`));
694
+ }
695
+
696
+ for (const fix of error.suggestedFixes) {
697
+ const riskColor = fix.risk === 'safe' ? chalk.green :
698
+ fix.risk === 'moderate' ? chalk.yellow : chalk.red;
699
+ console.log(chalk.cyan(` → ${fix.description}`) + riskColor(` [${fix.risk}]`));
700
+ if (fix.command) {
701
+ console.log(chalk.gray(` $ ${fix.command}`));
702
+ }
703
+ }
704
+ }
705
+
706
+ // ============================================================================
707
+ // MAIN HEAL FUNCTION
708
+ // ============================================================================
709
+
710
+ export async function heal(options: HealOptions = {}): Promise<HealResult> {
711
+ console.log(chalk.blue('\nšŸ„ CodeBakers Self-Healing System\n'));
712
+ console.log(chalk.gray('Scanning for issues...\n'));
713
+
714
+ const allErrors: ClassifiedError[] = [];
715
+
716
+ // Scan for all types of errors
717
+ const scanners = [
718
+ { name: 'TypeScript', fn: scanTypeScriptErrors },
719
+ { name: 'Build', fn: scanBuildErrors },
720
+ { name: 'Environment', fn: scanEnvironmentIssues },
721
+ { name: 'Security', fn: scanSecurityIssues },
722
+ { name: 'Database', fn: scanDatabaseIssues }
723
+ ];
724
+
725
+ for (const scanner of scanners) {
726
+ const spinner = ora(`Checking ${scanner.name}...`).start();
727
+ try {
728
+ const errors = await scanner.fn();
729
+ allErrors.push(...errors);
730
+ if (errors.length > 0) {
731
+ spinner.warn(`${scanner.name}: ${errors.length} issue(s)`);
732
+ } else {
733
+ spinner.succeed(`${scanner.name}: OK`);
734
+ }
735
+ } catch (error) {
736
+ spinner.fail(`${scanner.name}: Error scanning`);
737
+ }
738
+ }
739
+
740
+ // Filter by severity if specified
741
+ let errors = allErrors;
742
+ if (options.severity) {
743
+ errors = errors.filter(e => e.severity === options.severity);
744
+ }
745
+
746
+ // Remove duplicates by message
747
+ const seen = new Set<string>();
748
+ errors = errors.filter(e => {
749
+ if (seen.has(e.message)) return false;
750
+ seen.add(e.message);
751
+ return true;
752
+ });
753
+
754
+ console.log('');
755
+
756
+ if (errors.length === 0) {
757
+ console.log(chalk.green('✨ No issues found! Your project is healthy.\n'));
758
+ return { errors: [], fixed: 0, remaining: 0 };
759
+ }
760
+
761
+ console.log(chalk.yellow(`Found ${errors.length} issue(s):`));
762
+
763
+ // Sort by severity
764
+ const severityOrder: ErrorSeverity[] = ['critical', 'high', 'medium', 'low', 'info'];
765
+ errors.sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));
766
+
767
+ // Display all errors
768
+ for (const error of errors) {
769
+ displayError(error);
770
+ }
771
+
772
+ console.log('');
773
+
774
+ // Apply fixes
775
+ let fixed = 0;
776
+
777
+ if (!options.dryRun) {
778
+ const fixableErrors = errors.filter(e =>
779
+ e.autoFixable &&
780
+ e.confidence >= 80 &&
781
+ e.suggestedFixes.some(f => f.risk !== 'risky')
782
+ );
783
+
784
+ if (fixableErrors.length > 0) {
785
+ console.log(chalk.blue(`\nšŸ”§ Applying ${fixableErrors.length} auto-fix(es)...\n`));
786
+
787
+ for (const error of fixableErrors) {
788
+ const safeFix = error.suggestedFixes.find(f => f.risk !== 'risky');
789
+ if (!safeFix) continue;
790
+
791
+ if (!options.auto) {
792
+ // In non-auto mode, we just show what would be fixed
793
+ console.log(chalk.gray(`Would fix: ${error.category} - ${safeFix.description}`));
794
+ continue;
795
+ }
796
+
797
+ const spinner = ora(`Fixing: ${safeFix.description}`).start();
798
+
799
+ try {
800
+ const success = await applyFix(error, safeFix);
801
+ if (success) {
802
+ spinner.succeed(`Fixed: ${safeFix.description}`);
803
+ error.fixed = true;
804
+ fixed++;
805
+ } else {
806
+ spinner.fail(`Failed: ${safeFix.description}`);
807
+ }
808
+ } catch (err) {
809
+ spinner.fail(`Error: ${safeFix.description}`);
810
+ }
811
+ }
812
+ }
813
+ }
814
+
815
+ // Summary
816
+ const remaining = errors.length - fixed;
817
+
818
+ console.log(chalk.blue('\nšŸ“Š Summary'));
819
+ console.log(` Total issues: ${chalk.white(errors.length)}`);
820
+ console.log(` Fixed: ${chalk.green(fixed)}`);
821
+ console.log(` Remaining: ${remaining > 0 ? chalk.yellow(remaining) : chalk.green(0)}`);
822
+
823
+ if (options.dryRun) {
824
+ console.log(chalk.gray('\n [Dry run - no changes made]'));
825
+ } else if (!options.auto && errors.some(e => e.autoFixable)) {
826
+ console.log(chalk.gray('\n Run with --auto to apply fixes automatically'));
827
+ }
828
+
829
+ console.log('');
830
+
831
+ return {
832
+ errors,
833
+ fixed,
834
+ remaining
835
+ };
836
+ }
837
+
838
+ // ============================================================================
839
+ // WATCH MODE
840
+ // ============================================================================
841
+
842
+ export async function healWatch(): Promise<void> {
843
+ console.log(chalk.blue('\nšŸ‘ļø Self-Healing Watch Mode\n'));
844
+ console.log(chalk.gray('Monitoring for errors... (Ctrl+C to stop)\n'));
845
+
846
+ // Initial scan
847
+ await heal({ auto: true });
848
+
849
+ // Watch for file changes using dynamic import
850
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
851
+ let chokidarModule: any = null;
852
+ try {
853
+ // @ts-ignore - chokidar is an optional dependency for watch mode
854
+ chokidarModule = await import('chokidar');
855
+ } catch {
856
+ console.log(chalk.yellow('\nWatch mode requires chokidar:'));
857
+ console.log(chalk.cyan(' npm install -D chokidar\n'));
858
+ console.log(chalk.gray('For now, run `codebakers heal` manually after making changes.'));
859
+ return;
860
+ }
861
+
862
+ const watcher = chokidarModule.watch(['src/**/*.{ts,tsx}', 'app/**/*.{ts,tsx}'], {
863
+ ignored: /node_modules/,
864
+ persistent: true,
865
+ ignoreInitial: true
866
+ });
867
+
868
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
869
+
870
+ watcher.on('change', (filePath: string) => {
871
+ console.log(chalk.gray(`\nFile changed: ${filePath}`));
872
+
873
+ // Debounce to avoid running multiple times
874
+ if (debounceTimer) {
875
+ clearTimeout(debounceTimer);
876
+ }
877
+
878
+ debounceTimer = setTimeout(async () => {
879
+ await heal({ auto: true });
880
+ }, 1000);
881
+ });
882
+
883
+ // Keep process alive
884
+ process.on('SIGINT', () => {
885
+ console.log(chalk.blue('\n\nšŸ‘‹ Watch mode stopped\n'));
886
+ watcher.close();
887
+ process.exit(0);
888
+ });
889
+ }