forgecraft 1.4.0 → 2.0.0

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/cli/index.js CHANGED
@@ -6,18 +6,26 @@ import {
6
6
  Pipeline,
7
7
  Worker,
8
8
  buildAttachmentPrompt,
9
+ estimateCost,
9
10
  formatAttachmentList,
11
+ formatCostEstimate,
12
+ generateGitHubActions,
13
+ generateGitLabCI,
14
+ generateVisualization,
10
15
  getAdapter,
16
+ getTemplate,
11
17
  listAdapters,
18
+ listTemplates,
12
19
  loadAndValidateConfig,
13
20
  parseAttachments,
21
+ refreshAdapters,
14
22
  stageAttachments,
15
23
  stateManager
16
- } from "../chunk-NYOV5D5J.js";
24
+ } from "../chunk-U6CZUA5R.js";
17
25
 
18
26
  // src/cli/index.ts
19
27
  import { Command } from "commander";
20
- import chalk21 from "chalk";
28
+ import chalk27 from "chalk";
21
29
 
22
30
  // src/cli/commands/init.ts
23
31
  import fs from "fs/promises";
@@ -67,8 +75,11 @@ async function initCommand(options) {
67
75
  choices: [
68
76
  { name: "Next.js (TypeScript + Tailwind + App Router)", value: "nextjs" },
69
77
  { name: "React + Vite (TypeScript SPA)", value: "react" },
78
+ { name: "Vue 3 + Nuxt 3 (Composition API + Tailwind)", value: "vue" },
79
+ { name: "SvelteKit (Svelte 5 + Tailwind)", value: "svelte" },
70
80
  { name: "Django (Python + DRF)", value: "django" },
71
- { name: "Other (Express, Vue, Svelte, Go, Rust, etc.)", value: "generic" }
81
+ { name: "Flutter (Dart + Material Design 3)", value: "flutter" },
82
+ { name: "Other (Express, Go, Rust, etc.)", value: "generic" }
72
83
  ],
73
84
  default: options.framework || "nextjs"
74
85
  },
