@codebakers/cli 3.9.32 → 3.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/coherence.d.ts +10 -0
- package/dist/commands/coherence.js +396 -0
- package/dist/commands/go.js +68 -21
- package/dist/index.js +17 -1
- package/dist/mcp/server.js +728 -26
- package/package.json +1 -1
- package/src/commands/coherence.ts +455 -0
- package/src/commands/go.ts +9 -3
- package/src/index.ts +18 -1
- package/src/mcp/server.ts +768 -12
- package/tmpclaude-1ef9-cwd +1 -0
- package/tmpclaude-77b7-cwd +1 -0
package/src/mcp/server.ts
CHANGED
|
@@ -1464,6 +1464,29 @@ class CodeBakersServer {
|
|
|
1464
1464
|
properties: {},
|
|
1465
1465
|
},
|
|
1466
1466
|
},
|
|
1467
|
+
{
|
|
1468
|
+
name: 'coherence_audit',
|
|
1469
|
+
description:
|
|
1470
|
+
'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.',
|
|
1471
|
+
inputSchema: {
|
|
1472
|
+
type: 'object' as const,
|
|
1473
|
+
properties: {
|
|
1474
|
+
focus: {
|
|
1475
|
+
type: 'string',
|
|
1476
|
+
enum: ['all', 'imports', 'types', 'schema', 'api', 'env', 'circular', 'dead-code'],
|
|
1477
|
+
description: 'Focus area for the audit (default: all)',
|
|
1478
|
+
},
|
|
1479
|
+
autoFix: {
|
|
1480
|
+
type: 'boolean',
|
|
1481
|
+
description: 'Automatically fix issues that can be auto-fixed (default: false)',
|
|
1482
|
+
},
|
|
1483
|
+
includeNodeModules: {
|
|
1484
|
+
type: 'boolean',
|
|
1485
|
+
description: 'Include node_modules in analysis (default: false, very slow)',
|
|
1486
|
+
},
|
|
1487
|
+
},
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1467
1490
|
// ============================================
|
|
1468
1491
|
// PROJECT TRACKING - Server-Side Dashboard
|
|
1469
1492
|
// ============================================
|
|
@@ -1808,6 +1831,9 @@ class CodeBakersServer {
|
|
|
1808
1831
|
case 'guardian_status':
|
|
1809
1832
|
return this.handleGuardianStatus();
|
|
1810
1833
|
|
|
1834
|
+
case 'coherence_audit':
|
|
1835
|
+
return this.handleCoherenceAudit(args as { focus?: string; autoFix?: boolean; includeNodeModules?: boolean });
|
|
1836
|
+
|
|
1811
1837
|
// Project Tracking - Server-Side Dashboard
|
|
1812
1838
|
case 'project_sync':
|
|
1813
1839
|
return this.handleProjectSync(args as {
|
|
@@ -2808,10 +2834,10 @@ Or if user declines, call without fullDeploy:
|
|
|
2808
2834
|
// Use default
|
|
2809
2835
|
}
|
|
2810
2836
|
|
|
2811
|
-
results.push(`# 🎨 Adding CodeBakers v6.
|
|
2837
|
+
results.push(`# 🎨 Adding CodeBakers v6.17 to: ${projectName}\n`);
|
|
2812
2838
|
|
|
2813
|
-
// v6.
|
|
2814
|
-
const V6_CLAUDE_MD = `# CodeBakers v6.
|
|
2839
|
+
// v6.17 bootstrap content - magic phrase + rules at START and END + coherence
|
|
2840
|
+
const V6_CLAUDE_MD = `# CodeBakers v6.17
|
|
2815
2841
|
|
|
2816
2842
|
## 🪄 MAGIC PHRASE: "codebakers go"
|
|
2817
2843
|
When user says "codebakers go" in chat, start the onboarding conversation:
|
|
@@ -2838,8 +2864,11 @@ When user says "codebakers go" in chat, start the onboarding conversation:
|
|
|
2838
2864
|
project_status() → Verify connection FIRST
|
|
2839
2865
|
discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
|
|
2840
2866
|
validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
|
|
2867
|
+
coherence_audit() → Check wiring & dependencies
|
|
2841
2868
|
\`\`\`
|
|
2842
2869
|
|
|
2870
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
2871
|
+
|
|
2843
2872
|
Header (after project_status succeeds): 🍪 CodeBakers is working on this...
|
|
2844
2873
|
Header (if project_status fails): ⚠️ CodeBakers not connected
|
|
2845
2874
|
Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
@@ -2851,7 +2880,7 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
|
2851
2880
|
4. Show footer after code responses
|
|
2852
2881
|
`;
|
|
2853
2882
|
|
|
2854
|
-
const V6_CURSORRULES = `# CodeBakers v6.
|
|
2883
|
+
const V6_CURSORRULES = `# CodeBakers v6.17
|
|
2855
2884
|
|
|
2856
2885
|
## 🪄 "codebakers go" = Start onboarding conversation
|
|
2857
2886
|
Ask existing/new → Ask what to build → Call init_project() → Help them build
|
|
@@ -2868,6 +2897,9 @@ Ask existing/new → Ask what to build → Call init_project() → Help them bui
|
|
|
2868
2897
|
3. Show header without project_status succeeding
|
|
2869
2898
|
4. Skip writing tests for new features
|
|
2870
2899
|
|
|
2900
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
2901
|
+
Use coherence_audit() to check wiring & dependencies
|
|
2902
|
+
|
|
2871
2903
|
## 🚨 ALWAYS (Repeated at End)
|
|
2872
2904
|
1. project_status() FIRST
|
|
2873
2905
|
2. discover_patterns() before code
|
|
@@ -2878,8 +2910,8 @@ Ask existing/new → Ask what to build → Call init_project() → Help them bui
|
|
|
2878
2910
|
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
2879
2911
|
if (fs.existsSync(claudeMdPath)) {
|
|
2880
2912
|
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
2881
|
-
if (content.includes('v6.16') && content.includes('discover_patterns')) {
|
|
2882
|
-
results.push('✓ CodeBakers v6.
|
|
2913
|
+
if ((content.includes('v6.16') || content.includes('v6.17')) && content.includes('discover_patterns')) {
|
|
2914
|
+
results.push('✓ CodeBakers v6.17 already installed\n');
|
|
2883
2915
|
results.push('Patterns are server-enforced. Just call `discover_patterns` before coding!');
|
|
2884
2916
|
return {
|
|
2885
2917
|
content: [{ type: 'text' as const, text: results.join('\n') }],
|
|
@@ -2941,7 +2973,7 @@ Ask existing/new → Ask what to build → Call init_project() → Help them bui
|
|
|
2941
2973
|
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
2942
2974
|
|
|
2943
2975
|
results.push('\n---\n');
|
|
2944
|
-
results.push('## ✅ CodeBakers v6.
|
|
2976
|
+
results.push('## ✅ CodeBakers v6.17 Installed!\n');
|
|
2945
2977
|
results.push('**How it works now:**');
|
|
2946
2978
|
results.push('1. Call `discover_patterns` before writing code');
|
|
2947
2979
|
results.push('2. Server returns all patterns and rules');
|
|
@@ -7486,7 +7518,7 @@ ${handlers.join('\n')}
|
|
|
7486
7518
|
}
|
|
7487
7519
|
|
|
7488
7520
|
/**
|
|
7489
|
-
* Update to CodeBakers v6.
|
|
7521
|
+
* Update to CodeBakers v6.17 - server-enforced patterns with magic phrase + coherence
|
|
7490
7522
|
* This is the MCP equivalent of the `codebakers upgrade` CLI command
|
|
7491
7523
|
*/
|
|
7492
7524
|
private async handleUpdatePatterns(args: { force?: boolean }) {
|
|
@@ -7496,10 +7528,10 @@ ${handlers.join('\n')}
|
|
|
7496
7528
|
const claudeDir = path.join(cwd, '.claude');
|
|
7497
7529
|
const codebakersJson = path.join(cwd, '.codebakers.json');
|
|
7498
7530
|
|
|
7499
|
-
let response = `# 🔄 CodeBakers v6.
|
|
7531
|
+
let response = `# 🔄 CodeBakers v6.17 Update\n\n`;
|
|
7500
7532
|
|
|
7501
|
-
// v6.
|
|
7502
|
-
const V6_CLAUDE_MD = `# CodeBakers v6.
|
|
7533
|
+
// v6.17 bootstrap content - magic phrase + rules at START and END + coherence
|
|
7534
|
+
const V6_CLAUDE_MD = `# CodeBakers v6.17
|
|
7503
7535
|
|
|
7504
7536
|
## 🪄 MAGIC PHRASE: "codebakers go"
|
|
7505
7537
|
When user says "codebakers go" in chat, start the onboarding conversation:
|
|
@@ -7526,8 +7558,11 @@ When user says "codebakers go" in chat, start the onboarding conversation:
|
|
|
7526
7558
|
project_status() → Verify connection FIRST
|
|
7527
7559
|
discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
|
|
7528
7560
|
validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
|
|
7561
|
+
coherence_audit() → Check wiring & dependencies
|
|
7529
7562
|
\`\`\`
|
|
7530
7563
|
|
|
7564
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
7565
|
+
|
|
7531
7566
|
Header (after project_status succeeds): 🍪 CodeBakers is working on this...
|
|
7532
7567
|
Header (if project_status fails): ⚠️ CodeBakers not connected
|
|
7533
7568
|
Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
@@ -7539,7 +7574,7 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
|
7539
7574
|
4. Show footer after code responses
|
|
7540
7575
|
`;
|
|
7541
7576
|
|
|
7542
|
-
const V6_CURSORRULES = `# CodeBakers v6.
|
|
7577
|
+
const V6_CURSORRULES = `# CodeBakers v6.17
|
|
7543
7578
|
|
|
7544
7579
|
## 🪄 "codebakers go" = Start onboarding conversation
|
|
7545
7580
|
Ask existing/new → Ask what to build → Call init_project() → Help them build
|
|
@@ -7556,6 +7591,9 @@ Ask existing/new → Ask what to build → Call init_project() → Help them bui
|
|
|
7556
7591
|
3. Show header without project_status succeeding
|
|
7557
7592
|
4. Skip writing tests for new features
|
|
7558
7593
|
|
|
7594
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
7595
|
+
Use coherence_audit() to check wiring & dependencies
|
|
7596
|
+
|
|
7559
7597
|
## 🚨 ALWAYS (Repeated at End)
|
|
7560
7598
|
1. project_status() FIRST
|
|
7561
7599
|
2. discover_patterns() before code
|
|
@@ -8935,6 +8973,724 @@ ${events.includes('call-started') ? ` case 'call-started':
|
|
|
8935
8973
|
return { content: [{ type: 'text' as const, text: response }] };
|
|
8936
8974
|
}
|
|
8937
8975
|
|
|
8976
|
+
// ============================================================================
|
|
8977
|
+
// COHERENCE AUDIT - Full Codebase Wiring Check
|
|
8978
|
+
// ============================================================================
|
|
8979
|
+
|
|
8980
|
+
/**
|
|
8981
|
+
* Full coherence audit - checks all wiring, dependencies, and connections
|
|
8982
|
+
*/
|
|
8983
|
+
private handleCoherenceAudit(args: { focus?: string; autoFix?: boolean; includeNodeModules?: boolean }) {
|
|
8984
|
+
const { focus = 'all', autoFix = false } = args;
|
|
8985
|
+
const cwd = process.cwd();
|
|
8986
|
+
|
|
8987
|
+
interface CoherenceIssue {
|
|
8988
|
+
category: 'import' | 'export' | 'type' | 'schema' | 'api' | 'env' | 'circular' | 'dead-code';
|
|
8989
|
+
severity: 'error' | 'warning' | 'info';
|
|
8990
|
+
file: string;
|
|
8991
|
+
line?: number;
|
|
8992
|
+
message: string;
|
|
8993
|
+
fix?: string;
|
|
8994
|
+
autoFixable: boolean;
|
|
8995
|
+
}
|
|
8996
|
+
|
|
8997
|
+
const issues: CoherenceIssue[] = [];
|
|
8998
|
+
const stats = {
|
|
8999
|
+
filesScanned: 0,
|
|
9000
|
+
importsChecked: 0,
|
|
9001
|
+
exportsFound: 0,
|
|
9002
|
+
typesAnalyzed: 0,
|
|
9003
|
+
envVarsFound: 0,
|
|
9004
|
+
};
|
|
9005
|
+
|
|
9006
|
+
// Helper to extract imports from a file
|
|
9007
|
+
const extractImports = (content: string): Array<{ path: string; names: string[]; line: number; isTypeOnly: boolean }> => {
|
|
9008
|
+
const imports: Array<{ path: string; names: string[]; line: number; isTypeOnly: boolean }> = [];
|
|
9009
|
+
const lines = content.split('\n');
|
|
9010
|
+
|
|
9011
|
+
lines.forEach((line, i) => {
|
|
9012
|
+
// Match: import { X, Y } from 'path'
|
|
9013
|
+
const namedMatch = line.match(/import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/);
|
|
9014
|
+
if (namedMatch) {
|
|
9015
|
+
const isTypeOnly = !!namedMatch[1];
|
|
9016
|
+
const names = namedMatch[2].split(',').map(n => n.trim().split(' as ')[0].trim()).filter(Boolean);
|
|
9017
|
+
imports.push({ path: namedMatch[3], names, line: i + 1, isTypeOnly });
|
|
9018
|
+
}
|
|
9019
|
+
|
|
9020
|
+
// Match: import X from 'path'
|
|
9021
|
+
const defaultMatch = line.match(/import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
9022
|
+
if (defaultMatch && !namedMatch) {
|
|
9023
|
+
imports.push({ path: defaultMatch[3], names: ['default'], line: i + 1, isTypeOnly: !!defaultMatch[1] });
|
|
9024
|
+
}
|
|
9025
|
+
|
|
9026
|
+
// Match: import * as X from 'path'
|
|
9027
|
+
const namespaceMatch = line.match(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
9028
|
+
if (namespaceMatch) {
|
|
9029
|
+
imports.push({ path: namespaceMatch[2], names: ['*'], line: i + 1, isTypeOnly: false });
|
|
9030
|
+
}
|
|
9031
|
+
});
|
|
9032
|
+
|
|
9033
|
+
return imports;
|
|
9034
|
+
};
|
|
9035
|
+
|
|
9036
|
+
// Helper to extract exports from a file
|
|
9037
|
+
const extractExports = (content: string): string[] => {
|
|
9038
|
+
const exports: string[] = [];
|
|
9039
|
+
|
|
9040
|
+
// Named exports: export { X, Y }
|
|
9041
|
+
const namedExportMatches = content.matchAll(/export\s+{([^}]+)}/g);
|
|
9042
|
+
for (const match of namedExportMatches) {
|
|
9043
|
+
const names = match[1].split(',').map(n => n.trim().split(' as ').pop()?.trim() || '').filter(Boolean);
|
|
9044
|
+
exports.push(...names);
|
|
9045
|
+
}
|
|
9046
|
+
|
|
9047
|
+
// Direct exports: export const/function/class/type/interface X
|
|
9048
|
+
const directExportMatches = content.matchAll(/export\s+(const|let|var|function|class|type|interface|enum)\s+(\w+)/g);
|
|
9049
|
+
for (const match of directExportMatches) {
|
|
9050
|
+
exports.push(match[2]);
|
|
9051
|
+
}
|
|
9052
|
+
|
|
9053
|
+
// Default export
|
|
9054
|
+
if (content.includes('export default')) {
|
|
9055
|
+
exports.push('default');
|
|
9056
|
+
}
|
|
9057
|
+
|
|
9058
|
+
// Re-exports: export * from, export { X } from
|
|
9059
|
+
const reExportMatches = content.matchAll(/export\s+(?:\*|{[^}]+})\s+from\s+['"]([^'"]+)['"]/g);
|
|
9060
|
+
for (const match of reExportMatches) {
|
|
9061
|
+
exports.push(`__reexport:${match[1]}`);
|
|
9062
|
+
}
|
|
9063
|
+
|
|
9064
|
+
return exports;
|
|
9065
|
+
};
|
|
9066
|
+
|
|
9067
|
+
// Helper to resolve import path to file
|
|
9068
|
+
const resolveImportPath = (importPath: string, fromFile: string): string | null => {
|
|
9069
|
+
// Skip external packages
|
|
9070
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('@/') && !importPath.startsWith('~/')) {
|
|
9071
|
+
return null;
|
|
9072
|
+
}
|
|
9073
|
+
|
|
9074
|
+
// Handle @ alias (common Next.js pattern)
|
|
9075
|
+
let resolvedPath = importPath;
|
|
9076
|
+
if (importPath.startsWith('@/')) {
|
|
9077
|
+
resolvedPath = path.join(cwd, 'src', importPath.slice(2));
|
|
9078
|
+
} else if (importPath.startsWith('~/')) {
|
|
9079
|
+
resolvedPath = path.join(cwd, importPath.slice(2));
|
|
9080
|
+
} else {
|
|
9081
|
+
resolvedPath = path.resolve(path.dirname(fromFile), importPath);
|
|
9082
|
+
}
|
|
9083
|
+
|
|
9084
|
+
// Try different extensions
|
|
9085
|
+
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
|
|
9086
|
+
for (const ext of extensions) {
|
|
9087
|
+
const fullPath = resolvedPath + ext;
|
|
9088
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
9089
|
+
return fullPath;
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9092
|
+
|
|
9093
|
+
return null;
|
|
9094
|
+
};
|
|
9095
|
+
|
|
9096
|
+
// Build export map for all files
|
|
9097
|
+
const exportMap: Map<string, string[]> = new Map();
|
|
9098
|
+
const importGraph: Map<string, string[]> = new Map(); // file -> files it imports
|
|
9099
|
+
const usedExports: Set<string> = new Set(); // track which exports are actually used
|
|
9100
|
+
|
|
9101
|
+
const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks', 'pages'];
|
|
9102
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
9103
|
+
|
|
9104
|
+
// First pass: collect all exports
|
|
9105
|
+
const collectExports = (dir: string) => {
|
|
9106
|
+
if (!fs.existsSync(dir)) return;
|
|
9107
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9108
|
+
|
|
9109
|
+
for (const entry of entries) {
|
|
9110
|
+
const fullPath = path.join(dir, entry.name);
|
|
9111
|
+
|
|
9112
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
9113
|
+
collectExports(fullPath);
|
|
9114
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
9115
|
+
try {
|
|
9116
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9117
|
+
const exports = extractExports(content);
|
|
9118
|
+
exportMap.set(fullPath, exports);
|
|
9119
|
+
stats.exportsFound += exports.length;
|
|
9120
|
+
stats.filesScanned++;
|
|
9121
|
+
} catch {
|
|
9122
|
+
// Skip unreadable files
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
}
|
|
9126
|
+
};
|
|
9127
|
+
|
|
9128
|
+
// Collect exports from all directories
|
|
9129
|
+
for (const dir of searchDirs) {
|
|
9130
|
+
collectExports(path.join(cwd, dir));
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9133
|
+
// Also check root-level files
|
|
9134
|
+
try {
|
|
9135
|
+
const rootEntries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
9136
|
+
for (const entry of rootEntries) {
|
|
9137
|
+
if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
9138
|
+
const fullPath = path.join(cwd, entry.name);
|
|
9139
|
+
try {
|
|
9140
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9141
|
+
const exports = extractExports(content);
|
|
9142
|
+
exportMap.set(fullPath, exports);
|
|
9143
|
+
} catch {
|
|
9144
|
+
// Skip
|
|
9145
|
+
}
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
} catch {
|
|
9149
|
+
// Skip if can't read root
|
|
9150
|
+
}
|
|
9151
|
+
|
|
9152
|
+
// Second pass: check imports and build graph
|
|
9153
|
+
const checkImports = (dir: string) => {
|
|
9154
|
+
if (!fs.existsSync(dir)) return;
|
|
9155
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9156
|
+
|
|
9157
|
+
for (const entry of entries) {
|
|
9158
|
+
const fullPath = path.join(dir, entry.name);
|
|
9159
|
+
|
|
9160
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
9161
|
+
checkImports(fullPath);
|
|
9162
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
9163
|
+
try {
|
|
9164
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9165
|
+
const imports = extractImports(content);
|
|
9166
|
+
const relativePath = path.relative(cwd, fullPath);
|
|
9167
|
+
const importedFiles: string[] = [];
|
|
9168
|
+
|
|
9169
|
+
for (const imp of imports) {
|
|
9170
|
+
stats.importsChecked++;
|
|
9171
|
+
const resolvedPath = resolveImportPath(imp.path, fullPath);
|
|
9172
|
+
|
|
9173
|
+
if (resolvedPath === null) {
|
|
9174
|
+
// External package or unresolvable - skip
|
|
9175
|
+
continue;
|
|
9176
|
+
}
|
|
9177
|
+
|
|
9178
|
+
importedFiles.push(resolvedPath);
|
|
9179
|
+
|
|
9180
|
+
// Check if file exists
|
|
9181
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
9182
|
+
if (focus === 'all' || focus === 'imports') {
|
|
9183
|
+
issues.push({
|
|
9184
|
+
category: 'import',
|
|
9185
|
+
severity: 'error',
|
|
9186
|
+
file: relativePath,
|
|
9187
|
+
line: imp.line,
|
|
9188
|
+
message: `Import target not found: '${imp.path}'`,
|
|
9189
|
+
fix: `Create the file or update the import path`,
|
|
9190
|
+
autoFixable: false,
|
|
9191
|
+
});
|
|
9192
|
+
}
|
|
9193
|
+
continue;
|
|
9194
|
+
}
|
|
9195
|
+
|
|
9196
|
+
// Check if named imports exist in the target
|
|
9197
|
+
const targetExports = exportMap.get(resolvedPath) || [];
|
|
9198
|
+
|
|
9199
|
+
for (const name of imp.names) {
|
|
9200
|
+
if (name === '*' || name === 'default') {
|
|
9201
|
+
if (name === 'default' && !targetExports.includes('default')) {
|
|
9202
|
+
if (focus === 'all' || focus === 'imports') {
|
|
9203
|
+
issues.push({
|
|
9204
|
+
category: 'import',
|
|
9205
|
+
severity: 'error',
|
|
9206
|
+
file: relativePath,
|
|
9207
|
+
line: imp.line,
|
|
9208
|
+
message: `No default export in '${imp.path}'`,
|
|
9209
|
+
fix: `Add 'export default' or change to named import`,
|
|
9210
|
+
autoFixable: false,
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
continue;
|
|
9215
|
+
}
|
|
9216
|
+
|
|
9217
|
+
// Track that this export is used
|
|
9218
|
+
usedExports.add(`${resolvedPath}:${name}`);
|
|
9219
|
+
|
|
9220
|
+
if (!targetExports.includes(name)) {
|
|
9221
|
+
if (focus === 'all' || focus === 'imports') {
|
|
9222
|
+
issues.push({
|
|
9223
|
+
category: 'export',
|
|
9224
|
+
severity: 'error',
|
|
9225
|
+
file: relativePath,
|
|
9226
|
+
line: imp.line,
|
|
9227
|
+
message: `'${name}' is not exported from '${imp.path}'`,
|
|
9228
|
+
fix: `Add 'export { ${name} }' to ${path.basename(resolvedPath)} or update import`,
|
|
9229
|
+
autoFixable: false,
|
|
9230
|
+
});
|
|
9231
|
+
}
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
}
|
|
9235
|
+
|
|
9236
|
+
importGraph.set(fullPath, importedFiles);
|
|
9237
|
+
} catch {
|
|
9238
|
+
// Skip unreadable files
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9241
|
+
}
|
|
9242
|
+
};
|
|
9243
|
+
|
|
9244
|
+
// Run import checks
|
|
9245
|
+
for (const dir of searchDirs) {
|
|
9246
|
+
checkImports(path.join(cwd, dir));
|
|
9247
|
+
}
|
|
9248
|
+
|
|
9249
|
+
// Check for circular dependencies
|
|
9250
|
+
if (focus === 'all' || focus === 'circular') {
|
|
9251
|
+
const visited = new Set<string>();
|
|
9252
|
+
const recursionStack = new Set<string>();
|
|
9253
|
+
const circularPaths: string[][] = [];
|
|
9254
|
+
|
|
9255
|
+
const detectCircular = (file: string, pathStack: string[]): boolean => {
|
|
9256
|
+
if (recursionStack.has(file)) {
|
|
9257
|
+
// Found circular dependency
|
|
9258
|
+
const cycleStart = pathStack.indexOf(file);
|
|
9259
|
+
if (cycleStart !== -1) {
|
|
9260
|
+
circularPaths.push(pathStack.slice(cycleStart).concat(file));
|
|
9261
|
+
}
|
|
9262
|
+
return true;
|
|
9263
|
+
}
|
|
9264
|
+
|
|
9265
|
+
if (visited.has(file)) return false;
|
|
9266
|
+
|
|
9267
|
+
visited.add(file);
|
|
9268
|
+
recursionStack.add(file);
|
|
9269
|
+
|
|
9270
|
+
const imports = importGraph.get(file) || [];
|
|
9271
|
+
for (const imported of imports) {
|
|
9272
|
+
detectCircular(imported, [...pathStack, file]);
|
|
9273
|
+
}
|
|
9274
|
+
|
|
9275
|
+
recursionStack.delete(file);
|
|
9276
|
+
return false;
|
|
9277
|
+
};
|
|
9278
|
+
|
|
9279
|
+
for (const file of importGraph.keys()) {
|
|
9280
|
+
detectCircular(file, []);
|
|
9281
|
+
}
|
|
9282
|
+
|
|
9283
|
+
// Add unique circular dependencies as issues
|
|
9284
|
+
const seenCycles = new Set<string>();
|
|
9285
|
+
for (const cycle of circularPaths) {
|
|
9286
|
+
const cycleKey = cycle.map(f => path.relative(cwd, f)).sort().join(' -> ');
|
|
9287
|
+
if (!seenCycles.has(cycleKey)) {
|
|
9288
|
+
seenCycles.add(cycleKey);
|
|
9289
|
+
issues.push({
|
|
9290
|
+
category: 'circular',
|
|
9291
|
+
severity: 'warning',
|
|
9292
|
+
file: path.relative(cwd, cycle[0]),
|
|
9293
|
+
message: `Circular dependency: ${cycle.map(f => path.basename(f)).join(' → ')}`,
|
|
9294
|
+
fix: 'Break the cycle by extracting shared code to a separate module',
|
|
9295
|
+
autoFixable: false,
|
|
9296
|
+
});
|
|
9297
|
+
}
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
|
|
9301
|
+
// Check for dead code (unused exports)
|
|
9302
|
+
if (focus === 'all' || focus === 'dead-code') {
|
|
9303
|
+
for (const [file, exports] of exportMap.entries()) {
|
|
9304
|
+
const relativePath = path.relative(cwd, file);
|
|
9305
|
+
|
|
9306
|
+
// Skip entry points and config files
|
|
9307
|
+
if (
|
|
9308
|
+
relativePath.includes('page.tsx') ||
|
|
9309
|
+
relativePath.includes('layout.tsx') ||
|
|
9310
|
+
relativePath.includes('route.ts') ||
|
|
9311
|
+
relativePath.includes('middleware.ts') ||
|
|
9312
|
+
relativePath.endsWith('.config.ts') ||
|
|
9313
|
+
relativePath.endsWith('.config.js')
|
|
9314
|
+
) {
|
|
9315
|
+
continue;
|
|
9316
|
+
}
|
|
9317
|
+
|
|
9318
|
+
for (const exp of exports) {
|
|
9319
|
+
if (exp.startsWith('__reexport:') || exp === 'default') continue;
|
|
9320
|
+
|
|
9321
|
+
const exportKey = `${file}:${exp}`;
|
|
9322
|
+
if (!usedExports.has(exportKey)) {
|
|
9323
|
+
issues.push({
|
|
9324
|
+
category: 'dead-code',
|
|
9325
|
+
severity: 'info',
|
|
9326
|
+
file: relativePath,
|
|
9327
|
+
message: `Export '${exp}' is never imported`,
|
|
9328
|
+
fix: `Remove if unused, or verify it's used externally`,
|
|
9329
|
+
autoFixable: true,
|
|
9330
|
+
});
|
|
9331
|
+
}
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
|
|
9336
|
+
// Check environment variables
|
|
9337
|
+
if (focus === 'all' || focus === 'env') {
|
|
9338
|
+
const envVarsUsed = new Set<string>();
|
|
9339
|
+
const envVarsDefined = new Set<string>();
|
|
9340
|
+
|
|
9341
|
+
// Scan for process.env usage
|
|
9342
|
+
const scanEnvUsage = (dir: string) => {
|
|
9343
|
+
if (!fs.existsSync(dir)) return;
|
|
9344
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9345
|
+
|
|
9346
|
+
for (const entry of entries) {
|
|
9347
|
+
const fullPath = path.join(dir, entry.name);
|
|
9348
|
+
|
|
9349
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
9350
|
+
scanEnvUsage(fullPath);
|
|
9351
|
+
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
9352
|
+
try {
|
|
9353
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9354
|
+
const envMatches = content.matchAll(/process\.env\.(\w+)|process\.env\[['"](\w+)['"]\]/g);
|
|
9355
|
+
for (const match of envMatches) {
|
|
9356
|
+
const varName = match[1] || match[2];
|
|
9357
|
+
envVarsUsed.add(varName);
|
|
9358
|
+
stats.envVarsFound++;
|
|
9359
|
+
}
|
|
9360
|
+
} catch {
|
|
9361
|
+
// Skip
|
|
9362
|
+
}
|
|
9363
|
+
}
|
|
9364
|
+
}
|
|
9365
|
+
};
|
|
9366
|
+
|
|
9367
|
+
for (const dir of searchDirs) {
|
|
9368
|
+
scanEnvUsage(path.join(cwd, dir));
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9371
|
+
// Check .env.example and .env.local
|
|
9372
|
+
const envFiles = ['.env', '.env.local', '.env.example', '.env.development'];
|
|
9373
|
+
for (const envFile of envFiles) {
|
|
9374
|
+
const envPath = path.join(cwd, envFile);
|
|
9375
|
+
if (fs.existsSync(envPath)) {
|
|
9376
|
+
try {
|
|
9377
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
9378
|
+
const lines = content.split('\n');
|
|
9379
|
+
for (const line of lines) {
|
|
9380
|
+
const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
|
|
9381
|
+
if (match) {
|
|
9382
|
+
envVarsDefined.add(match[1]);
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
} catch {
|
|
9386
|
+
// Skip
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
}
|
|
9390
|
+
|
|
9391
|
+
// Find env vars used but not defined
|
|
9392
|
+
for (const varName of envVarsUsed) {
|
|
9393
|
+
// Skip common Next.js vars
|
|
9394
|
+
if (varName.startsWith('NEXT_') || varName === 'NODE_ENV') continue;
|
|
9395
|
+
|
|
9396
|
+
if (!envVarsDefined.has(varName)) {
|
|
9397
|
+
issues.push({
|
|
9398
|
+
category: 'env',
|
|
9399
|
+
severity: 'warning',
|
|
9400
|
+
file: '.env.example',
|
|
9401
|
+
message: `Environment variable '${varName}' is used but not defined in .env files`,
|
|
9402
|
+
fix: `Add ${varName}= to .env.example`,
|
|
9403
|
+
autoFixable: true,
|
|
9404
|
+
});
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
|
|
9408
|
+
// Find env vars defined but not used
|
|
9409
|
+
for (const varName of envVarsDefined) {
|
|
9410
|
+
if (!envVarsUsed.has(varName) && !varName.startsWith('NEXT_')) {
|
|
9411
|
+
issues.push({
|
|
9412
|
+
category: 'env',
|
|
9413
|
+
severity: 'info',
|
|
9414
|
+
file: '.env',
|
|
9415
|
+
message: `Environment variable '${varName}' is defined but never used`,
|
|
9416
|
+
fix: 'Remove if no longer needed',
|
|
9417
|
+
autoFixable: false,
|
|
9418
|
+
});
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
|
|
9423
|
+
// Check for Drizzle schema issues
|
|
9424
|
+
if (focus === 'all' || focus === 'schema') {
|
|
9425
|
+
const schemaPath = path.join(cwd, 'src', 'db', 'schema.ts');
|
|
9426
|
+
const altSchemaPath = path.join(cwd, 'drizzle', 'schema.ts');
|
|
9427
|
+
const schemaFile = fs.existsSync(schemaPath) ? schemaPath : fs.existsSync(altSchemaPath) ? altSchemaPath : null;
|
|
9428
|
+
|
|
9429
|
+
if (schemaFile) {
|
|
9430
|
+
try {
|
|
9431
|
+
const schemaContent = fs.readFileSync(schemaFile, 'utf-8');
|
|
9432
|
+
|
|
9433
|
+
// Extract table names
|
|
9434
|
+
const tableMatches = schemaContent.matchAll(/export\s+const\s+(\w+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable)\s*\(/g);
|
|
9435
|
+
const tableNames = new Set<string>();
|
|
9436
|
+
for (const match of tableMatches) {
|
|
9437
|
+
tableNames.add(match[1]);
|
|
9438
|
+
}
|
|
9439
|
+
|
|
9440
|
+
// Scan for .insert(), .update(), .delete() calls on non-existent tables
|
|
9441
|
+
const scanSchemaUsage = (dir: string) => {
|
|
9442
|
+
if (!fs.existsSync(dir)) return;
|
|
9443
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9444
|
+
|
|
9445
|
+
for (const entry of entries) {
|
|
9446
|
+
const fullPath = path.join(dir, entry.name);
|
|
9447
|
+
|
|
9448
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
9449
|
+
scanSchemaUsage(fullPath);
|
|
9450
|
+
} else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
9451
|
+
try {
|
|
9452
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9453
|
+
const relativePath = path.relative(cwd, fullPath);
|
|
9454
|
+
|
|
9455
|
+
// Check for db operations on tables
|
|
9456
|
+
const opMatches = content.matchAll(/db\.(insert|update|delete|select)\((\w+)\)/g);
|
|
9457
|
+
for (const match of opMatches) {
|
|
9458
|
+
const tableName = match[2];
|
|
9459
|
+
if (!tableNames.has(tableName) && !tableName.startsWith('sql')) {
|
|
9460
|
+
issues.push({
|
|
9461
|
+
category: 'schema',
|
|
9462
|
+
severity: 'error',
|
|
9463
|
+
file: relativePath,
|
|
9464
|
+
message: `Database operation on unknown table '${tableName}'`,
|
|
9465
|
+
fix: `Verify table exists in schema or fix the table name`,
|
|
9466
|
+
autoFixable: false,
|
|
9467
|
+
});
|
|
9468
|
+
}
|
|
9469
|
+
}
|
|
9470
|
+
} catch {
|
|
9471
|
+
// Skip
|
|
9472
|
+
}
|
|
9473
|
+
}
|
|
9474
|
+
}
|
|
9475
|
+
};
|
|
9476
|
+
|
|
9477
|
+
for (const dir of searchDirs) {
|
|
9478
|
+
scanSchemaUsage(path.join(cwd, dir));
|
|
9479
|
+
}
|
|
9480
|
+
} catch {
|
|
9481
|
+
// Skip if can't read schema
|
|
9482
|
+
}
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
|
|
9486
|
+
// Check API contracts (Next.js API routes vs fetch calls)
|
|
9487
|
+
if (focus === 'all' || focus === 'api') {
|
|
9488
|
+
const apiRoutes = new Map<string, { methods: string[]; file: string }>();
|
|
9489
|
+
|
|
9490
|
+
// Find all API routes
|
|
9491
|
+
const apiDir = path.join(cwd, 'src', 'app', 'api');
|
|
9492
|
+
const altApiDir = path.join(cwd, 'app', 'api');
|
|
9493
|
+
const pagesApiDir = path.join(cwd, 'pages', 'api');
|
|
9494
|
+
|
|
9495
|
+
const scanApiRoutes = (dir: string, prefix: string = '/api') => {
|
|
9496
|
+
if (!fs.existsSync(dir)) return;
|
|
9497
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9498
|
+
|
|
9499
|
+
for (const entry of entries) {
|
|
9500
|
+
const fullPath = path.join(dir, entry.name);
|
|
9501
|
+
|
|
9502
|
+
if (entry.isDirectory()) {
|
|
9503
|
+
scanApiRoutes(fullPath, `${prefix}/${entry.name}`);
|
|
9504
|
+
} else if (entry.name === 'route.ts' || entry.name === 'route.js') {
|
|
9505
|
+
try {
|
|
9506
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9507
|
+
const methods: string[] = [];
|
|
9508
|
+
|
|
9509
|
+
if (content.includes('export async function GET') || content.includes('export function GET')) methods.push('GET');
|
|
9510
|
+
if (content.includes('export async function POST') || content.includes('export function POST')) methods.push('POST');
|
|
9511
|
+
if (content.includes('export async function PUT') || content.includes('export function PUT')) methods.push('PUT');
|
|
9512
|
+
if (content.includes('export async function DELETE') || content.includes('export function DELETE')) methods.push('DELETE');
|
|
9513
|
+
if (content.includes('export async function PATCH') || content.includes('export function PATCH')) methods.push('PATCH');
|
|
9514
|
+
|
|
9515
|
+
apiRoutes.set(prefix, { methods, file: path.relative(cwd, fullPath) });
|
|
9516
|
+
} catch {
|
|
9517
|
+
// Skip
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
};
|
|
9522
|
+
|
|
9523
|
+
scanApiRoutes(apiDir);
|
|
9524
|
+
scanApiRoutes(altApiDir);
|
|
9525
|
+
|
|
9526
|
+
// Scan for fetch calls to API routes
|
|
9527
|
+
const scanFetchCalls = (dir: string) => {
|
|
9528
|
+
if (!fs.existsSync(dir)) return;
|
|
9529
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9530
|
+
|
|
9531
|
+
for (const entry of entries) {
|
|
9532
|
+
const fullPath = path.join(dir, entry.name);
|
|
9533
|
+
|
|
9534
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'api') {
|
|
9535
|
+
scanFetchCalls(fullPath);
|
|
9536
|
+
} else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
|
|
9537
|
+
try {
|
|
9538
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
9539
|
+
const relativePath = path.relative(cwd, fullPath);
|
|
9540
|
+
|
|
9541
|
+
// Match fetch('/api/...') or fetch(`/api/...`)
|
|
9542
|
+
const fetchMatches = content.matchAll(/fetch\s*\(\s*[`'"]([^`'"]+)[`'"]/g);
|
|
9543
|
+
for (const match of fetchMatches) {
|
|
9544
|
+
const url = match[1];
|
|
9545
|
+
if (url.startsWith('/api/')) {
|
|
9546
|
+
// Extract base path (before query params or dynamic segments)
|
|
9547
|
+
const basePath = url.split('?')[0].replace(/\$\{[^}]+\}/g, '[dynamic]');
|
|
9548
|
+
|
|
9549
|
+
// Check if this route exists
|
|
9550
|
+
let routeFound = false;
|
|
9551
|
+
for (const [route] of apiRoutes) {
|
|
9552
|
+
// Simple matching - could be improved
|
|
9553
|
+
if (basePath === route || basePath.startsWith(route + '/')) {
|
|
9554
|
+
routeFound = true;
|
|
9555
|
+
break;
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
9558
|
+
|
|
9559
|
+
if (!routeFound && !basePath.includes('[dynamic]')) {
|
|
9560
|
+
issues.push({
|
|
9561
|
+
category: 'api',
|
|
9562
|
+
severity: 'warning',
|
|
9563
|
+
file: relativePath,
|
|
9564
|
+
message: `Fetch to '${url}' but no matching API route found`,
|
|
9565
|
+
fix: `Create the API route or fix the URL`,
|
|
9566
|
+
autoFixable: false,
|
|
9567
|
+
});
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
}
|
|
9571
|
+
} catch {
|
|
9572
|
+
// Skip
|
|
9573
|
+
}
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
};
|
|
9577
|
+
|
|
9578
|
+
for (const dir of ['src', 'app', 'components', 'lib']) {
|
|
9579
|
+
scanFetchCalls(path.join(cwd, dir));
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9582
|
+
|
|
9583
|
+
// Generate report
|
|
9584
|
+
let response = `# 🔗 Coherence Audit Report\n\n`;
|
|
9585
|
+
|
|
9586
|
+
// Summary
|
|
9587
|
+
const errorCount = issues.filter(i => i.severity === 'error').length;
|
|
9588
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
9589
|
+
const infoCount = issues.filter(i => i.severity === 'info').length;
|
|
9590
|
+
const autoFixableCount = issues.filter(i => i.autoFixable).length;
|
|
9591
|
+
|
|
9592
|
+
response += `## 📊 Summary\n\n`;
|
|
9593
|
+
response += `| Metric | Value |\n`;
|
|
9594
|
+
response += `|--------|-------|\n`;
|
|
9595
|
+
response += `| Files scanned | ${stats.filesScanned} |\n`;
|
|
9596
|
+
response += `| Imports checked | ${stats.importsChecked} |\n`;
|
|
9597
|
+
response += `| Exports found | ${stats.exportsFound} |\n`;
|
|
9598
|
+
response += `| 🔴 Errors | ${errorCount} |\n`;
|
|
9599
|
+
response += `| 🟡 Warnings | ${warningCount} |\n`;
|
|
9600
|
+
response += `| 🔵 Info | ${infoCount} |\n`;
|
|
9601
|
+
response += `| 🔧 Auto-fixable | ${autoFixableCount} |\n\n`;
|
|
9602
|
+
|
|
9603
|
+
if (issues.length === 0) {
|
|
9604
|
+
response += `## ✅ All Clear!\n\n`;
|
|
9605
|
+
response += `No coherence issues found. Your codebase wiring is solid.\n`;
|
|
9606
|
+
} else {
|
|
9607
|
+
// Group issues by category
|
|
9608
|
+
const categories: Record<string, CoherenceIssue[]> = {
|
|
9609
|
+
import: [],
|
|
9610
|
+
export: [],
|
|
9611
|
+
type: [],
|
|
9612
|
+
schema: [],
|
|
9613
|
+
api: [],
|
|
9614
|
+
env: [],
|
|
9615
|
+
circular: [],
|
|
9616
|
+
'dead-code': [],
|
|
9617
|
+
};
|
|
9618
|
+
|
|
9619
|
+
for (const issue of issues) {
|
|
9620
|
+
categories[issue.category].push(issue);
|
|
9621
|
+
}
|
|
9622
|
+
|
|
9623
|
+
const categoryNames: Record<string, string> = {
|
|
9624
|
+
import: '🔴 Broken Imports',
|
|
9625
|
+
export: '🔴 Missing Exports',
|
|
9626
|
+
type: '🟠 Type Mismatches',
|
|
9627
|
+
schema: '🟠 Schema Issues',
|
|
9628
|
+
api: '🟡 API Contract Issues',
|
|
9629
|
+
env: '🟡 Environment Variables',
|
|
9630
|
+
circular: '⚪ Circular Dependencies',
|
|
9631
|
+
'dead-code': '🔵 Dead Code',
|
|
9632
|
+
};
|
|
9633
|
+
|
|
9634
|
+
for (const [category, catIssues] of Object.entries(categories)) {
|
|
9635
|
+
if (catIssues.length === 0) continue;
|
|
9636
|
+
|
|
9637
|
+
response += `## ${categoryNames[category]} (${catIssues.length})\n\n`;
|
|
9638
|
+
|
|
9639
|
+
// Show first 10 issues per category
|
|
9640
|
+
const displayIssues = catIssues.slice(0, 10);
|
|
9641
|
+
for (const issue of displayIssues) {
|
|
9642
|
+
response += `### \`${issue.file}${issue.line ? `:${issue.line}` : ''}\`\n`;
|
|
9643
|
+
response += `**Issue:** ${issue.message}\n`;
|
|
9644
|
+
if (issue.fix) {
|
|
9645
|
+
response += `**Fix:** ${issue.fix}${issue.autoFixable ? ' 🔧' : ''}\n`;
|
|
9646
|
+
}
|
|
9647
|
+
response += `\n`;
|
|
9648
|
+
}
|
|
9649
|
+
|
|
9650
|
+
if (catIssues.length > 10) {
|
|
9651
|
+
response += `*... and ${catIssues.length - 10} more ${category} issues*\n\n`;
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
9655
|
+
|
|
9656
|
+
// Recommendations
|
|
9657
|
+
response += `## 💡 Recommendations\n\n`;
|
|
9658
|
+
|
|
9659
|
+
if (errorCount > 0) {
|
|
9660
|
+
response += `1. **Fix ${errorCount} errors first** - These will cause runtime failures\n`;
|
|
9661
|
+
}
|
|
9662
|
+
if (warningCount > 0) {
|
|
9663
|
+
response += `2. **Review ${warningCount} warnings** - These may cause issues\n`;
|
|
9664
|
+
}
|
|
9665
|
+
response += `3. Run \`npx tsc --noEmit\` to verify TypeScript compiles\n`;
|
|
9666
|
+
response += `4. Run your test suite to verify functionality\n`;
|
|
9667
|
+
|
|
9668
|
+
if (autoFixableCount > 0) {
|
|
9669
|
+
response += `\n---\n\n`;
|
|
9670
|
+
response += `**${autoFixableCount} issues can be auto-fixed.** Run \`guardian_heal\` to fix them.\n`;
|
|
9671
|
+
}
|
|
9672
|
+
|
|
9673
|
+
// Save state for guardian_heal
|
|
9674
|
+
const statePath = path.join(cwd, '.codebakers', 'coherence-state.json');
|
|
9675
|
+
try {
|
|
9676
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
9677
|
+
fs.writeFileSync(statePath, JSON.stringify({
|
|
9678
|
+
lastAudit: new Date().toISOString(),
|
|
9679
|
+
focus,
|
|
9680
|
+
stats,
|
|
9681
|
+
issues: issues.map(i => ({
|
|
9682
|
+
...i,
|
|
9683
|
+
// Include only auto-fixable for healing
|
|
9684
|
+
})),
|
|
9685
|
+
summary: { errors: errorCount, warnings: warningCount, info: infoCount, autoFixable: autoFixableCount },
|
|
9686
|
+
}, null, 2));
|
|
9687
|
+
} catch {
|
|
9688
|
+
// Ignore write errors
|
|
9689
|
+
}
|
|
9690
|
+
|
|
9691
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
9692
|
+
}
|
|
9693
|
+
|
|
8938
9694
|
// ============================================================================
|
|
8939
9695
|
// PROJECT TRACKING - Server-Side Dashboard
|
|
8940
9696
|
// ============================================================================
|