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.cjs CHANGED
@@ -34,7 +34,7 @@ __export(index_exports, {
34
34
  VERSION: () => VERSION
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
- var import_chalk10 = __toESM(require("chalk"), 1);
37
+ var import_chalk11 = __toESM(require("chalk"), 1);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/boundaries.ts
@@ -99,7 +99,7 @@ function resolveWorkspacePackages(projectRoot, workspace) {
99
99
  ];
100
100
  packages.push({ name, path: absPath, relativePath, internalDeps: allDeps });
101
101
  }
102
- const packageNames = new Set(packages.map((p) => p.name));
102
+ const packageNames = new Set(packages.map((p3) => p3.name));
103
103
  for (const pkg of packages) {
104
104
  pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));
105
105
  }
@@ -129,23 +129,37 @@ async function boundariesCommand(options, cwd) {
129
129
  }
130
130
  displayRules(config);
131
131
  }
132
+ function countBoundaries(boundaries) {
133
+ if (!boundaries) return 0;
134
+ if (Array.isArray(boundaries)) return boundaries.length;
135
+ return Object.values(boundaries).reduce((sum, denied) => sum + denied.length, 0);
136
+ }
132
137
  function displayRules(config) {
133
- if (!config.boundaries || config.boundaries.length === 0) {
138
+ const total = countBoundaries(config.boundaries);
139
+ if (total === 0) {
134
140
  console.log(import_chalk.default.yellow("No boundary rules configured."));
135
141
  console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
136
142
  return;
137
143
  }
138
- const allowRules = config.boundaries.filter((r) => r.allow);
139
- const denyRules = config.boundaries.filter((r) => !r.allow);
140
144
  console.log(`
141
- ${import_chalk.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
145
+ ${import_chalk.default.bold(`Boundary rules (${total} rules):`)}
142
146
  `);
143
- for (const r of allowRules) {
144
- console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
145
- }
146
- for (const r of denyRules) {
147
- const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
148
- console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
147
+ if (Array.isArray(config.boundaries)) {
148
+ const allowRules = config.boundaries.filter((r) => r.allow);
149
+ const denyRules = config.boundaries.filter((r) => !r.allow);
150
+ for (const r of allowRules) {
151
+ console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
152
+ }
153
+ for (const r of denyRules) {
154
+ const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
155
+ console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
156
+ }
157
+ } else if (config.boundaries) {
158
+ for (const [from, denied] of Object.entries(config.boundaries)) {
159
+ for (const to of denied) {
160
+ console.log(` ${import_chalk.default.red("\u2717")} ${from} \u2192 ${to}`);
161
+ }
162
+ }
149
163
  }
150
164
  console.log(
151
165
  `
@@ -162,31 +176,29 @@ async function inferAndDisplay(projectRoot, config, configPath) {
162
176
  });
163
177
  console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
164
178
  const inferred = inferBoundaries(graph);
165
- if (inferred.length === 0) {
179
+ const entries = Object.entries(inferred);
180
+ if (entries.length === 0) {
166
181
  console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
167
182
  return;
168
183
  }
169
- const allow = inferred.filter((r) => r.allow);
170
- const deny = inferred.filter((r) => !r.allow);
184
+ const totalRules = entries.reduce((sum, [, denied]) => sum + denied.length, 0);
171
185
  console.log(`
172
186
  ${import_chalk.default.bold("Inferred boundary rules:")}
173
187
  `);
174
- for (const r of allow) {
175
- console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
176
- }
177
- for (const r of deny) {
178
- const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
179
- console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
188
+ for (const [from, denied] of entries) {
189
+ for (const to of denied) {
190
+ console.log(` ${import_chalk.default.red("\u2717")} ${from} \u2192 ${to}`);
191
+ }
180
192
  }
181
193
  console.log(`
182
- ${allow.length} allowed, ${deny.length} denied`);
194
+ ${totalRules} deny rules`);
183
195
  const shouldSave = await confirm("\nSave to viberails.config.json?");
184
196
  if (shouldSave) {
185
197
  config.boundaries = inferred;
186
198
  config.rules.enforceBoundaries = true;
187
199
  fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
188
200
  `);
189
- console.log(`${import_chalk.default.green("\u2713")} Saved ${inferred.length} rules`);
201
+ console.log(`${import_chalk.default.green("\u2713")} Saved ${totalRules} rules`);
190
202
  }
191
203
  }
192
204
  async function showGraph(projectRoot, config) {
@@ -267,6 +279,10 @@ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
267
279
  ".svelte-kit",
268
280
  ".turbo",
269
281
  "coverage",
282
+ "public",
283
+ "vendor",
284
+ "__generated__",
285
+ "generated",
270
286
  ".viberails"
271
287
  ]);
272
288
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
@@ -288,12 +304,19 @@ var NAMING_PATTERNS = {
288
304
  };
289
305
  function isIgnored(relPath, ignorePatterns) {
290
306
  for (const pattern of ignorePatterns) {
291
- if (pattern.endsWith("/**")) {
307
+ const startsGlob = pattern.startsWith("**/");
308
+ const endsGlob = pattern.endsWith("/**");
309
+ if (startsGlob && endsGlob) {
310
+ const middle = pattern.slice(3, -3);
311
+ if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
312
+ return true;
313
+ }
314
+ } else if (endsGlob) {
292
315
  const prefix = pattern.slice(0, -3);
293
316
  if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
294
- } else if (pattern.startsWith("**/")) {
317
+ } else if (startsGlob) {
295
318
  const suffix = pattern.slice(3);
296
- if (relPath.endsWith(suffix)) return true;
319
+ if (relPath.endsWith(suffix) || relPath === suffix) return true;
297
320
  } else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
298
321
  return true;
299
322
  }
@@ -413,13 +436,13 @@ function checkMissingTests(projectRoot, config, severity) {
413
436
  const testSuffix = testPattern.replace("*", "");
414
437
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
415
438
  for (const relFile of sourceFiles) {
416
- const basename6 = path5.basename(relFile);
417
- if (basename6.includes(".test.") || basename6.includes(".spec.") || basename6.startsWith("index.") || basename6.endsWith(".d.ts")) {
439
+ const basename7 = path5.basename(relFile);
440
+ if (basename7.includes(".test.") || basename7.includes(".spec.") || basename7.startsWith("index.") || basename7.endsWith(".d.ts")) {
418
441
  continue;
419
442
  }
420
- const ext = path5.extname(basename6);
443
+ const ext = path5.extname(basename7);
421
444
  if (!SOURCE_EXTS2.has(ext)) continue;
422
- const stem = basename6.slice(0, basename6.indexOf("."));
445
+ const stem = basename7.slice(0, basename7.indexOf("."));
423
446
  const expectedTestFile = `${stem}${testSuffix}`;
424
447
  const dir = path5.dirname(path5.join(projectRoot, relFile));
425
448
  const colocatedTest = path5.join(dir, expectedTestFile);
@@ -440,6 +463,50 @@ function checkMissingTests(projectRoot, config, severity) {
440
463
 
441
464
  // src/commands/check.ts
442
465
  var CONFIG_FILE2 = "viberails.config.json";
466
+ function isTestFile(relPath) {
467
+ const filename = path6.basename(relPath);
468
+ return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
469
+ }
470
+ function printGroupedViolations(violations, limit) {
471
+ const groups = /* @__PURE__ */ new Map();
472
+ for (const v of violations) {
473
+ const existing = groups.get(v.rule) ?? [];
474
+ existing.push(v);
475
+ groups.set(v.rule, existing);
476
+ }
477
+ const ruleOrder = ["file-size", "file-naming", "missing-test", "boundary-violation"];
478
+ const sortedKeys = [...groups.keys()].sort(
479
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
480
+ );
481
+ let totalShown = 0;
482
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
483
+ for (const rule of sortedKeys) {
484
+ const group = groups.get(rule);
485
+ if (!group) continue;
486
+ const remaining = totalLimit - totalShown;
487
+ if (remaining <= 0) break;
488
+ const toShow = group.slice(0, remaining);
489
+ const hidden = group.length - toShow.length;
490
+ for (const v of toShow) {
491
+ const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
492
+ console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
493
+ }
494
+ totalShown += toShow.length;
495
+ if (hidden > 0) {
496
+ console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
497
+ }
498
+ }
499
+ }
500
+ function printSummary(violations) {
501
+ const counts = /* @__PURE__ */ new Map();
502
+ for (const v of violations) {
503
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
504
+ }
505
+ const word = violations.length === 1 ? "violation" : "violations";
506
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
507
+ console.log(`
508
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
509
+ }
443
510
  async function checkCommand(options, cwd) {
444
511
  const startDir = cwd ?? process.cwd();
445
512
  const projectRoot = findProjectRoot(startDir);
@@ -476,13 +543,15 @@ async function checkCommand(options, cwd) {
476
543
  if (isIgnored(relPath, effectiveIgnore)) continue;
477
544
  if (!fs6.existsSync(absPath)) continue;
478
545
  const resolved = resolveConfigForFile(relPath, config);
479
- if (resolved.rules.maxFileLines > 0) {
546
+ const testFile = isTestFile(relPath);
547
+ const maxLines = testFile ? resolved.rules.maxTestFileLines : resolved.rules.maxFileLines;
548
+ if (maxLines > 0) {
480
549
  const lines = countFileLines(absPath);
481
- if (lines !== null && lines > resolved.rules.maxFileLines) {
550
+ if (lines !== null && lines > maxLines) {
482
551
  violations.push({
483
552
  file: relPath,
484
553
  rule: "file-size",
485
- message: `${lines} lines (max ${resolved.rules.maxFileLines}). Split into focused modules.`,
554
+ message: `${lines} lines (max ${maxLines}). Split into focused modules.`,
486
555
  severity
487
556
  });
488
557
  }
@@ -503,7 +572,8 @@ async function checkCommand(options, cwd) {
503
572
  const testViolations = checkMissingTests(projectRoot, config, severity);
504
573
  violations.push(...testViolations);
505
574
  }
506
- if (config.rules.enforceBoundaries && config.boundaries && config.boundaries.length > 0 && !options.noBoundaries) {
575
+ const hasBoundaries = config.boundaries ? Array.isArray(config.boundaries) ? config.boundaries.length > 0 : Object.keys(config.boundaries).length > 0 : false;
576
+ if (config.rules.enforceBoundaries && hasBoundaries && !options.noBoundaries) {
507
577
  const startTime = Date.now();
508
578
  const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
509
579
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
@@ -530,13 +600,10 @@ async function checkCommand(options, cwd) {
530
600
  console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
531
601
  return 0;
532
602
  }
533
- for (const v of violations) {
534
- const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
535
- console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
603
+ if (!options.quiet) {
604
+ printGroupedViolations(violations, options.limit);
536
605
  }
537
- const word = violations.length === 1 ? "violation" : "violations";
538
- console.log(`
539
- ${violations.length} ${word} found.`);
606
+ printSummary(violations);
540
607
  if (config.enforcement === "enforce") {
541
608
  console.log(import_chalk2.default.red("Fix violations before committing."));
542
609
  return 1;
@@ -784,8 +851,8 @@ var path9 = __toESM(require("path"), 1);
784
851
  function generateTestStub(sourceRelPath, config, projectRoot) {
785
852
  const { testPattern } = config.structure;
786
853
  if (!testPattern) return null;
787
- const basename6 = path9.basename(sourceRelPath);
788
- const stem = basename6.slice(0, basename6.indexOf("."));
854
+ const basename7 = path9.basename(sourceRelPath);
855
+ const stem = basename7.slice(0, basename7.indexOf("."));
789
856
  const testSuffix = testPattern.replace("*", "");
790
857
  const testFilename = `${stem}${testSuffix}`;
791
858
  const dir = path9.dirname(path9.join(projectRoot, sourceRelPath));
@@ -911,223 +978,10 @@ async function fixCommand(options, cwd) {
911
978
  // src/commands/init.ts
912
979
  var fs12 = __toESM(require("fs"), 1);
913
980
  var path13 = __toESM(require("path"), 1);
981
+ var p2 = __toESM(require("@clack/prompts"), 1);
914
982
  var import_config4 = require("@viberails/config");
915
983
  var import_scanner = require("@viberails/scanner");
916
- var import_chalk8 = __toESM(require("chalk"), 1);
917
-
918
- // src/display.ts
919
- var import_types3 = require("@viberails/types");
920
- var import_chalk6 = __toESM(require("chalk"), 1);
921
-
922
- // src/display-helpers.ts
923
- var import_types = require("@viberails/types");
924
- function groupByRole(directories) {
925
- const map = /* @__PURE__ */ new Map();
926
- for (const dir of directories) {
927
- if (dir.role === "unknown") continue;
928
- const existing = map.get(dir.role);
929
- if (existing) {
930
- existing.dirs.push(dir);
931
- } else {
932
- map.set(dir.role, { dirs: [dir] });
933
- }
934
- }
935
- const groups = [];
936
- for (const [role, { dirs }] of map) {
937
- const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
938
- const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
939
- groups.push({
940
- role,
941
- label,
942
- dirCount: dirs.length,
943
- totalFiles,
944
- singlePath: dirs.length === 1 ? dirs[0].path : void 0
945
- });
946
- }
947
- return groups;
948
- }
949
- function formatSummary(stats, packageCount) {
950
- const parts = [];
951
- if (packageCount && packageCount > 1) {
952
- parts.push(`${packageCount} packages`);
953
- }
954
- parts.push(`${stats.totalFiles.toLocaleString()} source files`);
955
- parts.push(`${stats.totalLines.toLocaleString()} lines`);
956
- parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
957
- return parts.join(" \xB7 ");
958
- }
959
- function formatExtensions(filesByExtension, maxEntries = 4) {
960
- return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
961
- }
962
- function formatRoleGroup(group) {
963
- const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
964
- if (group.singlePath) {
965
- return `${group.label} \u2014 ${group.singlePath} (${files})`;
966
- }
967
- const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
968
- return `${group.label} \u2014 ${dirs} (${files})`;
969
- }
970
-
971
- // src/display-monorepo.ts
972
- var import_types2 = require("@viberails/types");
973
- var import_chalk5 = __toESM(require("chalk"), 1);
974
- function formatPackageSummary(pkg) {
975
- const parts = [];
976
- if (pkg.stack.framework) {
977
- parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
978
- }
979
- if (pkg.stack.styling) {
980
- parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
981
- }
982
- const files = `${pkg.statistics.totalFiles} files`;
983
- const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
984
- return ` ${pkg.relativePath} \u2014 ${detail}`;
985
- }
986
- function displayMonorepoResults(scanResult) {
987
- const { stack, packages } = scanResult;
988
- console.log(`
989
- ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
990
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
991
- if (stack.packageManager) {
992
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
993
- }
994
- if (stack.linter) {
995
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
996
- }
997
- if (stack.formatter) {
998
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
999
- }
1000
- if (stack.testRunner) {
1001
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1002
- }
1003
- console.log("");
1004
- for (const pkg of packages) {
1005
- console.log(formatPackageSummary(pkg));
1006
- }
1007
- const packagesWithDirs = packages.filter(
1008
- (pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
1009
- );
1010
- if (packagesWithDirs.length > 0) {
1011
- console.log(`
1012
- ${import_chalk5.default.bold("Structure:")}`);
1013
- for (const pkg of packagesWithDirs) {
1014
- const groups = groupByRole(pkg.structure.directories);
1015
- if (groups.length === 0) continue;
1016
- console.log(` ${pkg.relativePath}:`);
1017
- for (const group of groups) {
1018
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1019
- }
1020
- }
1021
- }
1022
- displayConventions(scanResult);
1023
- displaySummarySection(scanResult);
1024
- console.log("");
1025
- }
1026
-
1027
- // src/display.ts
1028
- var CONVENTION_LABELS = {
1029
- fileNaming: "File naming",
1030
- componentNaming: "Component naming",
1031
- hookNaming: "Hook naming",
1032
- importAlias: "Import alias"
1033
- };
1034
- function formatItem(item, nameMap) {
1035
- const name = nameMap?.[item.name] ?? item.name;
1036
- return item.version ? `${name} ${item.version}` : name;
1037
- }
1038
- function confidenceLabel(convention) {
1039
- const pct = Math.round(convention.consistency);
1040
- if (convention.confidence === "high") {
1041
- return `${pct}% \u2014 high confidence, will enforce`;
1042
- }
1043
- return `${pct}% \u2014 medium confidence, suggested only`;
1044
- }
1045
- function displayConventions(scanResult) {
1046
- const conventionEntries = Object.entries(scanResult.conventions);
1047
- if (conventionEntries.length === 0) return;
1048
- console.log(`
1049
- ${import_chalk6.default.bold("Conventions:")}`);
1050
- for (const [key, convention] of conventionEntries) {
1051
- if (convention.confidence === "low") continue;
1052
- const label = CONVENTION_LABELS[key] ?? key;
1053
- if (scanResult.packages.length > 1) {
1054
- const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1055
- const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1056
- if (allSame || pkgValues.length <= 1) {
1057
- const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1058
- const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1059
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1060
- } else {
1061
- console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
1062
- for (const pv of pkgValues) {
1063
- const pct = Math.round(pv.convention.consistency);
1064
- console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1065
- }
1066
- }
1067
- } else {
1068
- const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1069
- const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1070
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1071
- }
1072
- }
1073
- }
1074
- function displaySummarySection(scanResult) {
1075
- const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1076
- console.log(`
1077
- ${import_chalk6.default.bold("Summary:")}`);
1078
- console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1079
- const ext = formatExtensions(scanResult.statistics.filesByExtension);
1080
- if (ext) {
1081
- console.log(` ${ext}`);
1082
- }
1083
- }
1084
- function displayScanResults(scanResult) {
1085
- if (scanResult.packages.length > 1) {
1086
- displayMonorepoResults(scanResult);
1087
- return;
1088
- }
1089
- const { stack } = scanResult;
1090
- console.log(`
1091
- ${import_chalk6.default.bold("Detected:")}`);
1092
- if (stack.framework) {
1093
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1094
- }
1095
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1096
- if (stack.styling) {
1097
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1098
- }
1099
- if (stack.backend) {
1100
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1101
- }
1102
- if (stack.linter) {
1103
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1104
- }
1105
- if (stack.formatter) {
1106
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1107
- }
1108
- if (stack.testRunner) {
1109
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1110
- }
1111
- if (stack.packageManager) {
1112
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1113
- }
1114
- if (stack.libraries.length > 0) {
1115
- for (const lib of stack.libraries) {
1116
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1117
- }
1118
- }
1119
- const groups = groupByRole(scanResult.structure.directories);
1120
- if (groups.length > 0) {
1121
- console.log(`
1122
- ${import_chalk6.default.bold("Structure:")}`);
1123
- for (const group of groups) {
1124
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1125
- }
1126
- }
1127
- displayConventions(scanResult);
1128
- displaySummarySection(scanResult);
1129
- console.log("");
1130
- }
984
+ var import_chalk9 = __toESM(require("chalk"), 1);
1131
985
 
1132
986
  // src/utils/write-generated-files.ts
1133
987
  var fs10 = __toESM(require("fs"), 1);
@@ -1158,18 +1012,60 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1158
1012
  // src/commands/init-hooks.ts
1159
1013
  var fs11 = __toESM(require("fs"), 1);
1160
1014
  var path12 = __toESM(require("path"), 1);
1161
- var import_chalk7 = __toESM(require("chalk"), 1);
1015
+ var import_chalk5 = __toESM(require("chalk"), 1);
1016
+ function setupClaudeCodeHook(projectRoot) {
1017
+ const claudeDir = path12.join(projectRoot, ".claude");
1018
+ if (!fs11.existsSync(claudeDir)) {
1019
+ fs11.mkdirSync(claudeDir, { recursive: true });
1020
+ }
1021
+ const settingsPath = path12.join(claudeDir, "settings.json");
1022
+ let settings = {};
1023
+ if (fs11.existsSync(settingsPath)) {
1024
+ try {
1025
+ settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
1026
+ } catch {
1027
+ }
1028
+ }
1029
+ const hooks = settings.hooks ?? {};
1030
+ const postToolUse = hooks.PostToolUse;
1031
+ if (Array.isArray(postToolUse)) {
1032
+ const hasViberails = postToolUse.some(
1033
+ (entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
1034
+ (h) => typeof h === "object" && h !== null && typeof h.command === "string" && h.command.includes("viberails")
1035
+ )
1036
+ );
1037
+ if (hasViberails) return;
1038
+ }
1039
+ const viberailsHook = {
1040
+ matcher: "Edit|Write",
1041
+ hooks: [
1042
+ {
1043
+ type: "command",
1044
+ command: "jq -r '.tool_input.file_path' | xargs npx viberails check --files"
1045
+ }
1046
+ ]
1047
+ };
1048
+ if (!hooks.PostToolUse) {
1049
+ hooks.PostToolUse = [viberailsHook];
1050
+ } else if (Array.isArray(hooks.PostToolUse)) {
1051
+ hooks.PostToolUse.push(viberailsHook);
1052
+ }
1053
+ settings.hooks = hooks;
1054
+ fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
1055
+ `);
1056
+ console.log(` ${import_chalk5.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
1057
+ }
1162
1058
  function setupPreCommitHook(projectRoot) {
1163
1059
  const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1164
1060
  if (fs11.existsSync(lefthookPath)) {
1165
1061
  addLefthookPreCommit(lefthookPath);
1166
- console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1062
+ console.log(` ${import_chalk5.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1167
1063
  return;
1168
1064
  }
1169
1065
  const huskyDir = path12.join(projectRoot, ".husky");
1170
1066
  if (fs11.existsSync(huskyDir)) {
1171
1067
  writeHuskyPreCommit(huskyDir);
1172
- console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1068
+ console.log(` ${import_chalk5.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1173
1069
  return;
1174
1070
  }
1175
1071
  const gitDir = path12.join(projectRoot, ".git");
@@ -1179,7 +1075,7 @@ function setupPreCommitHook(projectRoot) {
1179
1075
  fs11.mkdirSync(hooksDir, { recursive: true });
1180
1076
  }
1181
1077
  writeGitHookPreCommit(hooksDir);
1182
- console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
1078
+ console.log(` ${import_chalk5.default.green("\u2713")} .git/hooks/pre-commit`);
1183
1079
  }
1184
1080
  }
1185
1081
  function writeGitHookPreCommit(hooksDir) {
@@ -1228,6 +1124,187 @@ npx viberails check --staged
1228
1124
  fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1229
1125
  }
1230
1126
 
1127
+ // src/commands/init-wizard.ts
1128
+ var p = __toESM(require("@clack/prompts"), 1);
1129
+ var import_types4 = require("@viberails/types");
1130
+ var import_chalk8 = __toESM(require("chalk"), 1);
1131
+
1132
+ // src/display.ts
1133
+ var import_types3 = require("@viberails/types");
1134
+ var import_chalk7 = __toESM(require("chalk"), 1);
1135
+
1136
+ // src/display-helpers.ts
1137
+ var import_types = require("@viberails/types");
1138
+ function groupByRole(directories) {
1139
+ const map = /* @__PURE__ */ new Map();
1140
+ for (const dir of directories) {
1141
+ if (dir.role === "unknown") continue;
1142
+ const existing = map.get(dir.role);
1143
+ if (existing) {
1144
+ existing.dirs.push(dir);
1145
+ } else {
1146
+ map.set(dir.role, { dirs: [dir] });
1147
+ }
1148
+ }
1149
+ const groups = [];
1150
+ for (const [role, { dirs }] of map) {
1151
+ const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
1152
+ const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
1153
+ groups.push({
1154
+ role,
1155
+ label,
1156
+ dirCount: dirs.length,
1157
+ totalFiles,
1158
+ singlePath: dirs.length === 1 ? dirs[0].path : void 0
1159
+ });
1160
+ }
1161
+ return groups;
1162
+ }
1163
+ function formatSummary(stats, packageCount) {
1164
+ const parts = [];
1165
+ if (packageCount && packageCount > 1) {
1166
+ parts.push(`${packageCount} packages`);
1167
+ }
1168
+ parts.push(`${stats.totalFiles.toLocaleString()} source files`);
1169
+ parts.push(`${stats.totalLines.toLocaleString()} lines`);
1170
+ parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
1171
+ return parts.join(" \xB7 ");
1172
+ }
1173
+ function formatRoleGroup(group) {
1174
+ const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
1175
+ if (group.singlePath) {
1176
+ return `${group.label} \u2014 ${group.singlePath} (${files})`;
1177
+ }
1178
+ const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
1179
+ return `${group.label} \u2014 ${dirs} (${files})`;
1180
+ }
1181
+
1182
+ // src/display-monorepo.ts
1183
+ var import_types2 = require("@viberails/types");
1184
+ var import_chalk6 = __toESM(require("chalk"), 1);
1185
+
1186
+ // src/display.ts
1187
+ function formatItem(item, nameMap) {
1188
+ const name = nameMap?.[item.name] ?? item.name;
1189
+ return item.version ? `${name} ${item.version}` : name;
1190
+ }
1191
+
1192
+ // src/commands/init-wizard.ts
1193
+ var DEFAULT_WIZARD_RESULT = {
1194
+ enforcement: "warn",
1195
+ checks: {
1196
+ fileSize: true,
1197
+ naming: true,
1198
+ tests: true,
1199
+ boundaries: false
1200
+ },
1201
+ integration: ["pre-commit"]
1202
+ };
1203
+ async function runWizard(scanResult) {
1204
+ const isMonorepo = scanResult.packages.length > 1;
1205
+ displayScanSummary(scanResult);
1206
+ const enforcement = await p.select({
1207
+ message: "How strict should viberails be?",
1208
+ initialValue: "warn",
1209
+ options: [
1210
+ { value: "warn", label: "Warn", hint: "show issues, never block commits" },
1211
+ { value: "enforce", label: "Enforce", hint: "block commits with violations" }
1212
+ ]
1213
+ });
1214
+ if (p.isCancel(enforcement)) {
1215
+ p.cancel("Setup cancelled.");
1216
+ return null;
1217
+ }
1218
+ const checkOptions = [
1219
+ { value: "fileSize", label: "File size limit (300 lines)" },
1220
+ { value: "naming", label: "File naming conventions" },
1221
+ { value: "tests", label: "Missing test files" }
1222
+ ];
1223
+ if (isMonorepo) {
1224
+ checkOptions.push({ value: "boundaries", label: "Import boundaries" });
1225
+ }
1226
+ const enabledChecks = await p.multiselect({
1227
+ message: "Which checks should viberails run?",
1228
+ options: checkOptions,
1229
+ initialValues: ["fileSize", "naming", "tests"],
1230
+ required: false
1231
+ });
1232
+ if (p.isCancel(enabledChecks)) {
1233
+ p.cancel("Setup cancelled.");
1234
+ return null;
1235
+ }
1236
+ const checks = {
1237
+ fileSize: enabledChecks.includes("fileSize"),
1238
+ naming: enabledChecks.includes("naming"),
1239
+ tests: enabledChecks.includes("tests"),
1240
+ boundaries: enabledChecks.includes("boundaries")
1241
+ };
1242
+ const integrationOptions = [
1243
+ { value: "pre-commit", label: "Git pre-commit hook", hint: "runs on every commit" },
1244
+ {
1245
+ value: "claude-hook",
1246
+ label: "Claude Code hook",
1247
+ hint: "checks files as Claude edits them"
1248
+ },
1249
+ { value: "context-only", label: "Context files only", hint: "no hooks" }
1250
+ ];
1251
+ const integration = await p.multiselect({
1252
+ message: "Where should checks run?",
1253
+ options: integrationOptions,
1254
+ initialValues: ["pre-commit"],
1255
+ required: true
1256
+ });
1257
+ if (p.isCancel(integration)) {
1258
+ p.cancel("Setup cancelled.");
1259
+ return null;
1260
+ }
1261
+ const finalIntegration = integration.includes("context-only") ? ["context-only"] : integration;
1262
+ return {
1263
+ enforcement,
1264
+ checks,
1265
+ integration: finalIntegration
1266
+ };
1267
+ }
1268
+ function displayScanSummary(scanResult) {
1269
+ const { stack } = scanResult;
1270
+ const parts = [];
1271
+ if (stack.framework) parts.push(formatItem(stack.framework, import_types4.FRAMEWORK_NAMES));
1272
+ parts.push(formatItem(stack.language));
1273
+ if (stack.styling) parts.push(formatItem(stack.styling, import_types4.STYLING_NAMES));
1274
+ if (stack.backend) parts.push(formatItem(stack.backend, import_types4.FRAMEWORK_NAMES));
1275
+ p.log.info(`${import_chalk8.default.bold("Stack:")} ${parts.join(", ")}`);
1276
+ if (stack.linter || stack.formatter || stack.testRunner || stack.packageManager) {
1277
+ const tools = [];
1278
+ if (stack.linter) tools.push(formatItem(stack.linter));
1279
+ if (stack.formatter && stack.formatter !== stack.linter)
1280
+ tools.push(formatItem(stack.formatter));
1281
+ if (stack.testRunner) tools.push(formatItem(stack.testRunner));
1282
+ if (stack.packageManager) tools.push(formatItem(stack.packageManager));
1283
+ p.log.info(`${import_chalk8.default.bold("Tools:")} ${tools.join(", ")}`);
1284
+ }
1285
+ if (stack.libraries.length > 0) {
1286
+ const libs = stack.libraries.map((lib) => formatItem(lib, import_types4.LIBRARY_NAMES)).join(", ");
1287
+ p.log.info(`${import_chalk8.default.bold("Libraries:")} ${libs}`);
1288
+ }
1289
+ const groups = groupByRole(scanResult.structure.directories);
1290
+ if (groups.length > 0) {
1291
+ const structParts = groups.map((g) => formatRoleGroup(g));
1292
+ p.log.info(`${import_chalk8.default.bold("Structure:")} ${structParts.join(", ")}`);
1293
+ }
1294
+ const conventionEntries = Object.entries(scanResult.conventions).filter(
1295
+ ([, c]) => c.confidence !== "low"
1296
+ );
1297
+ if (conventionEntries.length > 0) {
1298
+ const convParts = conventionEntries.map(([, c]) => {
1299
+ const pct = Math.round(c.consistency);
1300
+ return `${c.value} (${pct}%)`;
1301
+ });
1302
+ p.log.info(`${import_chalk8.default.bold("Conventions:")} ${convParts.join(", ")}`);
1303
+ }
1304
+ const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1305
+ p.log.info(`${import_chalk8.default.bold("Summary:")} ${formatSummary(scanResult.statistics, pkgCount)}`);
1306
+ }
1307
+
1231
1308
  // src/commands/init.ts
1232
1309
  var CONFIG_FILE4 = "viberails.config.json";
1233
1310
  function filterHighConfidence(conventions) {
@@ -1242,6 +1319,13 @@ function filterHighConfidence(conventions) {
1242
1319
  }
1243
1320
  return filtered;
1244
1321
  }
1322
+ function applyWizardResult(config, wizard) {
1323
+ config.enforcement = wizard.enforcement;
1324
+ if (!wizard.checks.fileSize) config.rules.maxFileLines = 0;
1325
+ config.rules.enforceNaming = wizard.checks.naming;
1326
+ config.rules.requireTests = wizard.checks.tests;
1327
+ config.rules.enforceBoundaries = wizard.checks.boundaries;
1328
+ }
1245
1329
  async function initCommand(options, cwd) {
1246
1330
  const startDir = cwd ?? process.cwd();
1247
1331
  const projectRoot = findProjectRoot(startDir);
@@ -1253,64 +1337,69 @@ async function initCommand(options, cwd) {
1253
1337
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
1254
1338
  if (fs12.existsSync(configPath)) {
1255
1339
  console.log(
1256
- import_chalk8.default.yellow("!") + " viberails is already initialized in this project.\n Run " + import_chalk8.default.cyan("viberails sync") + " to update the generated files."
1340
+ import_chalk9.default.yellow("!") + " viberails is already initialized in this project.\n Run " + import_chalk9.default.cyan("viberails sync") + " to update the generated files."
1257
1341
  );
1258
1342
  return;
1259
1343
  }
1260
- console.log(import_chalk8.default.dim("Scanning project..."));
1344
+ p2.intro("viberails");
1345
+ const s = p2.spinner();
1346
+ s.start("Scanning project...");
1261
1347
  const scanResult = await (0, import_scanner.scan)(projectRoot);
1262
- displayScanResults(scanResult);
1348
+ s.stop("Scan complete");
1263
1349
  if (scanResult.statistics.totalFiles === 0) {
1264
- console.log(
1265
- import_chalk8.default.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + import_chalk8.default.cyan("viberails sync") + " after adding source files.\n"
1350
+ p2.log.warn(
1351
+ `No source files detected. viberails will generate context with minimal content.
1352
+ Run ${import_chalk9.default.cyan("viberails sync")} after adding source files.`
1266
1353
  );
1267
1354
  }
1268
- if (!options.yes) {
1269
- const accepted = await confirm("Does this look right?");
1270
- if (!accepted) {
1271
- console.log("Aborted.");
1272
- return;
1273
- }
1355
+ let wizard;
1356
+ if (options.yes) {
1357
+ wizard = { ...DEFAULT_WIZARD_RESULT };
1358
+ } else {
1359
+ const result = await runWizard(scanResult);
1360
+ if (!result) return;
1361
+ wizard = result;
1274
1362
  }
1275
1363
  const config = (0, import_config4.generateConfig)(scanResult);
1276
1364
  if (options.yes) {
1277
1365
  config.conventions = filterHighConfidence(config.conventions);
1278
1366
  }
1279
- if (config.workspace && config.workspace.packages.length > 0) {
1280
- let shouldInfer = options.yes;
1281
- if (!options.yes) {
1282
- shouldInfer = await confirm("Infer boundary rules from import patterns?");
1283
- }
1284
- if (shouldInfer) {
1285
- console.log(import_chalk8.default.dim("Building import graph..."));
1286
- const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1287
- const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1288
- const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
1289
- const inferred = inferBoundaries(graph);
1290
- if (inferred.length > 0) {
1291
- config.boundaries = inferred;
1292
- config.rules.enforceBoundaries = true;
1293
- console.log(` ${import_chalk8.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1294
- }
1367
+ applyWizardResult(config, wizard);
1368
+ if (wizard.checks.boundaries && config.workspace && config.workspace.packages.length > 0) {
1369
+ s.start("Inferring boundary rules...");
1370
+ const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1371
+ const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1372
+ const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
1373
+ const inferred = inferBoundaries(graph);
1374
+ const ruleCount = Object.values(inferred).reduce((sum, denied) => sum + denied.length, 0);
1375
+ if (ruleCount > 0) {
1376
+ config.boundaries = inferred;
1377
+ s.stop(`Inferred ${ruleCount} boundary rules`);
1378
+ } else {
1379
+ s.stop("No boundary rules could be inferred");
1380
+ config.rules.enforceBoundaries = false;
1295
1381
  }
1296
1382
  }
1297
1383
  fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1298
1384
  `);
1299
1385
  writeGeneratedFiles(projectRoot, config, scanResult);
1300
1386
  updateGitignore(projectRoot);
1301
- setupPreCommitHook(projectRoot);
1302
- console.log(`
1303
- ${import_chalk8.default.bold("Created:")}`);
1304
- console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
1305
- console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
1306
- console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
1307
- console.log(`
1308
- ${import_chalk8.default.bold("Next steps:")}`);
1309
- console.log(` 1. Review ${import_chalk8.default.cyan("viberails.config.json")} and adjust rules`);
1310
- console.log(
1311
- ` 2. Commit ${import_chalk8.default.cyan("viberails.config.json")} and ${import_chalk8.default.cyan(".viberails/context.md")}`
1387
+ if (wizard.integration.includes("pre-commit")) {
1388
+ setupPreCommitHook(projectRoot);
1389
+ }
1390
+ if (wizard.integration.includes("claude-hook")) {
1391
+ setupClaudeCodeHook(projectRoot);
1392
+ }
1393
+ p2.log.success(`${import_chalk9.default.bold("Created:")}`);
1394
+ console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE4}`);
1395
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md`);
1396
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json`);
1397
+ p2.outro(
1398
+ `${import_chalk9.default.bold("Next steps:")}
1399
+ 1. Review ${import_chalk9.default.cyan("viberails.config.json")} and adjust rules
1400
+ 2. Commit ${import_chalk9.default.cyan("viberails.config.json")} and ${import_chalk9.default.cyan(".viberails/context.md")}
1401
+ 3. Run ${import_chalk9.default.cyan("viberails check")} to verify your project passes`
1312
1402
  );
1313
- console.log(` 3. Run ${import_chalk8.default.cyan("viberails check")} to verify your project passes`);
1314
1403
  }
1315
1404
  function updateGitignore(projectRoot) {
1316
1405
  const gitignorePath = path13.join(projectRoot, ".gitignore");
@@ -1330,7 +1419,7 @@ var fs13 = __toESM(require("fs"), 1);
1330
1419
  var path14 = __toESM(require("path"), 1);
1331
1420
  var import_config5 = require("@viberails/config");
1332
1421
  var import_scanner2 = require("@viberails/scanner");
1333
- var import_chalk9 = __toESM(require("chalk"), 1);
1422
+ var import_chalk10 = __toESM(require("chalk"), 1);
1334
1423
  var CONFIG_FILE5 = "viberails.config.json";
1335
1424
  async function syncCommand(cwd) {
1336
1425
  const startDir = cwd ?? process.cwd();
@@ -1342,21 +1431,21 @@ async function syncCommand(cwd) {
1342
1431
  }
1343
1432
  const configPath = path14.join(projectRoot, CONFIG_FILE5);
1344
1433
  const existing = await (0, import_config5.loadConfig)(configPath);
1345
- console.log(import_chalk9.default.dim("Scanning project..."));
1434
+ console.log(import_chalk10.default.dim("Scanning project..."));
1346
1435
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
1347
1436
  const merged = (0, import_config5.mergeConfig)(existing, scanResult);
1348
1437
  fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1349
1438
  `);
1350
1439
  writeGeneratedFiles(projectRoot, merged, scanResult);
1351
1440
  console.log(`
1352
- ${import_chalk9.default.bold("Synced:")}`);
1353
- console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1354
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1355
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1441
+ ${import_chalk10.default.bold("Synced:")}`);
1442
+ console.log(` ${import_chalk10.default.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1443
+ console.log(` ${import_chalk10.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1444
+ console.log(` ${import_chalk10.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1356
1445
  }
1357
1446
 
1358
1447
  // src/index.ts
1359
- var VERSION = "0.2.2";
1448
+ var VERSION = "0.3.0";
1360
1449
  var program = new import_commander.Command();
1361
1450
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1362
1451
  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) => {
@@ -1364,7 +1453,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1364
1453
  await initCommand(options);
1365
1454
  } catch (err) {
1366
1455
  const message = err instanceof Error ? err.message : String(err);
1367
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1456
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1368
1457
  process.exit(1);
1369
1458
  }
1370
1459
  });
@@ -1373,30 +1462,32 @@ program.command("sync").description("Re-scan and update generated files").action
1373
1462
  await syncCommand();
1374
1463
  } catch (err) {
1375
1464
  const message = err instanceof Error ? err.message : String(err);
1376
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1465
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1377
1466
  process.exit(1);
1378
1467
  }
1379
1468
  });
1380
- 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) => {
1381
- try {
1382
- const exitCode = await checkCommand({
1383
- ...options,
1384
- noBoundaries: options.boundaries === false
1385
- });
1386
- process.exit(exitCode);
1387
- } catch (err) {
1388
- const message = err instanceof Error ? err.message : String(err);
1389
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1390
- process.exit(1);
1469
+ 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(
1470
+ async (options) => {
1471
+ try {
1472
+ const exitCode = await checkCommand({
1473
+ ...options,
1474
+ noBoundaries: options.boundaries === false
1475
+ });
1476
+ process.exit(exitCode);
1477
+ } catch (err) {
1478
+ const message = err instanceof Error ? err.message : String(err);
1479
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1480
+ process.exit(1);
1481
+ }
1391
1482
  }
1392
- });
1483
+ );
1393
1484
  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) => {
1394
1485
  try {
1395
1486
  const exitCode = await fixCommand(options);
1396
1487
  process.exit(exitCode);
1397
1488
  } catch (err) {
1398
1489
  const message = err instanceof Error ? err.message : String(err);
1399
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1490
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1400
1491
  process.exit(1);
1401
1492
  }
1402
1493
  });
@@ -1405,7 +1496,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1405
1496
  await boundariesCommand(options);
1406
1497
  } catch (err) {
1407
1498
  const message = err instanceof Error ? err.message : String(err);
1408
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1499
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1409
1500
  process.exit(1);
1410
1501
  }
1411
1502
  });