@codebakers/cli 3.9.32 → 3.9.34

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.
@@ -1353,6 +1353,28 @@ class CodeBakersServer {
1353
1353
  properties: {},
1354
1354
  },
1355
1355
  },
1356
+ {
1357
+ name: 'coherence_audit',
1358
+ description: 'Full codebase coherence audit. Checks all imports/exports, type flows, schema dependencies, API contracts, env vars, circular dependencies, and dead code. Use this for the /coherence command or when user asks to check wiring/dependencies.',
1359
+ inputSchema: {
1360
+ type: 'object',
1361
+ properties: {
1362
+ focus: {
1363
+ type: 'string',
1364
+ enum: ['all', 'imports', 'types', 'schema', 'api', 'env', 'circular', 'dead-code'],
1365
+ description: 'Focus area for the audit (default: all)',
1366
+ },
1367
+ autoFix: {
1368
+ type: 'boolean',
1369
+ description: 'Automatically fix issues that can be auto-fixed (default: false)',
1370
+ },
1371
+ includeNodeModules: {
1372
+ type: 'boolean',
1373
+ description: 'Include node_modules in analysis (default: false, very slow)',
1374
+ },
1375
+ },
1376
+ },
1377
+ },
1356
1378
  // ============================================
1357
1379
  // PROJECT TRACKING - Server-Side Dashboard
1358
1380
  // ============================================
@@ -1632,6 +1654,8 @@ class CodeBakersServer {
1632
1654
  return this.handleGuardianVerify(args);
1633
1655
  case 'guardian_status':
1634
1656
  return this.handleGuardianStatus();
1657
+ case 'coherence_audit':
1658
+ return this.handleCoherenceAudit(args);
1635
1659
  // Project Tracking - Server-Side Dashboard
1636
1660
  case 'project_sync':
1637
1661
  return this.handleProjectSync(args);
@@ -2466,9 +2490,17 @@ Or if user declines, call without fullDeploy:
2466
2490
  catch {
2467
2491
  // Use default
2468
2492
  }
2469
- results.push(`# 🎨 Adding CodeBakers v6.15 to: ${projectName}\n`);
2470
- // v6.15 bootstrap content - SHORT template with rules at START and END
2471
- const V6_CLAUDE_MD = `# CodeBakers v6.15
2493
+ results.push(`# 🎨 Adding CodeBakers v6.18 to: ${projectName}\n`);
2494
+ // v6.18 bootstrap content - magic phrase + rules at START and END + coherence
2495
+ const V6_CLAUDE_MD = `# CodeBakers v6.18
2496
+
2497
+ ## 🪄 MAGIC PHRASE: "codebakers go"
2498
+ When user says "codebakers go" in chat, start the onboarding conversation:
2499
+ 1. Ask: "Is this an existing project or are you starting fresh?" (1=existing, 2=new)
2500
+ 2. If existing: "Great! What would you like to work on?"
2501
+ 3. If new: "What do you want to build? Describe your project."
2502
+ 4. Then call \`init_project()\` to set up CodeBakers files
2503
+ 5. Continue helping them build
2472
2504
 
2473
2505
  ## 🚨 ALWAYS DO THIS (Critical - Read First)
2474
2506
  1. ALWAYS call \`project_status()\` FIRST → If works show "🍪 CodeBakers is working..." / If fails show "⚠️ Not connected"
@@ -2487,10 +2519,13 @@ Or if user declines, call without fullDeploy:
2487
2519
  project_status() → Verify connection FIRST
2488
2520
  discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
2489
2521
  validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
2522
+ coherence_audit() → Check wiring & dependencies
2490
2523
  \`\`\`
2491
2524
 
2525
+ Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
2526
+
2492
2527
  Header (after project_status succeeds): 🍪 CodeBakers is working on this...
2493
- Header (if project_status fails): ⚠️ CodeBakers not connected - run: codebakers go
2528
+ Header (if project_status fails): ⚠️ CodeBakers not connected
2494
2529
  Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
2495
2530
 
2496
2531
  ## 🚨 ALWAYS DO THIS (Critical - Repeated at End)
@@ -2499,7 +2534,10 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
2499
2534
  3. Call \`validate_complete()\` before done
2500
2535
  4. Show footer after code responses
2501
2536
  `;
2502
- const V6_CURSORRULES = `# CodeBakers v6.15
2537
+ const V6_CURSORRULES = `# CodeBakers v6.18
2538
+
2539
+ ## 🪄 "codebakers go" = Start onboarding conversation
2540
+ Ask existing/new → Ask what to build → Call init_project() → Help them build
2503
2541
 
2504
2542
  ## 🚨 ALWAYS (Read First)
2505
2543
  1. Call project_status() FIRST → 🍪 Working... or ⚠️ Not connected
@@ -2513,30 +2551,33 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
2513
2551
  3. Show header without project_status succeeding
2514
2552
  4. Skip writing tests for new features
2515
2553
 
2554
+ Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
2555
+ Use coherence_audit() to check wiring & dependencies
2556
+
2516
2557
  ## 🚨 ALWAYS (Repeated at End)
2517
2558
  1. project_status() FIRST
2518
2559
  2. discover_patterns() before code
2519
2560
  3. validate_complete() before done
2520
2561
  `;
2521
- // Check if already v6.15
2562
+ // Check if already v6.16
2522
2563
  const claudeMdPath = path.join(cwd, 'CLAUDE.md');
2523
2564
  if (fs.existsSync(claudeMdPath)) {
2524
2565
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
2525
- if (content.includes('v6.15') && content.includes('discover_patterns')) {
2526
- results.push('✓ CodeBakers v6.15 already installed\n');
2566
+ if ((content.includes('v6.16') || content.includes('v6.18')) && content.includes('discover_patterns')) {
2567
+ results.push('✓ CodeBakers v6.18 already installed\n');
2527
2568
  results.push('Patterns are server-enforced. Just call `discover_patterns` before coding!');
2528
2569
  return {
2529
2570
  content: [{ type: 'text', text: results.join('\n') }],
2530
2571
  };
2531
2572
  }
2532
- results.push('⚠️ Upgrading to v6.15 (server-enforced patterns)...\n');
2573
+ results.push('⚠️ Upgrading to v6.16 (server-enforced patterns)...\n');
2533
2574
  }
2534
2575
  try {
2535
- // Write v6.15 bootstrap files
2576
+ // Write v6.16 bootstrap files
2536
2577
  fs.writeFileSync(claudeMdPath, V6_CLAUDE_MD);
2537
- results.push('✓ Created CLAUDE.md (v6.15 bootstrap)');
2578
+ results.push('✓ Created CLAUDE.md (v6.16 bootstrap)');
2538
2579
  fs.writeFileSync(path.join(cwd, '.cursorrules'), V6_CURSORRULES);
2539
- results.push('✓ Created .cursorrules (v6.15 bootstrap)');
2580
+ results.push('✓ Created .cursorrules (v6.16 bootstrap)');
2540
2581
  // Remove old .claude folder if it exists (v5 → v6 migration)
2541
2582
  const claudeDir = path.join(cwd, '.claude');
2542
2583
  if (fs.existsSync(claudeDir)) {
@@ -2581,7 +2622,7 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
2581
2622
  state.updatedAt = new Date().toISOString();
2582
2623
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
2583
2624
  results.push('\n---\n');
2584
- results.push('## ✅ CodeBakers v6.15 Installed!\n');
2625
+ results.push('## ✅ CodeBakers v6.18 Installed!\n');
2585
2626
  results.push('**How it works now:**');
2586
2627
  results.push('1. Call `discover_patterns` before writing code');
2587
2628
  results.push('2. Server returns all patterns and rules');
@@ -6675,7 +6716,7 @@ ${handlers.join('\n')}
6675
6716
  `;
6676
6717
  }
