viberails 0.2.2 → 0.3.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import chalk10 from "chalk";
4
+ import chalk11 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/boundaries.ts
@@ -66,7 +66,7 @@ function resolveWorkspacePackages(projectRoot, workspace) {
66
66
  ];
67
67
  packages.push({ name, path: absPath, relativePath, internalDeps: allDeps });
68
68
  }
69
- const packageNames = new Set(packages.map((p) => p.name));
69
+ const packageNames = new Set(packages.map((p3) => p3.name));
70
70
  for (const pkg of packages) {
71
71
  pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));
72
72
  }
@@ -96,23 +96,37 @@ async function boundariesCommand(options, cwd) {
96
96
  }
97
97
  displayRules(config);
98
98
  }
99
+ function countBoundaries(boundaries) {
100
+ if (!boundaries) return 0;
101
+ if (Array.isArray(boundaries)) return boundaries.length;
102
+ return Object.values(boundaries).reduce((sum, denied) => sum + denied.length, 0);
103
+ }
99
104
  function displayRules(config) {
100
- if (!config.boundaries || config.boundaries.length === 0) {
105
+ const total = countBoundaries(config.boundaries);
106
+ if (total === 0) {
101
107
  console.log(chalk.yellow("No boundary rules configured."));
102
108
  console.log(`Run ${chalk.cyan("viberails boundaries --infer")} to generate rules.`);
103
109
  return;
104
110
  }
105
- const allowRules = config.boundaries.filter((r) => r.allow);
106
- const denyRules = config.boundaries.filter((r) => !r.allow);
107
111
  console.log(`
108
- ${chalk.bold(`Boundary rules (${config.boundaries.length} rules):`)}
112
+ ${chalk.bold(`Boundary rules (${total} rules):`)}
109
113
  `);
110
- for (const r of allowRules) {
111
- console.log(` ${chalk.green("\u2713")} ${r.from} \u2192 ${r.to}`);
112
- }
113
- for (const r of denyRules) {
114
- const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
115
- console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
114
+ if (Array.isArray(config.boundaries)) {
115
+ const allowRules = config.boundaries.filter((r) => r.allow);
116
+ const denyRules = config.boundaries.filter((r) => !r.allow);
117
+ for (const r of allowRules) {
118
+ console.log(` ${chalk.green("\u2713")} ${r.from} \u2192 ${r.to}`);
119
+ }
120
+ for (const r of denyRules) {
121
+ const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
122
+ console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
123
+ }
124
+ } else if (config.boundaries) {
125
+ for (const [from, denied] of Object.entries(config.boundaries)) {
126
+ for (const to of denied) {
127
+ console.log(` ${chalk.red("\u2717")} ${from} \u2192 ${to}`);
128
+ }
129
+ }
116
130
  }
117
131
  console.log(
118
132
  `
@@ -129,31 +143,29 @@ async function inferAndDisplay(projectRoot, config, configPath) {
129
143
  });
130
144
  console.log(chalk.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
131
145
  const inferred = inferBoundaries(graph);
132
- if (inferred.length === 0) {
146
+ const entries = Object.entries(inferred);
147
+ if (entries.length === 0) {
133
148
  console.log(chalk.yellow("No boundary rules could be inferred."));
134
149
  return;
135
150
  }
136
- const allow = inferred.filter((r) => r.allow);
137
- const deny = inferred.filter((r) => !r.allow);
151
+ const totalRules = entries.reduce((sum, [, denied]) => sum + denied.length, 0);
138
152
  console.log(`
139
153
  ${chalk.bold("Inferred boundary rules:")}
140
154
  `);
141
- for (const r of allow) {
142
- console.log(` ${chalk.green("\u2713")} ${r.from} \u2192 ${r.to}`);
143
- }
144
- for (const r of deny) {
145
- const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
146
- console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
155
+ for (const [from, denied] of entries) {
156
+ for (const to of denied) {
157
+ console.log(` ${chalk.red("\u2717")} ${from} \u2192 ${to}`);
158
+ }
147
159
  }
148
160
  console.log(`
149
- ${allow.length} allowed, ${deny.length} denied`);
161
+ ${totalRules} deny rules`);
150
162
  const shouldSave = await confirm("\nSave to viberails.config.json?");
151
163
  if (shouldSave) {
152
164
  config.boundaries = inferred;
153
165
  config.rules.enforceBoundaries = true;
154
166
  fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
155
167
  `);
156
- console.log(`${chalk.green("\u2713")} Saved ${inferred.length} rules`);
168
+ console.log(`${chalk.green("\u2713")} Saved ${totalRules} rules`);
157
169
  }
158
170
  }
159
171
  async function showGraph(projectRoot, config) {
@@ -234,6 +246,10 @@ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
234
246
  ".svelte-kit",
235
247
  ".turbo",
236
248
  "coverage",
249
+ "public",
250
+ "vendor",
251
+ "__generated__",
252
+ "generated",
237
253
  ".viberails"
238
254
  ]);
239
255
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
@@ -255,12 +271,19 @@ var NAMING_PATTERNS = {
255
271
  };
256
272
  function isIgnored(relPath, ignorePatterns) {
257
273
  for (const pattern of ignorePatterns) {
258
- if (pattern.endsWith("/**")) {
274
+ const startsGlob = pattern.startsWith("**/");
275
+ const endsGlob = pattern.endsWith("/**");
276
+ if (startsGlob && endsGlob) {
277
+ const middle = pattern.slice(3, -3);
278
+ if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
279
+ return true;
280
+ }
281
+ } else if (endsGlob) {
259
282
  const prefix = pattern.slice(0, -3);
260
283
  if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
261
- } else if (pattern.startsWith("**/")) {
284
+ } else if (startsGlob) {
262
285
  const suffix = pattern.slice(3);
263
- if (relPath.endsWith(suffix)) return true;
286
+ if (relPath.endsWith(suffix) || relPath === suffix) return true;
264
287
  } else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
265
288
  return true;
266
289
  }
@@ -380,13 +403,13 @@ function checkMissingTests(projectRoot, config, severity) {
380
403
  const testSuffix = testPattern.replace("*", "");
381
404
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
382
405
  for (const relFile of sourceFiles) {
383
- const basename6 = path5.basename(relFile);
384
- if (basename6.includes(".test.") || basename6.includes(".spec.") || basename6.startsWith("index.") || basename6.endsWith(".d.ts")) {
406
+ const basename7 = path5.basename(relFile);
407
+ if (basename7.includes(".test.") || basename7.includes(".spec.") || basename7.startsWith("index.") || basename7.endsWith(".d.ts")) {
385
408
  continue;
386
409
  }
387
- const ext = path5.extname(basename6);
410
+ const ext = path5.extname(basename7);
388
411
  if (!SOURCE_EXTS2.has(ext)) continue;
389
- const stem = basename6.slice(0, basename6.indexOf("."));
412
+ const stem = basename7.slice(0, basename7.indexOf("."));
390
413
  const expectedTestFile = `${stem}${testSuffix}`;
391
414
  const dir = path5.dirname(path5.join(projectRoot, relFile));
392
415
  const colocatedTest = path5.join(dir, expectedTestFile);
@@ -407,6 +430,50 @@ function checkMissingTests(projectRoot, config, severity) {
407
430
 
408
431
  // src/commands/check.ts
409
432
  var CONFIG_FILE2 = "viberails.config.json";
433
+ function isTestFile(relPath) {
434
+ const filename = path6.basename(relPath);
435
+ return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
436
+ }
437
+ function printGroupedViolations(violations, limit) {
438
+ const groups = /* @__PURE__ */ new Map();
439
+ for (const v of violations) {
440
+ const existing = groups.get(v.rule) ?? [];
441
+ existing.push(v);
442
+ groups.set(v.rule, existing);
443
+ }
444
+ const ruleOrder = ["file-size", "file-naming", "missing-test", "boundary-violation"];
445
+ const sortedKeys = [...groups.keys()].sort(
446
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
447
+ );
448
+ let totalShown = 0;
449
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
450
+ for (const rule of sortedKeys) {
451
+ const group = groups.get(rule);
452
+ if (!group) continue;
453
+ const remaining = totalLimit - totalShown;
454
+ if (remaining <= 0) break;
455
+ const toShow = group.slice(0, remaining);
456
+ const hidden = group.length - toShow.length;
457
+ for (const v of toShow) {
458
+ const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
459
+ console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
460
+ }
461
+ totalShown += toShow.length;
462
+ if (hidden > 0) {
463
+ console.log(chalk2.dim(` ... and ${hidden} more ${rule} violations`));
464
+ }
465
+ }
466
+ }
467
+ function printSummary(violations) {
468
+ const counts = /* @__PURE__ */ new Map();
469
+ for (const v of violations) {
470
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
471
+ }
472
+ const word = violations.length === 1 ? "violation" : "violations";
473
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
474
+ console.log(`
475
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
476
+ }
410
477
  async function checkCommand(options, cwd) {
411
478
  const startDir = cwd ?? process.cwd();
412
479
  const projectRoot = findProjectRoot(startDir);
@@ -443,13 +510,15 @@ async function checkCommand(options, cwd) {
443
510
  if (isIgnored(relPath, effectiveIgnore)) continue;
444
511
  if (!fs6.existsSync(absPath)) continue;
445
512
  const resolved = resolveConfigForFile(relPath, config);
446
- if (resolved.rules.maxFileLines > 0) {
513
+ const testFile = isTestFile(relPath);
514
+ const maxLines = testFile ? resolved.rules.maxTestFileLines : resolved.rules.maxFileLines;
515
+ if (maxLines > 0) {
447
516
  const lines = countFileLines(absPath);
448
- if (lines !== null && lines > resolved.rules.maxFileLines) {
517
+ if (lines !== null && lines > maxLines) {
449
518
  violations.push({
450
519
  file: relPath,
451
520
  rule: "file-size",
452
- message: `${lines} lines (max ${resolved.rules.maxFileLines}). Split into focused modules.`,
521
+ message: `${lines} lines (max ${maxLines}). Split into focused modules.`,
453
522
  severity
454
523
  });
455
524
  }
@@ -470,7 +539,8 @@ async function checkCommand(options, cwd) {
470
539
  const testViolations = checkMissingTests(projectRoot, config, severity);
471
540
  violations.push(...testViolations);
472
541
  }
473
- if (config.rules.enforceBoundaries && config.boundaries && config.boundaries.length > 0 && !options.noBoundaries) {
542
+ const hasBoundaries = config.boundaries ? Array.isArray(config.boundaries) ? config.boundaries.length > 0 : Object.keys(config.boundaries).length > 0 : false;
543
+ if (config.rules.enforceBoundaries && hasBoundaries && !options.noBoundaries) {
474
544
  const startTime = Date.now();
475
545
  const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
476
546
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
@@ -497,13 +567,10 @@ async function checkCommand(options, cwd) {
497
567
  console.log(`${chalk2.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
498
568
  return 0;
499
569
  }
500
- for (const v of violations) {
501
- const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
502
- console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
570
+ if (!options.quiet) {
571
+ printGroupedViolations(violations, options.limit);
503
572
  }
504
- const word = violations.length === 1 ? "violation" : "violations";
505
- console.log(`
506
- ${violations.length} ${word} found.`);
573
+ printSummary(violations);
507
574
  if (config.enforcement === "enforce") {
508
575
  console.log(chalk2.red("Fix violations before committing."));
509
576
  return 1;
@@ -751,8 +818,8 @@ import * as path9 from "path";
751
818
  function generateTestStub(sourceRelPath, config, projectRoot) {
752
819
  const { testPattern } = config.structure;
753
820
  if (!testPattern) return null;
754
- const basename6 = path9.basename(sourceRelPath);
755
- const stem = basename6.slice(0, basename6.indexOf("."));
821
+ const basename7 = path9.basename(sourceRelPath);
822
+ const stem = basename7.slice(0, basename7.indexOf("."));
756
823
  const testSuffix = testPattern.replace("*", "");
757
824
  const testFilename = `${stem}${testSuffix}`;
758
825
  const dir = path9.dirname(path9.join(projectRoot, sourceRelPath));
@@ -878,223 +945,10 @@ async function fixCommand(options, cwd) {
878
945
  // src/commands/init.ts
879
946
  import * as fs12 from "fs";
880
947
  import * as path13 from "path";
948
+ import * as p2 from "@clack/prompts";
881
949
  import { generateConfig } from "@viberails/config";
882
950
  import { scan } from "@viberails/scanner";
883
- import chalk8 from "chalk";
884
-
885
- // src/display.ts
886
- import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
887
- import chalk6 from "chalk";
888
-
889
- // src/display-helpers.ts
890
- import { ROLE_DESCRIPTIONS } from "@viberails/types";
891
- function groupByRole(directories) {
892
- const map = /* @__PURE__ */ new Map();
893
- for (const dir of directories) {
894
- if (dir.role === "unknown") continue;
895
- const existing = map.get(dir.role);
896
- if (existing) {
897
- existing.dirs.push(dir);
898
- } else {
899
- map.set(dir.role, { dirs: [dir] });
900
- }
901
- }
902
- const groups = [];
903
- for (const [role, { dirs }] of map) {
904
- const label = ROLE_DESCRIPTIONS[role] ?? role;
905
- const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
906
- groups.push({
907
- role,
908
- label,
909
- dirCount: dirs.length,
910
- totalFiles,
911
- singlePath: dirs.length === 1 ? dirs[0].path : void 0
912
- });
913
- }
914
- return groups;
915
- }
916
- function formatSummary(stats, packageCount) {
917
- const parts = [];
918
- if (packageCount && packageCount > 1) {
919
- parts.push(`${packageCount} packages`);
920
- }
921
- parts.push(`${stats.totalFiles.toLocaleString()} source files`);
922
- parts.push(`${stats.totalLines.toLocaleString()} lines`);
923
- parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
924
- return parts.join(" \xB7 ");
925
- }
926
- function formatExtensions(filesByExtension, maxEntries = 4) {
927
- return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
928
- }
929
- function formatRoleGroup(group) {
930
- const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
931
- if (group.singlePath) {
932
- return `${group.label} \u2014 ${group.singlePath} (${files})`;
933
- }
934
- const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
935
- return `${group.label} \u2014 ${dirs} (${files})`;
936
- }
937
-
938
- // src/display-monorepo.ts
939
- import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
940
- import chalk5 from "chalk";
941
- function formatPackageSummary(pkg) {
942
- const parts = [];
943
- if (pkg.stack.framework) {
944
- parts.push(formatItem(pkg.stack.framework, FRAMEWORK_NAMES));
945
- }
946
- if (pkg.stack.styling) {
947
- parts.push(formatItem(pkg.stack.styling, STYLING_NAMES));
948
- }
949
- const files = `${pkg.statistics.totalFiles} files`;
950
- const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
951
- return ` ${pkg.relativePath} \u2014 ${detail}`;
952
- }
953
- function displayMonorepoResults(scanResult) {
954
- const { stack, packages } = scanResult;
955
- console.log(`
956
- ${chalk5.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
957
- console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.language)}`);
958
- if (stack.packageManager) {
959
- console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.packageManager)}`);
960
- }
961
- if (stack.linter) {
962
- console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)}`);
963
- }
964
- if (stack.formatter) {
965
- console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.formatter)}`);
966
- }
967
- if (stack.testRunner) {
968
- console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.testRunner)}`);
969
- }
970
- console.log("");
971
- for (const pkg of packages) {
972
- console.log(formatPackageSummary(pkg));
973
- }
974
- const packagesWithDirs = packages.filter(
975
- (pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
976
- );
977
- if (packagesWithDirs.length > 0) {
978
- console.log(`
979
- ${chalk5.bold("Structure:")}`);
980
- for (const pkg of packagesWithDirs) {
981
- const groups = groupByRole(pkg.structure.directories);
982
- if (groups.length === 0) continue;
983
- console.log(` ${pkg.relativePath}:`);
984
- for (const group of groups) {
985
- console.log(` ${chalk5.green("\u2713")} ${formatRoleGroup(group)}`);
986
- }
987
- }
988
- }
989
- displayConventions(scanResult);
990
- displaySummarySection(scanResult);
991
- console.log("");
992
- }
993
-
994
- // src/display.ts
995
- var CONVENTION_LABELS = {
996
- fileNaming: "File naming",
997
- componentNaming: "Component naming",
998
- hookNaming: "Hook naming",
999
- importAlias: "Import alias"
1000
- };
1001
- function formatItem(item, nameMap) {
1002
- const name = nameMap?.[item.name] ?? item.name;
1003
- return item.version ? `${name} ${item.version}` : name;
1004
- }
1005
- function confidenceLabel(convention) {
1006
- const pct = Math.round(convention.consistency);
1007
- if (convention.confidence === "high") {
1008
- return `${pct}% \u2014 high confidence, will enforce`;
1009
- }
1010
- return `${pct}% \u2014 medium confidence, suggested only`;
1011
- }
1012
- function displayConventions(scanResult) {
1013
- const conventionEntries = Object.entries(scanResult.conventions);
1014
- if (conventionEntries.length === 0) return;
1015
- console.log(`
1016
- ${chalk6.bold("Conventions:")}`);
1017
- for (const [key, convention] of conventionEntries) {
1018
- if (convention.confidence === "low") continue;
1019
- const label = CONVENTION_LABELS[key] ?? key;
1020
- if (scanResult.packages.length > 1) {
1021
- const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1022
- const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1023
- if (allSame || pkgValues.length <= 1) {
1024
- const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
1025
- const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
1026
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1027
- } else {
1028
- console.log(` ${chalk6.yellow("~")} ${label}: varies by package`);
1029
- for (const pv of pkgValues) {
1030
- const pct = Math.round(pv.convention.consistency);
1031
- console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1032
- }
1033
- }
1034
- } else {
1035
- const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
1036
- const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
1037
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1038
- }
1039
- }
1040
- }
1041
- function displaySummarySection(scanResult) {
1042
- const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1043
- console.log(`
1044
- ${chalk6.bold("Summary:")}`);
1045
- console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1046
- const ext = formatExtensions(scanResult.statistics.filesByExtension);
1047
- if (ext) {
1048
- console.log(` ${ext}`);
1049
- }
1050
- }
1051
- function displayScanResults(scanResult) {
1052
- if (scanResult.packages.length > 1) {
1053
- displayMonorepoResults(scanResult);
1054
- return;
1055
- }
1056
- const { stack } = scanResult;
1057
- console.log(`
1058
- ${chalk6.bold("Detected:")}`);
1059
- if (stack.framework) {
1060
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
1061
- }
1062
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.language)}`);
1063
- if (stack.styling) {
1064
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
1065
- }
1066
- if (stack.backend) {
1067
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
1068
- }
1069
- if (stack.linter) {
1070
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.linter)}`);
1071
- }
1072
- if (stack.formatter) {
1073
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.formatter)}`);
1074
- }
1075
- if (stack.testRunner) {
1076
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.testRunner)}`);
1077
- }
1078
- if (stack.packageManager) {
1079
- console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.packageManager)}`);
1080
- }
1081
- if (stack.libraries.length > 0) {
1082
- for (const lib of stack.libraries) {
1083
- console.log(` ${chalk6.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
1084
- }
1085
- }
1086
- const groups = groupByRole(scanResult.structure.directories);
1087
- if (groups.length > 0) {
1088
- console.log(`
1089
- ${chalk6.bold("Structure:")}`);
1090
- for (const group of groups) {
1091
- console.log(` ${chalk6.green("\u2713")} ${formatRoleGroup(group)}`);
1092
- }
1093
- }
1094
- displayConventions(scanResult);
1095
- displaySummarySection(scanResult);
1096
- console.log("");
1097
- }
951
+ import chalk9 from "chalk";
1098
952
 
1099
953
  // src/utils/write-generated-files.ts
1100
954
  import * as fs10 from "fs";
@@ -1125,18 +979,60 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1125
979
  // src/commands/init-hooks.ts
1126
980
  import * as fs11 from "fs";
1127
981
  import * as path12 from "path";
1128
- import chalk7 from "chalk";
982
+ import chalk5 from "chalk";
983
+ function setupClaudeCodeHook(projectRoot) {
984
+ const claudeDir = path12.join(projectRoot, ".claude");
985
+ if (!fs11.existsSync(claudeDir)) {
986
+ fs11.mkdirSync(claudeDir, { recursive: true });
987
+ }
988
+ const settingsPath = path12.join(claudeDir, "settings.json");
989
+ let settings = {};
990
+ if (fs11.existsSync(settingsPath)) {
991
+ try {
992
+ settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
993
+ } catch {
994
+ }
995
+ }
996
+ const hooks = settings.hooks ?? {};
997
+ const postToolUse = hooks.PostToolUse;
998
+ if (Array.isArray(postToolUse)) {
999
+ const hasViberails = postToolUse.some(
1000
+ (entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
1001
+ (h) => typeof h === "object" && h !== null && typeof h.command === "string" && h.command.includes("viberails")
1002
+ )
1003
+ );
1004
+ if (hasViberails) return;
1005
+ }
1006
+ const viberailsHook = {
1007
+ matcher: "Edit|Write",
1008
+ hooks: [
1009
+ {
1010
+ type: "command",
1011
+ command: "jq -r '.tool_input.file_path' | xargs npx viberails check --files"
1012
+ }
1013
+ ]
1014
+ };
1015
+ if (!hooks.PostToolUse) {
1016
+ hooks.PostToolUse = [viberailsHook];
1017
+ } else if (Array.isArray(hooks.PostToolUse)) {
1018
+ hooks.PostToolUse.push(viberailsHook);
1019
+ }
1020
+ settings.hooks = hooks;
1021
+ fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
1022
+ `);
1023
+ console.log(` ${chalk5.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
1024
+ }
1129
1025
  function setupPreCommitHook(projectRoot) {
1130
1026
  const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1131
1027
  if (fs11.existsSync(lefthookPath)) {
1132
1028
  addLefthookPreCommit(lefthookPath);
1133
- console.log(` ${chalk7.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1029
+ console.log(` ${chalk5.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1134
1030
  return;
1135
1031
  }
1136
1032
  const huskyDir = path12.join(projectRoot, ".husky");
1137
1033
  if (fs11.existsSync(huskyDir)) {
1138
1034
  writeHuskyPreCommit(huskyDir);
1139
- console.log(` ${chalk7.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1035
+ console.log(` ${chalk5.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1140
1036
  return;
1141
1037
  }
1142
1038
  const gitDir = path12.join(projectRoot, ".git");
@@ -1146,7 +1042,7 @@ function setupPreCommitHook(projectRoot) {
1146
1042
  fs11.mkdirSync(hooksDir, { recursive: true });
1147
1043
  }
1148
1044
  writeGitHookPreCommit(hooksDir);
1149
- console.log(` ${chalk7.green("\u2713")} .git/hooks/pre-commit`);
1045
+ console.log(` ${chalk5.green("\u2713")} .git/hooks/pre-commit`);
1150
1046
  }
1151
1047
  }
1152
1048
  function writeGitHookPreCommit(hooksDir) {
@@ -1195,6 +1091,187 @@ npx viberails check --staged
1195
1091
  fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1196
1092
  }
1197
1093
 
1094
+ // src/commands/init-wizard.ts
1095
+ import * as p from "@clack/prompts";
1096
+ import { FRAMEWORK_NAMES as FRAMEWORK_NAMES3, LIBRARY_NAMES as LIBRARY_NAMES2, STYLING_NAMES as STYLING_NAMES3 } from "@viberails/types";
1097
+ import chalk8 from "chalk";
1098
+
1099
+ // src/display.ts
1100
+ import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
1101
+ import chalk7 from "chalk";
1102
+
1103
+ // src/display-helpers.ts
1104
+ import { ROLE_DESCRIPTIONS } from "@viberails/types";
1105
+ function groupByRole(directories) {
1106
+ const map = /* @__PURE__ */ new Map();
1107
+ for (const dir of directories) {
1108
+ if (dir.role === "unknown") continue;
1109
+ const existing = map.get(dir.role);
1110
+ if (existing) {
1111
+ existing.dirs.push(dir);
1112
+ } else {
1113
+ map.set(dir.role, { dirs: [dir] });
1114
+ }
1115
+ }
1116
+ const groups = [];
1117
+ for (const [role, { dirs }] of map) {
1118
+ const label = ROLE_DESCRIPTIONS[role] ?? role;
1119
+ const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
1120
+ groups.push({
1121
+ role,
1122
+ label,
1123
+ dirCount: dirs.length,
1124
+ totalFiles,
1125
+ singlePath: dirs.length === 1 ? dirs[0].path : void 0
1126
+ });
1127
+ }
1128
+ return groups;
1129
+ }
1130
+ function formatSummary(stats, packageCount) {
1131
+ const parts = [];
1132
+ if (packageCount && packageCount > 1) {
1133
+ parts.push(`${packageCount} packages`);
1134
+ }
1135
+ parts.push(`${stats.totalFiles.toLocaleString()} source files`);
1136
+ parts.push(`${stats.totalLines.toLocaleString()} lines`);
1137
+ parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
1138
+ return parts.join(" \xB7 ");
1139
+ }
1140
+ function formatRoleGroup(group) {
1141
+ const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
1142
+ if (group.singlePath) {
1143
+ return `${group.label} \u2014 ${group.singlePath} (${files})`;
1144
+ }
1145
+ const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
1146
+ return `${group.label} \u2014 ${dirs} (${files})`;
1147
+ }
1148
+
1149
+ // src/display-monorepo.ts
1150
+ import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
1151
+ import chalk6 from "chalk";
1152
+
1153
+ // src/display.ts
1154
+ function formatItem(item, nameMap) {
1155
+ const name = nameMap?.[item.name] ?? item.name;
1156
+ return item.version ? `${name} ${item.version}` : name;
1157
+ }
1158
+
1159
+ // src/commands/init-wizard.ts
1160
+ var DEFAULT_WIZARD_RESULT = {
1161
+ enforcement: "warn",
1162
+ checks: {
1163
+ fileSize: true,
1164
+ naming: true,
1165
+ tests: true,
1166
+ boundaries: false
1167
+ },
1168
+ integration: ["pre-commit"]
1169
+ };
1170
+ async function runWizard(scanResult) {
1171
+ const isMonorepo = scanResult.packages.length > 1;
1172
+ displayScanSummary(scanResult);
1173
+ const enforcement = await p.select({
1174
+ message: "How strict should viberails be?",
1175
+ initialValue: "warn",
1176
+ options: [
1177
+ { value: "warn", label: "Warn", hint: "show issues, never block commits" },
1178
+ { value: "enforce", label: "Enforce", hint: "block commits with violations" }
1179
+ ]
1180
+ });
1181
+ if (p.isCancel(enforcement)) {
1182
+ p.cancel("Setup cancelled.");
1183
+ return null;
1184
+ }
1185
+ const checkOptions = [
1186
+ { value: "fileSize", label: "File size limit (300 lines)" },
1187
+ { value: "naming", label: "File naming conventions" },
1188
+ { value: "tests", label: "Missing test files" }
1189
+ ];
1190
+ if (isMonorepo) {
1191
+ checkOptions.push({ value: "boundaries", label: "Import boundaries" });
1192
+ }
1193
+ const enabledChecks = await p.multiselect({
1194
+ message: "Which checks should viberails run?",
1195
+ options: checkOptions,
1196
+ initialValues: ["fileSize", "naming", "tests"],
1197
+ required: false
1198
+ });
1199
+ if (p.isCancel(enabledChecks)) {
1200
+ p.cancel("Setup cancelled.");
1201
+ return null;
1202
+ }
1203
+ const checks = {
1204
+ fileSize: enabledChecks.includes("fileSize"),
1205
+ naming: enabledChecks.includes("naming"),
1206
+ tests: enabledChecks.includes("tests"),
1207
+ boundaries: enabledChecks.includes("boundaries")
1208
+ };
1209
+ const integrationOptions = [
1210
+ { value: "pre-commit", label: "Git pre-commit hook", hint: "runs on every commit" },
1211
+ {
1212
+ value: "claude-hook",
1213
+ label: "Claude Code hook",
1214
+ hint: "checks files as Claude edits them"
1215
+ },
1216
+ { value: "context-only", label: "Context files only", hint: "no hooks" }
1217
+ ];
1218
+ const integration = await p.multiselect({
1219
+ message: "Where should checks run?",
1220
+ options: integrationOptions,
1221
+ initialValues: ["pre-commit"],
1222
+ required: true
1223
+ });
1224
+ if (p.isCancel(integration)) {
1225
+ p.cancel("Setup cancelled.");
1226
+ return null;
1227
+ }
1228
+ const finalIntegration = integration.includes("context-only") ? ["context-only"] : integration;
1229
+ return {
1230
+ enforcement,
1231
+ checks,
1232
+ integration: finalIntegration
1233
+ };
1234
+ }
1235
+ function displayScanSummary(scanResult) {
1236
+ const { stack } = scanResult;
1237
+ const parts = [];
1238
+ if (stack.framework) parts.push(formatItem(stack.framework, FRAMEWORK_NAMES3));
1239
+ parts.push(formatItem(stack.language));
1240
+ if (stack.styling) parts.push(formatItem(stack.styling, STYLING_NAMES3));
1241
+ if (stack.backend) parts.push(formatItem(stack.backend, FRAMEWORK_NAMES3));
1242
+ p.log.info(`${chalk8.bold("Stack:")} ${parts.join(", ")}`);
1243
+ if (stack.linter || stack.formatter || stack.testRunner || stack.packageManager) {
1244
+ const tools = [];
1245
+ if (stack.linter) tools.push(formatItem(stack.linter));
1246
+ if (stack.formatter && stack.formatter !== stack.linter)
1247
+ tools.push(formatItem(stack.formatter));
1248
+ if (stack.testRunner) tools.push(formatItem(stack.testRunner));
1249
+ if (stack.packageManager) tools.push(formatItem(stack.packageManager));
1250
+ p.log.info(`${chalk8.bold("Tools:")} ${tools.join(", ")}`);
1251
+ }
1252
+ if (stack.libraries.length > 0) {
1253
+ const libs = stack.libraries.map((lib) => formatItem(lib, LIBRARY_NAMES2)).join(", ");
1254
+ p.log.info(`${chalk8.bold("Libraries:")} ${libs}`);
1255
+ }
1256
+ const groups = groupByRole(scanResult.structure.directories);
1257
+ if (groups.length > 0) {
1258
+ const structParts = groups.map((g) => formatRoleGroup(g));
1259
+ p.log.info(`${chalk8.bold("Structure:")} ${structParts.join(", ")}`);
1260
+ }
1261
+ const conventionEntries = Object.entries(scanResult.conventions).filter(
1262
+ ([, c]) => c.confidence !== "low"
1263
+ );
1264
+ if (conventionEntries.length > 0) {
1265
+ const convParts = conventionEntries.map(([, c]) => {
1266
+ const pct = Math.round(c.consistency);
1267
+ return `${c.value} (${pct}%)`;
1268
+ });
1269
+ p.log.info(`${chalk8.bold("Conventions:")} ${convParts.join(", ")}`);
1270
+ }
1271
+ const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1272
+ p.log.info(`${chalk8.bold("Summary:")} ${formatSummary(scanResult.statistics, pkgCount)}`);
1273
+ }
1274
+
1198
1275
  // src/commands/init.ts
1199
1276
  var CONFIG_FILE4 = "viberails.config.json";
1200
1277
  function filterHighConfidence(conventions) {
@@ -1209,6 +1286,13 @@ function filterHighConfidence(conventions) {
1209
1286
  }
1210
1287
  return filtered;
1211
1288
  }
1289
+ function applyWizardResult(config, wizard) {
1290
+ config.enforcement = wizard.enforcement;
1291
+ if (!wizard.checks.fileSize) config.rules.maxFileLines = 0;
1292
+ config.rules.enforceNaming = wizard.checks.naming;
1293
+ config.rules.requireTests = wizard.checks.tests;
1294
+ config.rules.enforceBoundaries = wizard.checks.boundaries;
1295
+ }
1212
1296
  async function initCommand(options, cwd) {
1213
1297
  const startDir = cwd ?? process.cwd();
1214
1298
  const projectRoot = findProjectRoot(startDir);
@@ -1220,64 +1304,69 @@ async function initCommand(options, cwd) {
1220
1304
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
1221
1305
  if (fs12.existsSync(configPath)) {
1222
1306
  console.log(
1223
- chalk8.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk8.cyan("viberails sync") + " to update the generated files."
1307
+ chalk9.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk9.cyan("viberails sync") + " to update the generated files."
1224
1308
  );
1225
1309
  return;
1226
1310
  }
1227
- console.log(chalk8.dim("Scanning project..."));
1311
+ p2.intro("viberails");
1312
+ const s = p2.spinner();
1313
+ s.start("Scanning project...");
1228
1314
  const scanResult = await scan(projectRoot);
1229
- displayScanResults(scanResult);
1315
+ s.stop("Scan complete");
1230
1316
  if (scanResult.statistics.totalFiles === 0) {
1231
- console.log(
1232
- chalk8.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + chalk8.cyan("viberails sync") + " after adding source files.\n"
1317
+ p2.log.warn(
1318
+ `No source files detected. viberails will generate context with minimal content.
1319
+ Run ${chalk9.cyan("viberails sync")} after adding source files.`
1233
1320
  );
1234
1321
  }
1235
- if (!options.yes) {
1236
- const accepted = await confirm("Does this look right?");
1237
- if (!accepted) {
1238
- console.log("Aborted.");
1239
- return;
1240
- }
1322
+ let wizard;
1323
+ if (options.yes) {
1324
+ wizard = { ...DEFAULT_WIZARD_RESULT };
1325
+ } else {
1326
+ const result = await runWizard(scanResult);
1327
+ if (!result) return;
1328
+ wizard = result;
1241
1329
  }
1242
1330
  const config = generateConfig(scanResult);
1243
1331
  if (options.yes) {
1244
1332
  config.conventions = filterHighConfidence(config.conventions);
1245
1333
  }
1246
- if (config.workspace && config.workspace.packages.length > 0) {
1247
- let shouldInfer = options.yes;
1248
- if (!options.yes) {
1249
- shouldInfer = await confirm("Infer boundary rules from import patterns?");
1250
- }
1251
- if (shouldInfer) {
1252
- console.log(chalk8.dim("Building import graph..."));
1253
- const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1254
- const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1255
- const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
1256
- const inferred = inferBoundaries(graph);
1257
- if (inferred.length > 0) {
1258
- config.boundaries = inferred;
1259
- config.rules.enforceBoundaries = true;
1260
- console.log(` ${chalk8.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1261
- }
1334
+ applyWizardResult(config, wizard);
1335
+ if (wizard.checks.boundaries && config.workspace && config.workspace.packages.length > 0) {
1336
+ s.start("Inferring boundary rules...");
1337
+ const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1338
+ const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1339
+ const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
1340
+ const inferred = inferBoundaries(graph);
1341
+ const ruleCount = Object.values(inferred).reduce((sum, denied) => sum + denied.length, 0);
1342
+ if (ruleCount > 0) {
1343
+ config.boundaries = inferred;
1344
+ s.stop(`Inferred ${ruleCount} boundary rules`);
1345
+ } else {
1346
+ s.stop("No boundary rules could be inferred");
1347
+ config.rules.enforceBoundaries = false;
1262
1348
  }
1263
1349
  }
1264
1350
  fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1265
1351
  `);
1266
1352
  writeGeneratedFiles(projectRoot, config, scanResult);
1267
1353
  updateGitignore(projectRoot);
1268
- setupPreCommitHook(projectRoot);
1269
- console.log(`
1270
- ${chalk8.bold("Created:")}`);
1271
- console.log(` ${chalk8.green("\u2713")} ${CONFIG_FILE4}`);
1272
- console.log(` ${chalk8.green("\u2713")} .viberails/context.md`);
1273
- console.log(` ${chalk8.green("\u2713")} .viberails/scan-result.json`);
1274
- console.log(`
1275
- ${chalk8.bold("Next steps:")}`);
1276
- console.log(` 1. Review ${chalk8.cyan("viberails.config.json")} and adjust rules`);
1277
- console.log(
1278
- ` 2. Commit ${chalk8.cyan("viberails.config.json")} and ${chalk8.cyan(".viberails/context.md")}`
1354
+ if (wizard.integration.includes("pre-commit")) {
1355
+ setupPreCommitHook(projectRoot);
1356
+ }
1357
+ if (wizard.integration.includes("claude-hook")) {
1358
+ setupClaudeCodeHook(projectRoot);
1359
+ }
1360
+ p2.log.success(`${chalk9.bold("Created:")}`);
1361
+ console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE4}`);
1362
+ console.log(` ${chalk9.green("\u2713")} .viberails/context.md`);
1363
+ console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json`);
1364
+ p2.outro(
1365
+ `${chalk9.bold("Next steps:")}
1366
+ 1. Review ${chalk9.cyan("viberails.config.json")} and adjust rules
1367
+ 2. Commit ${chalk9.cyan("viberails.config.json")} and ${chalk9.cyan(".viberails/context.md")}
1368
+ 3. Run ${chalk9.cyan("viberails check")} to verify your project passes`
1279
1369
  );
1280
- console.log(` 3. Run ${chalk8.cyan("viberails check")} to verify your project passes`);
1281
1370
  }
1282
1371
  function updateGitignore(projectRoot) {
1283
1372
  const gitignorePath = path13.join(projectRoot, ".gitignore");
@@ -1297,7 +1386,7 @@ import * as fs13 from "fs";
1297
1386
  import * as path14 from "path";
1298
1387
  import { loadConfig as loadConfig4, mergeConfig } from "@viberails/config";
1299
1388
  import { scan as scan2 } from "@viberails/scanner";
1300
- import chalk9 from "chalk";
1389
+ import chalk10 from "chalk";
1301
1390
  var CONFIG_FILE5 = "viberails.config.json";
1302
1391
  async function syncCommand(cwd) {
1303
1392
  const startDir = cwd ?? process.cwd();
@@ -1309,21 +1398,21 @@ async function syncCommand(cwd) {
1309
1398
  }
1310
1399
  const configPath = path14.join(projectRoot, CONFIG_FILE5);
1311
1400
  const existing = await loadConfig4(configPath);
1312
- console.log(chalk9.dim("Scanning project..."));
1401
+ console.log(chalk10.dim("Scanning project..."));
1313
1402
  const scanResult = await scan2(projectRoot);
1314
1403
  const merged = mergeConfig(existing, scanResult);
1315
1404
  fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1316
1405
  `);
1317
1406
  writeGeneratedFiles(projectRoot, merged, scanResult);
1318
1407
  console.log(`
1319
- ${chalk9.bold("Synced:")}`);
1320
- console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1321
- console.log(` ${chalk9.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1322
- console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1408
+ ${chalk10.bold("Synced:")}`);
1409
+ console.log(` ${chalk10.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1410
+ console.log(` ${chalk10.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1411
+ console.log(` ${chalk10.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1323
1412
  }
1324
1413
 
1325
1414
  // src/index.ts
1326
- var VERSION = "0.2.2";
1415
+ var VERSION = "0.3.0";
1327
1416
  var program = new Command();
1328
1417
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1329
1418
  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)").action(async (options) => {
@@ -1331,7 +1420,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1331
1420
  await initCommand(options);
1332
1421
  } catch (err) {
1333
1422
  const message = err instanceof Error ? err.message : String(err);
1334
- console.error(`${chalk10.red("Error:")} ${message}`);
1423
+ console.error(`${chalk11.red("Error:")} ${message}`);
1335
1424
  process.exit(1);
1336
1425
  }
1337
1426
  });
@@ -1340,30 +1429,32 @@ program.command("sync").description("Re-scan and update generated files").action
1340
1429
  await syncCommand();
1341
1430
  } catch (err) {
1342
1431
  const message = err instanceof Error ? err.message : String(err);
1343
- console.error(`${chalk10.red("Error:")} ${message}`);
1432
+ console.error(`${chalk11.red("Error:")} ${message}`);
1344
1433
  process.exit(1);
1345
1434
  }
1346
1435
  });
1347
- program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").action(async (options) => {
1348
- try {
1349
- const exitCode = await checkCommand({
1350
- ...options,
1351
- noBoundaries: options.boundaries === false
1352
- });
1353
- process.exit(exitCode);
1354
- } catch (err) {
1355
- const message = err instanceof Error ? err.message : String(err);
1356
- console.error(`${chalk10.red("Error:")} ${message}`);
1357
- process.exit(1);
1436
+ program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).action(
1437
+ async (options) => {
1438
+ try {
1439
+ const exitCode = await checkCommand({
1440
+ ...options,
1441
+ noBoundaries: options.boundaries === false
1442
+ });
1443
+ process.exit(exitCode);
1444
+ } catch (err) {
1445
+ const message = err instanceof Error ? err.message : String(err);
1446
+ console.error(`${chalk11.red("Error:")} ${message}`);
1447
+ process.exit(1);
1448
+ }
1358
1449
  }
1359
- });
1450
+ );
1360
1451
  program.command("fix").description("Auto-fix file naming violations and generate missing test stubs").option("--dry-run", "Show planned fixes without applying them").option("--rule <rules...>", "Fix only specific rules (file-naming, missing-test)").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
1361
1452
  try {
1362
1453
  const exitCode = await fixCommand(options);
1363
1454
  process.exit(exitCode);
1364
1455
  } catch (err) {
1365
1456
  const message = err instanceof Error ? err.message : String(err);
1366
- console.error(`${chalk10.red("Error:")} ${message}`);
1457
+ console.error(`${chalk11.red("Error:")} ${message}`);
1367
1458
  process.exit(1);
1368
1459
  }
1369
1460
  });
@@ -1372,7 +1463,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1372
1463
  await boundariesCommand(options);
1373
1464
  } catch (err) {
1374
1465
  const message = err instanceof Error ? err.message : String(err);
1375
- console.error(`${chalk10.red("Error:")} ${message}`);
1466
+ console.error(`${chalk11.red("Error:")} ${message}`);
1376
1467
  process.exit(1);
1377
1468
  }
1378
1469
  });