@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.
- 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-0d19-cwd +1 -0
- package/tmpclaude-199b-cwd +1 -0
- package/tmpclaude-1ef9-cwd +1 -0
- package/tmpclaude-77b7-cwd +1 -0
- package/tmpclaude-c057-cwd +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.coherence = coherence;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
/**
|
|
12
|
+
* CLI coherence command - checks wiring and dependencies
|
|
13
|
+
*/
|
|
14
|
+
async function coherence(options = {}) {
|
|
15
|
+
const { focus = 'all', fix = false, verbose = false } = options;
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
console.log(chalk_1.default.blue(`
|
|
18
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
19
|
+
║ ║
|
|
20
|
+
║ ${chalk_1.default.bold.white('🔗 CodeBakers Coherence Audit')} ║
|
|
21
|
+
║ ║
|
|
22
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
23
|
+
`));
|
|
24
|
+
const spinner = (0, ora_1.default)('Scanning codebase for coherence issues...').start();
|
|
25
|
+
const issues = [];
|
|
26
|
+
const stats = {
|
|
27
|
+
filesScanned: 0,
|
|
28
|
+
importsChecked: 0,
|
|
29
|
+
exportsFound: 0,
|
|
30
|
+
envVarsFound: 0,
|
|
31
|
+
};
|
|
32
|
+
// Helper to extract imports from a file
|
|
33
|
+
const extractImports = (content) => {
|
|
34
|
+
const imports = [];
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
lines.forEach((line, i) => {
|
|
37
|
+
// Match: import { X, Y } from 'path'
|
|
38
|
+
const namedMatch = line.match(/import\s+(type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/);
|
|
39
|
+
if (namedMatch) {
|
|
40
|
+
const names = namedMatch[2].split(',').map(n => n.trim().split(' as ')[0].trim()).filter(Boolean);
|
|
41
|
+
imports.push({ path: namedMatch[3], names, line: i + 1 });
|
|
42
|
+
}
|
|
43
|
+
// Match: import X from 'path'
|
|
44
|
+
const defaultMatch = line.match(/import\s+(type\s+)?(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
45
|
+
if (defaultMatch && !namedMatch) {
|
|
46
|
+
imports.push({ path: defaultMatch[3], names: ['default'], line: i + 1 });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return imports;
|
|
50
|
+
};
|
|
51
|
+
// Helper to extract exports from a file
|
|
52
|
+
const extractExports = (content) => {
|
|
53
|
+
const exports = [];
|
|
54
|
+
// Named exports: export { X, Y }
|
|
55
|
+
const namedExportMatches = content.matchAll(/export\s+{([^}]+)}/g);
|
|
56
|
+
for (const match of namedExportMatches) {
|
|
57
|
+
const names = match[1].split(',').map(n => n.trim().split(' as ').pop()?.trim() || '').filter(Boolean);
|
|
58
|
+
exports.push(...names);
|
|
59
|
+
}
|
|
60
|
+
// Direct exports: export const/function/class/type/interface X
|
|
61
|
+
const directExportMatches = content.matchAll(/export\s+(const|let|var|function|class|type|interface|enum)\s+(\w+)/g);
|
|
62
|
+
for (const match of directExportMatches) {
|
|
63
|
+
exports.push(match[2]);
|
|
64
|
+
}
|
|
65
|
+
// Default export
|
|
66
|
+
if (content.includes('export default')) {
|
|
67
|
+
exports.push('default');
|
|
68
|
+
}
|
|
69
|
+
return exports;
|
|
70
|
+
};
|
|
71
|
+
// Helper to resolve import path to file
|
|
72
|
+
const resolveImportPath = (importPath, fromFile) => {
|
|
73
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('@/') && !importPath.startsWith('~/')) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
let resolvedPath = importPath;
|
|
77
|
+
if (importPath.startsWith('@/')) {
|
|
78
|
+
resolvedPath = (0, path_1.join)(cwd, 'src', importPath.slice(2));
|
|
79
|
+
}
|
|
80
|
+
else if (importPath.startsWith('~/')) {
|
|
81
|
+
resolvedPath = (0, path_1.join)(cwd, importPath.slice(2));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
resolvedPath = (0, path_1.resolve)((0, path_1.dirname)(fromFile), importPath);
|
|
85
|
+
}
|
|
86
|
+
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx', '/index.js'];
|
|
87
|
+
for (const ext of extensions) {
|
|
88
|
+
const fullPath = resolvedPath + ext;
|
|
89
|
+
if ((0, fs_1.existsSync)(fullPath) && (0, fs_1.statSync)(fullPath).isFile()) {
|
|
90
|
+
return fullPath;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks', 'pages'];
|
|
96
|
+
const fileExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
97
|
+
// Build export map
|
|
98
|
+
const exportMap = new Map();
|
|
99
|
+
const importGraph = new Map();
|
|
100
|
+
const usedExports = new Set();
|
|
101
|
+
// First pass: collect all exports
|
|
102
|
+
const collectExports = (dir) => {
|
|
103
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
104
|
+
return;
|
|
105
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
108
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
109
|
+
collectExports(fullPath);
|
|
110
|
+
}
|
|
111
|
+
else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
112
|
+
try {
|
|
113
|
+
const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
114
|
+
const exports = extractExports(content);
|
|
115
|
+
exportMap.set(fullPath, exports);
|
|
116
|
+
stats.exportsFound += exports.length;
|
|
117
|
+
stats.filesScanned++;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Skip unreadable files
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
spinner.text = 'Collecting exports...';
|
|
126
|
+
for (const dir of searchDirs) {
|
|
127
|
+
collectExports((0, path_1.join)(cwd, dir));
|
|
128
|
+
}
|
|
129
|
+
// Second pass: check imports
|
|
130
|
+
const checkImports = (dir) => {
|
|
131
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
132
|
+
return;
|
|
133
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
136
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
137
|
+
checkImports(fullPath);
|
|
138
|
+
}
|
|
139
|
+
else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
140
|
+
try {
|
|
141
|
+
const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
142
|
+
const imports = extractImports(content);
|
|
143
|
+
const relativePath = (0, path_1.relative)(cwd, fullPath);
|
|
144
|
+
const importedFiles = [];
|
|
145
|
+
for (const imp of imports) {
|
|
146
|
+
stats.importsChecked++;
|
|
147
|
+
const resolvedPath = resolveImportPath(imp.path, fullPath);
|
|
148
|
+
if (resolvedPath === null)
|
|
149
|
+
continue;
|
|
150
|
+
importedFiles.push(resolvedPath);
|
|
151
|
+
if (!(0, fs_1.existsSync)(resolvedPath)) {
|
|
152
|
+
if (focus === 'all' || focus === 'imports') {
|
|
153
|
+
issues.push({
|
|
154
|
+
category: 'import',
|
|
155
|
+
severity: 'error',
|
|
156
|
+
file: relativePath,
|
|
157
|
+
line: imp.line,
|
|
158
|
+
message: `Import target not found: '${imp.path}'`,
|
|
159
|
+
fix: `Create the file or update the import path`,
|
|
160
|
+
autoFixable: false,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const targetExports = exportMap.get(resolvedPath) || [];
|
|
166
|
+
for (const name of imp.names) {
|
|
167
|
+
if (name === '*' || name === 'default') {
|
|
168
|
+
if (name === 'default' && !targetExports.includes('default')) {
|
|
169
|
+
if (focus === 'all' || focus === 'imports') {
|
|
170
|
+
issues.push({
|
|
171
|
+
category: 'import',
|
|
172
|
+
severity: 'error',
|
|
173
|
+
file: relativePath,
|
|
174
|
+
line: imp.line,
|
|
175
|
+
message: `No default export in '${imp.path}'`,
|
|
176
|
+
fix: `Add 'export default' or change to named import`,
|
|
177
|
+
autoFixable: false,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
usedExports.add(`${resolvedPath}:${name}`);
|
|
184
|
+
if (!targetExports.includes(name)) {
|
|
185
|
+
if (focus === 'all' || focus === 'imports') {
|
|
186
|
+
issues.push({
|
|
187
|
+
category: 'export',
|
|
188
|
+
severity: 'error',
|
|
189
|
+
file: relativePath,
|
|
190
|
+
line: imp.line,
|
|
191
|
+
message: `'${name}' is not exported from '${imp.path}'`,
|
|
192
|
+
fix: `Add 'export { ${name} }' to ${(0, path_1.basename)(resolvedPath)} or update import`,
|
|
193
|
+
autoFixable: false,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
importGraph.set(fullPath, importedFiles);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Skip unreadable files
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
spinner.text = 'Checking imports...';
|
|
208
|
+
for (const dir of searchDirs) {
|
|
209
|
+
checkImports((0, path_1.join)(cwd, dir));
|
|
210
|
+
}
|
|
211
|
+
// Check for circular dependencies
|
|
212
|
+
if (focus === 'all' || focus === 'circular') {
|
|
213
|
+
spinner.text = 'Detecting circular dependencies...';
|
|
214
|
+
const visited = new Set();
|
|
215
|
+
const recursionStack = new Set();
|
|
216
|
+
const circularPaths = [];
|
|
217
|
+
const detectCircular = (file, pathStack) => {
|
|
218
|
+
if (recursionStack.has(file)) {
|
|
219
|
+
const cycleStart = pathStack.indexOf(file);
|
|
220
|
+
if (cycleStart !== -1) {
|
|
221
|
+
circularPaths.push(pathStack.slice(cycleStart).concat(file));
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (visited.has(file))
|
|
226
|
+
return;
|
|
227
|
+
visited.add(file);
|
|
228
|
+
recursionStack.add(file);
|
|
229
|
+
const imports = importGraph.get(file) || [];
|
|
230
|
+
for (const imported of imports) {
|
|
231
|
+
detectCircular(imported, [...pathStack, file]);
|
|
232
|
+
}
|
|
233
|
+
recursionStack.delete(file);
|
|
234
|
+
};
|
|
235
|
+
for (const file of importGraph.keys()) {
|
|
236
|
+
detectCircular(file, []);
|
|
237
|
+
}
|
|
238
|
+
const seenCycles = new Set();
|
|
239
|
+
for (const cycle of circularPaths) {
|
|
240
|
+
const cycleKey = cycle.map(f => (0, path_1.relative)(cwd, f)).sort().join(' -> ');
|
|
241
|
+
if (!seenCycles.has(cycleKey)) {
|
|
242
|
+
seenCycles.add(cycleKey);
|
|
243
|
+
issues.push({
|
|
244
|
+
category: 'circular',
|
|
245
|
+
severity: 'warning',
|
|
246
|
+
file: (0, path_1.relative)(cwd, cycle[0]),
|
|
247
|
+
message: `Circular dependency: ${cycle.map(f => (0, path_1.basename)(f)).join(' → ')}`,
|
|
248
|
+
fix: 'Break the cycle by extracting shared code to a separate module',
|
|
249
|
+
autoFixable: false,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Check environment variables
|
|
255
|
+
if (focus === 'all' || focus === 'env') {
|
|
256
|
+
spinner.text = 'Checking environment variables...';
|
|
257
|
+
const envVarsUsed = new Set();
|
|
258
|
+
const envVarsDefined = new Set();
|
|
259
|
+
const scanEnvUsage = (dir) => {
|
|
260
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
261
|
+
return;
|
|
262
|
+
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
265
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
266
|
+
scanEnvUsage(fullPath);
|
|
267
|
+
}
|
|
268
|
+
else if (entry.isFile() && fileExtensions.some(ext => entry.name.endsWith(ext))) {
|
|
269
|
+
try {
|
|
270
|
+
const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
|
|
271
|
+
const envMatches = content.matchAll(/process\.env\.(\w+)|process\.env\[['"](\w+)['"]\]/g);
|
|
272
|
+
for (const match of envMatches) {
|
|
273
|
+
const varName = match[1] || match[2];
|
|
274
|
+
envVarsUsed.add(varName);
|
|
275
|
+
stats.envVarsFound++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Skip
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
for (const dir of searchDirs) {
|
|
285
|
+
scanEnvUsage((0, path_1.join)(cwd, dir));
|
|
286
|
+
}
|
|
287
|
+
const envFiles = ['.env', '.env.local', '.env.example', '.env.development'];
|
|
288
|
+
for (const envFile of envFiles) {
|
|
289
|
+
const envPath = (0, path_1.join)(cwd, envFile);
|
|
290
|
+
if ((0, fs_1.existsSync)(envPath)) {
|
|
291
|
+
try {
|
|
292
|
+
const content = (0, fs_1.readFileSync)(envPath, 'utf-8');
|
|
293
|
+
const lines = content.split('\n');
|
|
294
|
+
for (const line of lines) {
|
|
295
|
+
const match = line.match(/^([A-Z][A-Z0-9_]*)=/);
|
|
296
|
+
if (match) {
|
|
297
|
+
envVarsDefined.add(match[1]);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Skip
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const varName of envVarsUsed) {
|
|
307
|
+
if (varName.startsWith('NEXT_') || varName === 'NODE_ENV')
|
|
308
|
+
continue;
|
|
309
|
+
if (!envVarsDefined.has(varName)) {
|
|
310
|
+
issues.push({
|
|
311
|
+
category: 'env',
|
|
312
|
+
severity: 'warning',
|
|
313
|
+
file: '.env.example',
|
|
314
|
+
message: `Environment variable '${varName}' is used but not defined in .env files`,
|
|
315
|
+
fix: `Add ${varName}= to .env.example`,
|
|
316
|
+
autoFixable: true,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
spinner.succeed('Coherence audit complete!');
|
|
322
|
+
// Display results
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(chalk_1.default.white(' 📊 Summary'));
|
|
325
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
326
|
+
console.log(` Files scanned: ${chalk_1.default.cyan(stats.filesScanned)}`);
|
|
327
|
+
console.log(` Imports checked: ${chalk_1.default.cyan(stats.importsChecked)}`);
|
|
328
|
+
console.log(` Exports found: ${chalk_1.default.cyan(stats.exportsFound)}`);
|
|
329
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
330
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
331
|
+
const infos = issues.filter(i => i.severity === 'info');
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log(` ${chalk_1.default.red('🔴 Errors:')} ${errors.length}`);
|
|
334
|
+
console.log(` ${chalk_1.default.yellow('🟡 Warnings:')} ${warnings.length}`);
|
|
335
|
+
console.log(` ${chalk_1.default.blue('🔵 Info:')} ${infos.length}`);
|
|
336
|
+
console.log('');
|
|
337
|
+
if (issues.length === 0) {
|
|
338
|
+
console.log(chalk_1.default.green(' ✅ No coherence issues found! Your codebase wiring is solid.\n'));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Group issues by category
|
|
342
|
+
const categories = {
|
|
343
|
+
import: [],
|
|
344
|
+
export: [],
|
|
345
|
+
circular: [],
|
|
346
|
+
env: [],
|
|
347
|
+
'dead-code': [],
|
|
348
|
+
};
|
|
349
|
+
for (const issue of issues) {
|
|
350
|
+
if (categories[issue.category]) {
|
|
351
|
+
categories[issue.category].push(issue);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const categoryLabels = {
|
|
355
|
+
import: '🔴 Broken Imports',
|
|
356
|
+
export: '🔴 Missing Exports',
|
|
357
|
+
circular: '⚪ Circular Dependencies',
|
|
358
|
+
env: '🟡 Environment Variables',
|
|
359
|
+
'dead-code': '🔵 Dead Code',
|
|
360
|
+
};
|
|
361
|
+
for (const [category, catIssues] of Object.entries(categories)) {
|
|
362
|
+
if (catIssues.length === 0)
|
|
363
|
+
continue;
|
|
364
|
+
console.log(chalk_1.default.white(` ${categoryLabels[category]} (${catIssues.length})`));
|
|
365
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
366
|
+
const displayIssues = verbose ? catIssues : catIssues.slice(0, 5);
|
|
367
|
+
for (const issue of displayIssues) {
|
|
368
|
+
const severityColor = issue.severity === 'error' ? chalk_1.default.red : issue.severity === 'warning' ? chalk_1.default.yellow : chalk_1.default.blue;
|
|
369
|
+
console.log(` ${severityColor('•')} ${chalk_1.default.gray(issue.file)}${issue.line ? chalk_1.default.gray(`:${issue.line}`) : ''}`);
|
|
370
|
+
console.log(` ${issue.message}`);
|
|
371
|
+
if (issue.fix) {
|
|
372
|
+
console.log(chalk_1.default.gray(` Fix: ${issue.fix}`));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (!verbose && catIssues.length > 5) {
|
|
376
|
+
console.log(chalk_1.default.gray(` ... and ${catIssues.length - 5} more (use --verbose to see all)`));
|
|
377
|
+
}
|
|
378
|
+
console.log('');
|
|
379
|
+
}
|
|
380
|
+
// Save state
|
|
381
|
+
const statePath = (0, path_1.join)(cwd, '.codebakers', 'coherence-state.json');
|
|
382
|
+
try {
|
|
383
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(statePath), { recursive: true });
|
|
384
|
+
(0, fs_1.writeFileSync)(statePath, JSON.stringify({
|
|
385
|
+
lastAudit: new Date().toISOString(),
|
|
386
|
+
stats,
|
|
387
|
+
issues,
|
|
388
|
+
summary: { errors: errors.length, warnings: warnings.length, info: infos.length },
|
|
389
|
+
}, null, 2));
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Ignore write errors
|
|
393
|
+
}
|
|
394
|
+
console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('npx tsc --noEmit') + chalk_1.default.gray(' to verify TypeScript compiles'));
|
|
395
|
+
console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('codebakers heal') + chalk_1.default.gray(' to auto-fix what\'s possible\n'));
|
|
396
|
+
}
|
package/dist/commands/go.js
CHANGED
|
@@ -1356,21 +1356,31 @@ async function showVSCodeClaudeInstructions() {
|
|
|
1356
1356
|
console.log(chalk_1.default.gray(' Look for the ') + chalk_1.default.cyan('Claude icon') + chalk_1.default.gray(' in the left sidebar'));
|
|
1357
1357
|
console.log(chalk_1.default.gray(' Click it to open the Claude Code chat panel\n'));
|
|
1358
1358
|
console.log(chalk_1.default.gray(' Or press ') + chalk_1.default.cyan('Cmd+Shift+P') + chalk_1.default.gray(' → type ') + chalk_1.default.cyan('"Claude Code: Open Chat"') + chalk_1.default.gray('\n'));
|
|
1359
|
-
console.log(chalk_1.default.yellow(' STEP 3:
|
|
1360
|
-
console.log(chalk_1.default.gray('
|
|
1361
|
-
console.log(chalk_1.default.
|
|
1362
|
-
console.log(chalk_1.default.
|
|
1363
|
-
console.log(chalk_1.default.
|
|
1359
|
+
console.log(chalk_1.default.yellow(' STEP 3: Type the magic phrase!\n'));
|
|
1360
|
+
console.log(chalk_1.default.gray(' In the chat, type:\n'));
|
|
1361
|
+
console.log(chalk_1.default.cyan.bold(' codebakers go\n'));
|
|
1362
|
+
console.log(chalk_1.default.gray(' This magic phrase unlocks the full CodeBakers feature set:'));
|
|
1363
|
+
console.log(chalk_1.default.gray(' • Guided onboarding conversation'));
|
|
1364
|
+
console.log(chalk_1.default.gray(' • Production-ready code patterns'));
|
|
1365
|
+
console.log(chalk_1.default.gray(' • Automatic testing & validation\n'));
|
|
1364
1366
|
console.log(chalk_1.default.green(' ✅ You are now done with the terminal!\n'));
|
|
1365
|
-
console.log(chalk_1.default.gray('
|
|
1366
|
-
console.log(chalk_1.default.gray(' The
|
|
1367
|
+
console.log(chalk_1.default.gray(' Just type ') + chalk_1.default.cyan('codebakers go') + chalk_1.default.gray(' in chat to get started.'));
|
|
1368
|
+
console.log(chalk_1.default.gray(' The AI will guide you from there.\n'));
|
|
1367
1369
|
console.log(chalk_1.default.gray(' ─────────────────────────────────────────────────────────\n'));
|
|
1368
1370
|
console.log(chalk_1.default.gray(' Tip: ') + chalk_1.default.white('Make sure you have the Claude Code extension installed.'));
|
|
1369
1371
|
console.log(chalk_1.default.gray(' Get it from: ') + chalk_1.default.cyan('https://marketplace.visualstudio.com/items?itemName=anthropics.claude-code\n'));
|
|
1370
1372
|
console.log(chalk_1.default.gray(' Having issues? Run: ') + chalk_1.default.cyan('codebakers doctor') + chalk_1.default.gray(' to diagnose\n'));
|
|
1371
1373
|
}
|
|
1372
|
-
// v6.
|
|
1373
|
-
const V6_CLAUDE_MD = `# CodeBakers v6.
|
|
1374
|
+
// v6.18 Bootstrap - SHORT template with magic phrase + rules at START and END
|
|
1375
|
+
const V6_CLAUDE_MD = `# CodeBakers v6.18
|
|
1376
|
+
|
|
1377
|
+
## 🪄 MAGIC PHRASE: "codebakers go"
|
|
1378
|
+
When user says "codebakers go" in chat, start the onboarding conversation:
|
|
1379
|
+
1. Ask: "Is this an existing project or are you starting fresh?" (1=existing, 2=new)
|
|
1380
|
+
2. If existing: "Great! What would you like to work on?"
|
|
1381
|
+
3. If new: "What do you want to build? Describe your project."
|
|
1382
|
+
4. Then call \`init_project()\` to set up CodeBakers files
|
|
1383
|
+
5. Continue helping them build
|
|
1374
1384
|
|
|
1375
1385
|
## 🚨 ALWAYS DO THIS (Critical - Read First)
|
|
1376
1386
|
1. ALWAYS call \`project_status()\` FIRST → If works show "🍪 CodeBakers is working..." / If fails show "⚠️ Not connected"
|
|
@@ -1389,10 +1399,13 @@ const V6_CLAUDE_MD = `# CodeBakers v6.15
|
|
|
1389
1399
|
project_status() → Verify connection FIRST
|
|
1390
1400
|
discover_patterns({ task: "what you're building" }) → Get patterns BEFORE code
|
|
1391
1401
|
validate_complete({ feature: "name", files: [...] }) → Validate BEFORE done
|
|
1402
|
+
coherence_audit() → Check wiring & dependencies
|
|
1392
1403
|
\`\`\`
|
|
1393
1404
|
|
|
1405
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
1406
|
+
|
|
1394
1407
|
Header (after project_status succeeds): 🍪 CodeBakers is working on this...
|
|
1395
|
-
Header (if project_status fails): ⚠️ CodeBakers not connected
|
|
1408
|
+
Header (if project_status fails): ⚠️ CodeBakers not connected
|
|
1396
1409
|
Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
1397
1410
|
|
|
1398
1411
|
## 🚨 ALWAYS DO THIS (Critical - Repeated at End)
|
|
@@ -1401,7 +1414,10 @@ Footer (after code): 🍪 **CodeBakers** | Patterns: X | TSC: ✅ | Tests: ✅
|
|
|
1401
1414
|
3. Call \`validate_complete()\` before done
|
|
1402
1415
|
4. Show footer after code responses
|
|
1403
1416
|
`;
|
|
1404
|
-
const V6_CURSORRULES = `# CodeBakers v6.
|
|
1417
|
+
const V6_CURSORRULES = `# CodeBakers v6.18
|
|
1418
|
+
|
|
1419
|
+
## 🪄 "codebakers go" = Start onboarding conversation
|
|
1420
|
+
Ask existing/new → Ask what to build → Call init_project() → Help them build
|
|
1405
1421
|
|
|
1406
1422
|
## 🚨 ALWAYS (Read First)
|
|
1407
1423
|
1. Call project_status() FIRST → 🍪 Working... or ⚠️ Not connected
|
|
@@ -1415,6 +1431,9 @@ const V6_CURSORRULES = `# CodeBakers v6.15
|
|
|
1415
1431
|
3. Show header without project_status succeeding
|
|
1416
1432
|
4. Skip writing tests for new features
|
|
1417
1433
|
|
|
1434
|
+
Commands: /build, /feature, /design, /status, /audit, /coherence, /upgrade
|
|
1435
|
+
Use coherence_audit() to check wiring & dependencies
|
|
1436
|
+
|
|
1418
1437
|
## 🚨 ALWAYS (Repeated at End)
|
|
1419
1438
|
1. project_status() FIRST
|
|
1420
1439
|
2. discover_patterns() before code
|
|
@@ -1422,7 +1441,7 @@ const V6_CURSORRULES = `# CodeBakers v6.15
|
|
|
1422
1441
|
`;
|
|
1423
1442
|
/**
|
|
1424
1443
|
* Complete project setup - handles everything:
|
|
1425
|
-
* -
|
|
1444
|
+
* - Ask user if existing or new project
|
|
1426
1445
|
* - Set up all tracking files
|
|
1427
1446
|
* - Configure Cursor and Claude Code MCP
|
|
1428
1447
|
* - Run guided questions for new projects
|
|
@@ -1430,10 +1449,33 @@ const V6_CURSORRULES = `# CodeBakers v6.15
|
|
|
1430
1449
|
*/
|
|
1431
1450
|
async function setupProject(options = {}, auth) {
|
|
1432
1451
|
const cwd = process.cwd();
|
|
1433
|
-
//
|
|
1452
|
+
// Auto-detect non-interactive mode (e.g., running from Claude Code)
|
|
1453
|
+
const isNonInteractive = !process.stdin.isTTY;
|
|
1454
|
+
// Detect if this looks like an existing project
|
|
1434
1455
|
const projectInfo = detectExistingProject(cwd);
|
|
1435
|
-
|
|
1436
|
-
|
|
1456
|
+
let isExistingProject;
|
|
1457
|
+
if (isNonInteractive) {
|
|
1458
|
+
// Non-interactive: use auto-detection
|
|
1459
|
+
isExistingProject = projectInfo.exists;
|
|
1460
|
+
}
|
|
1461
|
+
else {
|
|
1462
|
+
// Interactive: ASK the user
|
|
1463
|
+
console.log(chalk_1.default.cyan('\n ━━━ CodeBakers Setup ━━━\n'));
|
|
1464
|
+
if (projectInfo.exists) {
|
|
1465
|
+
// We detected existing code, but still ask
|
|
1466
|
+
console.log(chalk_1.default.gray(` Detected: ${projectInfo.files} files, ${projectInfo.stack.framework || 'unknown framework'}\n`));
|
|
1467
|
+
}
|
|
1468
|
+
console.log(chalk_1.default.white(' Is this an existing project or are you starting fresh?\n'));
|
|
1469
|
+
console.log(chalk_1.default.gray(' 1. ') + chalk_1.default.cyan('EXISTING PROJECT') + chalk_1.default.gray(' - I already have code here'));
|
|
1470
|
+
console.log(chalk_1.default.gray(' 2. ') + chalk_1.default.cyan('NEW PROJECT') + chalk_1.default.gray(' - Starting from scratch\n'));
|
|
1471
|
+
let projectChoice = '';
|
|
1472
|
+
while (!['1', '2'].includes(projectChoice)) {
|
|
1473
|
+
projectChoice = await prompt(' Enter 1 or 2: ');
|
|
1474
|
+
}
|
|
1475
|
+
isExistingProject = projectChoice === '1';
|
|
1476
|
+
}
|
|
1477
|
+
if (isExistingProject) {
|
|
1478
|
+
// Existing project
|
|
1437
1479
|
await setupExistingProject(cwd, projectInfo, options, auth);
|
|
1438
1480
|
}
|
|
1439
1481
|
else {
|
|
@@ -1442,14 +1484,19 @@ async function setupProject(options = {}, auth) {
|
|
|
1442
1484
|
}
|
|
1443
1485
|
}
|
|
1444
1486
|
async function setupNewProject(cwd, options = {}, auth) {
|
|
1445
|
-
console.log(chalk_1.default.cyan('\n ━━━ New Project Setup ━━━\n'));
|
|
1446
1487
|
// Auto-detect non-interactive mode (e.g., running from Claude Code)
|
|
1447
1488
|
const isNonInteractive = !process.stdin.isTTY;
|
|
1448
|
-
if (isNonInteractive
|
|
1449
|
-
console.log(chalk_1.default.
|
|
1450
|
-
options.type
|
|
1451
|
-
|
|
1452
|
-
|
|
1489
|
+
if (isNonInteractive) {
|
|
1490
|
+
console.log(chalk_1.default.cyan('\n ━━━ New Project Setup ━━━\n'));
|
|
1491
|
+
if (!options.type) {
|
|
1492
|
+
console.log(chalk_1.default.yellow(' ⚡ Non-interactive mode detected - using defaults\n'));
|
|
1493
|
+
options.type = 'personal';
|
|
1494
|
+
options.describe = options.describe || 'chat';
|
|
1495
|
+
options.skipReview = true;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
console.log(chalk_1.default.green('\n ✓ Setting up as NEW PROJECT\n'));
|
|
1453
1500
|
}
|
|
1454
1501
|
let projectType;
|
|
1455
1502
|
let projectName;
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const generate_js_1 = require("./commands/generate.js");
|
|
|
22
22
|
const upgrade_js_1 = require("./commands/upgrade.js");
|
|
23
23
|
const config_js_1 = require("./commands/config.js");
|
|
24
24
|
const audit_js_1 = require("./commands/audit.js");
|
|
25
|
+
const coherence_js_1 = require("./commands/coherence.js");
|
|
25
26
|
const heal_js_1 = require("./commands/heal.js");
|
|
26
27
|
const push_patterns_js_1 = require("./commands/push-patterns.js");
|
|
27
28
|
const go_js_1 = require("./commands/go.js");
|
|
@@ -198,11 +199,12 @@ function showWelcome() {
|
|
|
198
199
|
console.log(chalk_1.default.gray(' Generate a React component with TypeScript\n'));
|
|
199
200
|
console.log(chalk_1.default.white(' Quality:\n'));
|
|
200
201
|
console.log(chalk_1.default.cyan(' codebakers audit') + chalk_1.default.gray(' Run automated code quality checks'));
|
|
202
|
+
console.log(chalk_1.default.cyan(' codebakers coherence') + chalk_1.default.gray(' Check wiring, imports, and dependencies'));
|
|
201
203
|
console.log(chalk_1.default.cyan(' codebakers heal') + chalk_1.default.gray(' Auto-detect and fix common issues'));
|
|
202
204
|
console.log(chalk_1.default.cyan(' codebakers doctor') + chalk_1.default.gray(' Check CodeBakers setup\n'));
|
|
203
205
|
console.log(chalk_1.default.white(' All Commands:\n'));
|
|
204
206
|
console.log(chalk_1.default.gray(' go, extend, billing, build, build-status, setup, scaffold, init'));
|
|
205
|
-
console.log(chalk_1.default.gray(' generate, upgrade, status, audit, heal, doctor, config, login'));
|
|
207
|
+
console.log(chalk_1.default.gray(' generate, upgrade, status, audit, coherence, heal, doctor, config, login'));
|
|
206
208
|
console.log(chalk_1.default.gray(' serve, mcp-config, mcp-uninstall\n'));
|
|
207
209
|
console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('codebakers <command> --help') + chalk_1.default.gray(' for more info\n'));
|
|
208
210
|
}
|
|
@@ -316,6 +318,20 @@ program
|
|
|
316
318
|
.command('audit')
|
|
317
319
|
.description('Run automated code quality and security checks')
|
|
318
320
|
.action(async () => { await (0, audit_js_1.audit)(); });
|
|
321
|
+
program
|
|
322
|
+
.command('coherence')
|
|
323
|
+
.alias('wiring')
|
|
324
|
+
.description('Check codebase wiring, imports, exports, and dependencies')
|
|
325
|
+
.option('-f, --focus <area>', 'Focus area: all, imports, circular, env (default: all)')
|
|
326
|
+
.option('--fix', 'Automatically fix issues that can be auto-fixed')
|
|
327
|
+
.option('-v, --verbose', 'Show all issues (not just first 5 per category)')
|
|
328
|
+
.action(async (options) => {
|
|
329
|
+
await (0, coherence_js_1.coherence)({
|
|
330
|
+
focus: options.focus,
|
|
331
|
+
fix: options.fix,
|
|
332
|
+
verbose: options.verbose,
|
|
333
|
+
});
|
|
334
|
+
});
|
|
319
335
|
program
|
|
320
336
|
.command('heal')
|
|
321
337
|
.description('Auto-detect and fix common issues (TypeScript, deps, security)')
|