6677
6718
  /**
6678
- * Update to CodeBakers v6.14 - server-enforced patterns
6719
+ * Update to CodeBakers v6.18 - server-enforced patterns with magic phrase + coherence
6679
6720
  * This is the MCP equivalent of the `codebakers upgrade` CLI command
6680
6721
  */
6681
6722
  async handleUpdatePatterns(args) {
@@ -6684,9 +6725,17 @@ ${handlers.join('\n')}
6684
6725
  const claudeMdPath = path.join(cwd, 'CLAUDE.md');
6685
6726
  const claudeDir = path.join(cwd, '.claude');
6686
6727
  const codebakersJson = path.join(cwd, '.codebakers.json');
6687
- let response = `# 🔄 CodeBakers v6.15 Update\n\n`;
6688
- // v6.15 bootstrap content - SHORT template with rules at START and END
6689
- const V6_CLAUDE_MD = `# CodeBakers v6.15
6728
+ let response = `# 🔄 CodeBakers v6.18 Update\n\n`;
6729
+ // v6.18 bootstrap content - magic phrase + rules at START and END + coherence
6730
+ const V6_CLAUDE_MD = `# CodeBakers v6.18
6731
+
6732
+ ## 🪄 MAGIC PHRASE: "codebakers go"
6733
+ When user says "codebakers go" in chat, start the onboarding conversation:
6734
+ 1. Ask: "Is this an existing project or are you starting fresh?" (1=existing, 2=new)
6735
+ 2. If existing: "Great! What would you like to work on?"
6736
+ 3. If new: "What do you want to build? Describe your project."
6737
+ 4. Then call \`init_project()\` to set up CodeBakers files
6738
+ 5. Continue helping them build
6690
6739
 
6691
6740
  ## 🚨 ALWAYS DO THIS (Critical - Read First)
6692
6741
  1. ALWAYS call \`project_status()\` FIRST → If works show "🍪 CodeBakers is working..." / If fails show "⚠️ Not connected"
@@ -6705,10 +6754,13 @@ ${handlers.join('\n')}
6705
6754
  project_status() → Verify connection FIRST
6706
6755
  discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
6707
6756
  validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
6757
+ coherence_audit() → Check wiring & dependencies
6708
6758
  \`\`\`
6709
6759
 
6760
+ Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
6761
+
6710
6762
  Header (after project_status succeeds): 🍪 CodeBakers is working on this...
6711
- Header (if project_status fails): ⚠️ CodeBakers not connected - run: codebakers go
6763
+ Header (if project_status fails): ⚠️ CodeBakers not connected
6712
6764
  Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6713
6765
 
6714
6766
  ## 🚨 ALWAYS DO THIS (Critical - Repeated at End)
@@ -6717,7 +6769,10 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6717
6769
  3. Call \`validate_complete()\` before done
6718
6770
  4. Show footer after code responses
6719
6771
  `;
6720
- const V6_CURSORRULES = `# CodeBakers v6.15
6772
+ const V6_CURSORRULES = `# CodeBakers v6.18
6773
+
6774
+ ## 🪄 "codebakers go" = Start onboarding conversation
6775
+ Ask existing/new → Ask what to build → Call init_project() → Help them build
6721
6776
 
6722
6777
  ## 🚨 ALWAYS (Read First)
6723
6778
  1. Call project_status() FIRST → 🍪 Working... or ⚠️ Not connected
@@ -6731,6 +6786,9 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6731
6786
  3. Show header without project_status succeeding
6732
6787
  4. Skip writing tests for new features
6733
6788
 
6789
+ Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
6790
+ Use coherence_audit() to check wiring & dependencies
6791
+
6734
6792
  ## 🚨 ALWAYS (Repeated at End)
6735
6793
  1. project_status() FIRST
6736
6794
  2. discover_patterns() before code
@@ -6742,7 +6800,7 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6742
6800
  let isV6 = false;
6743
6801
  if (fs.existsSync(claudeMdPath)) {
6744
6802
  const content = fs.readFileSync(claudeMdPath, 'utf-8');
6745
- isV6 = content.includes('v6.15') && content.includes('discover_patterns');
6803
+ isV6 = content.includes('v6.16') && content.includes('discover_patterns');
6746
6804
  }
6747
6805
  if (fs.existsSync(codebakersJson)) {
6748
6806
  try {
@@ -6755,10 +6813,10 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6755
6813
  }
6756
6814
  response += `## Current Status\n`;
6757
6815
  response += `- Version: ${currentVersion || 'Unknown'}\n`;
6758
- response += `- v6.15 (Server-Enforced): ${isV6 ? 'Yes ✓' : 'No'}\n\n`;
6816
+ response += `- v6.16 (Server-Enforced): ${isV6 ? 'Yes ✓' : 'No'}\n\n`;
6759
6817
  // Check if already on v6
6760
6818
  if (isV6 && !force) {
6761
- response += `✅ **Already on v6.15!**\n\n`;
6819
+ response += `✅ **Already on v6.16!**\n\n`;
6762
6820
  response += `Your patterns are server-enforced. Just use \`discover_patterns\` before coding.\n`;
6763
6821
  response += `Use \`force: true\` to reinstall bootstrap files.\n`;
6764
6822
  response += this.getUpdateNotice();
@@ -6769,12 +6827,12 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
6769
6827
  }],
6770
6828
  };