@@ -114,6 +125,7 @@ async function initCommand(options) {
114
125
  await fs.mkdir(forgeDir, { recursive: true });
115
126
  await fs.mkdir(path.join(forgeDir, "snapshots"), { recursive: true });
116
127
  await fs.mkdir(path.join(forgeDir, "designs"), { recursive: true });
128
+ await fs.mkdir(path.join(forgeDir, "adapters"), { recursive: true });
117
129
  await fs.writeFile(
118
130
  path.join(process.cwd(), "forge.config.json"),
119
131
  JSON.stringify(config, null, 2)
@@ -148,6 +160,7 @@ async function initCommand(options) {
148
160
  console.log(chalk.dim(" .forge/state.json \u2014 Sprint state"));
149
161
  console.log(chalk.dim(" .forge/snapshots/ \u2014 Action snapshots"));
150
162
  console.log(chalk.dim(" .forge/designs/ \u2014 Design metadata"));
163
+ console.log(chalk.dim(" .forge/adapters/ \u2014 Custom framework adapters"));
151
164
  console.log(chalk.dim(" forge.config.json \u2014 Project config"));
152
165
  if (answers.githubSync) {
153
166
  console.log(
@@ -433,16 +446,25 @@ async function fixCommand(description, options) {
433
446
  console.log(chalk8.red('\n Please describe the fix: forge fix "description"\n'));
434
447
  return;
435
448
  }
436
- if (options?.image && !existsSync(options.image)) {
437
- console.log(chalk8.red(`
449
+ if (options?.image) {
450
+ const { resolve } = await import("path");
451
+ const imgPath = resolve(options.image);
452
+ if (!existsSync(imgPath)) {
453
+ console.log(chalk8.red(`
438
454
  Image not found: ${options.image}
439
455
  `));
440
- return;
456
+ return;
457
+ }
458
+ options.image = imgPath;
441
459
  }
442
- const parsed = parseAttachments(description);
443
- if (parsed.attachments.length > 0) {
444
- description = parsed.description;
445
- stageAttachments(parsed.attachments);
460
+ let parsed = { description, attachments: [] };
461
+ try {
462
+ parsed = parseAttachments(description);
463
+ if (parsed.attachments.length > 0) {
464
+ description = parsed.description;
465
+ stageAttachments(parsed.attachments);
466
+ }
467
+ } catch {
446
468
  }
447
469
  console.log(chalk8.bold("\n forge fix\n"));
448
470
  console.log(chalk8.dim(` "${description}"`));
@@ -457,15 +479,26 @@ async function fixCommand(description, options) {
457
479
  const orchestrator = new Orchestrator(config);
458
480
  const worker = new Worker(config, {});
459
481
  const git = new GitManager();
460
- const state = await stateManager.getState();
482
+ let state;
483
+ try {
484
+ state = await stateManager.getState();
485
+ } catch {
486
+ state = { currentPhase: "init", currentStory: null, workerMode: null, queue: [], history: [] };
487
+ }
461
488
  const spinner = ora3({ text: "Analyzing request...", indent: 2 }).start();
462
489
  let decision;
463
490
  try {
464
491
  decision = await orchestrator.routeUserInput(description, state);
465
492
  } catch (err) {
466
- spinner.fail("Failed to analyze request");
467
- console.log(chalk8.red(` ${err instanceof Error ? err.message : err}`));
468
- return;
493
+ const errMsg = err instanceof Error ? err.message : String(err);
494
+ if (errMsg.includes("JSON") || errMsg.includes("parse") || errMsg.includes("Unexpected token")) {
495
+ spinner.warn("AI response parsing failed \u2014 falling back to direct fix mode");
496
+ decision = { action: "route-to-worker", workerMode: "fix", prompt: description };
497
+ } else {
498
+ spinner.fail("Failed to analyze request");
499
+ console.log(chalk8.red(` ${errMsg}`));
500
+ return;
501
+ }
469
502
  }
470
503
  switch (decision.action) {
471
504
  case "route-to-worker": {
@@ -519,28 +552,33 @@ Read this image file to see the visual issue the user is referring to.`;
519
552
  }
520
553
  case "add-story": {
521
554
  spinner.succeed("New feature detected");
522
- const plan = await stateManager.getPlan();
523
- if (plan && plan.epics.length > 0) {
524
- const newStory = {
525
- id: `story-${Date.now()}`,
526
- title: decision.story?.title || description,
527
- description: decision.story?.description || description,
528
- type: decision.story?.type || "fullstack",
529
- status: "planned",
530
- branch: null,
531
- designApproved: false,
532
- tags: [],
533
- priority: 99,
534
- dependencies: []
535
- };
536
- plan.epics[plan.epics.length - 1].stories.push(newStory);
537
- await stateManager.savePlan(plan);
538
- await git.commitAll(`forge: add story "${newStory.title}"`);
539
- console.log(chalk8.green(` Added: ${newStory.title}`));
540
- console.log(chalk8.dim(` Run ${chalk8.white("forge build")} or ${chalk8.white("forge auto")} to build it.
555
+ try {
556
+ const plan = await stateManager.getPlan();
557
+ if (plan && plan.epics.length > 0) {
558
+ const newStory = {
559
+ id: `story-${Date.now()}`,
560
+ title: decision.story?.title || description,
561
+ description: decision.story?.description || description,
562
+ type: decision.story?.type || "fullstack",
563
+ status: "planned",
564
+ branch: null,
565
+ designApproved: false,
566
+ tags: [],
567
+ priority: 99,
568
+ dependencies: []
569
+ };
570
+ plan.epics[plan.epics.length - 1].stories.push(newStory);
571
+ await stateManager.savePlan(plan);
572
+ await git.commitAll(`forge: add story "${newStory.title}"`);
573
+ console.log(chalk8.green(` Added: ${newStory.title}`));
574
+ console.log(chalk8.dim(` Run ${chalk8.white("forge build")} or ${chalk8.white("forge auto")} to build it.
575
+ `));
576
+ } else {
577
+ console.log(chalk8.yellow(" No plan found. Run forge plan first to create a sprint plan.\n"));
578
+ }
579
+ } catch (err) {
580
+ console.log(chalk8.red(` Failed to add story: ${err instanceof Error ? err.message : err}
541
581
  `));
542
- } else {
543
- console.log(chalk8.yellow(" No plan found. Run forge plan first to create a sprint plan.\n"));
544
582
  }
545
583
  break;
546
584
  }
@@ -689,13 +727,52 @@ async function autoCommand(description, options) {
689
727
  console.log(chalk10.dim(" Diagnose: forge doctor\n"));
690
728
  return;
691
729
  }
692
- const rawConfig = await stateManager.getConfig();
730
+ let rawConfig = await stateManager.getConfig();
693
731
  if (!rawConfig) {
694
- console.log(chalk10.red("\n Forge not initialized. Run: forge init\n"));
695
- return;
732
+ const hasPackageJson = existsSync2("package.json");
733
+ const hasManagePy = existsSync2("manage.py");
734
+ const hasPubspec = existsSync2("pubspec.yaml");
735
+ const hasCargo = existsSync2("Cargo.toml");
736
+ if (hasPackageJson || hasManagePy || hasPubspec || hasCargo) {
737
+ console.log(chalk10.dim("\n Existing project detected \u2014 auto-initializing Forge...\n"));
738
+ let framework = "other";
739
+ if (hasPackageJson) {
740
+ try {
741
+ const { readFileSync: readFileSync2 } = await import("fs");
742
+ const pkg = JSON.parse(readFileSync2("package.json", "utf-8"));
743
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
744
+ if (deps["next"]) framework = "nextjs";
745
+ else if (deps["nuxt"]) framework = "nuxt";
746
+ else if (deps["@sveltejs/kit"]) framework = "sveltekit";
747
+ else if (deps["vue"]) framework = "vue";
748
+ else if (deps["react"]) framework = "react";
749
+ } catch {
750
+ }
751
+ } else if (hasManagePy) {
752
+ framework = "django";
753
+ } else if (hasPubspec) {
754
+ framework = "flutter";
755
+ }
756
+ rawConfig = {
757
+ framework,
758
+ model: "sonnet",
759
+ designPreview: "storybook",
760
+ githubSync: false,
761
+ githubRepo: null,
762
+ autoCommit: true,
763
+ storybook: { port: 6006 }
764
+ };
765
+ await stateManager.saveConfig(rawConfig);
766
+ console.log(chalk10.dim(` Framework: ${framework}
767
+ `));
768
+ } else {
769
+ console.log(chalk10.red("\n Forge not initialized. Run: forge init\n"));
770
+ return;
771
+ }
696
772
  }
697
773
  const config = loadAndValidateConfig(rawConfig);
698
774
  if (!config) return;
775
+ await refreshAdapters();
699
776
  const adapter = getAdapter(config.framework);
700
777
  const skipDesign = options.skipDesign || !adapter.designSupport;
701
778
  if (!description) {
@@ -728,6 +805,7 @@ async function autoCommand(description, options) {
728
805
  mute: options.mute ?? false,
729
806
  deploy: options.deploy ?? false,
730
807
  skipDesign,
808
+ skipTests: options.skipTests ?? false,
731
809
  allowedDomains,
732
810
  attachments
733
811
  });
@@ -1342,43 +1420,73 @@ async function checkoutCommand(version) {
1342
1420
  // src/cli/commands/start.ts
1343
1421
  import chalk18 from "chalk";
1344
1422
  import { spawn } from "child_process";
1423
+ import { existsSync as existsSync4, readFileSync } from "fs";
1424
+ import path6 from "path";
1345
1425
  async function startCommand() {
1426
+ console.log(chalk18.bold("\n forge") + chalk18.dim(" start\n"));
1427
+ let devCommand = null;
1428
+ let label = "Project";
1346
1429
  const config = await stateManager.getConfig();
1347
- if (!config) {
1348
- console.log(chalk18.red("\n Forge not initialized. Run: forge init\n"));
1349
- return;
1350
- }
1351
- const adapter = getAdapter(config.framework);
1352
- let devCommand = adapter.devCommand;
1353
- if (adapter.id === "generic") {
1430
+ if (config) {
1354
1431
  try {
1355
- const { readFileSync } = await import("fs");
1356
- const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
1357
- if (pkg.scripts?.dev) {
1358
- devCommand = "npm run dev";
1359
- } else if (pkg.scripts?.start) {
1360
- devCommand = "npm start";
1361
- } else if (pkg.scripts?.serve) {
1362
- devCommand = "npm run serve";
1363
- }
1432
+ await refreshAdapters();
1433
+ const adapter = getAdapter(config.framework);
1434
+ devCommand = adapter.devCommand;
1435
+ label = adapter.id === "generic" ? "Custom stack" : adapter.name;
1364
1436
  } catch {
1365
- const { existsSync: existsSync4 } = await import("fs");
1366
- if (existsSync4("manage.py")) {
1367
- devCommand = "python manage.py runserver";
1368
- } else if (existsSync4("Cargo.toml")) {
1369
- devCommand = "cargo run";
1370
- } else if (existsSync4("go.mod")) {
1371
- devCommand = "go run .";
1437
+ }
1438
+ }
1439
+ const detected = detectDevCommand();
1440
+ if (detected) {
1441
+ devCommand = detected;
1442
+ } else if (devCommand) {
1443
+ const npmMatch = devCommand.match(/^npm run (\S+)$/);
1444
+ if (npmMatch) {
1445
+ const scriptName = npmMatch[1];
1446
+ const pkgPath = path6.join(process.cwd(), "package.json");
1447
+ try {
1448
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1449
+ if (!pkg.scripts?.[scriptName]) {
1450
+ devCommand = null;
1451
+ }
1452
+ } catch {
1453
+ devCommand = null;
1372
1454
  }
1373
1455
  }
1374
1456
  }
1375
- const [cmd, ...args] = devCommand.split(" ");
1376
- console.log(chalk18.bold("\n forge") + chalk18.dim(" start"));
1377
- console.log(chalk18.dim(` ${adapter.id === "generic" ? "Custom stack" : adapter.name} dev server
1378
- `));
1457
+ if (!config && detected) {
1458
+ label = detectProjectType();
1459
+ }
1460
+ if (!devCommand) {
1461
+ console.log(chalk18.red(" Could not detect how to start this project.\n"));
1462
+ const pkgPath = path6.join(process.cwd(), "package.json");
1463
+ if (existsSync4(pkgPath)) {
1464
+ try {
1465
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1466
+ const scripts = Object.keys(pkg.scripts || {});
1467
+ if (scripts.length > 0) {
1468
+ console.log(chalk18.dim(" Available scripts in package.json:"));
1469
+ for (const s of scripts) {
1470
+ console.log(chalk18.white(` npm run ${s}`));
1471
+ }
1472
+ console.log("");
1473
+ return;
1474
+ }
1475
+ } catch {
1476
+ }
1477
+ }
1478
+ console.log(chalk18.dim(" Expected one of:"));
1479
+ console.log(chalk18.dim(" - package.json with dev/start/serve script"));
1480
+ console.log(chalk18.dim(" - manage.py (Django)"));
1481
+ console.log(chalk18.dim(" - Cargo.toml (Rust)"));
1482
+ console.log(chalk18.dim(" - go.mod (Go)"));
1483
+ console.log(chalk18.dim(" - pubspec.yaml (Flutter)\n"));
1484
+ return;
1485
+ }
1486
+ console.log(chalk18.dim(` ${label} dev server`));
1379
1487
  console.log(chalk18.dim(` Running: ${devCommand}
1380
1488
  `));
1381
- const child = spawn(cmd, args, {
1489
+ const child = spawn(devCommand, {
1382
1490
  cwd: process.cwd(),
1383
1491
  stdio: "inherit",
1384
1492
  shell: true
@@ -1397,6 +1505,48 @@ async function startCommand() {
1397
1505
  }
1398
1506
  });
1399
1507
  }
1508
+ function detectDevCommand() {
1509
+ const pkgPath = path6.join(process.cwd(), "package.json");
1510
+ if (existsSync4(pkgPath)) {
1511
+ try {
1512
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1513
+ const scripts = pkg.scripts || {};
1514
+ if (scripts.dev) return "npm run dev";
1515
+ if (scripts.start) return "npm start";
1516
+ if (scripts.serve) return "npm run serve";
1517
+ } catch {
1518
+ }
1519
+ }
1520
+ if (existsSync4(path6.join(process.cwd(), "manage.py"))) {
1521
+ return "python manage.py runserver";
1522
+ }
1523
+ if (existsSync4(path6.join(process.cwd(), "Cargo.toml"))) {
1524
+ return "cargo run";
1525
+ }
1526
+ if (existsSync4(path6.join(process.cwd(), "go.mod"))) {
1527
+ return "go run .";
1528
+ }
1529
+ if (existsSync4(path6.join(process.cwd(), "pubspec.yaml"))) {
1530
+ return "flutter run";
1531
+ }
1532
+ if (existsSync4(path6.join(process.cwd(), "Gemfile"))) {
1533
+ if (existsSync4(path6.join(process.cwd(), "bin", "rails"))) {
1534
+ return "bundle exec rails server";
1535
+ }
1536
+ return "bundle exec ruby app.rb";
1537
+ }
1538
+ return null;
1539
+ }
1540
+ function detectProjectType() {
1541
+ if (existsSync4(path6.join(process.cwd(), "next.config.ts")) || existsSync4(path6.join(process.cwd(), "next.config.js")) || existsSync4(path6.join(process.cwd(), "next.config.mjs"))) return "Next.js";
1542
+ if (existsSync4(path6.join(process.cwd(), "nuxt.config.ts"))) return "Nuxt";
1543
+ if (existsSync4(path6.join(process.cwd(), "svelte.config.js"))) return "SvelteKit";
1544
+ if (existsSync4(path6.join(process.cwd(), "manage.py"))) return "Django";
1545
+ if (existsSync4(path6.join(process.cwd(), "Cargo.toml"))) return "Rust";
1546
+ if (existsSync4(path6.join(process.cwd(), "go.mod"))) return "Go";
1547
+ if (existsSync4(path6.join(process.cwd(), "pubspec.yaml"))) return "Flutter";
1548
+ return "Project";
1549
+ }
1400
1550
 
1401
1551
  // src/cli/commands/push.ts
1402
1552
  import chalk19 from "chalk";
@@ -1497,12 +1647,331 @@ function getCurrentVersion() {
1497
1647
  }
1498
1648
  }
1499
1649
 
1650
+ // src/cli/commands/test.ts
1651
+ import chalk21 from "chalk";
1652
+ import ora6 from "ora";
1653
+ import fs5 from "fs/promises";
1654
+ import path7 from "path";
1655
+ async function testCommand(options) {
1656
+ console.log(chalk21.bold("\n forge") + chalk21.dim(" test\n"));
1657
+ const configPath = path7.join(process.cwd(), "forge.config.json");
1658
+ let config;
1659
+ try {
1660
+ const raw = await fs5.readFile(configPath, "utf-8");
1661
+ config = JSON.parse(raw);
1662
+ } catch {
1663
+ console.log(chalk21.red(" No forge.config.json found. Run: forge init\n"));
1664
+ return;
1665
+ }
1666
+ const plan = await stateManager.getPlan();
1667
+ if (!plan) {
1668
+ console.log(chalk21.red(" No sprint plan found. Run: forge plan\n"));
1669
+ return;
1670
+ }
1671
+ await refreshAdapters();
1672
+ let adapter = null;
1673
+ try {
1674
+ adapter = getAdapter(config.framework);
1675
+ } catch {
1676
+ }
1677
+ const worker = new Worker(config, { sandbox: false, yes: true });
1678
+ const git = new GitManager();
1679
+ const allStories = plan.epics.flatMap((e) => e.stories);
1680
+ let testable = allStories.filter(
1681
+ (s) => s.status === "reviewing" || s.status === "testing" || s.status === "done"
1682
+ );
1683
+ if (options.story) {
1684
+ testable = testable.filter((s) => s.id === options.story);
1685
+ if (testable.length === 0) {
1686
+ console.log(chalk21.red(` Story "${options.story}" not found or not built yet.
1687
+ `));
1688
+ return;
1689
+ }
1690
+ }
1691
+ if (testable.length === 0) {
1692
+ console.log(chalk21.yellow(" No built stories to test. Run: forge build\n"));
1693
+ return;
1694
+ }
1695
+ console.log(chalk21.dim(` ${testable.length} stories to test
1696
+ `));
1697
+ for (let i = 0; i < testable.length; i++) {
1698
+ const story = testable[i];
1699
+ const label = `[${i + 1}/${testable.length}] ${story.title}`;
1700
+ const spinner = ora6({ text: label, indent: 2 }).start();
1701
+ const isGeneric = !adapter || adapter.id === "generic";
1702
+ const testInfo = isGeneric ? "Detect the test framework from config files. Install test dependencies if needed." : `Test framework: ${adapter.testFramework || "Vitest"}
1703
+ Test command: ${adapter.testCommand || "npm test"}`;
1704
+ const prompt = `
1705
+ Generate tests for: "${story.title}"
1706
+ Description: ${story.description}
1707
+ App: ${plan.project} (${plan.framework})
1708
+
1709
+ ${testInfo}
1710
+
1711
+ 1. Read the source files to understand what was built
1712
+ 2. Create test files co-located with the source
1713
+ 3. Write meaningful tests (happy path, errors, edge cases)
1714
+ 4. Run the test suite \u2014 all tests must pass
1715
+ `;
1716
+ const result = await worker.run("test", prompt);
1717
+ if (result.success) {
1718
+ await git.commitAll(`test: ${story.title}`);
1719
+ spinner.succeed(`${label}` + chalk21.dim(` \xB7 ${result.filesCreated.length} test files`));
1720
+ } else {
1721
+ spinner.warn(`${label}` + chalk21.dim(" tests incomplete"));
1722
+ for (const err of result.errors) {
1723
+ console.log(chalk21.red(` ${err}`));
1724
+ }
1725
+ }
1726
+ }
1727
+ console.log(chalk21.green("\n Test generation complete.\n"));
1728
+ }
1729
+
1730
+ // src/cli/commands/template.ts
1731
+ import chalk22 from "chalk";
1732
+ import inquirer8 from "inquirer";
1733
+ async function templateCommand(options) {
1734
+ const templates = listTemplates();
1735
+ if (options.list) {
1736
+ console.log(chalk22.bold("\n Forge Templates\n"));
1737
+ for (const t of templates) {
1738
+ console.log(` ${chalk22.cyan(t.id.padEnd(16))} ${t.name}`);
1739
+ console.log(chalk22.dim(` ${"".padEnd(16)} ${t.description}`));
1740
+ console.log(chalk22.dim(` ${"".padEnd(16)} Frameworks: ${t.suggestedFrameworks.join(", ")}
1741
+ `));
1742
+ }
1743
+ return;
1744
+ }
1745
+ console.log(chalk22.bold("\n forge") + chalk22.dim(" template\n"));
1746
+ let templateId;
1747
+ try {
1748
+ const answer = await inquirer8.prompt([
1749
+ {
1750
+ type: "list",
1751
+ name: "templateId",
1752
+ message: "Choose a starter template:",
1753
+ choices: templates.map((t) => ({
1754
+ name: `${t.name} \u2014 ${t.description}`,
1755
+ value: t.id
1756
+ }))
1757
+ }
1758
+ ]);
1759
+ templateId = answer.templateId;
1760
+ } catch {
1761
+ console.log(chalk22.dim("\n Cancelled.\n"));
1762
+ return;
1763
+ }
1764
+ const template = getTemplate(templateId);
1765
+ if (!template) {
1766
+ console.log(chalk22.red(" Template not found.\n"));
1767
+ return;
1768
+ }
1769
+ console.log(chalk22.green(`
1770
+ Selected: ${template.name}`));
1771
+ console.log(chalk22.dim(` ${template.description}
1772
+ `));
1773
+ console.log(chalk22.dim(" Suggested frameworks: " + template.suggestedFrameworks.join(", ")));
1774
+ console.log(chalk22.bold("\n To use this template, run:"));
1775
+ console.log(chalk22.cyan(` forge auto "${template.planDescription.split("\n")[0].trim()}"
1776
+ `));
1777
+ console.log(chalk22.dim(" Or copy the full description:\n"));
1778
+ const lines = template.planDescription.split("\n");
1779
+ for (const line of lines) {
1780
+ console.log(chalk22.dim(` ${line}`));
1781
+ }
1782
+ console.log("");
1783
+ }
1784
+
1785
+ // src/cli/commands/estimate.ts
1786
+ import chalk23 from "chalk";
1787
+ import fs6 from "fs/promises";
1788
+ import path8 from "path";
1789
+ async function estimateCommand() {
1790
+ console.log(chalk23.bold("\n forge") + chalk23.dim(" estimate\n"));
1791
+ const configPath = path8.join(process.cwd(), "forge.config.json");
1792
+ let config;
1793
+ try {
1794
+ const raw = await fs6.readFile(configPath, "utf-8");
1795
+ config = JSON.parse(raw);
1796
+ } catch {
1797
+ console.log(chalk23.red(" No forge.config.json found. Run: forge init\n"));
1798
+ return;
1799
+ }
1800
+ const plan = await stateManager.getPlan();
1801
+ if (!plan) {
1802
+ console.log(chalk23.red(" No sprint plan found. Run: forge plan\n"));
1803
+ return;
1804
+ }
1805
+ const estimate = estimateCost(plan, config.model);
1806
+ console.log(formatCostEstimate(estimate));
1807
+ console.log("");
1808
+ }
1809
+
1810
+ // src/cli/commands/cicd.ts
1811
+ import chalk24 from "chalk";
1812
+ import inquirer9 from "inquirer";
1813
+ import fs7 from "fs/promises";
1814
+ import path9 from "path";
1815
+ async function cicdCommand(options) {
1816
+ console.log(chalk24.bold("\n forge") + chalk24.dim(" cicd\n"));
1817
+ const configPath = path9.join(process.cwd(), "forge.config.json");
1818
+ let config;
1819
+ try {
1820
+ const raw = await fs7.readFile(configPath, "utf-8");
1821
+ config = JSON.parse(raw);
1822
+ } catch {
1823
+ console.log(chalk24.red(" No forge.config.json found. Run: forge init\n"));
1824
+ return;
1825
+ }
1826
+ let provider = options.provider || "github";
1827
+ if (!options.provider) {
1828
+ try {
1829
+ const { selectedProvider } = await inquirer9.prompt([
1830
+ {
1831
+ type: "list",
1832
+ name: "selectedProvider",
1833
+ message: "CI/CD provider:",
1834
+ choices: [
1835
+ { name: "GitHub Actions", value: "github" },
1836
+ { name: "GitLab CI", value: "gitlab" }
1837
+ ]
1838
+ }
1839
+ ]);
1840
+ provider = selectedProvider;
1841
+ } catch {
1842
+ console.log(chalk24.dim("\n Cancelled.\n"));
1843
+ return;
1844
+ }
1845
+ }
1846
+ await refreshAdapters();
1847
+ const adapter = getAdapter(config.framework);
1848
+ let content;
1849
+ let filePath;
1850
+ if (provider === "github") {
1851
+ content = generateGitHubActions(adapter);
1852
+ filePath = path9.join(process.cwd(), ".github", "workflows", "ci.yml");
1853
+ await fs7.mkdir(path9.dirname(filePath), { recursive: true });
1854
+ } else {
1855
+ content = generateGitLabCI(adapter);
1856
+ filePath = path9.join(process.cwd(), ".gitlab-ci.yml");
1857
+ }
1858
+ await fs7.writeFile(filePath, content);
1859
+ const relative = path9.relative(process.cwd(), filePath);
1860
+ console.log(chalk24.green(` Created: ${relative}`));
1861
+ console.log(chalk24.dim(` Framework: ${adapter.name}`));
1862
+ console.log(chalk24.dim(` Provider: ${provider === "github" ? "GitHub Actions" : "GitLab CI"}
1863
+ `));
1864
+ }
1865
+
1866
+ // src/cli/commands/viz.ts
1867
+ import chalk25 from "chalk";
1868
+ import ora7 from "ora";
1869
+ async function vizCommand(targetPath, options) {
1870
+ console.log(chalk25.bold("\n forge") + chalk25.dim(" viz\n"));
1871
+ const spinner = ora7({ text: "Scanning project...", indent: 2 }).start();
1872
+ try {
1873
+ const shouldOpen = options.open !== false;
1874
+ const { outputPath, graph } = await generateVisualization({
1875
+ workingDir: targetPath || void 0,
1876
+ outputPath: options.output,
1877
+ open: shouldOpen
1878
+ });
1879
+ spinner.succeed(
1880
+ `Scanned ${graph.totalFiles} files (${formatLines(graph.totalLines)} lines)`
1881
+ );
1882
+ const categories = {};
1883
+ for (const f of graph.files) {
1884
+ categories[f.category] = (categories[f.category] || 0) + 1;
1885
+ }
1886
+ console.log(chalk25.dim(`
1887
+ Framework: ${graph.framework}`));
1888
+ console.log(chalk25.dim(` Languages: ${Object.keys(graph.languages).join(", ")}`));
1889
+ if (graph.apiRoutes.length > 0) {
1890
+ console.log(chalk25.dim(` API routes: ${graph.apiRoutes.length}`));
1891
+ }
1892
+ console.log(chalk25.dim(`
1893
+ Dashboard: ${outputPath}`));
1894
+ if (shouldOpen) {
1895
+ console.log(chalk25.green(" Opened in browser\n"));
1896
+ } else {
1897
+ console.log(chalk25.dim(" Open the file in your browser to view.\n"));
1898
+ }
1899
+ } catch (err) {
1900
+ spinner.fail("Failed to generate visualization");
1901
+ console.log(chalk25.red(` ${err instanceof Error ? err.message : err}
1902
+ `));
1903
+ }
1904
+ }
1905
+ function formatLines(n) {
1906
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
1907
+ return String(n);
1908
+ }
1909
+
1910
+ // src/cli/commands/claude.ts
1911
+ import chalk26 from "chalk";
1912
+ import { spawn as spawn2 } from "child_process";
1913
+ async function claudeCommand() {
1914
+ console.log(chalk26.bold("\n forge") + chalk26.dim(" claude\n"));
1915
+ const cmd = await findClaudeCLI();
1916
+ if (!cmd) {
1917
+ console.log(chalk26.red(" Claude Code CLI not found.\n"));
1918
+ console.log(chalk26.dim(" Install it with:"));
1919
+ console.log(chalk26.white(" npm install -g @anthropic-ai/claude-code"));
1920
+ console.log(chalk26.dim(" Then log in:"));
1921
+ console.log(chalk26.white(" claude login\n"));
1922
+ return;
1923
+ }
1924
+ console.log(chalk26.dim(` Starting Claude Code...
1925
+ `));
1926
+ const isWindows = process.platform === "win32";
1927
+ const child = spawn2(cmd.cmd, cmd.args, {
1928
+ cwd: process.cwd(),
1929
+ stdio: "inherit",
1930
+ shell: isWindows
1931
+ // On Windows, use shell mode to resolve commands from PATH
1932
+ });
1933
+ child.on("error", (err) => {
1934
+ if (err.code === "ENOENT") {
1935
+ console.log(chalk26.red("\n Claude Code CLI not found in PATH."));
1936
+ console.log(chalk26.dim(" Install: npm install -g @anthropic-ai/claude-code\n"));
1937
+ } else {
1938
+ console.log(chalk26.red(`
1939
+ Failed to start Claude: ${err.message}
1940
+ `));
1941
+ }
1942
+ });
1943
+ child.on("exit", (code) => {
1944
+ if (code && code !== 0) {
1945
+ console.log(chalk26.dim(`
1946
+ Claude exited with code ${code}
1947
+ `));
1948
+ }
1949
+ });
1950
+ }
1951
+ async function findClaudeCLI() {
1952
+ const { execSync: execSync4 } = await import("child_process");
1953
+ const isWindows = process.platform === "win32";
1954
+ try {
1955
+ const whichCmd = isWindows ? "where claude" : "which claude";
1956
+ execSync4(whichCmd, { stdio: "ignore" });
1957
+ return { cmd: "claude", args: [] };
1958
+ } catch {
1959
+ }
1960
+ try {
1961
+ const whichCmd = isWindows ? "where npx" : "which npx";
1962
+ execSync4(whichCmd, { stdio: "ignore" });
1963
+ return { cmd: "npx", args: ["@anthropic-ai/claude-code"] };
1964
+ } catch {
1965
+ }
1966
+ return null;
1967
+ }
1968
+
1500
1969
  // src/cli/index.ts
1501
1970
  var program = new Command();
1502
1971
  program.name("forge").description(
1503
- chalk21.bold("Forge") + " \u2014 AI Development Orchestration Framework\n Structured multi-agent pipeline: plan \u2192 design \u2192 build \u2192 review"
1504
- ).version("1.4.0");
1505
- program.command("auto [description]").description("Fully autonomous mode \u2014 plan, build, and review in one command").option("--no-sandbox", "Disable sandbox (not recommended)").option("-y, --yes", "Skip all confirmation prompts (auto-approve everything)").option("-q, --quiet", "Hide live agent output, show spinners only").option("--allow-network <domains>", "Comma-separated allowed network domains").option("--mute", "Suppress notification sounds").option("--deploy", "Configure GitHub Pages deployment after build").option("--skip-design", "Skip design phase (faster, no Storybook previews)").action(autoCommand);
1972
+ chalk27.bold("Forge") + " \u2014 AI Development Orchestration Framework\n Structured multi-agent pipeline: plan \u2192 design \u2192 build \u2192 review"
1973
+ ).version("2.0.0");
1974
+ program.command("auto [description]").description("Fully autonomous mode \u2014 plan, build, and review in one command").option("--no-sandbox", "Disable sandbox (not recommended)").option("-y, --yes", "Skip all confirmation prompts (auto-approve everything)").option("-q, --quiet", "Hide live agent output, show spinners only").option("--allow-network <domains>", "Comma-separated allowed network domains").option("--mute", "Suppress notification sounds").option("--deploy", "Configure GitHub Pages deployment after build").option("--skip-design", "Skip design phase (faster, no Storybook previews)").option("--skip-tests", "Skip test generation phase").action(autoCommand);
1506
1975
  program.command("resume").description("Resume an interrupted sprint from where it left off").option("--no-sandbox", "Disable sandbox").option("-y, --yes", "Skip all confirmation prompts (auto-approve everything)").option("-q, --quiet", "Spinners only").option("--mute", "Suppress sounds").option("--skip-design", "Skip design phase").action(resumeCommand);
1507
1976
  program.command("sprint [description]").description("Run full pipeline with human gates between phases").action(sprintCommand);
1508
1977
  program.command("init").description("Initialize Forge in the current project").option("-f, --framework <framework>", "Framework (nextjs, react, django)").option("--no-storybook", "Skip Storybook setup").action(initCommand);
@@ -1510,8 +1979,10 @@ program.command("plan [description]").description("Generate a sprint plan from a
1510
1979
  program.command("design").description("Generate and review design previews in Storybook").option("-s, --story <storyId>", "Design a specific story only").option("--import <path>", "Import design references (screenshots, mockups)").action(designCommand);
1511
1980
  program.command("build").description("Build stories sequentially with the Worker agent").option("-s, --story <storyId>", "Build a specific story only").action(buildCommand);
1512
1981
  program.command("review").description("Run QA review on completed stories").option("-s, --story <storyId>", "Review a specific story only").action(reviewCommand);
1982
+ program.command("test").description("Generate and run tests for built stories").option("-s, --story <storyId>", "Test a specific story only").action(testCommand);
1513
1983
  program.command("start").description("Start the dev server for the current project").action(startCommand);
1514
1984
  program.command("push").description("Push project to GitHub (commits + tags)").action(pushCommand);
1985
+ program.command("claude").description("Launch Claude Code CLI in the current project").action(claudeCommand);
1515
1986
  program.command("status").description("Show current sprint state and progress").action(statusCommand);
1516
1987
  program.command("map").description("Visual sprint map with story status and dependencies").action(mapCommand);
1517
1988
  program.command("diff <v1> [v2]").description("Show changes between two versions or tags").action(diffCommand);
@@ -1521,7 +1992,11 @@ program.command("history").description("Show version timeline, checkpoints, and
1521
1992
  program.command("checkout <version>").description("Jump to a specific version or checkpoint").action(checkoutCommand);
1522
1993
  program.command("export").description("Export sprint plan as markdown").option("-o, --output <path>", "Output file path", "sprint-plan.md").action(exportCommand);
1523
1994
  program.command("clean").description("Reset sprint state (keeps config)").option("-f, --force", "Skip confirmation prompt").option("--snapshots", "Only clean snapshots").action(cleanCommand);
1995
+ program.command("viz [path]").description("Interactive project map \u2014 visualize files, dependencies, and data flow").option("-o, --output <path>", "Output HTML file path").option("--no-open", "Don't auto-open in browser").action(vizCommand);
1524
1996
  program.command("doctor").description("Diagnose setup issues and check system requirements").action(doctorCommand);
1997
+ program.command("estimate").description("Estimate token usage and cost for the current sprint plan").action(estimateCommand);
1998
+ program.command("cicd").description("Generate CI/CD pipeline configuration").option("-p, --provider <provider>", "CI provider (github, gitlab)").action(cicdCommand);
1999
+ program.command("template").description("Browse and use starter app templates").option("-l, --list", "List all available templates").action(templateCommand);
1525
2000
  program.command("upgrade").description("Upgrade Forge to the latest version").action(upgradeCommand);
1526
2001
  program.parse();
1527
2002
  //# sourceMappingURL=index.js.map