@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/package.json
CHANGED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
4
|
+
import { join, relative, resolve, dirname, basename, extname } from 'path';
|
|
5
|
+
|
|
6
|
+
interface CoherenceIssue {
|
|
7
|
+
category: 'import' | 'export' | 'type' | 'schema' | 'api' | 'env' | 'circular' | 'dead-code';
|
|
8
|
+
severity: 'error' | 'warning' | 'info';
|
|
9
|
+
file: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
message: string;
|
|
12
|
+
fix?: string;
|
|
13
|
+
autoFixable: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface CoherenceOptions {
|
|
17
|
+
focus?: string;
|
|
18
|
+
fix?: boolean;
|
|
19
|
+
verbose?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* CLI coherence command - checks wiring and dependencies
|
|
24
|
+
*/
|
|
25
|
+
export async function coherence(options: CoherenceOptions = {}): Promise<void> {
|
|
26
|
+
const { focus = 'all', fix = false, verbose = false } = options;
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
|
|
29
|
+
console.log(chalk.blue(`
|
|
30
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
31
|
+
║ ║
|
|
32
|
+
║ ${chalk.bold.white('🔗 CodeBakers Coherence Audit')} ║
|
|
33
|
+
║ ║
|
|
34
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
35
|
+
`));
|
|
36
|
+
|
|
37
|
+
const spinner = ora('Scanning codebase for coherence issues...').start();
|
|
38
|
+
|
|
39
|
+
const issues: CoherenceIssue[] = [];
|
|
40
|
+
const stats = {
|
|
41
|
+
filesScanned: 0,
|
|
42
|
+
importsChecked: 0,
|
|
43
|
+
exportsFound: 0,
|
|
44
|
+
envVarsFound: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Helper to extract imports from a file
|
|
48
|
+
const extractImports = (content: string): Array<{ path: string; names: string[]; line: number }> => {
|
|
49
|
+
const imports: Array<{ path: string; names: string[]; line: number }> = [];
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
|
|
52
|
+
lines.forEach((line, i) => {
|
|
53
|
+
// Match: import { X, Y } from 'path'
|
|
54
|
+
const namedMatch = line.match(/import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/);
|
|
55
|
+
if (namedMatch) {
|
|
56
|
+
const names = namedMatch[2].split(',').map(n => n.trim().split(' as ')[0].trim()).filter(Boolean);
|
|
57
|
+
imports.push({ path: namedMatch[3], names, line: i + 1 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Match: import X from 'path'
|
|
61
|
+
const defaultMatch = line.match(/import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
62
|
+
if (defaultMatch && !namedMatch) {
|
|
63
|
+
imports.push({ path: defaultMatch[3], names: ['default'], line: i + 1 });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return imports;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Helper to extract exports from a file
|
|
71
|
+
const extractExports = (content: string): string[] => {
|
|
72
|
+
const exports: string[] = [];
|
|
73
|
+
|
|
74
|
+
// Named exports: export { X, Y }
|
|
75
|
+
const namedExportMatches = content.matchAll(/export\s+{([^}]+)}/g);
|
|
76
|
+
for (const match of namedExportMatches) {
|
|
77
|
+
const names = match[1].split(',').map(n => n.trim().split(' as ').pop()?.trim() || '').filter(Boolean);
|
|
78
|
+
exports.push(...names);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Direct exports: export const/function/class/type/interface X
|
|
82
|
+
const directExportMatches = content.matchAll(/export\s+(const|let|var|function|class|type|interface|enum)\s+(\w+)/g);
|
|
83
|
+
for (const match of directExportMatches) {
|
|
84
|
+
exports.push(match[2]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default export
|
|
88
|
+
if (content.includes('export default')) {
|
|
89
|
+
exports.push('default');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return exports;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Helper to resolve import path to file
|
|
96
|
+
const resolveImportPath = (importPath: string, fromFile: string): string | null => {
|
|
97
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('@/') && !importPath.startsWith('~/')) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let resolvedPath = importPath;
|
|
102
|
+
if (importPath.startsWith('@/')) {
|
|
103
|
+
resolvedPath = join(cwd, 'src', importPath.slice(2));
|
|
104
|
+
} else if (importPath.startsWith('~/')) {
|
|
105
|
+
resolvedPath = join(cwd, importPath.slice(2));
|
|
106
|
+
} else {
|
|
107
|
+
resolvedPath = resolve(dirname(fromFile), importPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
|
|
111
|
+
for (const ext of extensions) {
|
|
112
|
+
const fullPath = resolvedPath + ext;
|
|
113
|
+
if (existsSync(fullPath) && statSync(fullPath).isFile()) {
|
|
114
|
+
return fullPath;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks', 'pages'];
|
|
122
|
+
const fileExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
123
|
+
|
|
124
|
+
// Build export map
|
|
125
|
+
const exportMap: Map<string, string[]> = new Map();
|
|
126
|
+
const importGraph: Map<string, string[]> = new Map();
|
|
127
|
+
const usedExports: Set<string> = new Set();
|
|
128
|
+
|
|
129
|
+
// First pass: collect all exports
|
|
130
|
+
const collectExports = (dir: string) => {
|
|
131
|
+
if (!existsSync(dir)) return;
|
|
132
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
133
|
+
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const fullPath = join(dir, entry.name);
|
|
136
|
+
|
|
137
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
138
|
+
collectExports(fullPath);
|
|
139
|
+
} else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
140
|
+
try {
|
|
141
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
142
|
+
const exports = extractExports(content);
|
|
143
|
+
exportMap.set(fullPath, exports);
|
|
144
|
+
stats.exportsFound += exports.length;
|
|
145
|
+
stats.filesScanned++;
|
|
146
|
+
} catch {
|
|
147
|
+
// Skip unreadable files
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
spinner.text = 'Collecting exports...';
|
|
154
|
+
for (const dir of searchDirs) {
|
|
155
|
+
collectExports(join(cwd, dir));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Second pass: check imports
|
|
159
|
+
const checkImports = (dir: string) => {
|
|
160
|
+
if (!existsSync(dir)) return;
|
|
161
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
162
|
+
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const fullPath = join(dir, entry.name);
|
|
165
|
+
|
|
166
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
167
|
+
checkImports(fullPath);
|
|
168
|
+
} else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
169
|
+
try {
|
|
170
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
171
|
+
const imports = extractImports(content);
|
|
172
|
+
const relativePath = relative(cwd, fullPath);
|
|
173
|
+
const importedFiles: string[] = [];
|
|
174
|
+
|
|
175
|
+
for (const imp of imports) {
|
|
176
|
+
stats.importsChecked++;
|
|
177
|
+
const resolvedPath = resolveImportPath(imp.path, fullPath);
|
|
178
|
+
|
|
179
|
+
if (resolvedPath === null) continue;
|
|
180
|
+
|
|
181
|
+
importedFiles.push(resolvedPath);
|
|
182
|
+
|
|
183
|
+
if (!existsSync(resolvedPath)) {
|
|
184
|
+
if (focus === 'all' || focus === 'imports') {
|
|
185
|
+
issues.push({
|
|
186
|
+
category: 'import',
|
|
187
|
+
severity: 'error',
|
|
188
|
+
file: relativePath,
|
|
189
|
+
line: imp.line,
|
|
190
|
+
message: `Import target not found: '${imp.path}'`,
|
|
191
|
+
fix: `Create the file or update the import path`,
|
|
192
|
+
autoFixable: false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const targetExports = exportMap.get(resolvedPath) || [];
|
|
199
|
+
|
|
200
|
+
for (const name of imp.names) {
|
|
201
|
+
if (name === '*' || name === 'default') {
|
|
202
|
+
if (name === 'default' && !targetExports.includes('default')) {
|
|
203
|
+
if (focus === 'all' || focus === 'imports') {
|
|
204
|
+
issues.push({
|
|
205
|
+
category: 'import',
|
|
206
|
+
severity: 'error',
|
|
207
|
+
file: relativePath,
|
|
208
|
+
line: imp.line,
|
|
209
|
+
message: `No default export in '${imp.path}'`,
|
|
210
|
+
fix: `Add 'export default' or change to named import`,
|
|
211
|
+
autoFixable: false,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
usedExports.add(`${resolvedPath}:${name}`);
|
|
219
|
+
|
|
220
|
+
if (!targetExports.includes(name)) {
|
|
221
|
+
if (focus === 'all' || focus === 'imports') {
|
|
222
|
+
issues.push({
|
|
223
|
+
category: 'export',
|
|
224
|
+
severity: 'error',
|
|
225
|
+
file: relativePath,
|
|
226
|
+
line: imp.line,
|
|
227
|
+
message: `'${name}' is not exported from '${imp.path}'`,
|
|
228
|
+
fix: `Add 'export { ${name} }' to ${basename(resolvedPath)} or update import`,
|
|
229
|
+
autoFixable: false,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
importGraph.set(fullPath, importedFiles);
|
|
237
|
+
} catch {
|
|
238
|
+
// Skip unreadable files
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
spinner.text = 'Checking imports...';
|
|
245
|
+
for (const dir of searchDirs) {
|
|
246
|
+
checkImports(join(cwd, dir));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for circular dependencies
|
|
250
|
+
if (focus === 'all' || focus === 'circular') {
|
|
251
|
+
spinner.text = 'Detecting circular dependencies...';
|
|
252
|
+
const visited = new Set<string>();
|
|
253
|
+
const recursionStack = new Set<string>();
|
|
254
|
+
const circularPaths: string[][] = [];
|
|
255
|
+
|
|
256
|
+
const detectCircular = (file: string, pathStack: string[]) => {
|
|
257
|
+
if (recursionStack.has(file)) {
|
|
258
|
+
const cycleStart = pathStack.indexOf(file);
|
|
259
|
+
if (cycleStart !== -1) {
|
|
260
|
+
circularPaths.push(pathStack.slice(cycleStart).concat(file));
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (visited.has(file)) return;
|
|
266
|
+
|
|
267
|
+
visited.add(file);
|
|
268
|
+
recursionStack.add(file);
|
|
269
|
+
|
|
270
|
+
const imports = importGraph.get(file) || [];
|
|
271
|
+
for (const imported of imports) {
|
|
272
|
+
detectCircular(imported, [...pathStack, file]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
recursionStack.delete(file);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
for (const file of importGraph.keys()) {
|
|
279
|
+
detectCircular(file, []);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const seenCycles = new Set<string>();
|
|
283
|
+
for (const cycle of circularPaths) {
|
|
284
|
+
const cycleKey = cycle.map(f => relative(cwd, f)).sort().join(' -> ');
|
|
285
|
+
if (!seenCycles.has(cycleKey)) {
|
|
286
|
+
seenCycles.add(cycleKey);
|
|
287
|
+
issues.push({
|
|
288
|
+
category: 'circular',
|
|
289
|
+
severity: 'warning',
|
|
290
|
+
file: relative(cwd, cycle[0]),
|
|
291
|
+
message: `Circular dependency: ${cycle.map(f => basename(f)).join(' → ')}`,
|
|
292
|
+
fix: 'Break the cycle by extracting shared code to a separate module',
|
|
293
|
+
autoFixable: false,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check environment variables
|
|
300
|
+
if (focus === 'all' || focus === 'env') {
|
|
301
|
+
spinner.text = 'Checking environment variables...';
|
|
302
|
+
const envVarsUsed = new Set<string>();
|
|
303
|
+
const envVarsDefined = new Set<string>();
|
|
304
|
+
|
|
305
|
+
const scanEnvUsage = (dir: string) => {
|
|
306
|
+
if (!existsSync(dir)) return;
|
|
307
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
308
|
+
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const fullPath = join(dir, entry.name);
|
|
311
|
+
|
|
312
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
313
|
+
scanEnvUsage(fullPath);
|
|
314
|
+
} else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
315
|
+
try {
|
|
316
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
317
|
+
const envMatches = content.matchAll(/process\.env\.(\w+)|process\.env\[['"](\w+)['"]\]/g);
|
|
318
|
+
for (const match of envMatches) {
|
|
319
|
+
const varName = match[1] || match[2];
|
|
320
|
+
envVarsUsed.add(varName);
|
|
321
|
+
stats.envVarsFound++;
|
|
322
|
+
}
|
|
323
|
+
} catch {
|
|
324
|
+
// Skip
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
for (const dir of searchDirs) {
|
|
331
|
+
scanEnvUsage(join(cwd, dir));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const envFiles = ['.env', '.env.local', '.env.example', '.env.development'];
|
|
335
|
+
for (const envFile of envFiles) {
|
|
336
|
+
const envPath = join(cwd, envFile);
|
|
337
|
+
if (existsSync(envPath)) {
|
|
338
|
+
try {
|
|
339
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
340
|
+
const lines = content.split('\n');
|
|
341
|
+
for (const line of lines) {
|
|
342
|
+
const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
|
|
343
|
+
if (match) {
|
|
344
|
+
envVarsDefined.add(match[1]);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// Skip
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const varName of envVarsUsed) {
|
|
354
|
+
if (varName.startsWith('NEXT_') || varName === 'NODE_ENV') continue;
|
|
355
|
+
|
|
356
|
+
if (!envVarsDefined.has(varName)) {
|
|
357
|
+
issues.push({
|
|
358
|
+
category: 'env',
|
|
359
|
+
severity: 'warning',
|
|
360
|
+
file: '.env.example',
|
|
361
|
+
message: `Environment variable '${varName}' is used but not defined in .env files`,
|
|
362
|
+
fix: `Add ${varName}= to .env.example`,
|
|
363
|
+
autoFixable: true,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
spinner.succeed('Coherence audit complete!');
|
|
370
|
+
|
|
371
|
+
// Display results
|
|
372
|
+
console.log('');
|
|
373
|
+
console.log(chalk.white(' 📊 Summary'));
|
|
374
|
+
console.log(chalk.gray(' ─────────────────────────────────────'));
|
|
375
|
+
console.log(` Files scanned: ${chalk.cyan(stats.filesScanned)}`);
|
|
376
|
+
console.log(` Imports checked: ${chalk.cyan(stats.importsChecked)}`);
|
|
377
|
+
console.log(` Exports found: ${chalk.cyan(stats.exportsFound)}`);
|
|
378
|
+
|
|
379
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
380
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
381
|
+
const infos = issues.filter(i => i.severity === 'info');
|
|
382
|
+
|
|
383
|
+
console.log('');
|
|
384
|
+
console.log(` ${chalk.red('🔴 Errors:')} ${errors.length}`);
|
|
385
|
+
console.log(` ${chalk.yellow('🟡 Warnings:')} ${warnings.length}`);
|
|
386
|
+
console.log(` ${chalk.blue('🔵 Info:')} ${infos.length}`);
|
|
387
|
+
console.log('');
|
|
388
|
+
|
|
389
|
+
if (issues.length === 0) {
|
|
390
|
+
console.log(chalk.green(' ✅ No coherence issues found! Your codebase wiring is solid.\n'));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Group issues by category
|
|
395
|
+
const categories: Record<string, CoherenceIssue[]> = {
|
|
396
|
+
import: [],
|
|
397
|
+
export: [],
|
|
398
|
+
circular: [],
|
|
399
|
+
env: [],
|
|
400
|
+
'dead-code': [],
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
for (const issue of issues) {
|
|
404
|
+
if (categories[issue.category]) {
|
|
405
|
+
categories[issue.category].push(issue);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const categoryLabels: Record<string, string> = {
|
|
410
|
+
import: '🔴 Broken Imports',
|
|
411
|
+
export: '🔴 Missing Exports',
|
|
412
|
+
circular: '⚪ Circular Dependencies',
|
|
413
|
+
env: '🟡 Environment Variables',
|
|
414
|
+
'dead-code': '🔵 Dead Code',
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
for (const [category, catIssues] of Object.entries(categories)) {
|
|
418
|
+
if (catIssues.length === 0) continue;
|
|
419
|
+
|
|
420
|
+
console.log(chalk.white(` ${categoryLabels[category]} (${catIssues.length})`));
|
|
421
|
+
console.log(chalk.gray(' ─────────────────────────────────────'));
|
|
422
|
+
|
|
423
|
+
const displayIssues = verbose ? catIssues : catIssues.slice(0, 5);
|
|
424
|
+
for (const issue of displayIssues) {
|
|
425
|
+
const severityColor = issue.severity === 'error' ? chalk.red : issue.severity === 'warning' ? chalk.yellow : chalk.blue;
|
|
426
|
+
console.log(` ${severityColor('•')} ${chalk.gray(issue.file)}${issue.line ? chalk.gray(`:${issue.line}`) : ''}`);
|
|
427
|
+
console.log(` ${issue.message}`);
|
|
428
|
+
if (issue.fix) {
|
|
429
|
+
console.log(chalk.gray(` Fix: ${issue.fix}`));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!verbose && catIssues.length > 5) {
|
|
434
|
+
console.log(chalk.gray(` ... and ${catIssues.length - 5} more (use --verbose to see all)`));
|
|
435
|
+
}
|
|
436
|
+
console.log('');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Save state
|
|
440
|
+
const statePath = join(cwd, '.codebakers', 'coherence-state.json');
|
|
441
|
+
try {
|
|
442
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
443
|
+
writeFileSync(statePath, JSON.stringify({
|
|
444
|
+
lastAudit: new Date().toISOString(),
|
|
445
|
+
stats,
|
|
446
|
+
issues,
|
|
447
|
+
summary: { errors: errors.length, warnings: warnings.length, info: infos.length },
|
|
448
|
+
}, null, 2));
|
|
449
|
+
} catch {
|
|
450
|
+
// Ignore write errors
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(chalk.gray(' Run ') + chalk.cyan('npx tsc --noEmit') + chalk.gray(' to verify TypeScript compiles'));
|
|
454
|
+
console.log(chalk.gray(' Run ') + chalk.cyan('codebakers heal') + chalk.gray(' to auto-fix what\'s possible\n'));
|
|
455
|
+
}
|
package/src/commands/go.ts
CHANGED
|
@@ -1562,8 +1562,8 @@ async function showVSCodeClaudeInstructions(): Promise<void> {
|
|
|
1562
1562
|
console.log(chalk.gray(' Having issues? Run: ') + chalk.cyan('codebakers doctor') + chalk.gray(' to diagnose\n'));
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
|
-
// v6.
|
|
1566
|
-
const V6_CLAUDE_MD = `# CodeBakers v6.
|
|
1565
|
+
// v6.17 Bootstrap - SHORT template with magic phrase + rules at START and END
|
|
1566
|
+
const V6_CLAUDE_MD = `# CodeBakers v6.17
|
|
1567
1567
|
|
|
1568
1568
|
## 🪄 MAGIC PHRASE: "codebakers go"
|
|
1569
1569
|
When user says "codebakers go" in chat, start the onboarding conversation:
|
|
@@ -1590,8 +1590,11 @@ When user says "codebakers go" in chat, start the onboarding conversation:
|
|
|
1590
1590
|
project_status() → Verify connection FIRST
|
|
1591
1591
|
discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
|
|
1592
1592
|
validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
|
|
1593
|
+
coherence_audit() → Check wiring & dependencies
|
|
1593
1594
|
\`\`\`
|
|
1594
1595
|
|
|
1596
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
1597
|
+
|
|
1595
1598
|
Header (after project_status succeeds): 🍪 CodeBakers is working on this...
|
|
1596
1599
|
Header (if project_status fails): ⚠️ CodeBakers not connected
|
|
1597
1600
|
Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
@@ -1603,7 +1606,7 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
|
1603
1606
|
4. Show footer after code responses
|
|
1604
1607
|
`;
|
|
1605
1608
|
|
|
1606
|
-
const V6_CURSORRULES = `# CodeBakers v6.
|
|
1609
|
+
const V6_CURSORRULES = `# CodeBakers v6.17
|
|
1607
1610
|
|
|
1608
1611
|
## 🪄 "codebakers go" = Start onboarding conversation
|
|
1609
1612
|
Ask existing/new → Ask what to build → Call init_project() → Help them build
|
|
@@ -1620,6 +1623,9 @@ Ask existing/new → Ask what to build → Call init_project() → Help them bui
|
|
|
1620
1623
|
3. Show header without project_status succeeding
|
|
1621
1624
|
4. Skip writing tests for new features
|
|
1622
1625
|
|
|
1626
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
1627
|
+
Use coherence_audit() to check wiring & dependencies
|
|
1628
|
+
|
|
1623
1629
|
## 🚨 ALWAYS (Repeated at End)
|
|
1624
1630
|
1. project_status() FIRST
|
|
1625
1631
|
2. discover_patterns() before code
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { generate } from './commands/generate.js';
|
|
|
18
18
|
import { upgrade } from './commands/upgrade.js';
|
|
19
19
|
import { config } from './commands/config.js';
|
|
20
20
|
import { audit } from './commands/audit.js';
|
|
21
|
+
import { coherence } from './commands/coherence.js';
|
|
21
22
|
import { heal, healWatch } from './commands/heal.js';
|
|
22
23
|
import { pushPatterns, pushPatternsInteractive } from './commands/push-patterns.js';
|
|
23
24
|
import { go } from './commands/go.js';
|
|
@@ -214,12 +215,13 @@ function showWelcome(): void {
|
|
|
214
215
|
|
|
215
216
|
console.log(chalk.white(' Quality:\n'));
|
|
216
217
|
console.log(chalk.cyan(' codebakers audit') + chalk.gray(' Run automated code quality checks'));
|
|
218
|
+
console.log(chalk.cyan(' codebakers coherence') + chalk.gray(' Check wiring, imports, and dependencies'));
|
|
217
219
|
console.log(chalk.cyan(' codebakers heal') + chalk.gray(' Auto-detect and fix common issues'));
|
|
218
220
|
console.log(chalk.cyan(' codebakers doctor') + chalk.gray(' Check CodeBakers setup\n'));
|
|
219
221
|
|
|
220
222
|
console.log(chalk.white(' All Commands:\n'));
|
|
221
223
|
console.log(chalk.gray(' go, extend, billing, build, build-status, setup, scaffold, init'));
|
|
222
|
-
console.log(chalk.gray(' generate, upgrade, status, audit, heal, doctor, config, login'));
|
|
224
|
+
console.log(chalk.gray(' generate, upgrade, status, audit, coherence, heal, doctor, config, login'));
|
|
223
225
|
console.log(chalk.gray(' serve, mcp-config, mcp-uninstall\n'));
|
|
224
226
|
|
|
225
227
|
console.log(chalk.gray(' Run ') + chalk.cyan('codebakers <command> --help') + chalk.gray(' for more info\n'));
|
|
@@ -357,6 +359,21 @@ program
|
|
|
357
359
|
.description('Run automated code quality and security checks')
|
|
358
360
|
.action(async () => { await audit(); });
|
|
359
361
|
|
|
362
|
+
program
|
|
363
|
+
.command('coherence')
|
|
364
|
+
.alias('wiring')
|
|
365
|
+
.description('Check codebase wiring, imports, exports, and dependencies')
|
|
366
|
+
.option('-f, --focus <area>', 'Focus area: all, imports, circular, env (default: all)')
|
|
367
|
+
.option('--fix', 'Automatically fix issues that can be auto-fixed')
|
|
368
|
+
.option('-v, --verbose', 'Show all issues (not just first 5 per category)')
|
|
369
|
+
.action(async (options) => {
|
|
370
|
+
await coherence({
|
|
371
|
+
focus: options.focus,
|
|
372
|
+
fix: options.fix,
|
|
373
|
+
verbose: options.verbose,
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
360
377
|
program
|
|
361
378
|
.command('heal')
|
|
362
379
|
.description('Auto-detect and fix common issues (TypeScript, deps, security)')
|