@codebakers/cli 1.1.1 → 1.1.3
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/index.js +504 -31
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
11
|
+
import chalk4 from "chalk";
|
|
12
12
|
|
|
13
13
|
// src/commands/setup.ts
|
|
14
14
|
import inquirer from "inquirer";
|
|
@@ -93,6 +93,19 @@ async function fetchPatterns(patterns) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// src/commands/setup.ts
|
|
96
|
+
var CODEBAKERS_MARKER = "# CODEBAKERS SMART ROUTER";
|
|
97
|
+
var USER_CONTENT_SEPARATOR = `
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# USER CUSTOM INSTRUCTIONS
|
|
102
|
+
|
|
103
|
+
The following instructions were preserved from your original CLAUDE.md file.
|
|
104
|
+
CodeBakers patterns will be applied first, then these instructions.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
`;
|
|
96
109
|
var IDE_CONFIGS = {
|
|
97
110
|
cursor: {
|
|
98
111
|
file: ".cursor/mcp.json",
|
|
@@ -180,31 +193,27 @@ Validation failed: ${validation.error || "Unknown error"}`));
|
|
|
180
193
|
if (ide) {
|
|
181
194
|
await configureIDE(ide, options.force);
|
|
182
195
|
}
|
|
196
|
+
await installClaudeMd(apiKey, options.force);
|
|
183
197
|
const firstName = userName?.split(" ")[0];
|
|
184
198
|
const greeting = firstName ? `${firstName}, you're` : "You're";
|
|
185
199
|
console.log(chalk.bold.green("\n\u{1F389} Setup complete!\n"));
|
|
186
|
-
|
|
187
|
-
console.log(chalk.green(`\u2713 ${greeting} ready to build!
|
|
200
|
+
console.log(chalk.green(`\u2713 ${greeting} ready to build!
|
|
188
201
|
`));
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
console.log(chalk.dim(" \u2502 ") + chalk.green("\u2713") + chalk.dim(" Accessible, responsive design \u2502"));
|
|
205
|
-
console.log(chalk.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"));
|
|
206
|
-
console.log(chalk.dim("Start building: just describe what you need.\n"));
|
|
207
|
-
}
|
|
202
|
+
console.log(chalk.bold("4 Commands to Know:\n"));
|
|
203
|
+
console.log(chalk.cyan(" /build [idea]"));
|
|
204
|
+
console.log(chalk.dim(" Start a new project. AI asks questions, plans everything,"));
|
|
205
|
+
console.log(chalk.dim(" then builds phase by phase with tests.\n"));
|
|
206
|
+
console.log(chalk.cyan(" /feature [idea]"));
|
|
207
|
+
console.log(chalk.dim(" Add to an existing project. AI analyzes your codebase"));
|
|
208
|
+
console.log(chalk.dim(" and integrates the new feature properly.\n"));
|
|
209
|
+
console.log(chalk.cyan(" /status"));
|
|
210
|
+
console.log(chalk.dim(" See project progress, what's built, what's next.\n"));
|
|
211
|
+
console.log(chalk.cyan(" /audit"));
|
|
212
|
+
console.log(chalk.dim(" Review code quality, security, and get a score.\n"));
|
|
213
|
+
console.log(chalk.bold("Example:"));
|
|
214
|
+
console.log(chalk.white(" /build a project management tool for remote teams\n"));
|
|
215
|
+
console.log(chalk.dim("The AI will ask discovery questions, create a PRD, and"));
|
|
216
|
+
console.log(chalk.dim("build your app with production patterns + tests.\n"));
|
|
208
217
|
});
|
|
209
218
|
}
|
|
210
219
|
async function configureIDE(ide, force = false) {
|
|
@@ -276,6 +285,78 @@ async function configureMCP(ide, force, spinner) {
|
|
|
276
285
|
console.log(chalk.yellow("Restart your IDE to activate the MCP server.\n"));
|
|
277
286
|
}
|
|
278
287
|
}
|
|
288
|
+
async function fetchClaudeMdFromApi(apiKey) {
|
|
289
|
+
try {
|
|
290
|
+
const apiUrl = getApiUrl();
|
|
291
|
+
const response = await fetch(`${apiUrl}/api/cli/claude-md`, {
|
|
292
|
+
headers: {
|
|
293
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
294
|
+
"Content-Type": "application/json"
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const data = await response.json();
|
|
301
|
+
return data.content || null;
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function installClaudeMd(apiKey, force) {
|
|
307
|
+
const spinner = ora("Setting up CLAUDE.md...").start();
|
|
308
|
+
const claudeMdPath = join(process.cwd(), "CLAUDE.md");
|
|
309
|
+
try {
|
|
310
|
+
spinner.text = "Fetching CLAUDE.md from CodeBakers...";
|
|
311
|
+
const codebakersContent = await fetchClaudeMdFromApi(apiKey);
|
|
312
|
+
if (!codebakersContent) {
|
|
313
|
+
spinner.warn("Could not fetch CLAUDE.md from API. Skipping...");
|
|
314
|
+
console.log(chalk.dim(" You can manually add CLAUDE.md later.\n"));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (existsSync(claudeMdPath)) {
|
|
318
|
+
const existingContent = await readFile(claudeMdPath, "utf-8");
|
|
319
|
+
const isCodeBakersFile = existingContent.trim().startsWith(CODEBAKERS_MARKER);
|
|
320
|
+
if (isCodeBakersFile) {
|
|
321
|
+
await writeFile(claudeMdPath, codebakersContent, "utf-8");
|
|
322
|
+
spinner.succeed("Updated CLAUDE.md to latest version");
|
|
323
|
+
} else {
|
|
324
|
+
if (!force) {
|
|
325
|
+
spinner.stop();
|
|
326
|
+
console.log(chalk.cyan("\n \u2139\uFE0F Your custom instructions will be preserved at the end of the file."));
|
|
327
|
+
console.log(chalk.dim(" CodeBakers patterns load first, then your rules apply on top."));
|
|
328
|
+
console.log(chalk.dim(" Your original content remains intact and can still override patterns.\n"));
|
|
329
|
+
const { shouldMerge } = await inquirer.prompt([
|
|
330
|
+
{
|
|
331
|
+
type: "confirm",
|
|
332
|
+
name: "shouldMerge",
|
|
333
|
+
message: "Merge with CodeBakers?",
|
|
334
|
+
default: true
|
|
335
|
+
}
|
|
336
|
+
]);
|
|
337
|
+
if (!shouldMerge) {
|
|
338
|
+
console.log(chalk.yellow(" Skipping CLAUDE.md installation.\n"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
spinner.start("Merging CLAUDE.md...");
|
|
342
|
+
}
|
|
343
|
+
const mergedContent = codebakersContent + USER_CONTENT_SEPARATOR + existingContent;
|
|
344
|
+
await writeFile(claudeMdPath, mergedContent, "utf-8");
|
|
345
|
+
spinner.succeed("Merged CLAUDE.md (your custom instructions preserved at the end)");
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
await writeFile(claudeMdPath, codebakersContent, "utf-8");
|
|
349
|
+
spinner.succeed("Created CLAUDE.md");
|
|
350
|
+
}
|
|
351
|
+
console.log(chalk.green("\u2713 CLAUDE.md installed"));
|
|
352
|
+
console.log(chalk.dim(" This file tells your AI to use CodeBakers patterns.\n"));
|
|
353
|
+
} catch (error) {
|
|
354
|
+
spinner.fail("Failed to install CLAUDE.md");
|
|
355
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
356
|
+
console.log(chalk.dim(` Error: ${message}
|
|
357
|
+
`));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
279
360
|
|
|
280
361
|
// src/commands/serve.ts
|
|
281
362
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -476,34 +557,426 @@ ${text}`).join("\n\n---\n\n");
|
|
|
476
557
|
});
|
|
477
558
|
}
|
|
478
559
|
|
|
560
|
+
// src/commands/validate.ts
|
|
561
|
+
import chalk2 from "chalk";
|
|
562
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
563
|
+
import { join as join2 } from "path";
|
|
564
|
+
function validateStateFile(projectRoot) {
|
|
565
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
566
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
567
|
+
if (!existsSync2(statePath)) {
|
|
568
|
+
result.warnings.push("No .codebakers.json found");
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
const content = readFileSync(statePath, "utf-8");
|
|
573
|
+
JSON.parse(content);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
result.valid = false;
|
|
576
|
+
result.errors.push(`Invalid JSON in .codebakers.json: ${error}`);
|
|
577
|
+
}
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
function validateContextSummaries(projectRoot) {
|
|
581
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
582
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
583
|
+
if (!existsSync2(statePath)) return result;
|
|
584
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
585
|
+
if (!state.build?.fileToTaskMap) return result;
|
|
586
|
+
const trackedFiles = Object.keys(state.build.fileToTaskMap);
|
|
587
|
+
const contextFiles = state.context ? Object.keys(state.context) : [];
|
|
588
|
+
const missingContext = [];
|
|
589
|
+
for (const file of trackedFiles) {
|
|
590
|
+
if (file.endsWith(".json") || file.endsWith(".config.ts") || file.endsWith(".config.js")) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (file.startsWith("src/") && !contextFiles.includes(file)) {
|
|
594
|
+
missingContext.push(file);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (missingContext.length > 0) {
|
|
598
|
+
result.warnings.push(`Missing context summaries: ${missingContext.length} files`);
|
|
599
|
+
}
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
function validateContracts(projectRoot) {
|
|
603
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
604
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
605
|
+
if (!existsSync2(statePath)) return result;
|
|
606
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
607
|
+
if (!state.contracts) return result;
|
|
608
|
+
for (const [featureName, contract] of Object.entries(state.contracts)) {
|
|
609
|
+
if (!contract.expects) continue;
|
|
610
|
+
for (const depName of Object.keys(contract.expects)) {
|
|
611
|
+
if (depName === "environment" || depName === "database") continue;
|
|
612
|
+
if (typeof contract.expects[depName] === "object" && !state.contracts[depName]) {
|
|
613
|
+
result.warnings.push(`'${featureName}' expects '${depName}' but no contract found`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
function validateBuildState(projectRoot) {
|
|
620
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
621
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
622
|
+
if (!existsSync2(statePath)) return result;
|
|
623
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
624
|
+
if (!state.build) return result;
|
|
625
|
+
let inProgressCount = 0;
|
|
626
|
+
for (const phase of state.build.phases || []) {
|
|
627
|
+
for (const task of phase.tasks || []) {
|
|
628
|
+
if (task.status === "in_progress") {
|
|
629
|
+
inProgressCount++;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (inProgressCount > 1) {
|
|
634
|
+
result.warnings.push(`${inProgressCount} tasks marked in_progress (should be 1)`);
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
function validateFileExistence(projectRoot) {
|
|
639
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
640
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
641
|
+
if (!existsSync2(statePath)) return result;
|
|
642
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
643
|
+
if (!state.build?.fileToTaskMap) return result;
|
|
644
|
+
const missingFiles = [];
|
|
645
|
+
for (const file of Object.keys(state.build.fileToTaskMap)) {
|
|
646
|
+
const filePath = join2(projectRoot, file);
|
|
647
|
+
if (!existsSync2(filePath)) {
|
|
648
|
+
missingFiles.push(file);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (missingFiles.length > 0) {
|
|
652
|
+
result.errors.push(`${missingFiles.length} tracked files don't exist`);
|
|
653
|
+
result.valid = false;
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
function showBuildStatus(projectRoot) {
|
|
658
|
+
const statePath = join2(projectRoot, ".codebakers.json");
|
|
659
|
+
if (!existsSync2(statePath)) {
|
|
660
|
+
console.log(chalk2.dim("\nNo active build found."));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
664
|
+
if (!state.build) {
|
|
665
|
+
console.log(chalk2.dim("\nNo active build found."));
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
console.log(chalk2.bold(`
|
|
669
|
+
\u{1F4CA} Build: ${state.build.projectName || "Unnamed Project"}`));
|
|
670
|
+
console.log("");
|
|
671
|
+
for (const phase of state.build.phases || []) {
|
|
672
|
+
const tasks = phase.tasks || [];
|
|
673
|
+
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
674
|
+
const total = tasks.length;
|
|
675
|
+
const pct = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
676
|
+
const bar = "\u2588".repeat(Math.round(pct / 10)) + "\u2591".repeat(10 - Math.round(pct / 10));
|
|
677
|
+
let status = "";
|
|
678
|
+
if (phase.status === "complete") {
|
|
679
|
+
status = chalk2.green("\u2705");
|
|
680
|
+
} else if (phase.status === "in_progress") {
|
|
681
|
+
status = chalk2.yellow("\u{1F504}");
|
|
682
|
+
} else {
|
|
683
|
+
status = chalk2.dim("\u23F3");
|
|
684
|
+
}
|
|
685
|
+
console.log(`${status} Phase ${phase.id}: ${phase.name} [${bar}] ${pct}%`);
|
|
686
|
+
}
|
|
687
|
+
if (state.build.stats) {
|
|
688
|
+
console.log("");
|
|
689
|
+
console.log(chalk2.dim(`Tasks: ${state.build.stats.completedTasks}/${state.build.stats.totalTasks} completed`));
|
|
690
|
+
}
|
|
691
|
+
if (state.build.currentTaskId) {
|
|
692
|
+
for (const phase of state.build.phases || []) {
|
|
693
|
+
const task = phase.tasks?.find((t) => t.id === state.build.currentTaskId);
|
|
694
|
+
if (task) {
|
|
695
|
+
console.log("");
|
|
696
|
+
console.log(chalk2.bold("Current Task:"));
|
|
697
|
+
console.log(` ${task.id}: ${task.title}`);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (state.context) {
|
|
703
|
+
const contextCount = Object.keys(state.context).length;
|
|
704
|
+
console.log("");
|
|
705
|
+
console.log(chalk2.dim(`Context summaries: ${contextCount} files`));
|
|
706
|
+
}
|
|
707
|
+
if (state.contracts) {
|
|
708
|
+
const contractCount = Object.keys(state.contracts).length;
|
|
709
|
+
console.log(chalk2.dim(`Feature contracts: ${contractCount} defined`));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function validateCommand(program2) {
|
|
713
|
+
const cmd = program2.command("validate").description("Validate architectural coherence of the project").option("-v, --verbose", "Show detailed output").option("--status", "Show build status summary").action(async (options) => {
|
|
714
|
+
const projectRoot = process.cwd();
|
|
715
|
+
console.log(chalk2.bold("\n\u{1F50D} CodeBakers Coherence Validator\n"));
|
|
716
|
+
if (options.status) {
|
|
717
|
+
showBuildStatus(projectRoot);
|
|
718
|
+
console.log("");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const validators = [
|
|
722
|
+
{ name: "State File", fn: validateStateFile },
|
|
723
|
+
{ name: "Context Summaries", fn: validateContextSummaries },
|
|
724
|
+
{ name: "Contracts", fn: validateContracts },
|
|
725
|
+
{ name: "Build State", fn: validateBuildState },
|
|
726
|
+
{ name: "File Existence", fn: validateFileExistence }
|
|
727
|
+
];
|
|
728
|
+
let hasErrors = false;
|
|
729
|
+
let hasWarnings = false;
|
|
730
|
+
for (const { name, fn } of validators) {
|
|
731
|
+
const result = fn(projectRoot);
|
|
732
|
+
if (result.errors.length > 0) {
|
|
733
|
+
hasErrors = true;
|
|
734
|
+
console.log(chalk2.red(`\u274C ${name}:`));
|
|
735
|
+
for (const error of result.errors) {
|
|
736
|
+
console.log(chalk2.red(` ${error}`));
|
|
737
|
+
}
|
|
738
|
+
} else if (result.warnings.length > 0) {
|
|
739
|
+
hasWarnings = true;
|
|
740
|
+
console.log(chalk2.yellow(`\u26A0\uFE0F ${name}:`));
|
|
741
|
+
for (const warning of result.warnings) {
|
|
742
|
+
console.log(chalk2.yellow(` ${warning}`));
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
console.log(chalk2.green(`\u2705 ${name}`));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
console.log("");
|
|
749
|
+
if (hasErrors) {
|
|
750
|
+
console.log(chalk2.red("Validation FAILED"));
|
|
751
|
+
process.exit(1);
|
|
752
|
+
} else if (hasWarnings) {
|
|
753
|
+
console.log(chalk2.yellow("Validation passed with warnings"));
|
|
754
|
+
} else {
|
|
755
|
+
console.log(chalk2.green("All validations passed"));
|
|
756
|
+
}
|
|
757
|
+
showBuildStatus(projectRoot);
|
|
758
|
+
console.log("");
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/commands/summarize.ts
|
|
763
|
+
import chalk3 from "chalk";
|
|
764
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
765
|
+
import { join as join3, relative, extname } from "path";
|
|
766
|
+
function detectFileType(filePath) {
|
|
767
|
+
const ext = extname(filePath);
|
|
768
|
+
const name = filePath.toLowerCase();
|
|
769
|
+
if (name.includes("schema") || name.includes("/db/")) return "schema";
|
|
770
|
+
if (name.includes("/api/") || name.includes("route.ts")) return "api";
|
|
771
|
+
if (name.includes("middleware")) return "middleware";
|
|
772
|
+
if (name.includes("/components/")) return "component";
|
|
773
|
+
if (name.includes("/hooks/") || name.startsWith("use")) return "hook";
|
|
774
|
+
if (name.includes("/lib/") || name.includes("/utils/")) return "utility";
|
|
775
|
+
if (name.includes(".test.") || name.includes(".spec.")) return "test";
|
|
776
|
+
return "unknown";
|
|
777
|
+
}
|
|
778
|
+
function generateSummaryTemplate(filePath, content) {
|
|
779
|
+
const fileType = detectFileType(filePath);
|
|
780
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
781
|
+
const exportMatches = content.match(/export\s+(const|function|class|type|interface|enum)\s+(\w+)/g) || [];
|
|
782
|
+
const exports = exportMatches.map((m) => {
|
|
783
|
+
const match = m.match(/export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/);
|
|
784
|
+
return match ? match[1] : "";
|
|
785
|
+
}).filter(Boolean);
|
|
786
|
+
const base = {
|
|
787
|
+
purpose: `[TODO: Describe what this ${fileType} does]`,
|
|
788
|
+
exports: exports.length > 0 ? exports : void 0,
|
|
789
|
+
createdAt: now
|
|
790
|
+
};
|
|
791
|
+
switch (fileType) {
|
|
792
|
+
case "schema":
|
|
793
|
+
return {
|
|
794
|
+
...base,
|
|
795
|
+
purpose: "[TODO: Describe this database schema]",
|
|
796
|
+
schema: {},
|
|
797
|
+
relations: []
|
|
798
|
+
};
|
|
799
|
+
case "api":
|
|
800
|
+
return {
|
|
801
|
+
...base,
|
|
802
|
+
purpose: "[TODO: Describe this API endpoint]",
|
|
803
|
+
method: content.includes("POST") ? "POST" : content.includes("PUT") ? "PUT" : content.includes("DELETE") ? "DELETE" : "GET",
|
|
804
|
+
input: {},
|
|
805
|
+
output: {},
|
|
806
|
+
sideEffects: []
|
|
807
|
+
};
|
|
808
|
+
case "middleware":
|
|
809
|
+
return {
|
|
810
|
+
...base,
|
|
811
|
+
purpose: "[TODO: Describe this middleware]",
|
|
812
|
+
behavior: {},
|
|
813
|
+
protectedRoutes: [],
|
|
814
|
+
publicRoutes: []
|
|
815
|
+
};
|
|
816
|
+
case "component":
|
|
817
|
+
return {
|
|
818
|
+
...base,
|
|
819
|
+
purpose: "[TODO: Describe this component]",
|
|
820
|
+
props: [],
|
|
821
|
+
state: [],
|
|
822
|
+
events: []
|
|
823
|
+
};
|
|
824
|
+
case "hook":
|
|
825
|
+
return {
|
|
826
|
+
...base,
|
|
827
|
+
purpose: "[TODO: Describe this hook]",
|
|
828
|
+
params: [],
|
|
829
|
+
returns: "[TODO]",
|
|
830
|
+
sideEffects: []
|
|
831
|
+
};
|
|
832
|
+
case "utility":
|
|
833
|
+
return {
|
|
834
|
+
...base,
|
|
835
|
+
purpose: "[TODO: Describe this utility]",
|
|
836
|
+
dependencies: [],
|
|
837
|
+
patterns: {}
|
|
838
|
+
};
|
|
839
|
+
default:
|
|
840
|
+
return base;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function ensureStateFile(projectRoot) {
|
|
844
|
+
const statePath = join3(projectRoot, ".codebakers.json");
|
|
845
|
+
if (existsSync3(statePath)) {
|
|
846
|
+
return JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
847
|
+
}
|
|
848
|
+
const initial = {
|
|
849
|
+
version: "2.0",
|
|
850
|
+
context: {}
|
|
851
|
+
};
|
|
852
|
+
writeFileSync(statePath, JSON.stringify(initial, null, 2));
|
|
853
|
+
return initial;
|
|
854
|
+
}
|
|
855
|
+
function summarizeCommand(program2) {
|
|
856
|
+
program2.command("summarize [file]").description("Generate or update context summary for a file").option("-a, --all", "Generate summaries for all tracked files").option("--show", "Show existing summary without modifying").action(async (file, options) => {
|
|
857
|
+
const projectRoot = process.cwd();
|
|
858
|
+
const statePath = join3(projectRoot, ".codebakers.json");
|
|
859
|
+
console.log(chalk3.bold("\n\u{1F4DD} CodeBakers Context Summarizer\n"));
|
|
860
|
+
const state = ensureStateFile(projectRoot);
|
|
861
|
+
if (!state.context) {
|
|
862
|
+
state.context = {};
|
|
863
|
+
}
|
|
864
|
+
if (options.all) {
|
|
865
|
+
if (!state.build?.fileToTaskMap) {
|
|
866
|
+
console.log(chalk3.yellow("No tracked files found in build."));
|
|
867
|
+
console.log(chalk3.dim("Run /build command first to start tracking files."));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const files = Object.keys(state.build.fileToTaskMap);
|
|
871
|
+
let generated = 0;
|
|
872
|
+
let skipped = 0;
|
|
873
|
+
for (const f of files) {
|
|
874
|
+
if (f.endsWith(".json") || f.endsWith(".config.ts")) {
|
|
875
|
+
skipped++;
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
const filePath = join3(projectRoot, f);
|
|
879
|
+
if (!existsSync3(filePath)) {
|
|
880
|
+
console.log(chalk3.yellow(`\u26A0\uFE0F ${f} - file not found`));
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (state.context[f]) {
|
|
884
|
+
console.log(chalk3.dim(` ${f} - already has summary`));
|
|
885
|
+
skipped++;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
889
|
+
const summary = generateSummaryTemplate(f, content);
|
|
890
|
+
state.context[f] = summary;
|
|
891
|
+
generated++;
|
|
892
|
+
console.log(chalk3.green(`\u2705 ${f}`));
|
|
893
|
+
}
|
|
894
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
895
|
+
console.log("");
|
|
896
|
+
console.log(chalk3.bold(`Generated: ${generated} | Skipped: ${skipped}`));
|
|
897
|
+
console.log(chalk3.dim("\nEdit .codebakers.json to fill in [TODO] placeholders."));
|
|
898
|
+
} else if (file) {
|
|
899
|
+
const relativePath = relative(projectRoot, join3(projectRoot, file));
|
|
900
|
+
const filePath = join3(projectRoot, file);
|
|
901
|
+
if (!existsSync3(filePath)) {
|
|
902
|
+
console.log(chalk3.red(`File not found: ${file}`));
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
if (options.show && state.context[relativePath]) {
|
|
906
|
+
console.log(chalk3.bold(`Summary for ${relativePath}:
|
|
907
|
+
`));
|
|
908
|
+
console.log(JSON.stringify(state.context[relativePath], null, 2));
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
912
|
+
const summary = generateSummaryTemplate(relativePath, content);
|
|
913
|
+
if (state.context[relativePath]) {
|
|
914
|
+
console.log(chalk3.yellow(`Updating existing summary for ${relativePath}`));
|
|
915
|
+
state.context[relativePath] = {
|
|
916
|
+
...summary,
|
|
917
|
+
...state.context[relativePath],
|
|
918
|
+
exports: summary.exports
|
|
919
|
+
// Always update exports
|
|
920
|
+
};
|
|
921
|
+
} else {
|
|
922
|
+
state.context[relativePath] = summary;
|
|
923
|
+
console.log(chalk3.green(`Created summary for ${relativePath}`));
|
|
924
|
+
}
|
|
925
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
926
|
+
console.log("");
|
|
927
|
+
console.log(chalk3.bold("Summary:"));
|
|
928
|
+
console.log(JSON.stringify(state.context[relativePath], null, 2));
|
|
929
|
+
console.log("");
|
|
930
|
+
console.log(chalk3.dim("Edit .codebakers.json to fill in [TODO] placeholders."));
|
|
931
|
+
} else {
|
|
932
|
+
console.log("Usage:");
|
|
933
|
+
console.log(" codebakers summarize <file> Generate summary for a specific file");
|
|
934
|
+
console.log(" codebakers summarize --all Generate summaries for all tracked files");
|
|
935
|
+
console.log(" codebakers summarize <file> --show Show existing summary");
|
|
936
|
+
console.log("");
|
|
937
|
+
const contextCount = Object.keys(state.context).length;
|
|
938
|
+
const todoCount = Object.values(state.context).filter(
|
|
939
|
+
(c) => JSON.stringify(c).includes("[TODO]")
|
|
940
|
+
).length;
|
|
941
|
+
console.log(chalk3.bold("Current Status:"));
|
|
942
|
+
console.log(` Summaries: ${contextCount}`);
|
|
943
|
+
if (todoCount > 0) {
|
|
944
|
+
console.log(chalk3.yellow(` Need attention: ${todoCount} with [TODO] placeholders`));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
479
950
|
// src/index.ts
|
|
480
951
|
var program = new Command();
|
|
481
952
|
program.name("codebakers").description("CodeBakers CLI - AI prompt patterns for production-ready code").version("1.0.0");
|
|
482
953
|
setupCommand(program);
|
|
483
954
|
serveCommand(program);
|
|
955
|
+
validateCommand(program);
|
|
956
|
+
summarizeCommand(program);
|
|
484
957
|
program.command("status").description("Check CodeBakers configuration status").action(() => {
|
|
485
|
-
console.log(
|
|
958
|
+
console.log(chalk4.bold("\n\u{1F36A} CodeBakers Status\n"));
|
|
486
959
|
const apiKey = getApiKey();
|
|
487
960
|
if (apiKey) {
|
|
488
961
|
const maskedKey = apiKey.substring(0, 10) + "..." + apiKey.slice(-4);
|
|
489
|
-
console.log(
|
|
490
|
-
console.log(
|
|
962
|
+
console.log(chalk4.green("\u2713 API key configured"));
|
|
963
|
+
console.log(chalk4.dim(` Key: ${maskedKey}`));
|
|
491
964
|
} else {
|
|
492
|
-
console.log(
|
|
493
|
-
console.log(
|
|
965
|
+
console.log(chalk4.red("\u2717 No API key configured"));
|
|
966
|
+
console.log(chalk4.dim(" Run: codebakers setup"));
|
|
494
967
|
}
|
|
495
968
|
const apiUrl = getApiUrl();
|
|
496
|
-
console.log(
|
|
969
|
+
console.log(chalk4.dim(`
|
|
497
970
|
API URL: ${apiUrl}`));
|
|
498
971
|
if (apiUrl !== "https://codebakers.ai") {
|
|
499
|
-
console.log(
|
|
972
|
+
console.log(chalk4.yellow("\u26A0 Warning: API URL is not the default. Run `codebakers logout` to reset."));
|
|
500
973
|
}
|
|
501
|
-
console.log(
|
|
974
|
+
console.log(chalk4.dim(`Config path: ${getConfigPath()}
|
|
502
975
|
`));
|
|
503
976
|
});
|
|
504
977
|
program.command("logout").description("Remove stored API key").action(async () => {
|
|
505
978
|
const { clearConfig } = await import("./config-R2H6JKGW.js");
|
|
506
979
|
clearConfig();
|
|
507
|
-
console.log(
|
|
980
|
+
console.log(chalk4.green("\n\u2713 API key removed\n"));
|
|
508
981
|
});
|
|
509
982
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codebakers/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "CodeBakers CLI - AI prompt patterns for production-ready code",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
12
12
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
-
"test": "vitest run"
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
17
|
+
"test:unit": "vitest run tests/unit",
|
|
18
|
+
"test:integration": "vitest run tests/integration"
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
21
|
"dist"
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
"devDependencies": {
|
|
47
51
|
"@types/inquirer": "^9.0.7",
|
|
48
52
|
"@types/node": "^20.11.0",
|
|
53
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
49
54
|
"tsup": "^8.0.2",
|
|
50
55
|
"typescript": "^5.3.3",
|
|
51
56
|
"vitest": "^1.2.0"
|