viberails 0.2.0 → 0.2.2

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_chalk7 = __toESM(require("chalk"), 1);
37
+ var import_chalk10 = __toESM(require("chalk"), 1);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/boundaries.ts
@@ -256,6 +256,19 @@ function resolveIgnoreForFile(relPath, config) {
256
256
  var import_node_child_process = require("child_process");
257
257
  var fs4 = __toESM(require("fs"), 1);
258
258
  var path4 = __toESM(require("path"), 1);
259
+ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
260
+ "node_modules",
261
+ ".git",
262
+ "dist",
263
+ "build",
264
+ ".next",
265
+ ".expo",
266
+ ".output",
267
+ ".svelte-kit",
268
+ ".turbo",
269
+ "coverage",
270
+ ".viberails"
271
+ ]);
259
272
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
260
273
  ".ts",
261
274
  ".tsx",
@@ -304,7 +317,7 @@ function checkNaming(relPath, conventions) {
304
317
  const filename = path4.basename(relPath);
305
318
  const ext = path4.extname(filename);
306
319
  if (!SOURCE_EXTS.has(ext)) return void 0;
307
- if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".")) {
320
+ if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".") || filename.startsWith("_") || filename.startsWith("+") || filename.startsWith("$") || filename.startsWith("[")) {
308
321
  return void 0;
309
322
  }
310
323
  const bare = filename.slice(0, filename.indexOf("."));