6771
6829
  }
6772
- response += `## Upgrading to v6.15...\n\n`;
6773
- // Write v6.15 bootstrap files
6830
+ response += `## Upgrading to v6.16...\n\n`;
6831
+ // Write v6.16 bootstrap files
6774
6832
  fs.writeFileSync(claudeMdPath, V6_CLAUDE_MD);
6775
- response += `✓ Updated CLAUDE.md (v6.15 bootstrap)\n`;
6833
+ response += `✓ Updated CLAUDE.md (v6.16 bootstrap)\n`;
6776
6834
  fs.writeFileSync(path.join(cwd, '.cursorrules'), V6_CURSORRULES);
6777
- response += `✓ Updated .cursorrules (v6.15 bootstrap)\n`;
6835
+ response += `✓ Updated .cursorrules (v6.16 bootstrap)\n`;
6778
6836
  // Remove old .claude folder (v5 → v6 migration)
6779
6837
  if (fs.existsSync(claudeDir)) {
6780
6838
  try {
@@ -7950,6 +8008,650 @@ ${events.includes('call-started') ? ` case 'call-started':
7950
8008
  return { content: [{ type: 'text', text: response }] };
7951
8009
  }
7952
8010
  // ============================================================================
8011
+ // COHERENCE AUDIT - Full Codebase Wiring Check
8012
+ // ============================================================================
8013
+ /**
8014
+ * Full coherence audit - checks all wiring, dependencies, and connections
8015
+ */
8016
+ handleCoherenceAudit(args) {
8017
+ const { focus = 'all', autoFix = false } = args;
8018
+ const cwd = process.cwd();
8019
+ const issues = [];
8020
+ const stats = {
8021
+ filesScanned: 0,
8022
+ importsChecked: 0,
8023
+ exportsFound: 0,
8024
+ typesAnalyzed: 0,
8025
+ envVarsFound: 0,
8026
+ };
8027
+ // Helper to extract imports from a file
8028
+ const extractImports = (content) => {
8029
+ const imports = [];
8030
+ const lines = content.split('\n');
8031
+ lines.forEach((line, i) => {
8032
+ // Match: import { X, Y } from 'path'
8033
+ const namedMatch = line.match(/import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/);
8034
+ if (namedMatch) {
8035
+ const isTypeOnly = !!namedMatch[1];
8036
+ const names = namedMatch[2].split(',').map(n => n.trim().split(' as ')[0].trim()).filter(Boolean);
8037
+ imports.push({ path: namedMatch[3], names, line: i + 1, isTypeOnly });
8038
+ }
8039
+ // Match: import X from 'path'
8040
+ const defaultMatch = line.match(/import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/);
8041
+ if (defaultMatch && !namedMatch) {
8042
+ imports.push({ path: defaultMatch[3], names: ['default'], line: i + 1, isTypeOnly: !!defaultMatch[1] });
8043
+ }
8044
+ // Match: import * as X from 'path'
8045
+ const namespaceMatch = line.match(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
8046
+ if (namespaceMatch) {
8047
+ imports.push({ path: namespaceMatch[2], names: ['*'], line: i + 1, isTypeOnly: false });
8048
+ }
8049
+ });
8050
+ return imports;
8051
+ };
8052
+ // Helper to extract exports from a file
8053
+ const extractExports = (content) => {
8054
+ const exports = [];
8055
+ // Named exports: export { X, Y }
8056
+ const namedExportMatches = content.matchAll(/export\s+{([^}]+)}/g);
8057
+ for (const match of namedExportMatches) {
8058
+ const names = match[1].split(',').map(n => n.trim().split(' as ').pop()?.trim() || '').filter(Boolean);
8059
+ exports.push(...names);
8060
+ }
8061
+ // Direct exports: export const/function/class/type/interface X
8062
+ const directExportMatches = content.matchAll(/export\s+(const|let|var|function|class|type|interface|enum)\s+(\w+)/g);
8063
+ for (const match of directExportMatches) {
8064
+ exports.push(match[2]);
8065
+ }
8066
+ // Default export
8067
+ if (content.includes('export default')) {
8068
+ exports.push('default');
8069
+ }
8070
+ // Re-exports: export * from, export { X } from
8071
+ const reExportMatches = content.matchAll(/export\s+(?:\*|{[^}]+})\s+from\s+['"]([^'"]+)['"]/g);
8072
+ for (const match of reExportMatches) {
8073
+ exports.push(`__reexport:${match[1]}`);
8074
+ }
8075
+ return exports;
8076
+ };
8077
+ // Helper to resolve import path to file
8078
+ const resolveImportPath = (importPath, fromFile) => {
8079
+ // Skip external packages
8080
+ if (!importPath.startsWith('.') && !importPath.startsWith('@/') && !importPath.startsWith('~/')) {
8081
+ return null;
8082
+ }
8083
+ // Handle @ alias (common Next.js pattern)
8084
+ let resolvedPath = importPath;
8085
+ if (importPath.startsWith('@/')) {
8086
+ resolvedPath = path.join(cwd, 'src', importPath.slice(2));
8087
+ }
8088
+ else if (importPath.startsWith('~/')) {
8089
+ resolvedPath = path.join(cwd, importPath.slice(2));
8090
+ }
8091
+ else {
8092
+ resolvedPath = path.resolve(path.dirname(fromFile), importPath);
8093
+ }
8094
+ // Try different extensions
8095
+ const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
8096
+ for (const ext of extensions) {
8097
+ const fullPath = resolvedPath + ext;
8098
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
8099
+ return fullPath;
8100
+ }
8101
+ }
8102
+ return null;
8103
+ };
8104
+ // Build export map for all files
8105
+ const exportMap = new Map();
8106
+ const importGraph = new Map(); // file -> files it imports
8107
+ const usedExports = new Set(); // track which exports are actually used
8108
+ const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks', 'pages'];
8109
+ const extensions = ['.ts', '.tsx', '.js', '.jsx'];
8110
+ // First pass: collect all exports
8111
+ const collectExports = (dir) => {
8112
+ if (!fs.existsSync(dir))
8113
+ return;
8114
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8115
+ for (const entry of entries) {
8116
+ const fullPath = path.join(dir, entry.name);
8117
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
8118
+ collectExports(fullPath);
8119
+ }
8120
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
8121
+ try {
8122
+ const content = fs.readFileSync(fullPath, 'utf-8');
8123
+ const exports = extractExports(content);
8124
+ exportMap.set(fullPath, exports);
8125
+ stats.exportsFound += exports.length;
8126
+ stats.filesScanned++;
8127
+ }
8128
+ catch {
8129
+ // Skip unreadable files
8130
+ }
8131
+ }
8132
+ }
8133
+ };
8134
+ // Collect exports from all directories
8135
+ for (const dir of searchDirs) {
8136
+ collectExports(path.join(cwd, dir));
8137
+ }
8138
+ // Also check root-level files
8139
+ try {
8140
+ const rootEntries = fs.readdirSync(cwd, { withFileTypes: true });
8141
+ for (const entry of rootEntries) {
8142
+ if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
8143
+ const fullPath = path.join(cwd, entry.name);
8144
+ try {
8145
+ const content = fs.readFileSync(fullPath, 'utf-8');
8146
+ const exports = extractExports(content);
8147
+ exportMap.set(fullPath, exports);
8148
+ }
8149
+ catch {
8150
+ // Skip
8151
+ }
8152
+ }
8153
+ }
8154
+ }
8155
+ catch {
8156
+ // Skip if can't read root
8157
+ }
8158
+ // Second pass: check imports and build graph
8159
+ const checkImports = (dir) => {
8160
+ if (!fs.existsSync(dir))
8161
+ return;
8162
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8163
+ for (const entry of entries) {
8164
+ const fullPath = path.join(dir, entry.name);
8165
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
8166
+ checkImports(fullPath);
8167
+ }
8168
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
8169
+ try {
8170
+ const content = fs.readFileSync(fullPath, 'utf-8');
8171
+ const imports = extractImports(content);
8172
+ const relativePath = path.relative(cwd, fullPath);
8173
+ const importedFiles = [];
8174
+ for (const imp of imports) {
8175
+ stats.importsChecked++;
8176
+ const resolvedPath = resolveImportPath(imp.path, fullPath);
8177
+ if (resolvedPath === null) {
8178
+ // External package or unresolvable - skip
8179
+ continue;
8180
+ }
8181
+ importedFiles.push(resolvedPath);
8182
+ // Check if file exists
8183
+ if (!fs.existsSync(resolvedPath)) {
8184
+ if (focus === 'all' || focus === 'imports') {
8185
+ issues.push({
8186
+ category: 'import',
8187
+ severity: 'error',
8188
+ file: relativePath,
8189
+ line: imp.line,
8190
+ message: `Import target not found: '${imp.path}'`,
8191
+ fix: `Create the file or update the import path`,
8192
+ autoFixable: false,
8193
+ });
8194
+ }
8195
+ continue;
8196
+ }
8197
+ // Check if named imports exist in the target
8198
+ const targetExports = exportMap.get(resolvedPath) || [];
8199
+ for (const name of imp.names) {
8200
+ if (name === '*' || name === 'default') {
8201
+ if (name === 'default' && !targetExports.includes('default')) {
8202
+ if (focus === 'all' || focus === 'imports') {
8203
+ issues.push({
8204
+ category: 'import',
8205
+ severity: 'error',
8206
+ file: relativePath,
8207
+ line: imp.line,
8208
+ message: `No default export in '${imp.path}'`,
8209
+ fix: `Add 'export default' or change to named import`,
8210
+ autoFixable: false,
8211
+ });
8212
+ }
8213
+ }
8214
+ continue;
8215
+ }
8216
+ // Track that this export is used
8217
+ usedExports.add(`${resolvedPath}:${name}`);
8218
+ if (!targetExports.includes(name)) {
8219
+ if (focus === 'all' || focus === 'imports') {
8220
+ issues.push({
8221
+ category: 'export',
8222
+ severity: 'error',
8223
+ file: relativePath,
8224
+ line: imp.line,
8225
+ message: `'${name}' is not exported from '${imp.path}'`,
8226
+ fix: `Add 'export { ${name} }' to ${path.basename(resolvedPath)} or update import`,
8227
+ autoFixable: false,
8228
+ });
8229
+ }
8230
+ }
8231
+ }
8232
+ }
8233
+ importGraph.set(fullPath, importedFiles);
8234
+ }
8235
+ catch {
8236
+ // Skip unreadable files
8237
+ }
8238
+ }
8239
+ }
8240
+ };
8241
+ // Run import checks
8242
+ for (const dir of searchDirs) {
8243
+ checkImports(path.join(cwd, dir));
8244
+ }
8245
+ // Check for circular dependencies
8246
+ if (focus === 'all' || focus === 'circular') {
8247
+ const visited = new Set();
8248
+ const recursionStack = new Set();
8249
+ const circularPaths = [];
8250
+ const detectCircular = (file, pathStack) => {
8251
+ if (recursionStack.has(file)) {
8252
+ // Found circular dependency
8253
+ const cycleStart = pathStack.indexOf(file);
8254
+ if (cycleStart !== -1) {
8255
+ circularPaths.push(pathStack.slice(cycleStart).concat(file));
8256
+ }
8257
+ return true;
8258
+ }
8259
+ if (visited.has(file))
8260
+ return false;
8261
+ visited.add(file);
8262
+ recursionStack.add(file);
8263
+ const imports = importGraph.get(file) || [];
8264
+ for (const imported of imports) {
8265
+ detectCircular(imported, [...pathStack, file]);
8266
+ }
8267
+ recursionStack.delete(file);
8268
+ return false;
8269
+ };
8270
+ for (const file of importGraph.keys()) {
8271
+ detectCircular(file, []);
8272
+ }
8273
+ // Add unique circular dependencies as issues
8274
+ const seenCycles = new Set();
8275
+ for (const cycle of circularPaths) {
8276
+ const cycleKey = cycle.map(f => path.relative(cwd, f)).sort().join(' -> ');
8277
+ if (!seenCycles.has(cycleKey)) {
8278
+ seenCycles.add(cycleKey);
8279
+ issues.push({
8280
+ category: 'circular',
8281
+ severity: 'warning',
8282
+ file: path.relative(cwd, cycle[0]),
8283
+ message: `Circular dependency: ${cycle.map(f => path.basename(f)).join(' → ')}`,
8284
+ fix: 'Break the cycle by extracting shared code to a separate module',
8285
+ autoFixable: false,
8286
+ });
8287
+ }
8288
+ }
8289
+ }
8290
+ // Check for dead code (unused exports)
8291
+ if (focus === 'all' || focus === 'dead-code') {
8292
+ for (const [file, exports] of exportMap.entries()) {
8293
+ const relativePath = path.relative(cwd, file);
8294
+ // Skip entry points and config files
8295
+ if (relativePath.includes('page.tsx') ||
8296
+ relativePath.includes('layout.tsx') ||
8297
+ relativePath.includes('route.ts') ||
8298
+ relativePath.includes('middleware.ts') ||
8299
+ relativePath.endsWith('.config.ts') ||
8300
+ relativePath.endsWith('.config.js')) {
8301
+ continue;
8302
+ }
8303
+ for (const exp of exports) {
8304
+ if (exp.startsWith('__reexport:') || exp === 'default')
8305
+ continue;
8306
+ const exportKey = `${file}:${exp}`;
8307
+ if (!usedExports.has(exportKey)) {
8308
+ issues.push({
8309
+ category: 'dead-code',
8310
+ severity: 'info',
8311
+ file: relativePath,
8312
+ message: `Export '${exp}' is never imported`,
8313
+ fix: `Remove if unused, or verify it's used externally`,
8314
+ autoFixable: true,
8315
+ });
8316
+ }
8317
+ }
8318
+ }
8319
+ }
8320
+ // Check environment variables
8321
+ if (focus === 'all' || focus === 'env') {
8322
+ const envVarsUsed = new Set();
8323
+ const envVarsDefined = new Set();
8324
+ // Scan for process.env usage
8325
+ const scanEnvUsage = (dir) => {
8326
+ if (!fs.existsSync(dir))
8327
+ return;
8328
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8329
+ for (const entry of entries) {
8330
+ const fullPath = path.join(dir, entry.name);
8331
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
8332
+ scanEnvUsage(fullPath);
8333
+ }
8334
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
8335
+ try {
8336
+ const content = fs.readFileSync(fullPath, 'utf-8');
8337
+ const envMatches = content.matchAll(/process\.env\.(\w+)|process\.env\[['"](\w+)['"]\]/g);
8338
+ for (const match of envMatches) {
8339
+ const varName = match[1] || match[2];
8340
+ envVarsUsed.add(varName);
8341
+ stats.envVarsFound++;
8342
+ }
8343
+ }
8344
+ catch {
8345
+ // Skip
8346
+ }
8347
+ }
8348
+ }
8349
+ };
8350
+ for (const dir of searchDirs) {
8351
+ scanEnvUsage(path.join(cwd, dir));
8352
+ }
8353
+ // Check .env.example and .env.local
8354
+ const envFiles = ['.env', '.env.local', '.env.example', '.env.development'];
8355
+ for (const envFile of envFiles) {
8356
+ const envPath = path.join(cwd, envFile);
8357
+ if (fs.existsSync(envPath)) {
8358
+ try {
8359
+ const content = fs.readFileSync(envPath, 'utf-8');
8360
+ const lines = content.split('\n');
8361
+ for (const line of lines) {
8362
+ const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
8363
+ if (match) {
8364
+ envVarsDefined.add(match[1]);
8365
+ }
8366
+ }
8367
+ }
8368
+ catch {
8369
+ // Skip
8370
+ }
8371
+ }
8372
+ }
8373
+ // Find env vars used but not defined
8374
+ for (const varName of envVarsUsed) {
8375
+ // Skip common Next.js vars
8376
+ if (varName.startsWith('NEXT_') || varName === 'NODE_ENV')
8377
+ continue;
8378
+ if (!envVarsDefined.has(varName)) {
8379
+ issues.push({
8380
+ category: 'env',
8381
+ severity: 'warning',
8382
+ file: '.env.example',
8383
+ message: `Environment variable '${varName}' is used but not defined in .env files`,
8384
+ fix: `Add ${varName}= to .env.example`,
8385
+ autoFixable: true,
8386
+ });
8387
+ }
8388
+ }
8389
+ // Find env vars defined but not used
8390
+ for (const varName of envVarsDefined) {
8391
+ if (!envVarsUsed.has(varName) && !varName.startsWith('NEXT_')) {
8392
+ issues.push({
8393
+ category: 'env',
8394
+ severity: 'info',
8395
+ file: '.env',
8396
+ message: `Environment variable '${varName}' is defined but never used`,
8397
+ fix: 'Remove if no longer needed',
8398
+ autoFixable: false,
8399
+ });
8400
+ }
8401
+ }
8402
+ }
8403
+ // Check for Drizzle schema issues
8404
+ if (focus === 'all' || focus === 'schema') {
8405
+ const schemaPath = path.join(cwd, 'src', 'db', 'schema.ts');
8406
+ const altSchemaPath = path.join(cwd, 'drizzle', 'schema.ts');
8407
+ const schemaFile = fs.existsSync(schemaPath) ? schemaPath : fs.existsSync(altSchemaPath) ? altSchemaPath : null;
8408
+ if (schemaFile) {
8409
+ try {
8410
+ const schemaContent = fs.readFileSync(schemaFile, 'utf-8');
8411
+ // Extract table names
8412
+ const tableMatches = schemaContent.matchAll(/export\s+const\s+(\w+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable)\s*\(/g);
8413
+ const tableNames = new Set();
8414
+ for (const match of tableMatches) {
8415
+ tableNames.add(match[1]);
8416
+ }
8417
+ // Scan for .insert(), .update(), .delete() calls on non-existent tables
8418
+ const scanSchemaUsage = (dir) => {
8419
+ if (!fs.existsSync(dir))
8420
+ return;
8421
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8422
+ for (const entry of entries) {
8423
+ const fullPath = path.join(dir, entry.name);
8424
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
8425
+ scanSchemaUsage(fullPath);
8426
+ }
8427
+ else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
8428
+ try {
8429
+ const content = fs.readFileSync(fullPath, 'utf-8');
8430
+ const relativePath = path.relative(cwd, fullPath);
8431
+ // Check for db operations on tables
8432
+ const opMatches = content.matchAll(/db\.(insert|update|delete|select)\((\w+)\)/g);
8433
+ for (const match of opMatches) {
8434
+ const tableName = match[2];
8435
+ if (!tableNames.has(tableName) && !tableName.startsWith('sql')) {
8436
+ issues.push({
8437
+ category: 'schema',
8438
+ severity: 'error',
8439
+ file: relativePath,
8440
+ message: `Database operation on unknown table '${tableName}'`,
8441
+ fix: `Verify table exists in schema or fix the table name`,
8442
+ autoFixable: false,
8443
+ });
8444
+ }
8445
+ }
8446
+ }
8447
+ catch {
8448
+ // Skip
8449
+ }
8450
+ }
8451
+ }
8452
+ };
8453
+ for (const dir of searchDirs) {
8454
+ scanSchemaUsage(path.join(cwd, dir));
8455
+ }
8456
+ }
8457
+ catch {
8458
+ // Skip if can't read schema
8459
+ }
8460
+ }
8461
+ }
8462
+ // Check API contracts (Next.js API routes vs fetch calls)
8463
+ if (focus === 'all' || focus === 'api') {
8464
+ const apiRoutes = new Map();
8465
+ // Find all API routes
8466
+ const apiDir = path.join(cwd, 'src', 'app', 'api');
8467
+ const altApiDir = path.join(cwd, 'app', 'api');
8468
+ const pagesApiDir = path.join(cwd, 'pages', 'api');
8469
+ const scanApiRoutes = (dir, prefix = '/api') => {
8470
+ if (!fs.existsSync(dir))
8471
+ return;
8472
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8473
+ for (const entry of entries) {
8474
+ const fullPath = path.join(dir, entry.name);
8475
+ if (entry.isDirectory()) {
8476
+ scanApiRoutes(fullPath, `${prefix}/${entry.name}`);
8477
+ }
8478
+ else if (entry.name === 'route.ts' || entry.name === 'route.js') {
8479
+ try {
8480
+ const content = fs.readFileSync(fullPath, 'utf-8');
8481
+ const methods = [];
8482
+ if (content.includes('export async function GET') || content.includes('export function GET'))
8483
+ methods.push('GET');
8484
+ if (content.includes('export async function POST') || content.includes('export function POST'))
8485
+ methods.push('POST');
8486
+ if (content.includes('export async function PUT') || content.includes('export function PUT'))
8487
+ methods.push('PUT');
8488
+ if (content.includes('export async function DELETE') || content.includes('export function DELETE'))
8489
+ methods.push('DELETE');
8490
+ if (content.includes('export async function PATCH') || content.includes('export function PATCH'))
8491
+ methods.push('PATCH');
8492
+ apiRoutes.set(prefix, { methods, file: path.relative(cwd, fullPath) });
8493
+ }
8494
+ catch {
8495
+ // Skip
8496
+ }
8497
+ }
8498
+ }
8499
+ };
8500
+ scanApiRoutes(apiDir);
8501
+ scanApiRoutes(altApiDir);
8502
+ // Scan for fetch calls to API routes
8503
+ const scanFetchCalls = (dir) => {
8504
+ if (!fs.existsSync(dir))
8505
+ return;
8506
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8507
+ for (const entry of entries) {
8508
+ const fullPath = path.join(dir, entry.name);
8509
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'api') {
8510
+ scanFetchCalls(fullPath);
8511
+ }
8512
+ else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
8513
+ try {
8514
+ const content = fs.readFileSync(fullPath, 'utf-8');
8515
+ const relativePath = path.relative(cwd, fullPath);
8516
+ // Match fetch('/api/...') or fetch(`/api/...`)
8517
+ const fetchMatches = content.matchAll(/fetch\s*\(\s*[`'"]([^`'"]+)[`'"]/g);
8518
+ for (const match of fetchMatches) {
8519
+ const url = match[1];
8520
+ if (url.startsWith('/api/')) {
8521
+ // Extract base path (before query params or dynamic segments)
8522
+ const basePath = url.split('?')[0].replace(/\$\{[^}]+\}/g, '[dynamic]');
8523
+ // Check if this route exists
8524
+ let routeFound = false;
8525
+ for (const [route] of apiRoutes) {
8526
+ // Simple matching - could be improved
8527
+ if (basePath === route || basePath.startsWith(route + '/')) {
8528
+ routeFound = true;
8529
+ break;
8530
+ }
8531
+ }
8532
+ if (!routeFound && !basePath.includes('[dynamic]')) {
8533
+ issues.push({
8534
+ category: 'api',
8535
+ severity: 'warning',
8536
+ file: relativePath,
8537
+ message: `Fetch to '${url}' but no matching API route found`,
8538
+ fix: `Create the API route or fix the URL`,
8539
+ autoFixable: false,
8540
+ });
8541
+ }
8542
+ }
8543
+ }
8544
+ }
8545
+ catch {
8546
+ // Skip
8547
+ }
8548
+ }
8549
+ }
8550
+ };
8551
+ for (const dir of ['src', 'app', 'components', 'lib']) {
8552
+ scanFetchCalls(path.join(cwd, dir));
8553
+ }
8554
+ }
8555
+ // Generate report
8556
+ let response = `# 🔗 Coherence Audit Report\n\n`;
8557
+ // Summary
8558
+ const errorCount = issues.filter(i => i.severity === 'error').length;
8559
+ const warningCount = issues.filter(i => i.severity === 'warning').length;
8560
+ const infoCount = issues.filter(i => i.severity === 'info').length;
8561
+ const autoFixableCount = issues.filter(i => i.autoFixable).length;
8562
+ response += `## 📊 Summary\n\n`;
8563
+ response += `| Metric | Value |\n`;
8564
+ response += `|--------|-------|\n`;
8565
+ response += `| Files scanned | ${stats.filesScanned} |\n`;
8566
+ response += `| Imports checked | ${stats.importsChecked} |\n`;
8567
+ response += `| Exports found | ${stats.exportsFound} |\n`;
8568
+ response += `| 🔴 Errors | ${errorCount} |\n`;
8569
+ response += `| 🟡 Warnings | ${warningCount} |\n`;
8570
+ response += `| 🔵 Info | ${infoCount} |\n`;
8571
+ response += `| 🔧 Auto-fixable | ${autoFixableCount} |\n\n`;
8572
+ if (issues.length === 0) {
8573
+ response += `## ✅ All Clear!\n\n`;
8574
+ response += `No coherence issues found. Your codebase wiring is solid.\n`;
8575
+ }
8576
+ else {
8577
+ // Group issues by category
8578
+ const categories = {
8579
+ import: [],
8580
+ export: [],
8581
+ type: [],
8582
+ schema: [],
8583
+ api: [],
8584
+ env: [],
8585
+ circular: [],
8586
+ 'dead-code': [],
8587
+ };
8588
+ for (const issue of issues) {
8589
+ categories[issue.category].push(issue);
8590
+ }
8591
+ const categoryNames = {
8592
+ import: '🔴 Broken Imports',
8593
+ export: '🔴 Missing Exports',
8594
+ type: '🟠 Type Mismatches',
8595
+ schema: '🟠 Schema Issues',
8596
+ api: '🟡 API Contract Issues',
8597
+ env: '🟡 Environment Variables',
8598
+ circular: '⚪ Circular Dependencies',
8599
+ 'dead-code': '🔵 Dead Code',
8600
+ };
8601
+ for (const [category, catIssues] of Object.entries(categories)) {
8602
+ if (catIssues.length === 0)
8603
+ continue;
8604
+ response += `## ${categoryNames[category]} (${catIssues.length})\n\n`;
8605
+ // Show first 10 issues per category
8606
+ const displayIssues = catIssues.slice(0, 10);
8607
+ for (const issue of displayIssues) {
8608
+ response += `### \`${issue.file}${issue.line ? `:${issue.line}` : ''}\`\n`;
8609
+ response += `**Issue:** ${issue.message}\n`;
8610
+ if (issue.fix) {
8611
+ response += `**Fix:** ${issue.fix}${issue.autoFixable ? ' 🔧' : ''}\n`;
8612
+ }
8613
+ response += `\n`;
8614
+ }
8615
+ if (catIssues.length > 10) {
8616
+ response += `*... and ${catIssues.length - 10} more ${category} issues*\n\n`;
8617
+ }
8618
+ }
8619
+ }
8620
+ // Recommendations
8621
+ response += `## 💡 Recommendations\n\n`;
8622
+ if (errorCount > 0) {
8623
+ response += `1. **Fix ${errorCount} errors first** - These will cause runtime failures\n`;
8624
+ }
8625
+ if (warningCount > 0) {
8626
+ response += `2. **Review ${warningCount} warnings** - These may cause issues\n`;
8627
+ }
8628
+ response += `3. Run \`npx tsc --noEmit\` to verify TypeScript compiles\n`;
8629
+ response += `4. Run your test suite to verify functionality\n`;
8630
+ if (autoFixableCount > 0) {
8631
+ response += `\n---\n\n`;
8632
+ response += `**${autoFixableCount} issues can be auto-fixed.** Run \`guardian_heal\` to fix them.\n`;
8633
+ }
8634
+ // Save state for guardian_heal
8635
+ const statePath = path.join(cwd, '.codebakers', 'coherence-state.json');
8636
+ try {
8637
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
8638
+ fs.writeFileSync(statePath, JSON.stringify({
8639
+ lastAudit: new Date().toISOString(),
8640
+ focus,
8641
+ stats,
8642
+ issues: issues.map(i => ({
8643
+ ...i,
8644
+ // Include only auto-fixable for healing
8645
+ })),
8646
+ summary: { errors: errorCount, warnings: warningCount, info: infoCount, autoFixable: autoFixableCount },
8647
+ }, null, 2));
8648
+ }
8649
+ catch {
8650
+ // Ignore write errors
8651
+ }
8652
+ return { content: [{ type: 'text', text: response }] };
8653
+ }
8654
+ // ============================================================================
7953
8655
  // PROJECT TRACKING - Server-Side Dashboard
7954
8656
  // ============================================================================
7955
8657
  /**