@codebakers/cli 1.6.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,730 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.audit = audit;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ const child_process_1 = require("child_process");
11
+ /**
12
+ * Run automated code quality audit
13
+ */
14
+ async function audit() {
15
+ console.log(chalk_1.default.blue('\n CodeBakers Audit\n'));
16
+ console.log(chalk_1.default.gray(' Running automated checks...\n'));
17
+ const cwd = process.cwd();
18
+ const checks = [];
19
+ // ============================================================
20
+ // BUILD & TYPES
21
+ // ============================================================
22
+ console.log(chalk_1.default.white(' Build & Types:'));
23
+ // Check TypeScript
24
+ const tsCheck = await checkTypeScript(cwd);
25
+ checks.push(tsCheck);
26
+ printCheck(tsCheck);
27
+ // Check ESLint
28
+ const eslintCheck = await checkESLint(cwd);
29
+ checks.push(eslintCheck);
30
+ printCheck(eslintCheck);
31
+ // Check Build
32
+ const buildCheck = await checkBuild(cwd);
33
+ checks.push(buildCheck);
34
+ printCheck(buildCheck);
35
+ // ============================================================
36
+ // SECURITY
37
+ // ============================================================
38
+ console.log(chalk_1.default.white('\n Security:'));
39
+ // Check for secrets in code
40
+ const secretsCheck = checkSecretsInCode(cwd);
41
+ checks.push(secretsCheck);
42
+ printCheck(secretsCheck);
43
+ // Check npm audit
44
+ const npmAuditCheck = await checkNpmAudit(cwd);
45
+ checks.push(npmAuditCheck);
46
+ printCheck(npmAuditCheck);
47
+ // Check .env.local not committed
48
+ const envGitCheck = checkEnvNotCommitted(cwd);
49
+ checks.push(envGitCheck);
50
+ printCheck(envGitCheck);
51
+ // ============================================================
52
+ // CODE QUALITY
53
+ // ============================================================
54
+ console.log(chalk_1.default.white('\n Code Quality:'));
55
+ // Check for console.log
56
+ const consoleLogCheck = checkConsoleLog(cwd);
57
+ checks.push(consoleLogCheck);
58
+ printCheck(consoleLogCheck);
59
+ // Check for API route validation
60
+ const validationCheck = checkApiValidation(cwd);
61
+ checks.push(validationCheck);
62
+ printCheck(validationCheck);
63
+ // Check for error boundaries
64
+ const errorBoundaryCheck = checkErrorBoundary(cwd);
65
+ checks.push(errorBoundaryCheck);
66
+ printCheck(errorBoundaryCheck);
67
+ // ============================================================
68
+ // ENVIRONMENT
69
+ // ============================================================
70
+ console.log(chalk_1.default.white('\n Environment:'));
71
+ // Check .env.example exists
72
+ const envExampleCheck = checkEnvExample(cwd);
73
+ checks.push(envExampleCheck);
74
+ printCheck(envExampleCheck);
75
+ // Check env vars match
76
+ const envMatchCheck = checkEnvMatch(cwd);
77
+ checks.push(envMatchCheck);
78
+ printCheck(envMatchCheck);
79
+ // ============================================================
80
+ // PROJECT STRUCTURE
81
+ // ============================================================
82
+ console.log(chalk_1.default.white('\n Project Structure:'));
83
+ // Check for CodeBakers patterns
84
+ const patternsCheck = checkCodeBakersPatterns(cwd);
85
+ checks.push(patternsCheck);
86
+ printCheck(patternsCheck);
87
+ // Check for tests
88
+ const testsCheck = checkTests(cwd);
89
+ checks.push(testsCheck);
90
+ printCheck(testsCheck);
91
+ // ============================================================
92
+ // SUMMARY
93
+ // ============================================================
94
+ const passed = checks.filter(c => c.passed).length;
95
+ const total = checks.length;
96
+ const score = Math.round((passed / total) * 100);
97
+ console.log(chalk_1.default.white('\n ─────────────────────────────────────────────────\n'));
98
+ if (score >= 90) {
99
+ console.log(chalk_1.default.green(` ✅ Score: ${passed}/${total} checks passed (${score}%)\n`));
100
+ console.log(chalk_1.default.green(' Excellent! Your project is production-ready.\n'));
101
+ }
102
+ else if (score >= 70) {
103
+ console.log(chalk_1.default.yellow(` ⚠️ Score: ${passed}/${total} checks passed (${score}%)\n`));
104
+ console.log(chalk_1.default.yellow(' Good progress. Fix the issues above before deploying.\n'));
105
+ }
106
+ else {
107
+ console.log(chalk_1.default.red(` ❌ Score: ${passed}/${total} checks passed (${score}%)\n`));
108
+ console.log(chalk_1.default.red(' Needs attention. Address critical issues before deploying.\n'));
109
+ }
110
+ // Show critical issues summary
111
+ const criticalIssues = checks.filter(c => !c.passed && c.severity === 'error');
112
+ if (criticalIssues.length > 0) {
113
+ console.log(chalk_1.default.red(' Critical Issues:'));
114
+ for (const issue of criticalIssues) {
115
+ console.log(chalk_1.default.red(` • ${issue.message}`));
116
+ }
117
+ console.log('');
118
+ }
119
+ // Show warnings
120
+ const warnings = checks.filter(c => !c.passed && c.severity === 'warning');
121
+ if (warnings.length > 0) {
122
+ console.log(chalk_1.default.yellow(' Warnings:'));
123
+ for (const warning of warnings) {
124
+ console.log(chalk_1.default.yellow(` • ${warning.message}`));
125
+ }
126
+ console.log('');
127
+ }
128
+ console.log(chalk_1.default.gray(' Tip: Run /audit in Claude for full 100-point inspection.\n'));
129
+ return {
130
+ checks,
131
+ score,
132
+ maxScore: 100,
133
+ passed: score >= 70,
134
+ };
135
+ }
136
+ // ============================================================
137
+ // CHECK FUNCTIONS
138
+ // ============================================================
139
+ function printCheck(check) {
140
+ const icon = check.passed ? chalk_1.default.green('✓') : (check.severity === 'error' ? chalk_1.default.red('✗') : chalk_1.default.yellow('⚠'));
141
+ console.log(` ${icon} ${check.message}`);
142
+ if (!check.passed && check.details && check.details.length > 0) {
143
+ for (const detail of check.details.slice(0, 3)) {
144
+ console.log(chalk_1.default.gray(` └─ ${detail}`));
145
+ }
146
+ if (check.details.length > 3) {
147
+ console.log(chalk_1.default.gray(` └─ ...and ${check.details.length - 3} more`));
148
+ }
149
+ }
150
+ }
151
+ async function checkTypeScript(cwd) {
152
+ const tsconfigPath = (0, path_1.join)(cwd, 'tsconfig.json');
153
+ if (!(0, fs_1.existsSync)(tsconfigPath)) {
154
+ return {
155
+ name: 'typescript',
156
+ category: 'build',
157
+ passed: false,
158
+ message: 'No tsconfig.json found',
159
+ severity: 'warning',
160
+ };
161
+ }
162
+ try {
163
+ (0, child_process_1.execSync)('npx tsc --noEmit', { cwd, stdio: 'pipe' });
164
+ return {
165
+ name: 'typescript',
166
+ category: 'build',
167
+ passed: true,
168
+ message: 'TypeScript compiles (0 errors)',
169
+ severity: 'info',
170
+ };
171
+ }
172
+ catch (error) {
173
+ const output = error instanceof Error && 'stdout' in error
174
+ ? error.stdout?.toString() || ''
175
+ : '';
176
+ const errorCount = (output.match(/error TS/g) || []).length;
177
+ return {
178
+ name: 'typescript',
179
+ category: 'build',
180
+ passed: false,
181
+ message: `TypeScript errors (${errorCount} errors)`,
182
+ details: ['Run: npx tsc --noEmit to see details'],
183
+ severity: 'error',
184
+ };
185
+ }
186
+ }
187
+ async function checkESLint(cwd) {
188
+ const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
189
+ if (!(0, fs_1.existsSync)(packageJsonPath)) {
190
+ return {
191
+ name: 'eslint',
192
+ category: 'build',
193
+ passed: false,
194
+ message: 'No package.json found',
195
+ severity: 'warning',
196
+ };
197
+ }
198
+ try {
199
+ const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
200
+ const hasEslint = packageJson.devDependencies?.eslint || packageJson.dependencies?.eslint;
201
+ if (!hasEslint) {
202
+ return {
203
+ name: 'eslint',
204
+ category: 'build',
205
+ passed: false,
206
+ message: 'ESLint not installed',
207
+ details: ['Run: npm install -D eslint'],
208
+ severity: 'warning',
209
+ };
210
+ }
211
+ (0, child_process_1.execSync)('npx eslint . --max-warnings=0', { cwd, stdio: 'pipe' });
212
+ return {
213
+ name: 'eslint',
214
+ category: 'build',
215
+ passed: true,
216
+ message: 'ESLint passes (0 warnings)',
217
+ severity: 'info',
218
+ };
219
+ }
220
+ catch {
221
+ return {
222
+ name: 'eslint',
223
+ category: 'build',
224
+ passed: false,
225
+ message: 'ESLint has warnings or errors',
226
+ details: ['Run: npx eslint . to see details'],
227
+ severity: 'warning',
228
+ };
229
+ }
230
+ }
231
+ async function checkBuild(cwd) {
232
+ const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
233
+ if (!(0, fs_1.existsSync)(packageJsonPath)) {
234
+ return {
235
+ name: 'build',
236
+ category: 'build',
237
+ passed: false,
238
+ message: 'No package.json found',
239
+ severity: 'error',
240
+ };
241
+ }
242
+ try {
243
+ const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
244
+ const hasBuildScript = packageJson.scripts?.build;
245
+ if (!hasBuildScript) {
246
+ return {
247
+ name: 'build',
248
+ category: 'build',
249
+ passed: true,
250
+ message: 'No build script (skipped)',
251
+ severity: 'info',
252
+ };
253
+ }
254
+ // Check if .next exists (Next.js) or dist exists
255
+ const hasNextBuild = (0, fs_1.existsSync)((0, path_1.join)(cwd, '.next'));
256
+ const hasDistBuild = (0, fs_1.existsSync)((0, path_1.join)(cwd, 'dist'));
257
+ if (hasNextBuild || hasDistBuild) {
258
+ return {
259
+ name: 'build',
260
+ category: 'build',
261
+ passed: true,
262
+ message: 'Build output exists',
263
+ severity: 'info',
264
+ };
265
+ }
266
+ return {
267
+ name: 'build',
268
+ category: 'build',
269
+ passed: false,
270
+ message: 'No build output found',
271
+ details: ['Run: npm run build'],
272
+ severity: 'warning',
273
+ };
274
+ }
275
+ catch {
276
+ return {
277
+ name: 'build',
278
+ category: 'build',
279
+ passed: false,
280
+ message: 'Could not check build status',
281
+ severity: 'warning',
282
+ };
283
+ }
284
+ }
285
+ function checkSecretsInCode(cwd) {
286
+ const srcDir = (0, path_1.join)(cwd, 'src');
287
+ if (!(0, fs_1.existsSync)(srcDir)) {
288
+ return {
289
+ name: 'secrets',
290
+ category: 'security',
291
+ passed: true,
292
+ message: 'No src/ directory (skipped)',
293
+ severity: 'info',
294
+ };
295
+ }
296
+ const secretPatterns = [
297
+ /sk_live_[a-zA-Z0-9]+/, // Stripe live key
298
+ /sk_test_[a-zA-Z0-9]+/, // Stripe test key
299
+ /ghp_[a-zA-Z0-9]+/, // GitHub token
300
+ /AKIA[A-Z0-9]{16}/, // AWS access key
301
+ /-----BEGIN RSA PRIVATE KEY-----/, // RSA key
302
+ /-----BEGIN PRIVATE KEY-----/, // Generic private key
303
+ ];
304
+ const issues = [];
305
+ function scanDir(dir) {
306
+ try {
307
+ const files = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
308
+ for (const file of files) {
309
+ if (file.isDirectory() && !file.name.startsWith('.') && file.name !== 'node_modules') {
310
+ scanDir((0, path_1.join)(dir, file.name));
311
+ }
312
+ else if (file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.tsx') || file.name.endsWith('.js'))) {
313
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(dir, file.name), 'utf-8');
314
+ for (const pattern of secretPatterns) {
315
+ if (pattern.test(content)) {
316
+ issues.push((0, path_1.join)(dir, file.name).replace(cwd, ''));
317
+ break;
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // Ignore read errors
325
+ }
326
+ }
327
+ scanDir(srcDir);
328
+ if (issues.length > 0) {
329
+ return {
330
+ name: 'secrets',
331
+ category: 'security',
332
+ passed: false,
333
+ message: `Possible secrets in code (${issues.length} files)`,
334
+ details: issues,
335
+ severity: 'error',
336
+ };
337
+ }
338
+ return {
339
+ name: 'secrets',
340
+ category: 'security',
341
+ passed: true,
342
+ message: 'No secrets detected in code',
343
+ severity: 'info',
344
+ };
345
+ }
346
+ async function checkNpmAudit(cwd) {
347
+ try {
348
+ (0, child_process_1.execSync)('npm audit --audit-level=high --json', { cwd, stdio: 'pipe' });
349
+ return {
350
+ name: 'npm-audit',
351
+ category: 'security',
352
+ passed: true,
353
+ message: 'No high/critical vulnerabilities',
354
+ severity: 'info',
355
+ };
356
+ }
357
+ catch (error) {
358
+ try {
359
+ const output = error instanceof Error && 'stdout' in error
360
+ ? error.stdout?.toString() || '{}'
361
+ : '{}';
362
+ const auditResult = JSON.parse(output);
363
+ const high = auditResult.metadata?.vulnerabilities?.high || 0;
364
+ const critical = auditResult.metadata?.vulnerabilities?.critical || 0;
365
+ if (high > 0 || critical > 0) {
366
+ return {
367
+ name: 'npm-audit',
368
+ category: 'security',
369
+ passed: false,
370
+ message: `Vulnerabilities: ${critical} critical, ${high} high`,
371
+ details: ['Run: npm audit to see details', 'Run: npm audit fix to auto-fix'],
372
+ severity: 'error',
373
+ };
374
+ }
375
+ }
376
+ catch {
377
+ // Parse failed, assume passed
378
+ }
379
+ return {
380
+ name: 'npm-audit',
381
+ category: 'security',
382
+ passed: true,
383
+ message: 'No high/critical vulnerabilities',
384
+ severity: 'info',
385
+ };
386
+ }
387
+ }
388
+ function checkEnvNotCommitted(cwd) {
389
+ const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
390
+ if (!(0, fs_1.existsSync)(gitignorePath)) {
391
+ return {
392
+ name: 'env-git',
393
+ category: 'security',
394
+ passed: false,
395
+ message: 'No .gitignore file',
396
+ details: ['Create .gitignore and add .env.local'],
397
+ severity: 'warning',
398
+ };
399
+ }
400
+ const gitignore = (0, fs_1.readFileSync)(gitignorePath, 'utf-8');
401
+ const hasEnvLocal = gitignore.includes('.env.local') || gitignore.includes('.env*.local');
402
+ if (!hasEnvLocal) {
403
+ return {
404
+ name: 'env-git',
405
+ category: 'security',
406
+ passed: false,
407
+ message: '.env.local not in .gitignore',
408
+ details: ['Add .env.local to .gitignore'],
409
+ severity: 'error',
410
+ };
411
+ }
412
+ return {
413
+ name: 'env-git',
414
+ category: 'security',
415
+ passed: true,
416
+ message: '.env.local properly gitignored',
417
+ severity: 'info',
418
+ };
419
+ }
420
+ function checkConsoleLog(cwd) {
421
+ const srcDir = (0, path_1.join)(cwd, 'src');
422
+ if (!(0, fs_1.existsSync)(srcDir)) {
423
+ return {
424
+ name: 'console-log',
425
+ category: 'quality',
426
+ passed: true,
427
+ message: 'No src/ directory (skipped)',
428
+ severity: 'info',
429
+ };
430
+ }
431
+ const issues = [];
432
+ function scanDir(dir) {
433
+ try {
434
+ const files = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
435
+ for (const file of files) {
436
+ if (file.isDirectory() && !file.name.startsWith('.') && file.name !== 'node_modules') {
437
+ scanDir((0, path_1.join)(dir, file.name));
438
+ }
439
+ else if (file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.tsx'))) {
440
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(dir, file.name), 'utf-8');
441
+ // Match console.log but not console.error or console.warn
442
+ if (/console\.log\s*\(/.test(content)) {
443
+ issues.push((0, path_1.join)(dir, file.name).replace(cwd, ''));
444
+ }
445
+ }
446
+ }
447
+ }
448
+ catch {
449
+ // Ignore read errors
450
+ }
451
+ }
452
+ scanDir(srcDir);
453
+ if (issues.length > 0) {
454
+ return {
455
+ name: 'console-log',
456
+ category: 'quality',
457
+ passed: false,
458
+ message: `console.log found (${issues.length} files)`,
459
+ details: issues,
460
+ severity: 'warning',
461
+ };
462
+ }
463
+ return {
464
+ name: 'console-log',
465
+ category: 'quality',
466
+ passed: true,
467
+ message: 'No console.log in production code',
468
+ severity: 'info',
469
+ };
470
+ }
471
+ function checkApiValidation(cwd) {
472
+ const apiDir = (0, path_1.join)(cwd, 'src', 'app', 'api');
473
+ if (!(0, fs_1.existsSync)(apiDir)) {
474
+ return {
475
+ name: 'api-validation',
476
+ category: 'quality',
477
+ passed: true,
478
+ message: 'No API routes (skipped)',
479
+ severity: 'info',
480
+ };
481
+ }
482
+ const issues = [];
483
+ let totalRoutes = 0;
484
+ function scanDir(dir) {
485
+ try {
486
+ const files = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
487
+ for (const file of files) {
488
+ if (file.isDirectory()) {
489
+ scanDir((0, path_1.join)(dir, file.name));
490
+ }
491
+ else if (file.name === 'route.ts' || file.name === 'route.tsx') {
492
+ totalRoutes++;
493
+ const content = (0, fs_1.readFileSync)((0, path_1.join)(dir, file.name), 'utf-8');
494
+ // Check for Zod validation
495
+ if (!content.includes('zod') && !content.includes('.parse(') && !content.includes('.safeParse(')) {
496
+ issues.push((0, path_1.join)(dir, file.name).replace(cwd, ''));
497
+ }
498
+ }
499
+ }
500
+ }
501
+ catch {
502
+ // Ignore read errors
503
+ }
504
+ }
505
+ scanDir(apiDir);
506
+ if (issues.length > 0) {
507
+ return {
508
+ name: 'api-validation',
509
+ category: 'quality',
510
+ passed: false,
511
+ message: `API routes without validation (${issues.length}/${totalRoutes})`,
512
+ details: issues,
513
+ severity: 'warning',
514
+ };
515
+ }
516
+ return {
517
+ name: 'api-validation',
518
+ category: 'quality',
519
+ passed: true,
520
+ message: `All ${totalRoutes} API routes have validation`,
521
+ severity: 'info',
522
+ };
523
+ }
524
+ function checkErrorBoundary(cwd) {
525
+ const appDir = (0, path_1.join)(cwd, 'src', 'app');
526
+ if (!(0, fs_1.existsSync)(appDir)) {
527
+ return {
528
+ name: 'error-boundary',
529
+ category: 'quality',
530
+ passed: true,
531
+ message: 'No app/ directory (skipped)',
532
+ severity: 'info',
533
+ };
534
+ }
535
+ const errorPath = (0, path_1.join)(appDir, 'error.tsx');
536
+ const globalErrorPath = (0, path_1.join)(appDir, 'global-error.tsx');
537
+ if ((0, fs_1.existsSync)(errorPath) || (0, fs_1.existsSync)(globalErrorPath)) {
538
+ return {
539
+ name: 'error-boundary',
540
+ category: 'quality',
541
+ passed: true,
542
+ message: 'Error boundary exists',
543
+ severity: 'info',
544
+ };
545
+ }
546
+ return {
547
+ name: 'error-boundary',
548
+ category: 'quality',
549
+ passed: false,
550
+ message: 'No error boundary (error.tsx)',
551
+ details: ['Create src/app/error.tsx for error handling'],
552
+ severity: 'warning',
553
+ };
554
+ }
555
+ function checkEnvExample(cwd) {
556
+ const envExamplePath = (0, path_1.join)(cwd, '.env.example');
557
+ if ((0, fs_1.existsSync)(envExamplePath)) {
558
+ return {
559
+ name: 'env-example',
560
+ category: 'environment',
561
+ passed: true,
562
+ message: '.env.example exists',
563
+ severity: 'info',
564
+ };
565
+ }
566
+ return {
567
+ name: 'env-example',
568
+ category: 'environment',
569
+ passed: false,
570
+ message: 'No .env.example file',
571
+ details: ['Create .env.example with all required variables'],
572
+ severity: 'warning',
573
+ };
574
+ }
575
+ function checkEnvMatch(cwd) {
576
+ const envExamplePath = (0, path_1.join)(cwd, '.env.example');
577
+ const envLocalPath = (0, path_1.join)(cwd, '.env.local');
578
+ if (!(0, fs_1.existsSync)(envExamplePath)) {
579
+ return {
580
+ name: 'env-match',
581
+ category: 'environment',
582
+ passed: true,
583
+ message: 'No .env.example to compare (skipped)',
584
+ severity: 'info',
585
+ };
586
+ }
587
+ if (!(0, fs_1.existsSync)(envLocalPath)) {
588
+ return {
589
+ name: 'env-match',
590
+ category: 'environment',
591
+ passed: false,
592
+ message: 'No .env.local file',
593
+ details: ['Copy .env.example to .env.local and fill in values'],
594
+ severity: 'warning',
595
+ };
596
+ }
597
+ const exampleVars = parseEnvFile((0, fs_1.readFileSync)(envExamplePath, 'utf-8'));
598
+ const localVars = parseEnvFile((0, fs_1.readFileSync)(envLocalPath, 'utf-8'));
599
+ const missingInLocal = exampleVars.filter(v => !localVars.includes(v));
600
+ const extraInLocal = localVars.filter(v => !exampleVars.includes(v) && !v.startsWith('#'));
601
+ if (missingInLocal.length > 0) {
602
+ return {
603
+ name: 'env-match',
604
+ category: 'environment',
605
+ passed: false,
606
+ message: `Missing ${missingInLocal.length} vars from .env.example`,
607
+ details: missingInLocal,
608
+ severity: 'warning',
609
+ };
610
+ }
611
+ if (extraInLocal.length > 0) {
612
+ return {
613
+ name: 'env-match',
614
+ category: 'environment',
615
+ passed: false,
616
+ message: `${extraInLocal.length} vars in .env.local not in .env.example`,
617
+ details: extraInLocal,
618
+ severity: 'info',
619
+ };
620
+ }
621
+ return {
622
+ name: 'env-match',
623
+ category: 'environment',
624
+ passed: true,
625
+ message: 'Environment variables match',
626
+ severity: 'info',
627
+ };
628
+ }
629
+ function parseEnvFile(content) {
630
+ return content
631
+ .split('\n')
632
+ .filter(line => line.trim() && !line.startsWith('#'))
633
+ .map(line => line.split('=')[0].trim())
634
+ .filter(Boolean);
635
+ }
636
+ function checkCodeBakersPatterns(cwd) {
637
+ const claudeDir = (0, path_1.join)(cwd, '.claude');
638
+ const claudeMd = (0, path_1.join)(cwd, 'CLAUDE.md');
639
+ if (!(0, fs_1.existsSync)(claudeDir) && !(0, fs_1.existsSync)(claudeMd)) {
640
+ return {
641
+ name: 'patterns',
642
+ category: 'structure',
643
+ passed: false,
644
+ message: 'CodeBakers patterns not installed',
645
+ details: ['Run: codebakers init'],
646
+ severity: 'info',
647
+ };
648
+ }
649
+ if ((0, fs_1.existsSync)(claudeDir)) {
650
+ const files = (0, fs_1.readdirSync)(claudeDir).filter(f => f.endsWith('.md'));
651
+ return {
652
+ name: 'patterns',
653
+ category: 'structure',
654
+ passed: true,
655
+ message: `${files.length} CodeBakers modules installed`,
656
+ severity: 'info',
657
+ };
658
+ }
659
+ return {
660
+ name: 'patterns',
661
+ category: 'structure',
662
+ passed: true,
663
+ message: 'CLAUDE.md exists',
664
+ severity: 'info',
665
+ };
666
+ }
667
+ function checkTests(cwd) {
668
+ const testDirs = [
669
+ (0, path_1.join)(cwd, '__tests__'),
670
+ (0, path_1.join)(cwd, 'tests'),
671
+ (0, path_1.join)(cwd, 'test'),
672
+ (0, path_1.join)(cwd, 'src', '__tests__'),
673
+ ];
674
+ const testFiles = [];
675
+ for (const dir of testDirs) {
676
+ if ((0, fs_1.existsSync)(dir)) {
677
+ try {
678
+ const files = (0, fs_1.readdirSync)(dir, { recursive: true });
679
+ testFiles.push(...files.filter(f => f.endsWith('.test.ts') ||
680
+ f.endsWith('.test.tsx') ||
681
+ f.endsWith('.spec.ts') ||
682
+ f.endsWith('.spec.tsx')));
683
+ }
684
+ catch {
685
+ // Ignore
686
+ }
687
+ }
688
+ }
689
+ // Also check for test files in src
690
+ const srcDir = (0, path_1.join)(cwd, 'src');
691
+ if ((0, fs_1.existsSync)(srcDir)) {
692
+ function findTestFiles(dir) {
693
+ try {
694
+ const files = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
695
+ for (const file of files) {
696
+ if (file.isDirectory() && !file.name.startsWith('.') && file.name !== 'node_modules') {
697
+ findTestFiles((0, path_1.join)(dir, file.name));
698
+ }
699
+ else if (file.name.endsWith('.test.ts') ||
700
+ file.name.endsWith('.test.tsx') ||
701
+ file.name.endsWith('.spec.ts') ||
702
+ file.name.endsWith('.spec.tsx')) {
703
+ testFiles.push(file.name);
704
+ }
705
+ }
706
+ }
707
+ catch {
708
+ // Ignore
709
+ }
710
+ }
711
+ findTestFiles(srcDir);
712
+ }
713
+ if (testFiles.length === 0) {
714
+ return {
715
+ name: 'tests',
716
+ category: 'structure',
717
+ passed: false,
718
+ message: 'No test files found',
719
+ details: ['Add tests for critical paths'],
720
+ severity: 'warning',
721
+ };
722
+ }
723
+ return {
724
+ name: 'tests',
725
+ category: 'structure',
726
+ passed: true,
727
+ message: `${testFiles.length} test files found`,
728
+ severity: 'info',
729
+ };
730
+ }