@@ -337,7 +350,7 @@ function getAllSourceFiles(projectRoot, config) {
337
350
  for (const entry of entries) {
338
351
  const rel = path4.relative(projectRoot, path4.join(dir, entry.name));
339
352
  if (entry.isDirectory()) {
340
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
353
+ if (ALWAYS_SKIP_DIRS.has(entry.name)) {
341
354
  continue;
342
355
  }
343
356
  if (isIgnored(rel, config.ignore)) continue;
@@ -532,12 +545,56 @@ ${violations.length} ${word} found.`);
532
545
  }
533
546
 
534
547
  // src/commands/fix.ts
535
- var import_node_child_process2 = require("child_process");
536
548
  var fs9 = __toESM(require("fs"), 1);
537
549
  var path10 = __toESM(require("path"), 1);
538
- var import_node_readline = require("readline");
539
550
  var import_config3 = require("@viberails/config");
551
+ var import_chalk4 = __toESM(require("chalk"), 1);
552
+
553
+ // src/commands/fix-helpers.ts
554
+ var import_node_child_process2 = require("child_process");
555
+ var import_node_readline = require("readline");
540
556
  var import_chalk3 = __toESM(require("chalk"), 1);
557
+ function printPlan(renames, stubs) {
558
+ if (renames.length > 0) {
559
+ console.log(import_chalk3.default.bold("\nFile renames:"));
560
+ for (const r of renames) {
561
+ console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
562
+ }
563
+ }
564
+ if (stubs.length > 0) {
565
+ console.log(import_chalk3.default.bold("\nTest stubs to create:"));
566
+ for (const s of stubs) {
567
+ console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
568
+ }
569
+ }
570
+ }
571
+ function checkGitDirty(projectRoot) {
572
+ try {
573
+ const output = (0, import_node_child_process2.execSync)("git status --porcelain", {
574
+ cwd: projectRoot,
575
+ encoding: "utf-8"
576
+ });
577
+ return output.trim().length > 0;
578
+ } catch {
579
+ return false;
580
+ }
581
+ }
582
+ function getConventionValue(convention) {
583
+ if (typeof convention === "string") return convention;
584
+ if (convention && typeof convention === "object" && "value" in convention) {
585
+ return convention.value;
586
+ }
587
+ return void 0;
588
+ }
589
+ function promptConfirm(question) {
590
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
591
+ return new Promise((resolve4) => {
592
+ rl.question(`${question} (y/N) `, (answer) => {
593
+ rl.close();
594
+ resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
595
+ });
596
+ });
597
+ }
541
598
 
542
599
  // src/commands/fix-imports.ts
543
600
  var path7 = __toESM(require("path"), 1);
@@ -757,22 +814,22 @@ async function fixCommand(options, cwd) {
757
814
  const startDir = cwd ?? process.cwd();
758
815
  const projectRoot = findProjectRoot(startDir);
759
816
  if (!projectRoot) {
760
- console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
817
+ console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
761
818
  return 1;
762
819
  }
763
820
  const configPath = path10.join(projectRoot, CONFIG_FILE3);
764
821
  if (!fs9.existsSync(configPath)) {
765
822
  console.error(
766
- `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
823
+ `${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
767
824
  );
768
825
  return 1;
769
826
  }
770
827
  const config = await (0, import_config3.loadConfig)(configPath);
771
- if (!options.yes && !options.dryRun) {
828
+ if (!options.dryRun) {
772
829
  const isDirty = checkGitDirty(projectRoot);
773
830
  if (isDirty) {
774
831
  console.log(
775
- import_chalk3.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
832
+ import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
776
833
  );
777
834
  }
778
835
  }
@@ -802,12 +859,12 @@ async function fixCommand(options, cwd) {
802
859
  }
803
860
  }
804
861
  if (dedupedRenames.length === 0 && testStubs.length === 0) {
805
- console.log(`${import_chalk3.default.green("\u2713")} No fixable violations found.`);
862
+ console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
806
863
  return 0;
807
864
  }
808
865
  printPlan(dedupedRenames, testStubs);
809
866
  if (options.dryRun) {
810
- console.log(import_chalk3.default.dim("\nDry run \u2014 no changes applied."));
867
+ console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
811
868
  return 0;
812
869
  }
813
870
  if (!options.yes) {
@@ -838,94 +895,89 @@ async function fixCommand(options, cwd) {
838
895
  }
839
896
  console.log("");
840
897
  if (renameCount > 0) {
841
- console.log(`${import_chalk3.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
898
+ console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
842
899
  }
843
900
  if (importUpdateCount > 0) {
844
901
  console.log(
845
- `${import_chalk3.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
902
+ `${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
846
903
  );
847
904
  }
848
905
  if (stubCount > 0) {
849
- console.log(`${import_chalk3.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
906
+ console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
850
907
  }
851
908
  return 0;
852
909
  }
853
- function printPlan(renames, stubs) {
854
- if (renames.length > 0) {
855
- console.log(import_chalk3.default.bold("\nFile renames:"));
856
- for (const r of renames) {
857
- console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
858
- }
859
- }
860
- if (stubs.length > 0) {
861
- console.log(import_chalk3.default.bold("\nTest stubs to create:"));
862
- for (const s of stubs) {
863
- console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
864
- }
865
- }
866
- }
867
- function checkGitDirty(projectRoot) {
868
- try {
869
- const output = (0, import_node_child_process2.execSync)("git status --porcelain", {
870
- cwd: projectRoot,
871
- encoding: "utf-8"
872
- });
873
- return output.trim().length > 0;
874
- } catch {
875
- return false;
876
- }
877
- }
878
- function getConventionValue(convention) {
879
- if (typeof convention === "string") return convention;
880
- if (convention && typeof convention === "object" && "value" in convention) {
881
- return convention.value;
882
- }
883
- return void 0;
884
- }
885
- function promptConfirm(question) {
886
- const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
887
- return new Promise((resolve4) => {
888
- rl.question(`${question} (y/N) `, (answer) => {
889
- rl.close();
890
- resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
891
- });
892
- });
893
- }
894
910
 
895
911
  // src/commands/init.ts
896
- var fs11 = __toESM(require("fs"), 1);
897
- var path12 = __toESM(require("path"), 1);
912
+ var fs12 = __toESM(require("fs"), 1);
913
+ var path13 = __toESM(require("path"), 1);
898
914
  var import_config4 = require("@viberails/config");
899
915
  var import_scanner = require("@viberails/scanner");
900
- var import_chalk5 = __toESM(require("chalk"), 1);
916
+ var import_chalk8 = __toESM(require("chalk"), 1);
901
917
 
902
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
903
923
  var import_types = require("@viberails/types");
904
- var import_chalk4 = __toESM(require("chalk"), 1);
905
- var CONVENTION_LABELS = {
906
- fileNaming: "File naming",
907
- componentNaming: "Component naming",
908
- hookNaming: "Hook naming",
909
- importAlias: "Import alias"
910
- };
911
- function formatItem(item, nameMap) {
912
- const name = nameMap?.[item.name] ?? item.name;
913
- return item.version ? `${name} ${item.version}` : name;
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;
914
948
  }
915
- function confidenceLabel(convention) {
916
- const pct = Math.round(convention.consistency);
917
- if (convention.confidence === "high") {
918
- return `${pct}% \u2014 high confidence, will enforce`;
949
+ function formatSummary(stats, packageCount) {
950
+ const parts = [];
951
+ if (packageCount && packageCount > 1) {
952
+ parts.push(`${packageCount} packages`);
919
953
  }
920
- return `${pct}% \u2014 medium confidence, suggested only`;
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})`;
921
969
  }
970
+
971
+ // src/display-monorepo.ts
972
+ var import_types2 = require("@viberails/types");
973
+ var import_chalk5 = __toESM(require("chalk"), 1);
922
974
  function formatPackageSummary(pkg) {
923
975
  const parts = [];
924
976
  if (pkg.stack.framework) {
925
- parts.push(formatItem(pkg.stack.framework, import_types.FRAMEWORK_NAMES));
977
+ parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
926
978
  }
927
979
  if (pkg.stack.styling) {
928
- parts.push(formatItem(pkg.stack.styling, import_types.STYLING_NAMES));
980
+ parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
929
981
  }
930
982
  const files = `${pkg.statistics.totalFiles} files`;
931
983
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
@@ -934,16 +986,19 @@ function formatPackageSummary(pkg) {
934
986
  function displayMonorepoResults(scanResult) {
935
987
  const { stack, packages } = scanResult;
936
988
  console.log(`
937
- ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
938
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
989
+ ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
990
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
939
991
  if (stack.packageManager) {
940
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
992
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
941
993
  }
942
994
  if (stack.linter) {
943
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(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)}`);
944
999
  }
945
1000
  if (stack.testRunner) {
946
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1001
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
947
1002
  }
948
1003
  console.log("");
949
1004
  for (const pkg of packages) {
@@ -954,96 +1009,123 @@ ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`
954
1009
  );
955
1010
  if (packagesWithDirs.length > 0) {
956
1011
  console.log(`
957
- ${import_chalk4.default.bold("Structure:")}`);
1012
+ ${import_chalk5.default.bold("Structure:")}`);
958
1013
  for (const pkg of packagesWithDirs) {
959
- const meaningfulDirs = pkg.structure.directories.filter((d) => d.role !== "unknown");
960
- if (meaningfulDirs.length === 0) continue;
1014
+ const groups = groupByRole(pkg.structure.directories);
1015
+ if (groups.length === 0) continue;
961
1016
  console.log(` ${pkg.relativePath}:`);
962
- for (const dir of meaningfulDirs) {
963
- const label = import_types.ROLE_DESCRIPTIONS[dir.role] ?? dir.role;
964
- const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
965
- console.log(` ${import_chalk4.default.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
1017
+ for (const group of groups) {
1018
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
966
1019
  }
967
1020
  }
968
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) {
969
1046
  const conventionEntries = Object.entries(scanResult.conventions);
970
- if (conventionEntries.length > 0) {
971
- console.log(`
972
- ${import_chalk4.default.bold("Conventions:")}`);
973
- for (const [key, convention] of conventionEntries) {
974
- if (convention.confidence === "low") continue;
975
- const label = CONVENTION_LABELS[key] ?? key;
976
- const pkgValues = packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
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] }));
977
1055
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
978
1056
  if (allSame || pkgValues.length <= 1) {
979
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
980
- const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
1057
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1058
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
981
1059
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
982
1060
  } else {
983
- console.log(` ${import_chalk4.default.yellow("~")} ${label}: varies by package`);
1061
+ console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
984
1062
  for (const pv of pkgValues) {
985
1063
  const pct = Math.round(pv.convention.consistency);
986
1064
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
987
1065
  }
988
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}`);
989
1071
  }
990
1072
  }
991
- console.log("");
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
+ }
992
1083
  }
993
1084
  function displayScanResults(scanResult) {
994
1085
  if (scanResult.packages.length > 1) {
995
1086
  displayMonorepoResults(scanResult);
996
1087
  return;
997
1088
  }
998
- const { stack, conventions } = scanResult;
1089
+ const { stack } = scanResult;
999
1090
  console.log(`
1000
- ${import_chalk4.default.bold("Detected:")}`);
1091
+ ${import_chalk6.default.bold("Detected:")}`);
1001
1092
  if (stack.framework) {
1002
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.framework, import_types.FRAMEWORK_NAMES)}`);
1093
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1003
1094
  }
1004
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1095
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1005
1096
  if (stack.styling) {
1006
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.styling, import_types.STYLING_NAMES)}`);
1097
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1007
1098
  }
1008
1099
  if (stack.backend) {
1009
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.backend, import_types.FRAMEWORK_NAMES)}`);
1100
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1010
1101
  }
1011
1102
  if (stack.linter) {
1012
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(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)}`);
1013
1107
  }
1014
1108
  if (stack.testRunner) {
1015
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1109
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1016
1110
  }
1017
1111
  if (stack.packageManager) {
1018
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1112
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1019
1113
  }
1020
1114
  if (stack.libraries.length > 0) {
1021
1115
  for (const lib of stack.libraries) {
1022
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(lib, import_types.LIBRARY_NAMES)}`);
1116
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1023
1117
  }
