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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import chalk7 from "chalk";
4
+ import chalk10 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/boundaries.ts
@@ -223,6 +223,19 @@ function resolveIgnoreForFile(relPath, config) {
223
223
  import { execSync } from "child_process";
224
224
  import * as fs4 from "fs";
225
225
  import * as path4 from "path";
226
+ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
227
+ "node_modules",
228
+ ".git",
229
+ "dist",
230
+ "build",
231
+ ".next",
232
+ ".expo",
233
+ ".output",
234
+ ".svelte-kit",
235
+ ".turbo",
236
+ "coverage",
237
+ ".viberails"
238
+ ]);
226
239
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
227
240
  ".ts",
228
241
  ".tsx",
@@ -271,7 +284,7 @@ function checkNaming(relPath, conventions) {
271
284
  const filename = path4.basename(relPath);
272
285
  const ext = path4.extname(filename);
273
286
  if (!SOURCE_EXTS.has(ext)) return void 0;
274
- if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".")) {
287
+ if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".") || filename.startsWith("_") || filename.startsWith("+") || filename.startsWith("$") || filename.startsWith("[")) {
275
288
  return void 0;
276
289
  }
277
290
  const bare = filename.slice(0, filename.indexOf("."));
@@ -304,7 +317,7 @@ function getAllSourceFiles(projectRoot, config) {
304
317
  for (const entry of entries) {
305
318
  const rel = path4.relative(projectRoot, path4.join(dir, entry.name));
306
319
  if (entry.isDirectory()) {
307
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") {
320
+ if (ALWAYS_SKIP_DIRS.has(entry.name)) {
308
321
  continue;
309
322
  }
310
323
  if (isIgnored(rel, config.ignore)) continue;
@@ -499,12 +512,56 @@ ${violations.length} ${word} found.`);
499
512
  }
500
513
 
501
514
  // src/commands/fix.ts
502
- import { execSync as execSync2 } from "child_process";
503
515
  import * as fs9 from "fs";
504
516
  import * as path10 from "path";
505
- import { createInterface as createInterface2 } from "readline";
506
517
  import { loadConfig as loadConfig3 } from "@viberails/config";
518
+ import chalk4 from "chalk";
519
+
520
+ // src/commands/fix-helpers.ts
521
+ import { execSync as execSync2 } from "child_process";
522
+ import { createInterface as createInterface2 } from "readline";
507
523
  import chalk3 from "chalk";
524
+ function printPlan(renames, stubs) {
525
+ if (renames.length > 0) {
526
+ console.log(chalk3.bold("\nFile renames:"));
527
+ for (const r of renames) {
528
+ console.log(` ${chalk3.red(r.oldPath)} \u2192 ${chalk3.green(r.newPath)}`);
529
+ }
530
+ }
531
+ if (stubs.length > 0) {
532
+ console.log(chalk3.bold("\nTest stubs to create:"));
533
+ for (const s of stubs) {
534
+ console.log(` ${chalk3.green("+")} ${s.path}`);
535
+ }
536
+ }
537
+ }
538
+ function checkGitDirty(projectRoot) {
539
+ try {
540
+ const output = execSync2("git status --porcelain", {
541
+ cwd: projectRoot,
542
+ encoding: "utf-8"
543
+ });
544
+ return output.trim().length > 0;
545
+ } catch {
546
+ return false;
547
+ }
548
+ }
549
+ function getConventionValue(convention) {
550
+ if (typeof convention === "string") return convention;
551
+ if (convention && typeof convention === "object" && "value" in convention) {
552
+ return convention.value;
553
+ }
554
+ return void 0;
555
+ }
556
+ function promptConfirm(question) {
557
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
558
+ return new Promise((resolve4) => {
559
+ rl.question(`${question} (y/N) `, (answer) => {
560
+ rl.close();
561
+ resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
562
+ });
563
+ });
564
+ }
508
565
 
509
566
  // src/commands/fix-imports.ts
510
567
  import * as path7 from "path";
@@ -724,22 +781,22 @@ async function fixCommand(options, cwd) {
724
781
  const startDir = cwd ?? process.cwd();
725
782
  const projectRoot = findProjectRoot(startDir);
726
783
  if (!projectRoot) {
727
- console.error(`${chalk3.red("Error:")} No package.json found. Are you in a JS/TS project?`);
784
+ console.error(`${chalk4.red("Error:")} No package.json found. Are you in a JS/TS project?`);
728
785
  return 1;
729
786
  }
730
787
  const configPath = path10.join(projectRoot, CONFIG_FILE3);
731
788
  if (!fs9.existsSync(configPath)) {
732
789
  console.error(
733
- `${chalk3.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
790
+ `${chalk4.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
734
791
  );
735
792
  return 1;
736
793
  }
737
794
  const config = await loadConfig3(configPath);
738
- if (!options.yes && !options.dryRun) {
795
+ if (!options.dryRun) {
739
796
  const isDirty = checkGitDirty(projectRoot);
740
797
  if (isDirty) {
741
798
  console.log(
742
- chalk3.yellow("Warning: You have uncommitted changes. Consider committing first.")
799
+ chalk4.yellow("Warning: You have uncommitted changes. Consider committing first.")
743
800
  );
744
801
  }
745
802
  }
@@ -769,12 +826,12 @@ async function fixCommand(options, cwd) {
769
826
  }
770
827
  }
771
828
  if (dedupedRenames.length === 0 && testStubs.length === 0) {
772
- console.log(`${chalk3.green("\u2713")} No fixable violations found.`);
829
+ console.log(`${chalk4.green("\u2713")} No fixable violations found.`);
773
830
  return 0;
774
831
  }
775
832
  printPlan(dedupedRenames, testStubs);
776
833
  if (options.dryRun) {
777
- console.log(chalk3.dim("\nDry run \u2014 no changes applied."));
834
+ console.log(chalk4.dim("\nDry run \u2014 no changes applied."));
778
835
  return 0;
779
836
  }
780
837
  if (!options.yes) {
@@ -805,87 +862,82 @@ async function fixCommand(options, cwd) {
805
862
  }
806
863
  console.log("");
807
864
  if (renameCount > 0) {
808
- console.log(`${chalk3.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
865
+ console.log(`${chalk4.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
809
866
  }
810
867
  if (importUpdateCount > 0) {
811
868
  console.log(
812
- `${chalk3.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
869
+ `${chalk4.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
813
870
  );
814
871
  }
815
872
  if (stubCount > 0) {
816
- console.log(`${chalk3.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
873
+ console.log(`${chalk4.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
817
874
  }
818
875
  return 0;
819
876
  }
820
- function printPlan(renames, stubs) {
821
- if (renames.length > 0) {
822
- console.log(chalk3.bold("\nFile renames:"));
823
- for (const r of renames) {
824
- console.log(` ${chalk3.red(r.oldPath)} \u2192 ${chalk3.green(r.newPath)}`);
825
- }
826
- }
827
- if (stubs.length > 0) {
828
- console.log(chalk3.bold("\nTest stubs to create:"));
829
- for (const s of stubs) {
830
- console.log(` ${chalk3.green("+")} ${s.path}`);
877
+
878
+ // src/commands/init.ts
879
+ import * as fs12 from "fs";
880
+ import * as path13 from "path";
881
+ import { generateConfig } from "@viberails/config";
882
+ import { scan } from "@viberails/scanner";
883
+ import chalk8 from "chalk";
884
+
885
+ // src/display.ts
886
+ import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
887
+ import chalk6 from "chalk";
888
+
889
+ // src/display-helpers.ts
890
+ import { ROLE_DESCRIPTIONS } from "@viberails/types";
891
+ function groupByRole(directories) {
892
+ const map = /* @__PURE__ */ new Map();
893
+ for (const dir of directories) {
894
+ if (dir.role === "unknown") continue;
895
+ const existing = map.get(dir.role);
896
+ if (existing) {
897
+ existing.dirs.push(dir);
898
+ } else {
899
+ map.set(dir.role, { dirs: [dir] });
831
900
  }
832
901
  }
833
- }
834
- function checkGitDirty(projectRoot) {
835
- try {
836
- const output = execSync2("git status --porcelain", {
837
- cwd: projectRoot,
838
- encoding: "utf-8"
902
+ const groups = [];
903
+ for (const [role, { dirs }] of map) {
904
+ const label = ROLE_DESCRIPTIONS[role] ?? role;
905
+ const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
906
+ groups.push({
907
+ role,
908
+ label,
909
+ dirCount: dirs.length,
910
+ totalFiles,
911
+ singlePath: dirs.length === 1 ? dirs[0].path : void 0
839
912
  });
840
- return output.trim().length > 0;
841
- } catch {
842
- return false;
843
913
  }
914
+ return groups;
844
915
  }
845
- function getConventionValue(convention) {
846
- if (typeof convention === "string") return convention;
847
- if (convention && typeof convention === "object" && "value" in convention) {
848
- return convention.value;
916
+ function formatSummary(stats, packageCount) {
917
+ const parts = [];
918
+ if (packageCount && packageCount > 1) {
919
+ parts.push(`${packageCount} packages`);
849
920
  }
850
- return void 0;
921
+ parts.push(`${stats.totalFiles.toLocaleString()} source files`);
922
+ parts.push(`${stats.totalLines.toLocaleString()} lines`);
923
+ parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
924
+ return parts.join(" \xB7 ");
851
925
  }
852
- function promptConfirm(question) {
853
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
854
- return new Promise((resolve4) => {
855
- rl.question(`${question} (y/N) `, (answer) => {
856
- rl.close();
857
- resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
858
- });
859
- });
926
+ function formatExtensions(filesByExtension, maxEntries = 4) {
927
+ return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
860
928
  }
861
-
862
- // src/commands/init.ts
863
- import * as fs11 from "fs";
864
- import * as path12 from "path";
865
- import { generateConfig } from "@viberails/config";
866
- import { scan } from "@viberails/scanner";
867
- import chalk5 from "chalk";
868
-
869
- // src/display.ts
870
- import { FRAMEWORK_NAMES, LIBRARY_NAMES, ROLE_DESCRIPTIONS, STYLING_NAMES } from "@viberails/types";
871
- import chalk4 from "chalk";
872
- var CONVENTION_LABELS = {
873
- fileNaming: "File naming",
874
- componentNaming: "Component naming",
875
- hookNaming: "Hook naming",
876
- importAlias: "Import alias"
877
- };
878
- function formatItem(item, nameMap) {
879
- const name = nameMap?.[item.name] ?? item.name;
880
- return item.version ? `${name} ${item.version}` : name;
881
- }
882
- function confidenceLabel(convention) {
883
- const pct = Math.round(convention.consistency);
884
- if (convention.confidence === "high") {
885
- return `${pct}% \u2014 high confidence, will enforce`;
929
+ function formatRoleGroup(group) {
930
+ const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
931
+ if (group.singlePath) {
932
+ return `${group.label} \u2014 ${group.singlePath} (${files})`;
886
933
  }
887
- return `${pct}% \u2014 medium confidence, suggested only`;
934
+ const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
935
+ return `${group.label} \u2014 ${dirs} (${files})`;
888
936
  }
937
+
938
+ // src/display-monorepo.ts
939
+ import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
940
+ import chalk5 from "chalk";
889
941
  function formatPackageSummary(pkg) {
890
942
  const parts = [];
891
943
  if (pkg.stack.framework) {
@@ -901,16 +953,19 @@ function formatPackageSummary(pkg) {
901
953
  function displayMonorepoResults(scanResult) {
902
954
  const { stack, packages } = scanResult;
903
955
  console.log(`
904
- ${chalk4.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
905
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.language)}`);
956
+ ${chalk5.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
957
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.language)}`);
906
958
  if (stack.packageManager) {
907
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.packageManager)}`);
959
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.packageManager)}`);
908
960
  }
909
961
  if (stack.linter) {
910
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)}`);
962
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)}`);
963
+ }
964
+ if (stack.formatter) {
965
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.formatter)}`);
911
966
  }
912
967
  if (stack.testRunner) {
913
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.testRunner)}`);
968
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.testRunner)}`);
914
969
  }
915
970
  console.log("");
916
971
  for (const pkg of packages) {
@@ -921,96 +976,123 @@ ${chalk4.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
921
976
  );
922
977
  if (packagesWithDirs.length > 0) {
923
978
  console.log(`
