forgecraft 1.3.3 → 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-CKB64IR3.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
  });
@@ -740,12 +818,13 @@ async function autoCommand(description, options) {
740
818
  // src/cli/commands/resume.ts
741
819
  import chalk11 from "chalk";
742
820
  import inquirer6 from "inquirer";
821
+ import os from "os";
743
822
  async function resumeCommand(options) {
744
823
  let workingDir;
745
824
  try {
746
825
  workingDir = process.cwd();
747
826
  } catch {
748
- const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
827
+ const home = process.env.HOME || process.env.USERPROFILE || os.tmpdir();
749
828
  process.chdir(home);
750
829
  console.log(chalk11.yellow("\n Working directory no longer exists."));
751
830
  console.log(chalk11.dim(` Falling back to: ${home}`));
@@ -1341,43 +1420,73 @@ async function checkoutCommand(version) {
1341
1420
  // src/cli/commands/start.ts
1342
1421
  import chalk18 from "chalk";
1343
1422
  import { spawn } from "child_process";
1423
+ import { existsSync as existsSync4, readFileSync } from "fs";
1424
+ import path6 from "path";
1344
1425
  async function startCommand() {
1426
+ console.log(chalk18.bold("\n forge") + chalk18.dim(" start\n"));
1427
+ let devCommand = null;
1428
+ let label = "Project";
1345
1429
  const config = await stateManager.getConfig();
1346
- if (!config) {
1347
- console.log(chalk18.red("\n Forge not initialized. Run: forge init\n"));
1348
- return;
1349
- }
1350
- const adapter = getAdapter(config.framework);
1351
- let devCommand = adapter.devCommand;
1352
- if (adapter.id === "generic") {
1430
+ if (config) {
1353
1431
  try {
1354
- const { readFileSync } = await import("fs");
1355
- const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
1356
- if (pkg.scripts?.dev) {
1357
- devCommand = "npm run dev";
1358
- } else if (pkg.scripts?.start) {
1359
- devCommand = "npm start";
1360
- } else if (pkg.scripts?.serve) {
1361
- devCommand = "npm run serve";
1362
- }
1432
+ await refreshAdapters();
1433
+ const adapter = getAdapter(config.framework);
1434
+ devCommand = adapter.devCommand;
1435
+ label = adapter.id === "generic" ? "Custom stack" : adapter.name;
1363
1436
  } catch {
1364
- const { existsSync: existsSync4 } = await import("fs");
1365
- if (existsSync4("manage.py")) {
1366
- devCommand = "python manage.py runserver";
1367
- } else if (existsSync4("Cargo.toml")) {
1368
- devCommand = "cargo run";
1369
- } else if (existsSync4("go.mod")) {
1370
- 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;
1371
1454
  }
1372
1455
  }
1373
1456
  }
1374
- const [cmd, ...args] = devCommand.split(" ");
1375
- console.log(chalk18.bold("\n forge") + chalk18.dim(" start"));
1376
- console.log(chalk18.dim(` ${adapter.id === "generic" ? "Custom stack" : adapter.name} dev server
1377
- `));
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`));
1378
1487
  console.log(chalk18.dim(` Running: ${devCommand}
1379
1488
  `));
1380
- const child = spawn(cmd, args, {
1489
+ const child = spawn(devCommand, {
1381
1490
  cwd: process.cwd(),
1382
1491
  stdio: "inherit",
1383
1492
  shell: true
@@ -1396,6 +1505,48 @@ async function startCommand() {
1396
1505
  }
1397
1506
  });
1398
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
+ }
1399
1550
 
1400
1551
  // src/cli/commands/push.ts
1401
1552
  import chalk19 from "chalk";
@@ -1471,9 +1622,14 @@ async function upgradeCommand() {
1471
1622
  } catch (err) {
1472
1623
  upgradeSpinner.fail("Upgrade failed");
1473
1624
  const msg = err instanceof Error ? err.stderr || err.message : String(err);
1474
- if (msg.includes("EACCES") || msg.includes("permission")) {
1475
- console.log(chalk20.dim(" Try running with sudo:\n"));
1476
- console.log(chalk20.white(" sudo npm install -g forgecraft@latest\n"));
1625
+ if (msg.includes("EACCES") || msg.includes("permission") || msg.includes("EPERM")) {
1626
+ if (process.platform === "win32") {
1627
+ console.log(chalk20.dim(" Try running as Administrator:\n"));
1628
+ console.log(chalk20.white(" npm install -g forgecraft@latest\n"));
1629
+ } else {
1630
+ console.log(chalk20.dim(" Try running with sudo:\n"));
1631
+ console.log(chalk20.white(" sudo npm install -g forgecraft@latest\n"));
1632
+ }
1477
1633
  } else {
1478
1634
  console.log(chalk20.dim(` ${msg.split("\n")[0]}
1479
1635
  `));
@@ -1491,12 +1647,331 @@ function getCurrentVersion() {
1491
1647
  }
1492
1648
  }
1493
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
+
1494
1969
  // src/cli/index.ts
1495
1970
  var program = new Command();
1496
1971
  program.name("forge").description(
1497
- chalk21.bold("Forge") + " \u2014 AI Development Orchestration Framework\n Structured multi-agent pipeline: plan \u2192 design \u2192 build \u2192 review"
1498
- ).version("1.3.0");
1499
- 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);
1500
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);
1501
1976
  program.command("sprint [description]").description("Run full pipeline with human gates between phases").action(sprintCommand);
1502
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);
@@ -1504,8 +1979,10 @@ program.command("plan [description]").description("Generate a sprint plan from a
1504
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);
1505
1980
  program.command("build").description("Build stories sequentially with the Worker agent").option("-s, --story <storyId>", "Build a specific story only").action(buildCommand);
1506
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);
1507
1983
  program.command("start").description("Start the dev server for the current project").action(startCommand);
1508
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);
1509
1986
  program.command("status").description("Show current sprint state and progress").action(statusCommand);
1510
1987
  program.command("map").description("Visual sprint map with story status and dependencies").action(mapCommand);
1511
1988
  program.command("diff <v1> [v2]").description("Show changes between two versions or tags").action(diffCommand);
@@ -1515,7 +1992,11 @@ program.command("history").description("Show version timeline, checkpoints, and
1515
1992
  program.command("checkout <version>").description("Jump to a specific version or checkpoint").action(checkoutCommand);
1516
1993
  program.command("export").description("Export sprint plan as markdown").option("-o, --output <path>", "Output file path", "sprint-plan.md").action(exportCommand);
1517
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);
1518
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);
1519
2000
  program.command("upgrade").description("Upgrade Forge to the latest version").action(upgradeCommand);
1520
2001
  program.parse();
1521
2002
  //# sourceMappingURL=index.js.map