1024
1118
  }
1025
- const meaningfulDirs = scanResult.structure.directories.filter((d) => d.role !== "unknown");
1026
- if (meaningfulDirs.length > 0) {
1119
+ const groups = groupByRole(scanResult.structure.directories);
1120
+ if (groups.length > 0) {
1027
1121
  console.log(`
1028
- ${import_chalk4.default.bold("Structure:")}`);
1029
- for (const dir of meaningfulDirs) {
1030
- const label = import_types.ROLE_DESCRIPTIONS[dir.role] ?? dir.role;
1031
- const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
1032
- console.log(` ${import_chalk4.default.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
1033
- }
1034
- }
1035
- const conventionEntries = Object.entries(conventions);
1036
- if (conventionEntries.length > 0) {
1037
- console.log(`
1038
- ${import_chalk4.default.bold("Conventions:")}`);
1039
- for (const [key, convention] of conventionEntries) {
1040
- if (convention.confidence === "low") continue;
1041
- const label = CONVENTION_LABELS[key] ?? key;
1042
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
1043
- const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
1044
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1122
+ ${import_chalk6.default.bold("Structure:")}`);
1123
+ for (const group of groups) {
1124
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1045
1125
  }
1046
1126
  }
1127
+ displayConventions(scanResult);
1128
+ displaySummarySection(scanResult);
1047
1129
  console.log("");
1048
1130
  }
1049
1131
 
@@ -1073,6 +1155,79 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1073
1155
  }
1074
1156
  }
1075
1157
 
1158
+ // src/commands/init-hooks.ts
1159
+ var fs11 = __toESM(require("fs"), 1);
1160
+ var path12 = __toESM(require("path"), 1);
1161
+ var import_chalk7 = __toESM(require("chalk"), 1);
1162
+ function setupPreCommitHook(projectRoot) {
1163
+ const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1164
+ if (fs11.existsSync(lefthookPath)) {
1165
+ addLefthookPreCommit(lefthookPath);
1166
+ console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1167
+ return;
1168
+ }
1169
+ const huskyDir = path12.join(projectRoot, ".husky");
1170
+ if (fs11.existsSync(huskyDir)) {
1171
+ writeHuskyPreCommit(huskyDir);
1172
+ console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1173
+ return;
1174
+ }
1175
+ const gitDir = path12.join(projectRoot, ".git");
1176
+ if (fs11.existsSync(gitDir)) {
1177
+ const hooksDir = path12.join(gitDir, "hooks");
1178
+ if (!fs11.existsSync(hooksDir)) {
1179
+ fs11.mkdirSync(hooksDir, { recursive: true });
1180
+ }
1181
+ writeGitHookPreCommit(hooksDir);
1182
+ console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
1183
+ }
1184
+ }
1185
+ function writeGitHookPreCommit(hooksDir) {
1186
+ const hookPath = path12.join(hooksDir, "pre-commit");
1187
+ if (fs11.existsSync(hookPath)) {
1188
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1189
+ if (existing.includes("viberails")) return;
1190
+ fs11.writeFileSync(
1191
+ hookPath,
1192
+ `${existing.trimEnd()}
1193
+
1194
+ # viberails check
1195
+ npx viberails check --staged
1196
+ `
1197
+ );
1198
+ return;
1199
+ }
1200
+ const script = [
1201
+ "#!/bin/sh",
1202
+ "# Generated by viberails \u2014 https://viberails.sh",
1203
+ "",
1204
+ "npx viberails check --staged",
1205
+ ""
1206
+ ].join("\n");
1207
+ fs11.writeFileSync(hookPath, script, { mode: 493 });
1208
+ }
1209
+ function addLefthookPreCommit(lefthookPath) {
1210
+ const content = fs11.readFileSync(lefthookPath, "utf-8");
1211
+ if (content.includes("viberails")) return;
1212
+ const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1213
+ fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1214
+ ${addition}
1215
+ `);
1216
+ }
1217
+ function writeHuskyPreCommit(huskyDir) {
1218
+ const hookPath = path12.join(huskyDir, "pre-commit");
1219
+ if (fs11.existsSync(hookPath)) {
1220
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1221
+ if (!existing.includes("viberails")) {
1222
+ fs11.writeFileSync(hookPath, `${existing.trimEnd()}
1223
+ npx viberails check --staged
1224
+ `);
1225
+ }
1226
+ return;
1227
+ }
1228
+ fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1229
+ }
1230
+
1076
1231
  // src/commands/init.ts
1077
1232
  var CONFIG_FILE4 = "viberails.config.json";
1078
1233
  function filterHighConfidence(conventions) {
@@ -1095,19 +1250,19 @@ async function initCommand(options, cwd) {
1095
1250
  "No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
1096
1251
  );
1097
1252
  }
1098
- const configPath = path12.join(projectRoot, CONFIG_FILE4);
1099
- if (fs11.existsSync(configPath)) {
1253
+ const configPath = path13.join(projectRoot, CONFIG_FILE4);
1254
+ if (fs12.existsSync(configPath)) {
1100
1255
  console.log(
1101
- import_chalk5.default.yellow("!") + " viberails is already initialized in this project.\n Run " + import_chalk5.default.cyan("viberails sync") + " to update the generated files."
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."
1102
1257
  );
1103
1258
  return;
1104
1259
  }
1105
- console.log(import_chalk5.default.dim("Scanning project..."));
1260
+ console.log(import_chalk8.default.dim("Scanning project..."));
1106
1261
  const scanResult = await (0, import_scanner.scan)(projectRoot);
1107
1262
  displayScanResults(scanResult);
1108
1263
  if (scanResult.statistics.totalFiles === 0) {
1109
1264
  console.log(
1110
- import_chalk5.default.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + import_chalk5.default.cyan("viberails sync") + " after adding source files.\n"
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"
1111
1266
  );
1112
1267
  }
1113
1268
  if (!options.yes) {
@@ -1127,7 +1282,7 @@ async function initCommand(options, cwd) {
1127
1282
  shouldInfer = await confirm("Infer boundary rules from import patterns?");
1128
1283
  }
1129
1284
  if (shouldInfer) {
1130
- console.log(import_chalk5.default.dim("Building import graph..."));
1285
+ console.log(import_chalk8.default.dim("Building import graph..."));
1131
1286
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1132
1287
  const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1133
1288
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -1135,115 +1290,47 @@ async function initCommand(options, cwd) {
1135
1290
  if (inferred.length > 0) {
1136
1291
  config.boundaries = inferred;
1137
1292
  config.rules.enforceBoundaries = true;
1138
- console.log(` ${import_chalk5.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1293
+ console.log(` ${import_chalk8.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1139
1294
  }
1140
1295
  }
1141
1296
  }
1142
- fs11.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1297
+ fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1143
1298
  `);
1144
1299
  writeGeneratedFiles(projectRoot, config, scanResult);
1145
1300
  updateGitignore(projectRoot);
1146
1301
  setupPreCommitHook(projectRoot);
1147
1302
  console.log(`
1148
- ${import_chalk5.default.bold("Created:")}`);
1149
- console.log(` ${import_chalk5.default.green("\u2713")} ${CONFIG_FILE4}`);
1150
- console.log(` ${import_chalk5.default.green("\u2713")} .viberails/context.md`);
1151
- console.log(` ${import_chalk5.default.green("\u2713")} .viberails/scan-result.json`);
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`);
1152
1307
  console.log(`
1153
- ${import_chalk5.default.bold("Next steps:")}`);
1154
- console.log(` 1. Review ${import_chalk5.default.cyan("viberails.config.json")} and adjust rules`);
1308
+ ${import_chalk8.default.bold("Next steps:")}`);
1309
+ console.log(` 1. Review ${import_chalk8.default.cyan("viberails.config.json")} and adjust rules`);
1155
1310
  console.log(
1156
- ` 2. Commit ${import_chalk5.default.cyan("viberails.config.json")} and ${import_chalk5.default.cyan(".viberails/context.md")}`
1311
+ ` 2. Commit ${import_chalk8.default.cyan("viberails.config.json")} and ${import_chalk8.default.cyan(".viberails/context.md")}`
1157
1312
  );
1158
- console.log(` 3. Run ${import_chalk5.default.cyan("viberails check")} to verify your project passes`);
1313
+ console.log(` 3. Run ${import_chalk8.default.cyan("viberails check")} to verify your project passes`);
1159
1314
  }
1160
1315
  function updateGitignore(projectRoot) {
1161
- const gitignorePath = path12.join(projectRoot, ".gitignore");
1316
+ const gitignorePath = path13.join(projectRoot, ".gitignore");
1162
1317
  let content = "";
1163
- if (fs11.existsSync(gitignorePath)) {
1164
- content = fs11.readFileSync(gitignorePath, "utf-8");
1318
+ if (fs12.existsSync(gitignorePath)) {
1319
+ content = fs12.readFileSync(gitignorePath, "utf-8");
1165
1320
  }
1166
1321
  if (!content.includes(".viberails/scan-result.json")) {
1167
1322
  const block = "\n# viberails\n.viberails/scan-result.json\n";
1168
- fs11.writeFileSync(gitignorePath, `${content.trimEnd()}
1323
+ fs12.writeFileSync(gitignorePath, `${content.trimEnd()}
1169
1324
  ${block}`);
1170
1325
  }
1171
1326
  }
1172
- function setupPreCommitHook(projectRoot) {
1173
- const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1174
- if (fs11.existsSync(lefthookPath)) {
1175
- addLefthookPreCommit(lefthookPath);
1176
- console.log(` ${import_chalk5.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1177
- return;
1178
- }
1179
- const huskyDir = path12.join(projectRoot, ".husky");
1180
- if (fs11.existsSync(huskyDir)) {
1181
- writeHuskyPreCommit(huskyDir);
1182
- console.log(` ${import_chalk5.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1183
- return;
1184
- }
1185
- const gitDir = path12.join(projectRoot, ".git");
1186
- if (fs11.existsSync(gitDir)) {
1187
- const hooksDir = path12.join(gitDir, "hooks");
1188
- if (!fs11.existsSync(hooksDir)) {
1189
- fs11.mkdirSync(hooksDir, { recursive: true });
1190
- }
1191
- writeGitHookPreCommit(hooksDir);
1192
- console.log(` ${import_chalk5.default.green("\u2713")} .git/hooks/pre-commit`);
1193
- }
1194
- }
1195
- function writeGitHookPreCommit(hooksDir) {
1196
- const hookPath = path12.join(hooksDir, "pre-commit");
1197
- if (fs11.existsSync(hookPath)) {
1198
- const existing = fs11.readFileSync(hookPath, "utf-8");
1199
- if (existing.includes("viberails")) return;
1200
- fs11.writeFileSync(
1201
- hookPath,
1202
- `${existing.trimEnd()}
1203
-
1204
- # viberails check
1205
- npx viberails check --staged
1206
- `
1207
- );
1208
- return;
1209
- }
1210
- const script = [
1211
- "#!/bin/sh",
1212
- "# Generated by viberails \u2014 https://viberails.sh",
1213
- "",
1214
- "npx viberails check --staged",
1215
- ""
1216
- ].join("\n");
1217
- fs11.writeFileSync(hookPath, script, { mode: 493 });
1218
- }
1219
- function addLefthookPreCommit(lefthookPath) {
1220
- const content = fs11.readFileSync(lefthookPath, "utf-8");
1221
- if (content.includes("viberails")) return;
1222
- const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1223
- fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1224
- ${addition}
1225
- `);
1226
- }
1227
- function writeHuskyPreCommit(huskyDir) {
1228
- const hookPath = path12.join(huskyDir, "pre-commit");
1229
- if (fs11.existsSync(hookPath)) {
1230
- const existing = fs11.readFileSync(hookPath, "utf-8");
1231
- if (!existing.includes("viberails")) {
1232
- fs11.writeFileSync(hookPath, `${existing.trimEnd()}
1233
- npx viberails check --staged
1234
- `);
1235
- }
1236
- return;
1237
- }
1238
- fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1239
- }
1240
1327
 
1241
1328
  // src/commands/sync.ts
1242
- var fs12 = __toESM(require("fs"), 1);
1243
- var path13 = __toESM(require("path"), 1);
1329
+ var fs13 = __toESM(require("fs"), 1);
1330
+ var path14 = __toESM(require("path"), 1);
1244
1331
  var import_config5 = require("@viberails/config");
1245
1332
  var import_scanner2 = require("@viberails/scanner");
1246
- var import_chalk6 = __toESM(require("chalk"), 1);
1333
+ var import_chalk9 = __toESM(require("chalk"), 1);
1247
1334
  var CONFIG_FILE5 = "viberails.config.json";
1248
1335
  async function syncCommand(cwd) {
1249
1336
  const startDir = cwd ?? process.cwd();
@@ -1253,23 +1340,23 @@ async function syncCommand(cwd) {
1253
1340
  "No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
1254
1341
  );
1255
1342
  }
1256
- const configPath = path13.join(projectRoot, CONFIG_FILE5);
1343
+ const configPath = path14.join(projectRoot, CONFIG_FILE5);
1257
1344
  const existing = await (0, import_config5.loadConfig)(configPath);
1258
- console.log(import_chalk6.default.dim("Scanning project..."));
1345
+ console.log(import_chalk9.default.dim("Scanning project..."));
1259
1346
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
1260
1347
  const merged = (0, import_config5.mergeConfig)(existing, scanResult);
1261
- fs12.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1348
+ fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1262
1349
  `);
1263
1350
  writeGeneratedFiles(projectRoot, merged, scanResult);
1264
1351
  console.log(`
1265
- ${import_chalk6.default.bold("Synced:")}`);
1266
- console.log(` ${import_chalk6.default.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1267
- console.log(` ${import_chalk6.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1268
- console.log(` ${import_chalk6.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
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`);
1269
1356
  }
1270
1357
 
1271
1358
  // src/index.ts
1272
- var VERSION = "0.2.0";
1359
+ var VERSION = "0.2.2";
1273
1360
  var program = new import_commander.Command();
1274
1361
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1275
1362
  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) => {
@@ -1277,7 +1364,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1277
1364
  await initCommand(options);
1278
1365
  } catch (err) {
1279
1366
  const message = err instanceof Error ? err.message : String(err);
1280
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1367
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1281
1368
  process.exit(1);
1282
1369
  }
1283
1370
  });
@@ -1286,7 +1373,7 @@ program.command("sync").description("Re-scan and update generated files").action
1286
1373
  await syncCommand();
1287
1374
  } catch (err) {
1288
1375
  const message = err instanceof Error ? err.message : String(err);
1289
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1376
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1290
1377
  process.exit(1);
1291
1378
  }
1292
1379
  });
@@ -1299,7 +1386,7 @@ program.command("check").description("Check files against enforced rules").optio
1299
1386
  process.exit(exitCode);
1300
1387
  } catch (err) {
1301
1388
  const message = err instanceof Error ? err.message : String(err);
1302
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1389
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1303
1390
  process.exit(1);
1304
1391
  }
1305
1392
  });
@@ -1309,7 +1396,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
1309
1396
  process.exit(exitCode);
1310
1397
  } catch (err) {
1311
1398
  const message = err instanceof Error ? err.message : String(err);
1312
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1399
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1313
1400
  process.exit(1);
1314
1401
  }
1315
1402
  });
@@ -1318,7 +1405,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1318
1405
  await boundariesCommand(options);
1319
1406
  } catch (err) {
1320
1407
  const message = err instanceof Error ? err.message : String(err);
1321
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1408
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1322
1409
  process.exit(1);
1323
1410
  }
1324
1411
  });