924
- ${chalk4.bold("Structure:")}`);
979
+ ${chalk5.bold("Structure:")}`);
925
980
  for (const pkg of packagesWithDirs) {
926
- const meaningfulDirs = pkg.structure.directories.filter((d) => d.role !== "unknown");
927
- if (meaningfulDirs.length === 0) continue;
981
+ const groups = groupByRole(pkg.structure.directories);
982
+ if (groups.length === 0) continue;
928
983
  console.log(` ${pkg.relativePath}:`);
929
- for (const dir of meaningfulDirs) {
930
- const label = ROLE_DESCRIPTIONS[dir.role] ?? dir.role;
931
- const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
932
- console.log(` ${chalk4.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
984
+ for (const group of groups) {
985
+ console.log(` ${chalk5.green("\u2713")} ${formatRoleGroup(group)}`);
933
986
  }
934
987
  }
935
988
  }
989
+ displayConventions(scanResult);
990
+ displaySummarySection(scanResult);
991
+ console.log("");
992
+ }
993
+
994
+ // src/display.ts
995
+ var CONVENTION_LABELS = {
996
+ fileNaming: "File naming",
997
+ componentNaming: "Component naming",
998
+ hookNaming: "Hook naming",
999
+ importAlias: "Import alias"
1000
+ };
1001
+ function formatItem(item, nameMap) {
1002
+ const name = nameMap?.[item.name] ?? item.name;
1003
+ return item.version ? `${name} ${item.version}` : name;
1004
+ }
1005
+ function confidenceLabel(convention) {
1006
+ const pct = Math.round(convention.consistency);
1007
+ if (convention.confidence === "high") {
1008
+ return `${pct}% \u2014 high confidence, will enforce`;
1009
+ }
1010
+ return `${pct}% \u2014 medium confidence, suggested only`;
1011
+ }
1012
+ function displayConventions(scanResult) {
936
1013
  const conventionEntries = Object.entries(scanResult.conventions);
937
- if (conventionEntries.length > 0) {
938
- console.log(`
939
- ${chalk4.bold("Conventions:")}`);
940
- for (const [key, convention] of conventionEntries) {
941
- if (convention.confidence === "low") continue;
942
- const label = CONVENTION_LABELS[key] ?? key;
943
- const pkgValues = packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1014
+ if (conventionEntries.length === 0) return;
1015
+ console.log(`
1016
+ ${chalk6.bold("Conventions:")}`);
1017
+ for (const [key, convention] of conventionEntries) {
1018
+ if (convention.confidence === "low") continue;
1019
+ const label = CONVENTION_LABELS[key] ?? key;
1020
+ if (scanResult.packages.length > 1) {
1021
+ const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
944
1022
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
945
1023
  if (allSame || pkgValues.length <= 1) {
946
- const ind = convention.confidence === "high" ? chalk4.green("\u2713") : chalk4.yellow("~");
947
- const detail = chalk4.dim(`(${confidenceLabel(convention)})`);
1024
+ const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
1025
+ const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
948
1026
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
949
1027
  } else {
950
- console.log(` ${chalk4.yellow("~")} ${label}: varies by package`);
1028
+ console.log(` ${chalk6.yellow("~")} ${label}: varies by package`);
951
1029
  for (const pv of pkgValues) {
952
1030
  const pct = Math.round(pv.convention.consistency);
953
1031
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
954
1032
  }
955
1033
  }
1034
+ } else {
1035
+ const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
1036
+ const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
1037
+ console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
956
1038
  }
957
1039
  }
958
- console.log("");
1040
+ }
1041
+ function displaySummarySection(scanResult) {
1042
+ const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1043
+ console.log(`
1044
+ ${chalk6.bold("Summary:")}`);
1045
+ console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1046
+ const ext = formatExtensions(scanResult.statistics.filesByExtension);
1047
+ if (ext) {
1048
+ console.log(` ${ext}`);
1049
+ }
959
1050
  }
960
1051
  function displayScanResults(scanResult) {
961
1052
  if (scanResult.packages.length > 1) {
962
1053
  displayMonorepoResults(scanResult);
963
1054
  return;
964
1055
  }
965
- const { stack, conventions } = scanResult;
1056
+ const { stack } = scanResult;
966
1057
  console.log(`
967
- ${chalk4.bold("Detected:")}`);
1058
+ ${chalk6.bold("Detected:")}`);
968
1059
  if (stack.framework) {
969
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES)}`);
1060
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
970
1061
  }
971
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.language)}`);
1062
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.language)}`);
972
1063
  if (stack.styling) {
973
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES)}`);
1064
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
974
1065
  }
975
1066
  if (stack.backend) {
976
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES)}`);
1067
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
977
1068
  }
978
1069
  if (stack.linter) {
979
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)}`);
1070
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.linter)}`);
1071
+ }
1072
+ if (stack.formatter) {
1073
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.formatter)}`);
980
1074
  }
981
1075
  if (stack.testRunner) {
982
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.testRunner)}`);
1076
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.testRunner)}`);
983
1077
  }
984
1078
  if (stack.packageManager) {
985
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.packageManager)}`);
1079
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.packageManager)}`);
986
1080
  }
987
1081
  if (stack.libraries.length > 0) {
988
1082
  for (const lib of stack.libraries) {
989
- console.log(` ${chalk4.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
990
- }
991
- }
992
- const meaningfulDirs = scanResult.structure.directories.filter((d) => d.role !== "unknown");
993
- if (meaningfulDirs.length > 0) {
994
- console.log(`
995
- ${chalk4.bold("Structure:")}`);
996
- for (const dir of meaningfulDirs) {
997
- const label = ROLE_DESCRIPTIONS[dir.role] ?? dir.role;
998
- const files = dir.fileCount === 1 ? "1 file" : `${dir.fileCount} files`;
999
- console.log(` ${chalk4.green("\u2713")} ${dir.path} \u2014 ${label} (${files})`);
1083
+ console.log(` ${chalk6.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
1000
1084
  }
1001
1085
  }
1002
- const conventionEntries = Object.entries(conventions);
1003
- if (conventionEntries.length > 0) {
1086
+ const groups = groupByRole(scanResult.structure.directories);
1087
+ if (groups.length > 0) {
1004
1088
  console.log(`
1005
- ${chalk4.bold("Conventions:")}`);
1006
- for (const [key, convention] of conventionEntries) {
1007
- if (convention.confidence === "low") continue;
1008
- const label = CONVENTION_LABELS[key] ?? key;
1009
- const ind = convention.confidence === "high" ? chalk4.green("\u2713") : chalk4.yellow("~");
1010
- const detail = chalk4.dim(`(${confidenceLabel(convention)})`);
1011
- console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1089
+ ${chalk6.bold("Structure:")}`);
1090
+ for (const group of groups) {
1091
+ console.log(` ${chalk6.green("\u2713")} ${formatRoleGroup(group)}`);
1012
1092
  }
1013
1093
  }
1094
+ displayConventions(scanResult);
1095
+ displaySummarySection(scanResult);
1014
1096
  console.log("");
1015
1097
  }
1016
1098
 
@@ -1040,6 +1122,79 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1040
1122
  }
1041
1123
  }
1042
1124
 
1125
+ // src/commands/init-hooks.ts
1126
+ import * as fs11 from "fs";
1127
+ import * as path12 from "path";
1128
+ import chalk7 from "chalk";
1129
+ function setupPreCommitHook(projectRoot) {
1130
+ const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1131
+ if (fs11.existsSync(lefthookPath)) {
1132
+ addLefthookPreCommit(lefthookPath);
1133
+ console.log(` ${chalk7.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1134
+ return;
1135
+ }
1136
+ const huskyDir = path12.join(projectRoot, ".husky");
1137
+ if (fs11.existsSync(huskyDir)) {
1138
+ writeHuskyPreCommit(huskyDir);
1139
+ console.log(` ${chalk7.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1140
+ return;
1141
+ }
1142
+ const gitDir = path12.join(projectRoot, ".git");
1143
+ if (fs11.existsSync(gitDir)) {
1144
+ const hooksDir = path12.join(gitDir, "hooks");
1145
+ if (!fs11.existsSync(hooksDir)) {
1146
+ fs11.mkdirSync(hooksDir, { recursive: true });
1147
+ }
1148
+ writeGitHookPreCommit(hooksDir);
1149
+ console.log(` ${chalk7.green("\u2713")} .git/hooks/pre-commit`);
1150
+ }
1151
+ }
1152
+ function writeGitHookPreCommit(hooksDir) {
1153
+ const hookPath = path12.join(hooksDir, "pre-commit");
1154
+ if (fs11.existsSync(hookPath)) {
1155
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1156
+ if (existing.includes("viberails")) return;
1157
+ fs11.writeFileSync(
1158
+ hookPath,
1159
+ `${existing.trimEnd()}
1160
+
1161
+ # viberails check
1162
+ npx viberails check --staged
1163
+ `
1164
+ );
1165
+ return;
1166
+ }
1167
+ const script = [
1168
+ "#!/bin/sh",
1169
+ "# Generated by viberails \u2014 https://viberails.sh",
1170
+ "",
1171
+ "npx viberails check --staged",
1172
+ ""
1173
+ ].join("\n");
1174
+ fs11.writeFileSync(hookPath, script, { mode: 493 });
1175
+ }
1176
+ function addLefthookPreCommit(lefthookPath) {
1177
+ const content = fs11.readFileSync(lefthookPath, "utf-8");
1178
+ if (content.includes("viberails")) return;
1179
+ const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1180
+ fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1181
+ ${addition}
1182
+ `);
1183
+ }
1184
+ function writeHuskyPreCommit(huskyDir) {
1185
+ const hookPath = path12.join(huskyDir, "pre-commit");
1186
+ if (fs11.existsSync(hookPath)) {
1187
+ const existing = fs11.readFileSync(hookPath, "utf-8");
1188
+ if (!existing.includes("viberails")) {
1189
+ fs11.writeFileSync(hookPath, `${existing.trimEnd()}
1190
+ npx viberails check --staged
1191
+ `);
1192
+ }
1193
+ return;
1194
+ }
1195
+ fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1196
+ }
1197
+
1043
1198
  // src/commands/init.ts
1044
1199
  var CONFIG_FILE4 = "viberails.config.json";
1045
1200
  function filterHighConfidence(conventions) {
@@ -1062,19 +1217,19 @@ async function initCommand(options, cwd) {
1062
1217
  "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"
1063
1218
  );
1064
1219
  }
1065
- const configPath = path12.join(projectRoot, CONFIG_FILE4);
1066
- if (fs11.existsSync(configPath)) {
1220
+ const configPath = path13.join(projectRoot, CONFIG_FILE4);
1221
+ if (fs12.existsSync(configPath)) {
1067
1222
  console.log(
1068
- chalk5.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk5.cyan("viberails sync") + " to update the generated files."
1223
+ chalk8.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk8.cyan("viberails sync") + " to update the generated files."
1069
1224
  );
1070
1225
  return;
1071
1226
  }
1072
- console.log(chalk5.dim("Scanning project..."));
1227
+ console.log(chalk8.dim("Scanning project..."));
1073
1228
  const scanResult = await scan(projectRoot);
1074
1229
  displayScanResults(scanResult);
1075
1230
  if (scanResult.statistics.totalFiles === 0) {
1076
1231
  console.log(
1077
- chalk5.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + chalk5.cyan("viberails sync") + " after adding source files.\n"
1232
+ chalk8.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + chalk8.cyan("viberails sync") + " after adding source files.\n"
1078
1233
  );
1079
1234
  }
1080
1235
  if (!options.yes) {
@@ -1094,7 +1249,7 @@ async function initCommand(options, cwd) {
1094
1249
  shouldInfer = await confirm("Infer boundary rules from import patterns?");
1095
1250
  }
1096
1251
  if (shouldInfer) {
1097
- console.log(chalk5.dim("Building import graph..."));
1252
+ console.log(chalk8.dim("Building import graph..."));
1098
1253
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1099
1254
  const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1100
1255
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -1102,115 +1257,47 @@ async function initCommand(options, cwd) {
1102
1257
  if (inferred.length > 0) {
1103
1258
  config.boundaries = inferred;
1104
1259
  config.rules.enforceBoundaries = true;
1105
- console.log(` ${chalk5.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1260
+ console.log(` ${chalk8.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1106
1261
  }
1107
1262
  }
1108
1263
  }
1109
- fs11.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1264
+ fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1110
1265
  `);
1111
1266
  writeGeneratedFiles(projectRoot, config, scanResult);
1112
1267
  updateGitignore(projectRoot);
1113
1268
  setupPreCommitHook(projectRoot);
1114
1269
  console.log(`
1115
- ${chalk5.bold("Created:")}`);
1116
- console.log(` ${chalk5.green("\u2713")} ${CONFIG_FILE4}`);
1117
- console.log(` ${chalk5.green("\u2713")} .viberails/context.md`);
1118
- console.log(` ${chalk5.green("\u2713")} .viberails/scan-result.json`);
1270
+ ${chalk8.bold("Created:")}`);
1271
+ console.log(` ${chalk8.green("\u2713")} ${CONFIG_FILE4}`);
1272
+ console.log(` ${chalk8.green("\u2713")} .viberails/context.md`);
1273
+ console.log(` ${chalk8.green("\u2713")} .viberails/scan-result.json`);
1119
1274
  console.log(`
1120
- ${chalk5.bold("Next steps:")}`);
1121
- console.log(` 1. Review ${chalk5.cyan("viberails.config.json")} and adjust rules`);
1275
+ ${chalk8.bold("Next steps:")}`);
1276
+ console.log(` 1. Review ${chalk8.cyan("viberails.config.json")} and adjust rules`);
1122
1277
  console.log(
1123
- ` 2. Commit ${chalk5.cyan("viberails.config.json")} and ${chalk5.cyan(".viberails/context.md")}`
1278
+ ` 2. Commit ${chalk8.cyan("viberails.config.json")} and ${chalk8.cyan(".viberails/context.md")}`
1124
1279
  );
1125
- console.log(` 3. Run ${chalk5.cyan("viberails check")} to verify your project passes`);
1280
+ console.log(` 3. Run ${chalk8.cyan("viberails check")} to verify your project passes`);
1126
1281
  }
1127
1282
  function updateGitignore(projectRoot) {
1128
- const gitignorePath = path12.join(projectRoot, ".gitignore");
1283
+ const gitignorePath = path13.join(projectRoot, ".gitignore");
1129
1284
  let content = "";
1130
- if (fs11.existsSync(gitignorePath)) {
1131
- content = fs11.readFileSync(gitignorePath, "utf-8");
1285
+ if (fs12.existsSync(gitignorePath)) {
1286
+ content = fs12.readFileSync(gitignorePath, "utf-8");
1132
1287
  }
1133
1288
  if (!content.includes(".viberails/scan-result.json")) {
1134
1289
  const block = "\n# viberails\n.viberails/scan-result.json\n";
1135
- fs11.writeFileSync(gitignorePath, `${content.trimEnd()}
1290
+ fs12.writeFileSync(gitignorePath, `${content.trimEnd()}
1136
1291
  ${block}`);
1137
1292
  }
1138
1293
  }
1139
- function setupPreCommitHook(projectRoot) {
1140
- const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1141
- if (fs11.existsSync(lefthookPath)) {
1142
- addLefthookPreCommit(lefthookPath);
1143
- console.log(` ${chalk5.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1144
- return;
1145
- }
1146
- const huskyDir = path12.join(projectRoot, ".husky");
1147
- if (fs11.existsSync(huskyDir)) {
1148
- writeHuskyPreCommit(huskyDir);
1149
- console.log(` ${chalk5.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1150
- return;
1151
- }
1152
- const gitDir = path12.join(projectRoot, ".git");
1153
- if (fs11.existsSync(gitDir)) {
1154
- const hooksDir = path12.join(gitDir, "hooks");
1155
- if (!fs11.existsSync(hooksDir)) {
1156
- fs11.mkdirSync(hooksDir, { recursive: true });
1157
- }
1158
- writeGitHookPreCommit(hooksDir);
1159
- console.log(` ${chalk5.green("\u2713")} .git/hooks/pre-commit`);
1160
- }
1161
- }
1162
- function writeGitHookPreCommit(hooksDir) {
1163
- const hookPath = path12.join(hooksDir, "pre-commit");
1164
- if (fs11.existsSync(hookPath)) {
1165
- const existing = fs11.readFileSync(hookPath, "utf-8");
1166
- if (existing.includes("viberails")) return;
1167
- fs11.writeFileSync(
1168
- hookPath,
1169
- `${existing.trimEnd()}
1170
-
1171
- # viberails check
1172
- npx viberails check --staged
1173
- `
1174
- );
1175
- return;
1176
- }
1177
- const script = [
1178
- "#!/bin/sh",
1179
- "# Generated by viberails \u2014 https://viberails.sh",
1180
- "",
1181
- "npx viberails check --staged",
1182
- ""
1183
- ].join("\n");
1184
- fs11.writeFileSync(hookPath, script, { mode: 493 });
1185
- }
1186
- function addLefthookPreCommit(lefthookPath) {
1187
- const content = fs11.readFileSync(lefthookPath, "utf-8");
1188
- if (content.includes("viberails")) return;
1189
- const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1190
- fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1191
- ${addition}
1192
- `);
1193
- }
1194
- function writeHuskyPreCommit(huskyDir) {
1195
- const hookPath = path12.join(huskyDir, "pre-commit");
1196
- if (fs11.existsSync(hookPath)) {
1197
- const existing = fs11.readFileSync(hookPath, "utf-8");
1198
- if (!existing.includes("viberails")) {
1199
- fs11.writeFileSync(hookPath, `${existing.trimEnd()}
1200
- npx viberails check --staged
1201
- `);
1202
- }
1203
- return;
1204
- }
1205
- fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
1206
- }
1207
1294
 
1208
1295
  // src/commands/sync.ts
1209
- import * as fs12 from "fs";
1210
- import * as path13 from "path";
1296
+ import * as fs13 from "fs";
1297
+ import * as path14 from "path";
1211
1298
  import { loadConfig as loadConfig4, mergeConfig } from "@viberails/config";
1212
1299
  import { scan as scan2 } from "@viberails/scanner";
1213
- import chalk6 from "chalk";
1300
+ import chalk9 from "chalk";
1214
1301
  var CONFIG_FILE5 = "viberails.config.json";
1215
1302
  async function syncCommand(cwd) {
1216
1303
  const startDir = cwd ?? process.cwd();
@@ -1220,23 +1307,23 @@ async function syncCommand(cwd) {
1220
1307
  "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"
1221
1308
  );
1222
1309
  }
1223
- const configPath = path13.join(projectRoot, CONFIG_FILE5);
1310
+ const configPath = path14.join(projectRoot, CONFIG_FILE5);
1224
1311
  const existing = await loadConfig4(configPath);
1225
- console.log(chalk6.dim("Scanning project..."));
1312
+ console.log(chalk9.dim("Scanning project..."));
1226
1313
  const scanResult = await scan2(projectRoot);
1227
1314
  const merged = mergeConfig(existing, scanResult);
1228
- fs12.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1315
+ fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1229
1316
  `);
1230
1317
  writeGeneratedFiles(projectRoot, merged, scanResult);
1231
1318
  console.log(`
1232
- ${chalk6.bold("Synced:")}`);
1233
- console.log(` ${chalk6.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1234
- console.log(` ${chalk6.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1235
- console.log(` ${chalk6.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1319
+ ${chalk9.bold("Synced:")}`);
1320
+ console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1321
+ console.log(` ${chalk9.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1322
+ console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1236
1323
  }
1237
1324
 
1238
1325
  // src/index.ts
1239
- var VERSION = "0.2.0";
1326
+ var VERSION = "0.2.2";
1240
1327
  var program = new Command();
1241
1328
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1242
1329
  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) => {
@@ -1244,7 +1331,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1244
1331
  await initCommand(options);
1245
1332
  } catch (err) {
1246
1333
  const message = err instanceof Error ? err.message : String(err);
1247
- console.error(`${chalk7.red("Error:")} ${message}`);
1334
+ console.error(`${chalk10.red("Error:")} ${message}`);
1248
1335
  process.exit(1);
1249
1336
  }
1250
1337
  });
@@ -1253,7 +1340,7 @@ program.command("sync").description("Re-scan and update generated files").action
1253
1340
  await syncCommand();
1254
1341
  } catch (err) {
1255
1342
  const message = err instanceof Error ? err.message : String(err);
1256
- console.error(`${chalk7.red("Error:")} ${message}`);
1343
+ console.error(`${chalk10.red("Error:")} ${message}`);
1257
1344
  process.exit(1);
1258
1345
  }
1259
1346
  });
@@ -1266,7 +1353,7 @@ program.command("check").description("Check files against enforced rules").optio
1266
1353
  process.exit(exitCode);
1267
1354
  } catch (err) {
1268
1355
  const message = err instanceof Error ? err.message : String(err);
1269
- console.error(`${chalk7.red("Error:")} ${message}`);
1356
+ console.error(`${chalk10.red("Error:")} ${message}`);
1270
1357
  process.exit(1);
1271
1358
  }
1272
1359
  });
@@ -1276,7 +1363,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
1276
1363
  process.exit(exitCode);
1277
1364
  } catch (err) {
1278
1365
  const message = err instanceof Error ? err.message : String(err);
1279
- console.error(`${chalk7.red("Error:")} ${message}`);
1366
+ console.error(`${chalk10.red("Error:")} ${message}`);
1280
1367
  process.exit(1);
1281
1368
  }
1282
1369
  });
@@ -1285,7 +1372,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1285
1372
  await boundariesCommand(options);
1286
1373
  } catch (err) {
1287
1374
  const message = err instanceof Error ? err.message : String(err);
1288
- console.error(`${chalk7.red("Error:")} ${message}`);
1375
+ console.error(`${chalk10.red("Error:")} ${message}`);
1289
1376
  process.exit(1);
1290
1377
  }
1291
1378
  });