viberails 0.6.3 → 0.6.4

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.cjs CHANGED
@@ -64,11 +64,34 @@ function findProjectRoot(startDir) {
64
64
  var clack5 = __toESM(require("@clack/prompts"), 1);
65
65
 
66
66
  // src/utils/prompt-integrations.ts
67
- var import_node_child_process = require("child_process");
68
67
  var clack = __toESM(require("@clack/prompts"), 1);
68
+
69
+ // src/utils/spawn-async.ts
70
+ var import_node_child_process = require("child_process");
71
+ function spawnAsync(command, cwd) {
72
+ return new Promise((resolve4) => {
73
+ const child = (0, import_node_child_process.spawn)(command, { cwd, shell: true, stdio: "pipe" });
74
+ let stdout = "";
75
+ let stderr = "";
76
+ child.stdout.on("data", (d) => {
77
+ stdout += d.toString();
78
+ });
79
+ child.stderr.on("data", (d) => {
80
+ stderr += d.toString();
81
+ });
82
+ child.on("close", (status) => {
83
+ resolve4({ status, stdout, stderr });
84
+ });
85
+ child.on("error", () => {
86
+ resolve4({ status: 1, stdout, stderr });
87
+ });
88
+ });
89
+ }
90
+
91
+ // src/utils/prompt-integrations.ts
69
92
  async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
70
93
  const choice = await clack.select({
71
- message: "No git hook manager detected. Install Lefthook for shareable pre-commit hooks?",
94
+ message: "No shared git hook manager detected. Install Lefthook?",
72
95
  options: [
73
96
  {
74
97
  value: "install",
@@ -88,12 +111,7 @@ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace
88
111
  const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
89
112
  const s = clack.spinner();
90
113
  s.start("Installing Lefthook...");
91
- const result = (0, import_node_child_process.spawnSync)(installCmd, {
92
- cwd: projectRoot,
93
- shell: true,
94
- encoding: "utf-8",
95
- stdio: "pipe"
96
- });
114
+ const result = await spawnAsync(installCmd, projectRoot);
97
115
  if (result.status === 0) {
98
116
  const fs21 = await import("fs");
99
117
  const path21 = await import("path");
@@ -161,7 +179,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
161
179
  );
162
180
  const initialValues = isBareHook ? options.filter((o) => o.value !== "preCommit").map((o) => o.value) : options.map((o) => o.value);
163
181
  const result = await clack.multiselect({
164
- message: "Set up integrations?",
182
+ message: "Optional integrations",
165
183
  options,
166
184
  initialValues,
167
185
  required: false
@@ -568,16 +586,49 @@ async function confirmDangerous(message) {
568
586
  assertNotCancelled(result);
569
587
  return result;
570
588
  }
589
+ async function promptExistingConfigAction(configFile) {
590
+ const result = await clack5.select({
591
+ message: `${configFile} already exists. What do you want to do?`,
592
+ options: [
593
+ {
594
+ value: "edit",
595
+ label: "Edit existing config",
596
+ hint: "open the current rules and save updates in place"
597
+ },
598
+ {
599
+ value: "replace",
600
+ label: "Replace with a fresh scan",
601
+ hint: "re-scan the project and overwrite the current config"
602
+ },
603
+ {
604
+ value: "cancel",
605
+ label: "Cancel",
606
+ hint: "leave the current setup unchanged"
607
+ }
608
+ ]
609
+ });
610
+ assertNotCancelled(result);
611
+ return result;
612
+ }
571
613
  async function promptInitDecision() {
572
614
  const result = await clack5.select({
573
- message: "Accept these rules?",
615
+ message: "How do you want to proceed?",
574
616
  options: [
575
617
  {
576
618
  value: "accept",
577
- label: "Yes, looks good",
578
- hint: "warns on violation; use --enforce in CI to block"
619
+ label: "Accept defaults",
620
+ hint: "writes the config with these defaults; use --enforce in CI to block"
621
+ },
622
+ {
623
+ value: "customize",
624
+ label: "Customize rules",
625
+ hint: "edit limits, naming, test coverage, and package overrides"
579
626
  },
580
- { value: "customize", label: "Let me customize rules" }
627
+ {
628
+ value: "review",
629
+ label: "Review detected details",
630
+ hint: "show the full scan report with package and structure details"
631
+ }
581
632
  ]
582
633
  });
583
634
  assertNotCancelled(result);
@@ -1624,6 +1675,15 @@ function formatMonorepoResultsText(scanResult) {
1624
1675
  }
1625
1676
 
1626
1677
  // src/display.ts
1678
+ var INIT_OVERVIEW_NAMES = {
1679
+ typescript: "TypeScript",
1680
+ javascript: "JavaScript",
1681
+ eslint: "ESLint",
1682
+ prettier: "Prettier",
1683
+ jest: "Jest",
1684
+ vitest: "Vitest",
1685
+ biome: "Biome"
1686
+ };
1627
1687
  function formatItem(item, nameMap) {
1628
1688
  const name = nameMap?.[item.name] ?? item.name;
1629
1689
  return item.version ? `${name} ${item.version}` : name;
@@ -1753,11 +1813,40 @@ function displayRulesPreview(config) {
1753
1813
  );
1754
1814
  console.log("");
1755
1815
  }
1756
- function displayInitSummary(config, exemptedPackages) {
1816
+ function formatDetectedOverview(scanResult) {
1817
+ const { stack } = scanResult;
1818
+ const primaryParts = [];
1819
+ const secondaryParts = [];
1820
+ const formatOverviewItem = (item, nameMap) => formatItem(item, { ...INIT_OVERVIEW_NAMES, ...nameMap });
1821
+ if (scanResult.packages.length > 1) {
1822
+ primaryParts.push("monorepo");
1823
+ primaryParts.push(`${scanResult.packages.length} packages`);
1824
+ } else if (stack.framework) {
1825
+ primaryParts.push(formatItem(stack.framework, import_types3.FRAMEWORK_NAMES));
1826
+ } else {
1827
+ primaryParts.push("single package");
1828
+ }
1829
+ primaryParts.push(formatOverviewItem(stack.language));
1830
+ if (stack.styling) {
1831
+ primaryParts.push(formatOverviewItem(stack.styling, import_types3.STYLING_NAMES));
1832
+ }
1833
+ if (stack.packageManager) secondaryParts.push(formatOverviewItem(stack.packageManager));
1834
+ if (stack.linter) secondaryParts.push(formatOverviewItem(stack.linter));
1835
+ if (stack.formatter) secondaryParts.push(formatOverviewItem(stack.formatter));
1836
+ if (stack.testRunner) secondaryParts.push(formatOverviewItem(stack.testRunner));
1837
+ const primary = primaryParts.map((part) => import_chalk5.default.cyan(part)).join(import_chalk5.default.dim(" \xB7 "));
1838
+ const secondary = secondaryParts.join(import_chalk5.default.dim(" \xB7 "));
1839
+ return secondary ? `${primary}
1840
+ ${import_chalk5.default.dim(secondary)}` : primary;
1841
+ }
1842
+ function displayInitOverview(scanResult, config, exemptedPackages) {
1757
1843
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1758
1844
  const isMonorepo = config.packages.length > 1;
1759
1845
  const ok = import_chalk5.default.green("\u2713");
1760
- const off = import_chalk5.default.dim("\u25CB");
1846
+ const info = import_chalk5.default.yellow("~");
1847
+ console.log("");
1848
+ console.log(` ${import_chalk5.default.bold("Ready to initialize:")}`);
1849
+ console.log(` ${formatDetectedOverview(scanResult)}`);
1761
1850
  console.log("");
1762
1851
  console.log(` ${import_chalk5.default.bold("Rules to apply:")}`);
1763
1852
  console.log(` ${ok} Max file size: ${import_chalk5.default.cyan(`${config.rules.maxFileLines} lines`)}`);
@@ -1765,7 +1854,7 @@ function displayInitSummary(config, exemptedPackages) {
1765
1854
  if (config.rules.enforceNaming && fileNaming) {
1766
1855
  console.log(` ${ok} File naming: ${import_chalk5.default.cyan(fileNaming)}`);
1767
1856
  } else {
1768
- console.log(` ${off} File naming: ${import_chalk5.default.dim("not enforced")}`);
1857
+ console.log(` ${info} File naming: ${import_chalk5.default.dim("not enforced")}`);
1769
1858
  }
1770
1859
  const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1771
1860
  if (config.rules.enforceMissingTests && testPattern) {
@@ -1773,7 +1862,7 @@ function displayInitSummary(config, exemptedPackages) {
1773
1862
  } else if (config.rules.enforceMissingTests) {
1774
1863
  console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan("enforced")}`);
1775
1864
  } else {
1776
- console.log(` ${off} Missing tests: ${import_chalk5.default.dim("not enforced")}`);
1865
+ console.log(` ${info} Missing tests: ${import_chalk5.default.dim("not enforced")}`);
1777
1866
  }
1778
1867
  if (config.rules.testCoverage > 0) {
1779
1868
  if (isMonorepo) {
@@ -1787,21 +1876,68 @@ function displayInitSummary(config, exemptedPackages) {
1787
1876
  console.log(` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)}`);
1788
1877
  }
1789
1878
  } else {
1790
- console.log(` ${off} Coverage: ${import_chalk5.default.dim("disabled")}`);
1879
+ console.log(` ${info} Coverage: ${import_chalk5.default.dim("disabled")}`);
1791
1880
  }
1792
1881
  if (exemptedPackages.length > 0) {
1793
1882
  console.log(
1794
1883
  ` ${import_chalk5.default.dim(" exempted:")} ${import_chalk5.default.dim(exemptedPackages.join(", "))} ${import_chalk5.default.dim("(types-only)")}`
1795
1884
  );
1796
1885
  }
1886
+ console.log("");
1887
+ console.log(` ${import_chalk5.default.bold("Also available:")}`);
1797
1888
  if (isMonorepo) {
1798
- console.log(
1799
- `
1800
- ${import_chalk5.default.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1801
- );
1889
+ console.log(` ${info} Infer boundaries from current imports`);
1890
+ }
1891
+ console.log(` ${info} Set up hooks, Claude integration, and CI checks`);
1892
+ console.log(
1893
+ `
1894
+ ${import_chalk5.default.dim("Defaults warn locally. Use --enforce in CI when you want failures to block.")}`
1895
+ );
1896
+ console.log("");
1897
+ }
1898
+ function summarizeSelectedIntegrations(integrations, opts) {
1899
+ const lines = [];
1900
+ if (opts.hasBoundaries) {
1901
+ lines.push("\u2713 Boundary rules: inferred from current imports");
1802
1902
  } else {
1803
- console.log(`
1804
- ${import_chalk5.default.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1903
+ lines.push("~ Boundary rules: not enabled");
1904
+ }
1905
+ if (opts.hasCoverage) {
1906
+ lines.push("\u2713 Coverage checks: enabled");
1907
+ } else {
1908
+ lines.push("~ Coverage checks: disabled");
1909
+ }
1910
+ const selectedIntegrations = [
1911
+ integrations.preCommitHook ? "pre-commit hook" : void 0,
1912
+ integrations.typecheckHook ? "typecheck" : void 0,
1913
+ integrations.lintHook ? "lint check" : void 0,
1914
+ integrations.claudeCodeHook ? "Claude Code hook" : void 0,
1915
+ integrations.claudeMdRef ? "CLAUDE.md reference" : void 0,
1916
+ integrations.githubAction ? "GitHub Actions workflow" : void 0
1917
+ ].filter(Boolean);
1918
+ if (selectedIntegrations.length > 0) {
1919
+ lines.push(`\u2713 Integrations: ${selectedIntegrations.join(" \xB7 ")}`);
1920
+ } else {
1921
+ lines.push("~ Integrations: none selected");
1922
+ }
1923
+ return lines;
1924
+ }
1925
+ function displaySetupPlan(config, integrations, opts = {}) {
1926
+ const configFile = opts.configFile ?? "viberails.config.json";
1927
+ const lines = summarizeSelectedIntegrations(integrations, {
1928
+ hasBoundaries: config.rules.enforceBoundaries,
1929
+ hasCoverage: config.rules.testCoverage > 0
1930
+ });
1931
+ console.log("");
1932
+ console.log(` ${import_chalk5.default.bold("Ready to write:")}`);
1933
+ console.log(
1934
+ ` ${opts.replacingExistingConfig ? import_chalk5.default.yellow("!") : import_chalk5.default.green("\u2713")} ${configFile}${opts.replacingExistingConfig ? import_chalk5.default.dim(" (replacing existing config)") : ""}`
1935
+ );
1936
+ console.log(` ${import_chalk5.default.green("\u2713")} .viberails/context.md`);
1937
+ console.log(` ${import_chalk5.default.green("\u2713")} .viberails/scan-result.json`);
1938
+ for (const line of lines) {
1939
+ const icon = line.startsWith("\u2713") ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
1940
+ console.log(` ${icon} ${line.slice(2)}`);
1805
1941
  }
1806
1942
  console.log("");
1807
1943
  }
@@ -2113,10 +2249,12 @@ async function configCommand(options, cwd) {
2113
2249
  }
2114
2250
  const configPath = path9.join(projectRoot, CONFIG_FILE3);
2115
2251
  if (!fs10.existsSync(configPath)) {
2116
- console.log(`${import_chalk6.default.yellow("!")} No config found. Run ${import_chalk6.default.cyan("viberails init")} first.`);
2252
+ console.log(`${import_chalk6.default.yellow("!")} No config found. Run ${import_chalk6.default.cyan("viberails")} first.`);
2117
2253
  return;
2118
2254
  }
2119
- clack6.intro("viberails config");
2255
+ if (!options.suppressIntro) {
2256
+ clack6.intro("viberails config");
2257
+ }
2120
2258
  const config = await (0, import_config6.loadConfig)(configPath);
2121
2259
  let scanResult = options.rescan ? await rescanAndMerge(projectRoot, config) : void 0;
2122
2260
  clack6.note(formatRulesText(config).join("\n"), "Current rules");
@@ -2690,7 +2828,6 @@ var import_scanner2 = require("@viberails/scanner");
2690
2828
  var import_chalk12 = __toESM(require("chalk"), 1);
2691
2829
 
2692
2830
  // src/utils/check-prerequisites.ts
2693
- var import_node_child_process5 = require("child_process");
2694
2831
  var fs14 = __toESM(require("fs"), 1);
2695
2832
  var path14 = __toESM(require("path"), 1);
2696
2833
  var clack7 = __toESM(require("@clack/prompts"), 1);
@@ -2739,7 +2876,7 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
2739
2876
  const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
2740
2877
  return `\u2717 ${p.label} \u2014 ${detail}`;
2741
2878
  }).join("\n");
2742
- clack7.note(prereqLines, "Coverage prerequisites");
2879
+ clack7.note(prereqLines, "Coverage support");
2743
2880
  let disableCoverage = false;
2744
2881
  for (const m of missing) {
2745
2882
  if (!m.installCommand) continue;
@@ -2750,13 +2887,13 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
2750
2887
  options: [
2751
2888
  {
2752
2889
  value: "install",
2753
- label: `Yes, install now`,
2890
+ label: "Install now",
2754
2891
  hint: m.installCommand
2755
2892
  },
2756
2893
  {
2757
2894
  value: "disable",
2758
- label: "No, disable coverage percentage checks",
2759
- hint: "missing-test checks still active"
2895
+ label: "Disable coverage checks",
2896
+ hint: "missing-test checks still stay active"
2760
2897
  },
2761
2898
  {
2762
2899
  value: "skip",
@@ -2769,12 +2906,7 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
2769
2906
  if (choice === "install") {
2770
2907
  const is = clack7.spinner();
2771
2908
  is.start(`Installing ${m.label}...`);
2772
- const result = (0, import_node_child_process5.spawnSync)(m.installCommand, {
2773
- cwd: projectRoot,
2774
- shell: true,
2775
- encoding: "utf-8",
2776
- stdio: "pipe"
2777
- });
2909
+ const result = await spawnAsync(m.installCommand, projectRoot);
2778
2910
  if (result.status === 0) {
2779
2911
  is.stop(`Installed ${m.label}`);
2780
2912
  } else {
@@ -3221,9 +3353,12 @@ async function initCommand(options, cwd) {
3221
3353
  }
3222
3354
  const configPath = path19.join(projectRoot, CONFIG_FILE5);
3223
3355
  if (fs19.existsSync(configPath) && !options.force) {
3356
+ if (!options.yes) {
3357
+ return initInteractive(projectRoot, configPath, options);
3358
+ }
3224
3359
  console.log(
3225
3360
  `${import_chalk12.default.yellow("!")} viberails is already initialized.
3226
- Run ${import_chalk12.default.cyan("viberails config")} to edit rules, ${import_chalk12.default.cyan("viberails sync")} to update, or ${import_chalk12.default.cyan("viberails init --force")} to start fresh.`
3361
+ Run ${import_chalk12.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk12.default.cyan("viberails sync")} to update generated files, or ${import_chalk12.default.cyan("viberails init --force")} to replace it.`
3227
3362
  );
3228
3363
  return;
3229
3364
  }
@@ -3301,6 +3436,19 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
3301
3436
  }
3302
3437
  async function initInteractive(projectRoot, configPath, options) {
3303
3438
  clack8.intro("viberails");
3439
+ const replacingExistingConfig = fs19.existsSync(configPath);
3440
+ if (fs19.existsSync(configPath) && !options.force) {
3441
+ const action = await promptExistingConfigAction(path19.basename(configPath));
3442
+ if (action === "cancel") {
3443
+ clack8.outro("Aborted. No files were written.");
3444
+ return;
3445
+ }
3446
+ if (action === "edit") {
3447
+ await configCommand({ suppressIntro: true }, projectRoot);
3448
+ return;
3449
+ }
3450
+ options.force = true;
3451
+ }
3304
3452
  if (fs19.existsSync(configPath) && options.force) {
3305
3453
  const replace = await confirmDangerous(
3306
3454
  `${path19.basename(configPath)} already exists and will be replaced. Continue?`
@@ -3320,10 +3468,18 @@ async function initInteractive(projectRoot, configPath, options) {
3320
3468
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3321
3469
  );
3322
3470
  }
3323
- clack8.note(formatScanResultsText(scanResult), "Scan results");
3324
3471
  const exemptedPkgs = getExemptedPackages(config);
3325
- displayInitSummary(config, exemptedPkgs);
3326
- const decision = await promptInitDecision();
3472
+ let decision;
3473
+ while (true) {
3474
+ displayInitOverview(scanResult, config, exemptedPkgs);
3475
+ const nextDecision = await promptInitDecision();
3476
+ if (nextDecision === "review") {
3477
+ clack8.note(formatScanResultsText(scanResult), "Detected details");
3478
+ continue;
3479
+ }
3480
+ decision = nextDecision;
3481
+ break;
3482
+ }
3327
3483
  if (decision === "customize") {
3328
3484
  const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
3329
3485
  const overrides = await promptRuleMenu({
@@ -3340,10 +3496,10 @@ async function initInteractive(projectRoot, configPath, options) {
3340
3496
  }
3341
3497
  if (config.packages.length > 1) {
3342
3498
  clack8.note(
3343
- "Boundary rules prevent packages from importing where they\nshouldn't. viberails scans your existing imports and creates\nrules based on what's already working.",
3499
+ "Optional for monorepos. viberails can infer package boundaries\nfrom imports that already work today, so you start with rules\nthat match the current codebase.",
3344
3500
  "Boundaries"
3345
3501
  );
3346
- const shouldInfer = await confirm3("Infer boundary rules from import patterns?");
3502
+ const shouldInfer = await confirm3("Infer boundary rules from current import patterns?");
3347
3503
  if (shouldInfer) {
3348
3504
  const bs = clack8.spinner();
3349
3505
  bs.start("Building import graph...");
@@ -3379,7 +3535,11 @@ async function initInteractive(projectRoot, configPath, options) {
3379
3535
  packageManager: rootPkgStack?.packageManager?.split("@")[0],
3380
3536
  isWorkspace: config.packages.length > 1
3381
3537
  });
3382
- const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
3538
+ displaySetupPlan(config, integrations, {
3539
+ replacingExistingConfig,
3540
+ configFile: path19.basename(configPath)
3541
+ });
3542
+ const shouldWrite = await confirm3("Apply this setup?");
3383
3543
  if (!shouldWrite) {
3384
3544
  clack8.outro("Aborted. No files were written.");
3385
3545
  return;
@@ -3410,7 +3570,7 @@ async function initInteractive(projectRoot, configPath, options) {
3410
3570
  var fs20 = __toESM(require("fs"), 1);
3411
3571
  var path20 = __toESM(require("path"), 1);
3412
3572
  var clack9 = __toESM(require("@clack/prompts"), 1);
3413
- var import_config9 = require("@viberails/config");
3573
+ var import_config10 = require("@viberails/config");
3414
3574
  var import_scanner3 = require("@viberails/scanner");
3415
3575
  var import_chalk13 = __toESM(require("chalk"), 1);
3416
3576
  var CONFIG_FILE6 = "viberails.config.json";
@@ -3436,14 +3596,14 @@ async function syncCommand(options, cwd) {
3436
3596
  );
3437
3597
  }
3438
3598
  const configPath = path20.join(projectRoot, CONFIG_FILE6);
3439
- const existing = await (0, import_config9.loadConfig)(configPath);
3599
+ const existing = await (0, import_config10.loadConfig)(configPath);
3440
3600
  const previousStats = loadPreviousStats(projectRoot);
3441
3601
  const s = clack9.spinner();
3442
3602
  s.start("Scanning project...");
3443
3603
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3444
3604
  s.stop("Scan complete");
3445
- const merged = (0, import_config9.mergeConfig)(existing, scanResult);
3446
- const compacted = (0, import_config9.compactConfig)(merged);
3605
+ const merged = (0, import_config10.mergeConfig)(existing, scanResult);
3606
+ const compacted = (0, import_config10.compactConfig)(merged);
3447
3607
  const compactedJson = JSON.stringify(compacted, null, 2);
3448
3608
  const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
3449
3609
  const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
@@ -3491,7 +3651,7 @@ ${import_chalk13.default.bold("Changes:")}`);
3491
3651
  packageOverrides: merged.packages
3492
3652
  });
3493
3653
  applyRuleOverrides(merged, overrides);
3494
- const recompacted = (0, import_config9.compactConfig)(merged);
3654
+ const recompacted = (0, import_config10.compactConfig)(merged);
3495
3655
  fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3496
3656
  `);
3497
3657
  writeGeneratedFiles(projectRoot, merged, scanResult);
@@ -3515,7 +3675,7 @@ ${import_chalk13.default.bold("Synced:")}`);
3515
3675
  }
3516
3676
 
3517
3677
  // src/index.ts
3518
- var VERSION = "0.6.3";
3678
+ var VERSION = "0.6.4";
3519
3679
  var program = new import_commander.Command();
3520
3680
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3521
3681
  program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {