viberails 0.2.0 → 0.2.1

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
@@ -532,12 +532,56 @@ ${violations.length} ${word} found.`);
532
532
  }
533
533
 
534
534
  // src/commands/fix.ts
535
- var import_node_child_process2 = require("child_process");
536
535
  var fs9 = __toESM(require("fs"), 1);
537
536
  var path10 = __toESM(require("path"), 1);
538
- var import_node_readline = require("readline");
539
537
  var import_config3 = require("@viberails/config");
538
+ var import_chalk4 = __toESM(require("chalk"), 1);
539
+
540
+ // src/commands/fix-helpers.ts
541
+ var import_node_child_process2 = require("child_process");
542
+ var import_node_readline = require("readline");
540
543
  var import_chalk3 = __toESM(require("chalk"), 1);
544
+ function printPlan(renames, stubs) {
545
+ if (renames.length > 0) {
546
+ console.log(import_chalk3.default.bold("\nFile renames:"));
547
+ for (const r of renames) {
548
+ console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
549
+ }
550
+ }
551
+ if (stubs.length > 0) {
552
+ console.log(import_chalk3.default.bold("\nTest stubs to create:"));
553
+ for (const s of stubs) {
554
+ console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
555
+ }
556
+ }
557
+ }
558
+ function checkGitDirty(projectRoot) {
559
+ try {
560
+ const output = (0, import_node_child_process2.execSync)("git status --porcelain", {
561
+ cwd: projectRoot,
562
+ encoding: "utf-8"
563
+ });
564
+ return output.trim().length > 0;
565
+ } catch {
566
+ return false;
567
+ }
568
+ }
569
+ function getConventionValue(convention) {
570
+ if (typeof convention === "string") return convention;
571
+ if (convention && typeof convention === "object" && "value" in convention) {
572
+ return convention.value;
573
+ }
574
+ return void 0;
575
+ }
576
+ function promptConfirm(question) {
577
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
578
+ return new Promise((resolve4) => {
579
+ rl.question(`${question} (y/N) `, (answer) => {
580
+ rl.close();
581
+ resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
582
+ });
583
+ });
584
+ }
541
585
 
542
586
  // src/commands/fix-imports.ts
543
587
  var path7 = __toESM(require("path"), 1);
@@ -757,13 +801,13 @@ async function fixCommand(options, cwd) {
757
801
  const startDir = cwd ?? process.cwd();
758
802
  const projectRoot = findProjectRoot(startDir);
759
803
  if (!projectRoot) {
760
- console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
804
+ console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
761
805
  return 1;
762
806
  }
763
807
  const configPath = path10.join(projectRoot, CONFIG_FILE3);
764
808
  if (!fs9.existsSync(configPath)) {
765
809
  console.error(
766
- `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
810
+ `${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
767
811
  );
768
812
  return 1;
769
813
  }
@@ -772,7 +816,7 @@ async function fixCommand(options, cwd) {
772
816
  const isDirty = checkGitDirty(projectRoot);
773
817
  if (isDirty) {
774
818
  console.log(
775
- import_chalk3.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
819
+ import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
776
820
  );
777
821
  }
778
822
  }
@@ -802,12 +846,12 @@ async function fixCommand(options, cwd) {
802
846
  }
803
847
  }
804
848
  if (dedupedRenames.length === 0 && testStubs.length === 0) {
805
- console.log(`${import_chalk3.default.green("\u2713")} No fixable violations found.`);
849
+ console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
806
850
  return 0;
807
851
  }
808
852
  printPlan(dedupedRenames, testStubs);
809
853
  if (options.dryRun) {
810
- console.log(import_chalk3.default.dim("\nDry run \u2014 no changes applied."));
854
+ console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
811
855
  return 0;
812
856
  }
813
857
  if (!options.yes) {
@@ -838,94 +882,89 @@ async function fixCommand(options, cwd) {
838
882
  }
839
883
  console.log("");
840
884
  if (renameCount > 0) {
841
- console.log(`${import_chalk3.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
885
+ console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
842
886
  }
843
887
  if (importUpdateCount > 0) {
844
888
  console.log(
845
- `${import_chalk3.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
889
+ `${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
846
890
  );
847
891
  }
848
892
  if (stubCount > 0) {
849
- console.log(`${import_chalk3.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
893
+ console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
850
894
  }
851
895
  return 0;
852
896
  }
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
897
 
895
898
  // src/commands/init.ts
896
- var fs11 = __toESM(require("fs"), 1);
897
- var path12 = __toESM(require("path"), 1);
899
+ var fs12 = __toESM(require("fs"), 1);
900
+ var path13 = __toESM(require("path"), 1);
898
901
  var import_config4 = require("@viberails/config");
899
902
  var import_scanner = require("@viberails/scanner");
900
- var import_chalk5 = __toESM(require("chalk"), 1);
903
+ var import_chalk8 = __toESM(require("chalk"), 1);
901
904
 
902
905
  // src/display.ts
906
+ var import_types3 = require("@viberails/types");
907
+ var import_chalk6 = __toESM(require("chalk"), 1);
908
+
909
+ // src/display-helpers.ts
903
910
  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;
911
+ function groupByRole(directories) {
912
+ const map = /* @__PURE__ */ new Map();
913
+ for (const dir of directories) {
914
+ if (dir.role === "unknown") continue;
915
+ const existing = map.get(dir.role);
916
+ if (existing) {
917
+ existing.dirs.push(dir);
918
+ } else {
919
+ map.set(dir.role, { dirs: [dir] });
920
+ }
921
+ }
922
+ const groups = [];
923
+ for (const [role, { dirs }] of map) {
924
+ const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
925
+ const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
926
+ groups.push({
927
+ role,
928
+ label,
929
+ dirCount: dirs.length,
930
+ totalFiles,
931
+ singlePath: dirs.length === 1 ? dirs[0].path : void 0
932
+ });
933
+ }
934
+ return groups;
914
935
  }
915
- function confidenceLabel(convention) {
916
- const pct = Math.round(convention.consistency);
917
- if (convention.confidence === "high") {
918
- return `${pct}% \u2014 high confidence, will enforce`;
936
+ function formatSummary(stats, packageCount) {
937
+ const parts = [];
938
+ if (packageCount && packageCount > 1) {
939
+ parts.push(`${packageCount} packages`);
919
940
  }
920
- return `${pct}% \u2014 medium confidence, suggested only`;
941
+ parts.push(`${stats.totalFiles.toLocaleString()} source files`);
942
+ parts.push(`${stats.totalLines.toLocaleString()} lines`);
943
+ parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
944
+ return parts.join(" \xB7 ");
945
+ }
946
+ function formatExtensions(filesByExtension, maxEntries = 4) {
947
+ return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
948
+ }
949
+ function formatRoleGroup(group) {
950
+ const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
951
+ if (group.singlePath) {
952
+ return `${group.label} \u2014 ${group.singlePath} (${files})`;
953
+ }
954
+ const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
955
+ return `${group.label} \u2014 ${dirs} (${files})`;
921
956
  }
957
+
958
+ // src/display-monorepo.ts
959
+ var import_types2 = require("@viberails/types");
960
+ var import_chalk5 = __toESM(require("chalk"), 1);
922
961
  function formatPackageSummary(pkg) {
923
962
  const parts = [];
924
963
  if (pkg.stack.framework) {
925
- parts.push(formatItem(pkg.stack.framework, import_types.FRAMEWORK_NAMES));
964
+ parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
926
965
  }
927
966
  if (pkg.stack.styling) {
928
- parts.push(formatItem(pkg.stack.styling, import_types.STYLING_NAMES));
967
+ parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
929
968
  }
930
969
  const files = `${pkg.statistics.totalFiles} files`;
931
970
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
@@ -934,16 +973,19 @@ function formatPackageSummary(pkg) {
934
973
  function displayMonorepoResults(scanResult) {
935
974
  const { stack, packages } = scanResult;
936
975
  console.log(`
937
- ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
938
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
976
+ ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
977
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
939
978
  if (stack.packageManager) {
940
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
979
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
941
980
  }
942
981
  if (stack.linter) {
943
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
982
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
983
+ }
984
+ if (stack.formatter) {
985
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
944
986
  }
945
987
  if (stack.testRunner) {
946
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
988
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
947
989
  }
948
990
  console.log("");
949
991
  for (const pkg of packages) {
@@ -954,96 +996,123 @@ ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`
954
996
  );
955
997
  if (packagesWithDirs.length > 0) {
956
998
  console.log(`
957
- ${import_chalk4.default.bold("Structure:")}`);
999
+ ${import_chalk5.default.bold("Structure:")}`);
958
1000
  for (const pkg of packagesWithDirs) {
959
- const meaningfulDirs = pkg.structure.directories.filter((d) => d.role !== "unknown");
960
- if (meaningfulDirs.length === 0) continue;
1001
+ const groups = groupByRole(pkg.structure.directories);
1002
+ if (groups.length === 0) continue;
961
1003
  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})`);
1004
+ for (const group of groups) {
1005
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
966
1006
  }
967
1007
  }
968
1008
  }
1009
+ displayConventions(scanResult);
1010
+ displaySummarySection(scanResult);
1011
+ console.log("");
1012
+ }
1013
+
1014
+ // src/display.ts
1015
+ var CONVENTION_LABELS = {
1016
+ fileNaming: "File naming",
1017
+ componentNaming: "Component naming",
1018
+ hookNaming: "Hook naming",
1019
+ importAlias: "Import alias"
1020
+ };
1021
+ function formatItem(item, nameMap) {
1022
+ const name = nameMap?.[item.name] ?? item.name;
1023
+ return item.version ? `${name} ${item.version}` : name;
1024
+ }
1025
+ function confidenceLabel(convention) {
1026
+ const pct = Math.round(convention.consistency);
1027
+ if (convention.confidence === "high") {
1028
+ return `${pct}% \u2014 high confidence, will enforce`;
1029
+ }
1030
+ return `${pct}% \u2014 medium confidence, suggested only`;
1031
+ }
1032
+ function displayConventions(scanResult) {
969
1033
  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] }));
1034
+ if (conventionEntries.length === 0) return;
1035
+ console.log(`
1036
+ ${import_chalk6.default.bold("Conventions:")}`);
1037
+ for (const [key, convention] of conventionEntries) {
1038
+ if (convention.confidence === "low") continue;
1039
+ const label = CONVENTION_LABELS[key] ?? key;
1040
+ if (scanResult.packages.length > 1) {
1041
+ const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
977
1042
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
978
1043
  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)})`);
1044
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1045
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
981
1046
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
982
1047
  } else {
983
- console.log(` ${import_chalk4.default.yellow("~")} ${label}: varies by package`);
1048
+ console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
984
1049
  for (const pv of pkgValues) {
985
1050
  const pct = Math.round(pv.convention.consistency);
986
1051
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
987
1052
  }
988
1053
  }
1054
+ } else {
1055
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1056
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1057
+ console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
989
1058
  }
990
1059
  }
991
- console.log("");
1060
+ }
1061
+ function displaySummarySection(scanResult) {
1062
+ const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1063
+ console.log(`
1064
+ ${import_chalk6.default.bold("Summary:")}`);
1065
+ console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1066
+ const ext = formatExtensions(scanResult.statistics.filesByExtension);
1067
+ if (ext) {
1068
+ console.log(` ${ext}`);
1069
+ }
992
1070
  }
993
1071
  function displayScanResults(scanResult) {
994
1072
  if (scanResult.packages.length > 1) {
995
1073
  displayMonorepoResults(scanResult);
996
1074
  return;
997
1075
  }
998
- const { stack, conventions } = scanResult;
1076
+ const { stack } = scanResult;
999
1077
  console.log(`
1000
- ${import_chalk4.default.bold("Detected:")}`);
1078
+ ${import_chalk6.default.bold("Detected:")}`);
1001
1079
  if (stack.framework) {
1002
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.framework, import_types.FRAMEWORK_NAMES)}`);
1080
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1003
1081
  }
1004
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1082
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1005
1083
  if (stack.styling) {
1006
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.styling, import_types.STYLING_NAMES)}`);
1084
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1007
1085
  }
1008
1086
  if (stack.backend) {
1009
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.backend, import_types.FRAMEWORK_NAMES)}`);
1087
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1010
1088
  }
1011
1089
  if (stack.linter) {
1012
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1090
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1091
+ }
1092
+ if (stack.formatter) {
1093
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1013
1094
  }
1014
1095
  if (stack.testRunner) {
1015
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1096
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1016
1097
  }
1017
1098
  if (stack.packageManager) {
1018
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1099
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1019
1100
  }
1020
1101
  if (stack.libraries.length > 0) {
1021
1102
  for (const lib of stack.libraries) {
1022
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(lib, import_types.LIBRARY_NAMES)}`);
1103
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1023
1104
  }
1024
1105
  }
1025
- const meaningfulDirs = scanResult.structure.directories.filter((d) => d.role !== "unknown");
1026
- if (meaningfulDirs.length > 0) {
1106
+ const groups = groupByRole(scanResult.structure.directories);
1107
+ if (groups.length > 0) {
1027
1108
  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}`);
1109
+ ${import_chalk6.default.bold("Structure:")}`);
1110
+ for (const group of groups) {
1111
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1045
1112
  }
1046
1113
  }
1114
+ displayConventions(scanResult);
1115
+ displaySummarySection(scanResult);
1047
1116
  console.log("");
1048
1117
  }
1049
1118
 
@@ -1073,6 +1142,79 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1073
1142
  }
1074
1143
  }
1075
1144
 
1145
+ // src/commands/init-hooks.ts
1146
+ var fs11 = __toESM(require("fs"), 1);
1147
+ var path12 = __toESM(require("path"), 1);
1148
+ var import_chalk7 = __toESM(require("chalk"), 1);
1149
+ function setupPreCommitHook(projectRoot) {
1150
+ const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1151
+ if (fs11.existsSync(lefthookPath)) {
1152
+ addLefthookPreCommit(lefthookPath);
1153
+ console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1154
+ return;
1155
+ }
1156
+ const huskyDir = path12.join(projectRoot, ".husky");
1157
+ if (fs11.existsSync(huskyDir)) {
1158
+ writeHuskyPreCommit(huskyDir);
1159
+ console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1160
+ return;
1161
+ }
1162
+ const gitDir = path12.join(projectRoot, ".git");
1163
+ if (fs11.existsSync(gitDir)) {
1164
+ const hooksDir = path12.join(gitDir, "hooks");
1165
+ if (!fs11.existsSync(hooksDir)) {
1166
+ fs11.mkdirSync(hooksDir, { recursive: true });
1167
+ }
1168
+ writeGitHookPreCommit(hooksDir);
1169
+ console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
1170
+ }
1171
+ }
1172
+ function writeGitHookPreCommit(hooksDir) {
1173
+ const hookPath = path12.join(hooksDir, "pre-commit");
1174
+ if (fs11.existsSync(hookPath)) {
1175
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1176
+ if (existing.includes("viberails")) return;
1177
+ fs11.writeFileSync(
1178
+ hookPath,
1179
+ `${existing.trimEnd()}
1180
+
1181
+ # viberails check
1182
+ npx viberails check --staged
1183
+ `
1184
+ );
1185
+ return;
1186
+ }
1187
+ const script = [
1188
+ "#!/bin/sh",
1189
+ "# Generated by viberails \u2014 https://viberails.sh",
1190
+ "",
1191
+ "npx viberails check --staged",
1192
+ ""
1193
+ ].join("\n");
1194
+ fs11.writeFileSync(hookPath, script, { mode: 493 });
1195
+ }
1196
+ function addLefthookPreCommit(lefthookPath) {
1197
+ const content = fs11.readFileSync(lefthookPath, "utf-8");
1198
+ if (content.includes("viberails")) return;
1199
+ const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1200
+ fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1201
+ ${addition}
1202
+ `);
1203
+ }
1204
+ function writeHuskyPreCommit(huskyDir) {
1205
+ const hookPath = path12.join(huskyDir, "pre-commit");
1206
+ if (fs11.existsSync(hookPath)) {
1207
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1208
+ if (!existing.includes("viberails")) {
1209
+ fs11.writeFileSync(hookPath, `${existing.trimEnd()}
1210
+ npx viberails check --staged
1211
+ `);
1212
+ }
1213
+ return;
1214
+ }
1215
+ fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1216
+ }
1217
+
1076
1218
  // src/commands/init.ts
1077
1219
  var CONFIG_FILE4 = "viberails.config.json";
1078
1220
  function filterHighConfidence(conventions) {
@@ -1095,19 +1237,19 @@ async function initCommand(options, cwd) {
1095
1237
  "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
1238
  );
1097
1239
  }
1098
- const configPath = path12.join(projectRoot, CONFIG_FILE4);
1099
- if (fs11.existsSync(configPath)) {
1240
+ const configPath = path13.join(projectRoot, CONFIG_FILE4);
1241
+ if (fs12.existsSync(configPath)) {
1100
1242
  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."
1243
+ 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
1244
  );
1103
1245
  return;
1104
1246
  }
1105
- console.log(import_chalk5.default.dim("Scanning project..."));
1247
+ console.log(import_chalk8.default.dim("Scanning project..."));
1106
1248
  const scanResult = await (0, import_scanner.scan)(projectRoot);
1107
1249
  displayScanResults(scanResult);
1108
1250
  if (scanResult.statistics.totalFiles === 0) {
1109
1251
  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"
1252
+ 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
1253
  );
1112
1254
  }
1113
1255
  if (!options.yes) {
@@ -1127,7 +1269,7 @@ async function initCommand(options, cwd) {
1127
1269
  shouldInfer = await confirm("Infer boundary rules from import patterns?");
1128
1270
  }
1129
1271
  if (shouldInfer) {
1130
- console.log(import_chalk5.default.dim("Building import graph..."));
1272
+ console.log(import_chalk8.default.dim("Building import graph..."));
1131
1273
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1132
1274
  const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1133
1275
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -1135,115 +1277,47 @@ async function initCommand(options, cwd) {
1135
1277
  if (inferred.length > 0) {
1136
1278
  config.boundaries = inferred;
1137
1279
  config.rules.enforceBoundaries = true;
1138
- console.log(` ${import_chalk5.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1280
+ console.log(` ${import_chalk8.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1139
1281
  }
1140
1282
  }
1141
1283
  }
1142
- fs11.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1284
+ fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1143
1285
  `);
1144
1286
  writeGeneratedFiles(projectRoot, config, scanResult);
1145
1287
  updateGitignore(projectRoot);
1146
1288
  setupPreCommitHook(projectRoot);
1147
1289
  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`);
1290
+ ${import_chalk8.default.bold("Created:")}`);
1291
+ console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
1292
+ console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
1293
+ console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
1152
1294
  console.log(`
1153
- ${import_chalk5.default.bold("Next steps:")}`);
1154
- console.log(` 1. Review ${import_chalk5.default.cyan("viberails.config.json")} and adjust rules`);
1295
+ ${import_chalk8.default.bold("Next steps:")}`);
1296
+ console.log(` 1. Review ${import_chalk8.default.cyan("viberails.config.json")} and adjust rules`);
1155
1297
  console.log(
1156
- ` 2. Commit ${import_chalk5.default.cyan("viberails.config.json")} and ${import_chalk5.default.cyan(".viberails/context.md")}`
1298
+ ` 2. Commit ${import_chalk8.default.cyan("viberails.config.json")} and ${import_chalk8.default.cyan(".viberails/context.md")}`
1157
1299
  );
1158
- console.log(` 3. Run ${import_chalk5.default.cyan("viberails check")} to verify your project passes`);
1300
+ console.log(` 3. Run ${import_chalk8.default.cyan("viberails check")} to verify your project passes`);
1159
1301
  }
1160
1302
  function updateGitignore(projectRoot) {
1161
- const gitignorePath = path12.join(projectRoot, ".gitignore");
1303
+ const gitignorePath = path13.join(projectRoot, ".gitignore");
1162
1304
  let content = "";
1163
- if (fs11.existsSync(gitignorePath)) {
1164
- content = fs11.readFileSync(gitignorePath, "utf-8");
1305
+ if (fs12.existsSync(gitignorePath)) {
1306
+ content = fs12.readFileSync(gitignorePath, "utf-8");
1165
1307
  }
1166
1308
  if (!content.includes(".viberails/scan-result.json")) {
1167
1309
  const block = "\n# viberails\n.viberails/scan-result.json\n";
1168
- fs11.writeFileSync(gitignorePath, `${content.trimEnd()}
1310
+ fs12.writeFileSync(gitignorePath, `${content.trimEnd()}
1169
1311
  ${block}`);
1170
1312
  }
1171
1313
  }
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
1314
 
1241
1315
  // src/commands/sync.ts
1242
- var fs12 = __toESM(require("fs"), 1);
1243
- var path13 = __toESM(require("path"), 1);
1316
+ var fs13 = __toESM(require("fs"), 1);
1317
+ var path14 = __toESM(require("path"), 1);
1244
1318
  var import_config5 = require("@viberails/config");
1245
1319
  var import_scanner2 = require("@viberails/scanner");
1246
- var import_chalk6 = __toESM(require("chalk"), 1);
1320
+ var import_chalk9 = __toESM(require("chalk"), 1);
1247
1321
  var CONFIG_FILE5 = "viberails.config.json";
1248
1322
  async function syncCommand(cwd) {
1249
1323
  const startDir = cwd ?? process.cwd();
@@ -1253,23 +1327,23 @@ async function syncCommand(cwd) {
1253
1327
  "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
1328
  );
1255
1329
  }
1256
- const configPath = path13.join(projectRoot, CONFIG_FILE5);
1330
+ const configPath = path14.join(projectRoot, CONFIG_FILE5);
1257
1331
  const existing = await (0, import_config5.loadConfig)(configPath);
1258
- console.log(import_chalk6.default.dim("Scanning project..."));
1332
+ console.log(import_chalk9.default.dim("Scanning project..."));
1259
1333
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
1260
1334
  const merged = (0, import_config5.mergeConfig)(existing, scanResult);
1261
- fs12.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1335
+ fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1262
1336
  `);
1263
1337
  writeGeneratedFiles(projectRoot, merged, scanResult);
1264
1338
  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`);
1339
+ ${import_chalk9.default.bold("Synced:")}`);
1340
+ console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1341
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1342
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1269
1343
  }
1270
1344
 
1271
1345
  // src/index.ts
1272
- var VERSION = "0.2.0";
1346
+ var VERSION = "0.2.1";
1273
1347
  var program = new import_commander.Command();
1274
1348
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1275
1349
  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 +1351,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1277
1351
  await initCommand(options);
1278
1352
  } catch (err) {
1279
1353
  const message = err instanceof Error ? err.message : String(err);
1280
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1354
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1281
1355
  process.exit(1);
1282
1356
  }
1283
1357
  });
@@ -1286,7 +1360,7 @@ program.command("sync").description("Re-scan and update generated files").action
1286
1360
  await syncCommand();
1287
1361
  } catch (err) {
1288
1362
  const message = err instanceof Error ? err.message : String(err);
1289
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1363
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1290
1364
  process.exit(1);
1291
1365
  }
1292
1366
  });
@@ -1299,7 +1373,7 @@ program.command("check").description("Check files against enforced rules").optio
1299
1373
  process.exit(exitCode);
1300
1374
  } catch (err) {
1301
1375
  const message = err instanceof Error ? err.message : String(err);
1302
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1376
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1303
1377
  process.exit(1);
1304
1378
  }
1305
1379
  });
@@ -1309,7 +1383,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
1309
1383
  process.exit(exitCode);
1310
1384
  } catch (err) {
1311
1385
  const message = err instanceof Error ? err.message : String(err);
1312
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1386
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1313
1387
  process.exit(1);
1314
1388
  }
1315
1389
  });
@@ -1318,7 +1392,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1318
1392
  await boundariesCommand(options);
1319
1393
  } catch (err) {
1320
1394
  const message = err instanceof Error ? err.message : String(err);
1321
- console.error(`${import_chalk7.default.red("Error:")} ${message}`);
1395
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1322
1396
  process.exit(1);
1323
1397
  }
1324
1398
  });