viberails 0.3.1 → 0.3.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,14 +34,14 @@ __export(index_exports, {
34
34
  VERSION: () => VERSION
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
- var import_chalk11 = __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
41
41
  var fs3 = __toESM(require("fs"), 1);
42
42
  var path3 = __toESM(require("path"), 1);
43
43
  var import_config = require("@viberails/config");
44
- var import_chalk2 = __toESM(require("chalk"), 1);
44
+ var import_chalk = __toESM(require("chalk"), 1);
45
45
 
46
46
  // src/utils/find-project-root.ts
47
47
  var fs = __toESM(require("fs"), 1);
@@ -61,43 +61,104 @@ function findProjectRoot(startDir) {
61
61
  }
62
62
 
63
63
  // src/utils/prompt.ts
64
- var readline = __toESM(require("readline"), 1);
65
- var import_chalk = __toESM(require("chalk"), 1);
66
- async function confirm(message) {
67
- const rl = readline.createInterface({
68
- input: process.stdin,
69
- output: process.stdout
70
- });
71
- return new Promise((resolve4) => {
72
- rl.question(`${message} (Y/n) `, (answer) => {
73
- rl.close();
74
- const trimmed = answer.trim().toLowerCase();
75
- resolve4(trimmed === "" || trimmed === "y" || trimmed === "yes");
76
- });
64
+ var clack = __toESM(require("@clack/prompts"), 1);
65
+ function assertNotCancelled(value) {
66
+ if (clack.isCancel(value)) {
67
+ clack.cancel("Setup cancelled.");
68
+ process.exit(0);
69
+ }
70
+ }
71
+ async function confirm2(message) {
72
+ const result = await clack.confirm({ message, initialValue: true });
73
+ assertNotCancelled(result);
74
+ return result;
75
+ }
76
+ async function confirmDangerous(message) {
77
+ const result = await clack.confirm({ message, initialValue: false });
78
+ assertNotCancelled(result);
79
+ return result;
80
+ }
81
+ async function promptInitDecision() {
82
+ const result = await clack.select({
83
+ message: "Accept these settings?",
84
+ options: [
85
+ { value: "accept", label: "Yes, looks good", hint: "recommended" },
86
+ { value: "customize", label: "Let me customize" }
87
+ ]
77
88
  });
89
+ assertNotCancelled(result);
90
+ return result;
78
91
  }
79
- async function selectIntegrations(hookManager) {
80
- const result = { preCommitHook: true, claudeCodeHook: true };
81
- const hookLabel = hookManager ? `Pre-commit hook (detected: ${hookManager})` : "Pre-commit hook (git hook)";
82
- console.log("Set up integrations:");
83
- const rl = readline.createInterface({
84
- input: process.stdin,
85
- output: process.stdout
92
+ async function promptRuleCustomization(defaults) {
93
+ const maxFileLinesResult = await clack.text({
94
+ message: "Maximum lines per source file?",
95
+ placeholder: String(defaults.maxFileLines),
96
+ initialValue: String(defaults.maxFileLines),
97
+ validate: (v) => {
98
+ const n = Number.parseInt(v, 10);
99
+ if (Number.isNaN(n) || n < 1) return "Enter a positive number";
100
+ }
86
101
  });
87
- const askYn = (label, defaultYes) => new Promise((resolve4) => {
88
- const hint = defaultYes ? "Y/n" : "y/N";
89
- rl.question(` ${label}? (${hint}) `, (answer) => {
90
- const trimmed = answer.trim().toLowerCase();
91
- if (trimmed === "") resolve4(defaultYes);
92
- else resolve4(trimmed === "y" || trimmed === "yes");
93
- });
102
+ assertNotCancelled(maxFileLinesResult);
103
+ const requireTestsResult = await clack.confirm({
104
+ message: "Require matching test files for source files?",
105
+ initialValue: defaults.requireTests
94
106
  });
95
- console.log(` ${import_chalk.default.dim("Runs viberails check automatically when you commit")}`);
96
- result.preCommitHook = await askYn(hookLabel, true);
97
- console.log(` ${import_chalk.default.dim("Checks files against your rules when Claude edits them")}`);
98
- result.claudeCodeHook = await askYn("Claude Code hook", true);
99
- rl.close();
100
- return result;
107
+ assertNotCancelled(requireTestsResult);
108
+ const namingLabel = defaults.fileNamingValue ? `Enforce file naming? (detected: ${defaults.fileNamingValue})` : "Enforce file naming?";
109
+ const enforceNamingResult = await clack.confirm({
110
+ message: namingLabel,
111
+ initialValue: defaults.enforceNaming
112
+ });
113
+ assertNotCancelled(enforceNamingResult);
114
+ const enforcementResult = await clack.select({
115
+ message: "Enforcement mode",
116
+ options: [
117
+ {
118
+ value: "warn",
119
+ label: "warn",
120
+ hint: "show violations but don't block commits (recommended)"
121
+ },
122
+ {
123
+ value: "enforce",
124
+ label: "enforce",
125
+ hint: "block commits with violations"
126
+ }
127
+ ],
128
+ initialValue: defaults.enforcement
129
+ });
130
+ assertNotCancelled(enforcementResult);
131
+ return {
132
+ maxFileLines: Number.parseInt(maxFileLinesResult, 10),
133
+ requireTests: requireTestsResult,
134
+ enforceNaming: enforceNamingResult,
135
+ enforcement: enforcementResult
136
+ };
137
+ }
138
+ async function promptIntegrations(hookManager) {
139
+ const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook (git hook)";
140
+ const result = await clack.multiselect({
141
+ message: "Set up integrations?",
142
+ options: [
143
+ {
144
+ value: "preCommit",
145
+ label: hookLabel,
146
+ hint: "runs checks when you commit"
147
+ },
148
+ {
149
+ value: "claude",
150
+ label: "Claude Code hook",
151
+ hint: "checks files when Claude edits them"
152
+ }
153
+ ],
154
+ initialValues: ["preCommit", "claude"],
155
+ required: false
156
+ });
157
+ assertNotCancelled(result);
158
+ return {
159
+ preCommitHook: result.includes("preCommit"),
160
+ claudeCodeHook: result.includes("claude")
161
+ };
101
162
  }
102
163
 
103
164
  // src/utils/resolve-workspace-packages.ts
@@ -155,66 +216,67 @@ async function boundariesCommand(options, cwd) {
155
216
  }
156
217
  function displayRules(config) {
157
218
  if (!config.boundaries || config.boundaries.length === 0) {
158
- console.log(import_chalk2.default.yellow("No boundary rules configured."));
159
- console.log(`Run ${import_chalk2.default.cyan("viberails boundaries --infer")} to generate rules.`);
219
+ console.log(import_chalk.default.yellow("No boundary rules configured."));
220
+ console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
160
221
  return;
161
222
  }
162
223
  const allowRules = config.boundaries.filter((r) => r.allow);
163
224
  const denyRules = config.boundaries.filter((r) => !r.allow);
164
225
  console.log(`
165
- ${import_chalk2.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
226
+ ${import_chalk.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
166
227
  `);
167
228
  for (const r of allowRules) {
168
- console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
229
+ console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
169
230
  }
170
231
  for (const r of denyRules) {
171
- const reason = r.reason ? import_chalk2.default.dim(` (${r.reason})`) : "";
172
- console.log(` ${import_chalk2.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
232
+ const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
233
+ console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
173
234
  }
174
235
  console.log(
175
236
  `
176
- Enforcement: ${config.rules.enforceBoundaries ? import_chalk2.default.green("on") : import_chalk2.default.yellow("off")}`
237
+ Enforcement: ${config.rules.enforceBoundaries ? import_chalk.default.green("on") : import_chalk.default.yellow("off")}`
177
238
  );
178
239
  }
179
240
  async function inferAndDisplay(projectRoot, config, configPath) {
180
- console.log(import_chalk2.default.dim("Analyzing imports..."));
241
+ console.log(import_chalk.default.dim("Analyzing imports..."));
181
242
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
182
243
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
183
244
  const graph = await buildImportGraph(projectRoot, {
184
245
  packages,
185
246
  ignore: config.ignore
186
247
  });
187
- console.log(import_chalk2.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
248
+ console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
188
249
  const inferred = inferBoundaries(graph);
189
250
  if (inferred.length === 0) {
190
- console.log(import_chalk2.default.yellow("No boundary rules could be inferred."));
251
+ console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
191
252
  return;
192
253
  }
193
254
  const allow = inferred.filter((r) => r.allow);
194
255
  const deny = inferred.filter((r) => !r.allow);
195
256
  console.log(`
196
- ${import_chalk2.default.bold("Inferred boundary rules:")}
257
+ ${import_chalk.default.bold("Inferred boundary rules:")}
197
258
  `);
198
259
  for (const r of allow) {
199
- console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
260
+ console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
200
261
  }
201
262
  for (const r of deny) {
202
- const reason = r.reason ? import_chalk2.default.dim(` (${r.reason})`) : "";
203
- console.log(` ${import_chalk2.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
263
+ const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
264
+ console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
204
265
  }
205
266
  console.log(`
206
267
  ${allow.length} allowed, ${deny.length} denied`);
207
- const shouldSave = await confirm("\nSave to viberails.config.json?");
268
+ console.log("");
269
+ const shouldSave = await confirm2("Save to viberails.config.json?");
208
270
  if (shouldSave) {
209
271
  config.boundaries = inferred;
210
272
  config.rules.enforceBoundaries = true;
211
273
  fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
212
274
  `);
213
- console.log(`${import_chalk2.default.green("\u2713")} Saved ${inferred.length} rules`);
275
+ console.log(`${import_chalk.default.green("\u2713")} Saved ${inferred.length} rules`);
214
276
  }
215
277
  }
216
278
  async function showGraph(projectRoot, config) {
217
- console.log(import_chalk2.default.dim("Building import graph..."));
279
+ console.log(import_chalk.default.dim("Building import graph..."));
218
280
  const { buildImportGraph } = await import("@viberails/graph");
219
281
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
220
282
  const graph = await buildImportGraph(projectRoot, {
@@ -222,20 +284,20 @@ async function showGraph(projectRoot, config) {
222
284
  ignore: config.ignore
223
285
  });
224
286
  console.log(`
225
- ${import_chalk2.default.bold("Import dependency graph:")}
287
+ ${import_chalk.default.bold("Import dependency graph:")}
226
288
  `);
227
289
  console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
228
290
  `);
229
291
  if (graph.packages.length > 0) {
230
292
  for (const pkg of graph.packages) {
231
293
  const deps = pkg.internalDeps.length > 0 ? `
232
- ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk2.default.dim(" (no internal deps)");
294
+ ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk.default.dim(" (no internal deps)");
233
295
  console.log(` ${pkg.name}${deps}`);
234
296
  }
235
297
  }
236
298
  if (graph.cycles.length > 0) {
237
299
  console.log(`
238
- ${import_chalk2.default.yellow("Cycles detected:")}`);
300
+ ${import_chalk.default.yellow("Cycles detected:")}`);
239
301
  for (const cycle of graph.cycles) {
240
302
  const paths = cycle.map((f) => path3.relative(projectRoot, f));
241
303
  console.log(` ${paths.join(" \u2192 ")}`);
@@ -247,7 +309,7 @@ ${import_chalk2.default.yellow("Cycles detected:")}`);
247
309
  var fs6 = __toESM(require("fs"), 1);
248
310
  var path6 = __toESM(require("path"), 1);
249
311
  var import_config2 = require("@viberails/config");
250
- var import_chalk3 = __toESM(require("chalk"), 1);
312
+ var import_chalk2 = __toESM(require("chalk"), 1);
251
313
 
252
314
  // src/commands/check-config.ts
253
315
  function resolveConfigForFile(relPath, config) {
@@ -485,12 +547,12 @@ function printGroupedViolations(violations, limit) {
485
547
  const toShow = group.slice(0, remaining);
486
548
  const hidden = group.length - toShow.length;
487
549
  for (const v of toShow) {
488
- const icon = v.severity === "error" ? import_chalk3.default.red("\u2717") : import_chalk3.default.yellow("!");
489
- console.log(`${icon} ${import_chalk3.default.dim(v.rule)} ${v.file}: ${v.message}`);
550
+ const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
551
+ console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
490
552
  }
491
553
  totalShown += toShow.length;
492
554
  if (hidden > 0) {
493
- console.log(import_chalk3.default.dim(` ... and ${hidden} more ${rule} violations`));
555
+ console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
494
556
  }
495
557
  }
496
558
  }
@@ -508,13 +570,13 @@ async function checkCommand(options, cwd) {
508
570
  const startDir = cwd ?? process.cwd();
509
571
  const projectRoot = findProjectRoot(startDir);
510
572
  if (!projectRoot) {
511
- console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
573
+ console.error(`${import_chalk2.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
512
574
  return 1;
513
575
  }
514
576
  const configPath = path6.join(projectRoot, CONFIG_FILE2);
515
577
  if (!fs6.existsSync(configPath)) {
516
578
  console.error(
517
- `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
579
+ `${import_chalk2.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
518
580
  );
519
581
  return 1;
520
582
  }
@@ -528,7 +590,7 @@ async function checkCommand(options, cwd) {
528
590
  filesToCheck = getAllSourceFiles(projectRoot, config);
529
591
  }
530
592
  if (filesToCheck.length === 0) {
531
- console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
593
+ console.log(`${import_chalk2.default.green("\u2713")} No files to check.`);
532
594
  return 0;
533
595
  }
534
596
  const violations = [];
@@ -590,7 +652,7 @@ async function checkCommand(options, cwd) {
590
652
  });
591
653
  }
592
654
  const elapsed = Date.now() - startTime;
593
- console.log(import_chalk3.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
655
+ console.log(import_chalk2.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
594
656
  }
595
657
  if (options.format === "json") {
596
658
  console.log(
@@ -603,7 +665,7 @@ async function checkCommand(options, cwd) {
603
665
  return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
604
666
  }
605
667
  if (violations.length === 0) {
606
- console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
668
+ console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
607
669
  return 0;
608
670
  }
609
671
  if (!options.quiet) {
@@ -611,7 +673,7 @@ async function checkCommand(options, cwd) {
611
673
  }
612
674
  printSummary(violations);
613
675
  if (config.enforcement === "enforce") {
614
- console.log(import_chalk3.default.red("Fix violations before committing."));
676
+ console.log(import_chalk2.default.red("Fix violations before committing."));
615
677
  return 1;
616
678
  }
617
679
  return 0;
@@ -621,23 +683,22 @@ async function checkCommand(options, cwd) {
621
683
  var fs9 = __toESM(require("fs"), 1);
622
684
  var path10 = __toESM(require("path"), 1);
623
685
  var import_config3 = require("@viberails/config");
624
- var import_chalk5 = __toESM(require("chalk"), 1);
686
+ var import_chalk4 = __toESM(require("chalk"), 1);
625
687
 
626
688
  // src/commands/fix-helpers.ts
627
689
  var import_node_child_process2 = require("child_process");
628
- var import_node_readline = require("readline");
629
- var import_chalk4 = __toESM(require("chalk"), 1);
690
+ var import_chalk3 = __toESM(require("chalk"), 1);
630
691
  function printPlan(renames, stubs) {
631
692
  if (renames.length > 0) {
632
- console.log(import_chalk4.default.bold("\nFile renames:"));
693
+ console.log(import_chalk3.default.bold("\nFile renames:"));
633
694
  for (const r of renames) {
634
- console.log(` ${import_chalk4.default.red(r.oldPath)} \u2192 ${import_chalk4.default.green(r.newPath)}`);
695
+ console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
635
696
  }
636
697
  }
637
698
  if (stubs.length > 0) {
638
- console.log(import_chalk4.default.bold("\nTest stubs to create:"));
699
+ console.log(import_chalk3.default.bold("\nTest stubs to create:"));
639
700
  for (const s of stubs) {
640
- console.log(` ${import_chalk4.default.green("+")} ${s.path}`);
701
+ console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
641
702
  }
642
703
  }
643
704
  }
@@ -659,15 +720,6 @@ function getConventionValue(convention) {
659
720
  }
660
721
  return void 0;
661
722
  }
662
- function promptConfirm(question) {
663
- const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
664
- return new Promise((resolve4) => {
665
- rl.question(`${question} (y/N) `, (answer) => {
666
- rl.close();
667
- resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
668
- });
669
- });
670
- }
671
723
 
672
724
  // src/commands/fix-imports.ts
673
725
  var path7 = __toESM(require("path"), 1);
@@ -888,13 +940,13 @@ async function fixCommand(options, cwd) {
888
940
  const startDir = cwd ?? process.cwd();
889
941
  const projectRoot = findProjectRoot(startDir);
890
942
  if (!projectRoot) {
891
- console.error(`${import_chalk5.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
943
+ console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
892
944
  return 1;
893
945
  }
894
946
  const configPath = path10.join(projectRoot, CONFIG_FILE3);
895
947
  if (!fs9.existsSync(configPath)) {
896
948
  console.error(
897
- `${import_chalk5.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
949
+ `${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
898
950
  );
899
951
  return 1;
900
952
  }
@@ -903,7 +955,7 @@ async function fixCommand(options, cwd) {
903
955
  const isDirty = checkGitDirty(projectRoot);
904
956
  if (isDirty) {
905
957
  console.log(
906
- import_chalk5.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
958
+ import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
907
959
  );
908
960
  }
909
961
  }
@@ -933,16 +985,16 @@ async function fixCommand(options, cwd) {
933
985
  }
934
986
  }
935
987
  if (dedupedRenames.length === 0 && testStubs.length === 0) {
936
- console.log(`${import_chalk5.default.green("\u2713")} No fixable violations found.`);
988
+ console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
937
989
  return 0;
938
990
  }
939
991
  printPlan(dedupedRenames, testStubs);
940
992
  if (options.dryRun) {
941
- console.log(import_chalk5.default.dim("\nDry run \u2014 no changes applied."));
993
+ console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
942
994
  return 0;
943
995
  }
944
996
  if (!options.yes) {
945
- const confirmed = await promptConfirm("Apply these fixes?");
997
+ const confirmed = await confirmDangerous("Apply these fixes?");
946
998
  if (!confirmed) {
947
999
  console.log("Aborted.");
948
1000
  return 0;
@@ -969,15 +1021,15 @@ async function fixCommand(options, cwd) {
969
1021
  }
970
1022
  console.log("");
971
1023
  if (renameCount > 0) {
972
- console.log(`${import_chalk5.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
1024
+ console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
973
1025
  }
974
1026
  if (importUpdateCount > 0) {
975
1027
  console.log(
976
- `${import_chalk5.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
1028
+ `${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
977
1029
  );
978
1030
  }
979
1031
  if (stubCount > 0) {
980
- console.log(`${import_chalk5.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
1032
+ console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
981
1033
  }
982
1034
  return 0;
983
1035
  }
@@ -985,13 +1037,14 @@ async function fixCommand(options, cwd) {
985
1037
  // src/commands/init.ts
986
1038
  var fs12 = __toESM(require("fs"), 1);
987
1039
  var path13 = __toESM(require("path"), 1);
1040
+ var clack2 = __toESM(require("@clack/prompts"), 1);
988
1041
  var import_config4 = require("@viberails/config");
989
1042
  var import_scanner = require("@viberails/scanner");
990
- var import_chalk9 = __toESM(require("chalk"), 1);
1043
+ var import_chalk8 = __toESM(require("chalk"), 1);
991
1044
 
992
1045
  // src/display.ts
993
1046
  var import_types3 = require("@viberails/types");
994
- var import_chalk7 = __toESM(require("chalk"), 1);
1047
+ var import_chalk6 = __toESM(require("chalk"), 1);
995
1048
 
996
1049
  // src/display-helpers.ts
997
1050
  var import_types = require("@viberails/types");
@@ -1044,7 +1097,7 @@ function formatRoleGroup(group) {
1044
1097
 
1045
1098
  // src/display-monorepo.ts
1046
1099
  var import_types2 = require("@viberails/types");
1047
- var import_chalk6 = __toESM(require("chalk"), 1);
1100
+ var import_chalk5 = __toESM(require("chalk"), 1);
1048
1101
  function formatPackageSummary(pkg) {
1049
1102
  const parts = [];
1050
1103
  if (pkg.stack.framework) {
@@ -1060,19 +1113,19 @@ function formatPackageSummary(pkg) {
1060
1113
  function displayMonorepoResults(scanResult) {
1061
1114
  const { stack, packages } = scanResult;
1062
1115
  console.log(`
1063
- ${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1064
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1116
+ ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1117
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1065
1118
  if (stack.packageManager) {
1066
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1119
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1067
1120
  }
1068
1121
  if (stack.linter) {
1069
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1122
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1070
1123
  }
1071
1124
  if (stack.formatter) {
1072
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1125
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1073
1126
  }
1074
1127
  if (stack.testRunner) {
1075
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1128
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1076
1129
  }
1077
1130
  console.log("");
1078
1131
  for (const pkg of packages) {
@@ -1083,13 +1136,13 @@ ${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`
1083
1136
  );
1084
1137
  if (packagesWithDirs.length > 0) {
1085
1138
  console.log(`
1086
- ${import_chalk6.default.bold("Structure:")}`);
1139
+ ${import_chalk5.default.bold("Structure:")}`);
1087
1140
  for (const pkg of packagesWithDirs) {
1088
1141
  const groups = groupByRole(pkg.structure.directories);
1089
1142
  if (groups.length === 0) continue;
1090
1143
  console.log(` ${pkg.relativePath}:`);
1091
1144
  for (const group of groups) {
1092
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1145
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1093
1146
  }
1094
1147
  }
1095
1148
  }
@@ -1097,6 +1150,58 @@ ${import_chalk6.default.bold("Structure:")}`);
1097
1150
  displaySummarySection(scanResult);
1098
1151
  console.log("");
1099
1152
  }
1153
+ function formatPackageSummaryPlain(pkg) {
1154
+ const parts = [];
1155
+ if (pkg.stack.framework) {
1156
+ parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
1157
+ }
1158
+ if (pkg.stack.styling) {
1159
+ parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
1160
+ }
1161
+ const files = `${pkg.statistics.totalFiles} files`;
1162
+ const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1163
+ return ` ${pkg.relativePath} \u2014 ${detail}`;
1164
+ }
1165
+ function formatMonorepoResultsText(scanResult, config) {
1166
+ const lines = [];
1167
+ const { stack, packages } = scanResult;
1168
+ lines.push(`Detected: (monorepo, ${packages.length} packages)`);
1169
+ const sharedParts = [formatItem(stack.language)];
1170
+ if (stack.packageManager) sharedParts.push(formatItem(stack.packageManager));
1171
+ if (stack.linter) sharedParts.push(formatItem(stack.linter));
1172
+ if (stack.formatter) sharedParts.push(formatItem(stack.formatter));
1173
+ if (stack.testRunner) sharedParts.push(formatItem(stack.testRunner));
1174
+ lines.push(` \u2713 ${sharedParts.join(" \xB7 ")}`);
1175
+ lines.push("");
1176
+ for (const pkg of packages) {
1177
+ lines.push(formatPackageSummaryPlain(pkg));
1178
+ }
1179
+ const packagesWithDirs = packages.filter(
1180
+ (pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
1181
+ );
1182
+ if (packagesWithDirs.length > 0) {
1183
+ lines.push("");
1184
+ lines.push("Structure:");
1185
+ for (const pkg of packagesWithDirs) {
1186
+ const groups = groupByRole(pkg.structure.directories);
1187
+ if (groups.length === 0) continue;
1188
+ lines.push(` ${pkg.relativePath}:`);
1189
+ for (const group of groups) {
1190
+ lines.push(` \u2713 ${formatRoleGroup(group)}`);
1191
+ }
1192
+ }
1193
+ }
1194
+ lines.push(...formatConventionsText(scanResult));
1195
+ const pkgCount = packages.length > 1 ? packages.length : void 0;
1196
+ lines.push("");
1197
+ lines.push(formatSummary(scanResult.statistics, pkgCount));
1198
+ const ext = formatExtensions(scanResult.statistics.filesByExtension);
1199
+ if (ext) {
1200
+ lines.push(ext);
1201
+ }
1202
+ lines.push(...formatRulesText(config));
1203
+ return lines.join("\n");
1204
+ }
1100
1205
 
1101
1206
  // src/display.ts
1102
1207
  var CONVENTION_LABELS = {
@@ -1120,7 +1225,7 @@ function displayConventions(scanResult) {
1120
1225
  const conventionEntries = Object.entries(scanResult.conventions);
1121
1226
  if (conventionEntries.length === 0) return;
1122
1227
  console.log(`
1123
- ${import_chalk7.default.bold("Conventions:")}`);
1228
+ ${import_chalk6.default.bold("Conventions:")}`);
1124
1229
  for (const [key, convention] of conventionEntries) {
1125
1230
  if (convention.confidence === "low") continue;
1126
1231
  const label = CONVENTION_LABELS[key] ?? key;
@@ -1128,19 +1233,19 @@ ${import_chalk7.default.bold("Conventions:")}`);
1128
1233
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1129
1234
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1130
1235
  if (allSame || pkgValues.length <= 1) {
1131
- const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
1132
- const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
1236
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1237
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1133
1238
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1134
1239
  } else {
1135
- console.log(` ${import_chalk7.default.yellow("~")} ${label}: varies by package`);
1240
+ console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
1136
1241
  for (const pv of pkgValues) {
1137
1242
  const pct = Math.round(pv.convention.consistency);
1138
1243
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1139
1244
  }
1140
1245
  }
1141
1246
  } else {
1142
- const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
1143
- const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
1247
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1248
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1144
1249
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1145
1250
  }
1146
1251
  }
@@ -1148,7 +1253,7 @@ ${import_chalk7.default.bold("Conventions:")}`);
1148
1253
  function displaySummarySection(scanResult) {
1149
1254
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1150
1255
  console.log(`
1151
- ${import_chalk7.default.bold("Summary:")}`);
1256
+ ${import_chalk6.default.bold("Summary:")}`);
1152
1257
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1153
1258
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1154
1259
  if (ext) {
@@ -1162,43 +1267,43 @@ function displayScanResults(scanResult) {
1162
1267
  }
1163
1268
  const { stack } = scanResult;
1164
1269
  console.log(`
1165
- ${import_chalk7.default.bold("Detected:")}`);
1270
+ ${import_chalk6.default.bold("Detected:")}`);
1166
1271
  if (stack.framework) {
1167
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1272
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1168
1273
  }
1169
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.language)}`);
1274
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1170
1275
  if (stack.styling) {
1171
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1276
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1172
1277
  }
1173
1278
  if (stack.backend) {
1174
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1279
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1175
1280
  }
1176
1281
  if (stack.orm) {
1177
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1282
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1178
1283
  }
1179
1284
  if (stack.linter) {
1180
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.linter)}`);
1285
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1181
1286
  }
1182
1287
  if (stack.formatter) {
1183
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1288
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1184
1289
  }
1185
1290
  if (stack.testRunner) {
1186
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1291
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1187
1292
  }
1188
1293
  if (stack.packageManager) {
1189
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1294
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1190
1295
  }
1191
1296
  if (stack.libraries.length > 0) {
1192
1297
  for (const lib of stack.libraries) {
1193
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1298
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1194
1299
  }
1195
1300
  }
1196
1301
  const groups = groupByRole(scanResult.structure.directories);
1197
1302
  if (groups.length > 0) {
1198
1303
  console.log(`
1199
- ${import_chalk7.default.bold("Structure:")}`);
1304
+ ${import_chalk6.default.bold("Structure:")}`);
1200
1305
  for (const group of groups) {
1201
- console.log(` ${import_chalk7.default.green("\u2713")} ${formatRoleGroup(group)}`);
1306
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1202
1307
  }
1203
1308
  }
1204
1309
  displayConventions(scanResult);
@@ -1209,37 +1314,145 @@ function getConventionStr(cv) {
1209
1314
  return typeof cv === "string" ? cv : cv.value;
1210
1315
  }
1211
1316
  function displayRulesPreview(config) {
1212
- console.log(`${import_chalk7.default.bold("Rules:")}`);
1213
- console.log(` ${import_chalk7.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1317
+ console.log(`${import_chalk6.default.bold("Rules:")}`);
1318
+ console.log(` ${import_chalk6.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1214
1319
  if (config.rules.requireTests && config.structure.testPattern) {
1215
1320
  console.log(
1216
- ` ${import_chalk7.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
1321
+ ` ${import_chalk6.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
1217
1322
  );
1218
1323
  } else if (config.rules.requireTests) {
1219
- console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: yes`);
1324
+ console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: yes`);
1220
1325
  } else {
1221
- console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: no`);
1326
+ console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: no`);
1222
1327
  }
1223
1328
  if (config.rules.enforceNaming && config.conventions.fileNaming) {
1224
1329
  console.log(
1225
- ` ${import_chalk7.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
1330
+ ` ${import_chalk6.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
1226
1331
  );
1227
1332
  } else {
1228
- console.log(` ${import_chalk7.default.dim("\u2022")} Enforce file naming: no`);
1333
+ console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
1229
1334
  }
1230
1335
  console.log(
1231
- ` ${import_chalk7.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1336
+ ` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1232
1337
  );
1233
1338
  console.log("");
1234
1339
  if (config.enforcement === "enforce") {
1235
- console.log(`${import_chalk7.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
1340
+ console.log(`${import_chalk6.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
1236
1341
  } else {
1237
1342
  console.log(
1238
- `${import_chalk7.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
1343
+ `${import_chalk6.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
1239
1344
  );
1240
1345
  }
1241
1346
  console.log("");
1242
1347
  }
1348
+ function plainConfidenceLabel(convention) {
1349
+ const pct = Math.round(convention.consistency);
1350
+ if (convention.confidence === "high") {
1351
+ return `${pct}%`;
1352
+ }
1353
+ return `${pct}%, suggested only`;
1354
+ }
1355
+ function formatConventionsText(scanResult) {
1356
+ const lines = [];
1357
+ const conventionEntries = Object.entries(scanResult.conventions);
1358
+ if (conventionEntries.length === 0) return lines;
1359
+ lines.push("");
1360
+ lines.push("Conventions:");
1361
+ for (const [key, convention] of conventionEntries) {
1362
+ if (convention.confidence === "low") continue;
1363
+ const label = CONVENTION_LABELS[key] ?? key;
1364
+ if (scanResult.packages.length > 1) {
1365
+ const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1366
+ const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1367
+ if (allSame || pkgValues.length <= 1) {
1368
+ const ind = convention.confidence === "high" ? "\u2713" : "~";
1369
+ lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
1370
+ } else {
1371
+ lines.push(` ~ ${label}: varies by package`);
1372
+ for (const pv of pkgValues) {
1373
+ const pct = Math.round(pv.convention.consistency);
1374
+ lines.push(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1375
+ }
1376
+ }
1377
+ } else {
1378
+ const ind = convention.confidence === "high" ? "\u2713" : "~";
1379
+ lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
1380
+ }
1381
+ }
1382
+ return lines;
1383
+ }
1384
+ function formatRulesText(config) {
1385
+ const lines = [];
1386
+ lines.push("");
1387
+ lines.push("Rules:");
1388
+ lines.push(` \u2022 Max file size: ${config.rules.maxFileLines} lines`);
1389
+ if (config.rules.requireTests && config.structure.testPattern) {
1390
+ lines.push(` \u2022 Require test files: yes (${config.structure.testPattern})`);
1391
+ } else if (config.rules.requireTests) {
1392
+ lines.push(" \u2022 Require test files: yes");
1393
+ } else {
1394
+ lines.push(" \u2022 Require test files: no");
1395
+ }
1396
+ if (config.rules.enforceNaming && config.conventions.fileNaming) {
1397
+ lines.push(` \u2022 Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`);
1398
+ } else {
1399
+ lines.push(" \u2022 Enforce file naming: no");
1400
+ }
1401
+ lines.push(` \u2022 Enforcement mode: ${config.enforcement}`);
1402
+ return lines;
1403
+ }
1404
+ function formatScanResultsText(scanResult, config) {
1405
+ if (scanResult.packages.length > 1) {
1406
+ return formatMonorepoResultsText(scanResult, config);
1407
+ }
1408
+ const lines = [];
1409
+ const { stack } = scanResult;
1410
+ lines.push("Detected:");
1411
+ if (stack.framework) {
1412
+ lines.push(` \u2713 ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1413
+ }
1414
+ lines.push(` \u2713 ${formatItem(stack.language)}`);
1415
+ if (stack.styling) {
1416
+ lines.push(` \u2713 ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1417
+ }
1418
+ if (stack.backend) {
1419
+ lines.push(` \u2713 ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1420
+ }
1421
+ if (stack.orm) {
1422
+ lines.push(` \u2713 ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1423
+ }
1424
+ const secondaryParts = [];
1425
+ if (stack.packageManager) secondaryParts.push(formatItem(stack.packageManager));
1426
+ if (stack.linter) secondaryParts.push(formatItem(stack.linter));
1427
+ if (stack.formatter) secondaryParts.push(formatItem(stack.formatter));
1428
+ if (stack.testRunner) secondaryParts.push(formatItem(stack.testRunner));
1429
+ if (secondaryParts.length > 0) {
1430
+ lines.push(` \u2713 ${secondaryParts.join(" \xB7 ")}`);
1431
+ }
1432
+ if (stack.libraries.length > 0) {
1433
+ for (const lib of stack.libraries) {
1434
+ lines.push(` \u2713 ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1435
+ }
1436
+ }
1437
+ const groups = groupByRole(scanResult.structure.directories);
1438
+ if (groups.length > 0) {
1439
+ lines.push("");
1440
+ lines.push("Structure:");
1441
+ for (const group of groups) {
1442
+ lines.push(` \u2713 ${formatRoleGroup(group)}`);
1443
+ }
1444
+ }
1445
+ lines.push(...formatConventionsText(scanResult));
1446
+ const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1447
+ lines.push("");
1448
+ lines.push(formatSummary(scanResult.statistics, pkgCount));
1449
+ const ext = formatExtensions(scanResult.statistics.filesByExtension);
1450
+ if (ext) {
1451
+ lines.push(ext);
1452
+ }
1453
+ lines.push(...formatRulesText(config));
1454
+ return lines.join("\n");
1455
+ }
1243
1456
 
1244
1457
  // src/utils/write-generated-files.ts
1245
1458
  var fs10 = __toESM(require("fs"), 1);
@@ -1270,18 +1483,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1270
1483
  // src/commands/init-hooks.ts
1271
1484
  var fs11 = __toESM(require("fs"), 1);
1272
1485
  var path12 = __toESM(require("path"), 1);
1273
- var import_chalk8 = __toESM(require("chalk"), 1);
1486
+ var import_chalk7 = __toESM(require("chalk"), 1);
1274
1487
  function setupPreCommitHook(projectRoot) {
1275
1488
  const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1276
1489
  if (fs11.existsSync(lefthookPath)) {
1277
1490
  addLefthookPreCommit(lefthookPath);
1278
- console.log(` ${import_chalk8.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1491
+ console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1279
1492
  return;
1280
1493
  }
1281
1494
  const huskyDir = path12.join(projectRoot, ".husky");
1282
1495
  if (fs11.existsSync(huskyDir)) {
1283
1496
  writeHuskyPreCommit(huskyDir);
1284
- console.log(` ${import_chalk8.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1497
+ console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1285
1498
  return;
1286
1499
  }
1287
1500
  const gitDir = path12.join(projectRoot, ".git");
@@ -1291,7 +1504,7 @@ function setupPreCommitHook(projectRoot) {
1291
1504
  fs11.mkdirSync(hooksDir, { recursive: true });
1292
1505
  }
1293
1506
  writeGitHookPreCommit(hooksDir);
1294
- console.log(` ${import_chalk8.default.green("\u2713")} .git/hooks/pre-commit`);
1507
+ console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
1295
1508
  }
1296
1509
  }
1297
1510
  function writeGitHookPreCommit(hooksDir) {
@@ -1361,7 +1574,7 @@ function setupClaudeCodeHook(projectRoot) {
1361
1574
  settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
1362
1575
  } catch {
1363
1576
  console.warn(
1364
- ` ${import_chalk8.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
1577
+ ` ${import_chalk7.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
1365
1578
  );
1366
1579
  settings = {};
1367
1580
  }
@@ -1386,7 +1599,7 @@ function setupClaudeCodeHook(projectRoot) {
1386
1599
  settings.hooks = hooks;
1387
1600
  fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
1388
1601
  `);
1389
- console.log(` ${import_chalk8.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
1602
+ console.log(` ${import_chalk7.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
1390
1603
  }
1391
1604
  function writeHuskyPreCommit(huskyDir) {
1392
1605
  const hookPath = path12.join(huskyDir, "pre-commit");
@@ -1416,6 +1629,14 @@ function filterHighConfidence(conventions) {
1416
1629
  }
1417
1630
  return filtered;
1418
1631
  }
1632
+ function getConventionStr2(cv) {
1633
+ if (!cv) return void 0;
1634
+ return typeof cv === "string" ? cv : cv.value;
1635
+ }
1636
+ function hasConventionOverrides(config) {
1637
+ if (!config.packages || config.packages.length === 0) return false;
1638
+ return config.packages.some((pkg) => pkg.conventions && Object.keys(pkg.conventions).length > 0);
1639
+ }
1419
1640
  async function initCommand(options, cwd) {
1420
1641
  const startDir = cwd ?? process.cwd();
1421
1642
  const projectRoot = findProjectRoot(startDir);
@@ -1427,82 +1648,137 @@ async function initCommand(options, cwd) {
1427
1648
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
1428
1649
  if (fs12.existsSync(configPath)) {
1429
1650
  console.log(
1430
- import_chalk9.default.yellow("!") + " viberails is already initialized in this project.\n Run " + import_chalk9.default.cyan("viberails sync") + " to update the generated files."
1651
+ `${import_chalk8.default.yellow("!")} viberails is already initialized.
1652
+ Run ${import_chalk8.default.cyan("viberails sync")} to update, or delete viberails.config.json to start fresh.`
1431
1653
  );
1432
1654
  return;
1433
1655
  }
1434
- console.log(import_chalk9.default.dim("Scanning project..."));
1435
- const scanResult = await (0, import_scanner.scan)(projectRoot);
1436
- const config = (0, import_config4.generateConfig)(scanResult);
1437
1656
  if (options.yes) {
1438
- config.conventions = filterHighConfidence(config.conventions);
1657
+ console.log(import_chalk8.default.dim("Scanning project..."));
1658
+ const scanResult2 = await (0, import_scanner.scan)(projectRoot);
1659
+ const config2 = (0, import_config4.generateConfig)(scanResult2);
1660
+ config2.conventions = filterHighConfidence(config2.conventions);
1661
+ displayScanResults(scanResult2);
1662
+ displayRulesPreview(config2);
1663
+ if (config2.workspace?.packages && config2.workspace.packages.length > 0) {
1664
+ console.log(import_chalk8.default.dim("Building import graph..."));
1665
+ const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1666
+ const packages = resolveWorkspacePackages(projectRoot, config2.workspace);
1667
+ const graph = await buildImportGraph(projectRoot, {
1668
+ packages,
1669
+ ignore: config2.ignore
1670
+ });
1671
+ const inferred = inferBoundaries(graph);
1672
+ if (inferred.length > 0) {
1673
+ config2.boundaries = inferred;
1674
+ config2.rules.enforceBoundaries = true;
1675
+ console.log(` Inferred ${inferred.length} boundary rules`);
1676
+ }
1677
+ }
1678
+ fs12.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
1679
+ `);
1680
+ writeGeneratedFiles(projectRoot, config2, scanResult2);
1681
+ updateGitignore(projectRoot);
1682
+ console.log(`
1683
+ Created:`);
1684
+ console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
1685
+ console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
1686
+ console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
1687
+ return;
1439
1688
  }
1440
- displayScanResults(scanResult);
1689
+ clack2.intro("viberails");
1690
+ const s = clack2.spinner();
1691
+ s.start("Scanning project...");
1692
+ const scanResult = await (0, import_scanner.scan)(projectRoot);
1693
+ const config = (0, import_config4.generateConfig)(scanResult);
1694
+ s.stop("Scan complete");
1441
1695
  if (scanResult.statistics.totalFiles === 0) {
1442
- console.log(
1443
- import_chalk9.default.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + import_chalk9.default.cyan("viberails sync") + " after adding source files.\n"
1696
+ clack2.log.warn(
1697
+ "No source files detected. viberails will generate context\nwith minimal content. Run viberails sync after adding files."
1444
1698
  );
1445
1699
  }
1446
- displayRulesPreview(config);
1447
- if (!options.yes) {
1448
- const accepted = await confirm("Proceed with these settings?");
1449
- if (!accepted) {
1450
- console.log("Aborted.");
1451
- return;
1700
+ const resultsText = formatScanResultsText(scanResult, config);
1701
+ clack2.note(resultsText, "Scan results");
1702
+ const decision = await promptInitDecision();
1703
+ if (decision === "customize") {
1704
+ clack2.note(
1705
+ "Rules control what viberails checks for.\nYou can change these later in viberails.config.json.",
1706
+ "Rules"
1707
+ );
1708
+ const overrides = await promptRuleCustomization({
1709
+ maxFileLines: config.rules.maxFileLines,
1710
+ requireTests: config.rules.requireTests,
1711
+ enforceNaming: config.rules.enforceNaming,
1712
+ enforcement: config.enforcement,
1713
+ fileNamingValue: getConventionStr2(config.conventions.fileNaming)
1714
+ });
1715
+ config.rules.maxFileLines = overrides.maxFileLines;
1716
+ config.rules.requireTests = overrides.requireTests;
1717
+ config.rules.enforceNaming = overrides.enforceNaming;
1718
+ config.enforcement = overrides.enforcement;
1719
+ if (config.workspace?.packages && config.workspace.packages.length > 0) {
1720
+ clack2.note(
1721
+ 'These rules apply globally. To customize per package,\nedit the "packages" section in viberails.config.json.',
1722
+ "Per-package overrides"
1723
+ );
1452
1724
  }
1453
1725
  }
1454
- if (config.workspace && config.workspace.packages.length > 0) {
1455
- let shouldInfer = options.yes;
1456
- if (!options.yes) {
1457
- console.log(import_chalk9.default.dim(" Scans imports between packages to suggest dependency rules"));
1458
- shouldInfer = await confirm("Infer boundary rules from import patterns?");
1459
- }
1726
+ if (config.workspace?.packages && config.workspace.packages.length > 0) {
1727
+ clack2.note(
1728
+ "Boundary rules prevent packages from importing where they\nshouldn't. viberails scans your existing imports and creates\nrules based on what's already working.",
1729
+ "Boundaries"
1730
+ );
1731
+ const shouldInfer = await confirm2("Infer boundary rules from import patterns?");
1460
1732
  if (shouldInfer) {
1461
- console.log(import_chalk9.default.dim("Building import graph..."));
1733
+ const bs = clack2.spinner();
1734
+ bs.start("Building import graph...");
1462
1735
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1463
1736
  const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1464
- const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
1737
+ const graph = await buildImportGraph(projectRoot, {
1738
+ packages,
1739
+ ignore: config.ignore
1740
+ });
1465
1741
  const inferred = inferBoundaries(graph);
1466
1742
  if (inferred.length > 0) {
1467
1743
  config.boundaries = inferred;
1468
1744
  config.rules.enforceBoundaries = true;
1469
- console.log(` ${import_chalk9.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1745
+ bs.stop(`Inferred ${inferred.length} boundary rules`);
1746
+ } else {
1747
+ bs.stop("No boundary rules inferred");
1470
1748
  }
1471
1749
  }
1472
1750
  }
1473
1751
  const hookManager = detectHookManager(projectRoot);
1474
- let integrations = { preCommitHook: true, claudeCodeHook: true };
1475
- if (!options.yes) {
1476
- console.log("");
1477
- integrations = await selectIntegrations(hookManager);
1752
+ const integrations = await promptIntegrations(hookManager);
1753
+ if (hasConventionOverrides(config)) {
1754
+ clack2.note(
1755
+ "Some packages use different conventions. Per-package\noverrides have been saved in viberails.config.json \u2014\nreview and adjust as needed.",
1756
+ "Per-package conventions"
1757
+ );
1478
1758
  }
1479
1759
  fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1480
1760
  `);
1481
1761
  writeGeneratedFiles(projectRoot, config, scanResult);
1482
1762
  updateGitignore(projectRoot);
1483
- console.log(`
1484
- ${import_chalk9.default.bold("Created:")}`);
1485
- console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE4}`);
1486
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md`);
1487
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json`);
1763
+ const createdFiles = [
1764
+ CONFIG_FILE4,
1765
+ ".viberails/context.md",
1766
+ ".viberails/scan-result.json"
1767
+ ];
1488
1768
  if (integrations.preCommitHook) {
1489
1769
  setupPreCommitHook(projectRoot);
1770
+ const hookMgr = detectHookManager(projectRoot);
1771
+ if (hookMgr) {
1772
+ createdFiles.push(`lefthook.yml \u2014 added viberails pre-commit`);
1773
+ }
1490
1774
  }
1491
1775
  if (integrations.claudeCodeHook) {
1492
1776
  setupClaudeCodeHook(projectRoot);
1777
+ createdFiles.push(".claude/settings.json \u2014 added viberails hook");
1493
1778
  }
1494
- const filesToCommit = [
1495
- `${import_chalk9.default.cyan("viberails.config.json")}`,
1496
- import_chalk9.default.cyan(".viberails/context.md")
1497
- ];
1498
- if (integrations.claudeCodeHook) {
1499
- filesToCommit.push(import_chalk9.default.cyan(".claude/settings.json"));
1500
- }
1501
- console.log(`
1502
- ${import_chalk9.default.bold("Next steps:")}`);
1503
- console.log(` 1. Review ${import_chalk9.default.cyan("viberails.config.json")} and adjust rules`);
1504
- console.log(` 2. Commit ${filesToCommit.join(", ")}`);
1505
- console.log(` 3. Run ${import_chalk9.default.cyan("viberails check")} to verify your project passes`);
1779
+ clack2.log.success(`Created:
1780
+ ${createdFiles.map((f) => ` ${f}`).join("\n")}`);
1781
+ clack2.outro("Done! Next: review viberails.config.json, then run viberails check");
1506
1782
  }
1507
1783
  function updateGitignore(projectRoot) {
1508
1784
  const gitignorePath = path13.join(projectRoot, ".gitignore");
@@ -1523,7 +1799,7 @@ var fs13 = __toESM(require("fs"), 1);
1523
1799
  var path14 = __toESM(require("path"), 1);
1524
1800
  var import_config5 = require("@viberails/config");
1525
1801
  var import_scanner2 = require("@viberails/scanner");
1526
- var import_chalk10 = __toESM(require("chalk"), 1);
1802
+ var import_chalk9 = __toESM(require("chalk"), 1);
1527
1803
  var CONFIG_FILE5 = "viberails.config.json";
1528
1804
  async function syncCommand(cwd) {
1529
1805
  const startDir = cwd ?? process.cwd();
@@ -1535,7 +1811,7 @@ async function syncCommand(cwd) {
1535
1811
  }
1536
1812
  const configPath = path14.join(projectRoot, CONFIG_FILE5);
1537
1813
  const existing = await (0, import_config5.loadConfig)(configPath);
1538
- console.log(import_chalk10.default.dim("Scanning project..."));
1814
+ console.log(import_chalk9.default.dim("Scanning project..."));
1539
1815
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
1540
1816
  const merged = (0, import_config5.mergeConfig)(existing, scanResult);
1541
1817
  const existingJson = JSON.stringify(existing, null, 2);
@@ -1543,25 +1819,25 @@ async function syncCommand(cwd) {
1543
1819
  const configChanged = existingJson !== mergedJson;
1544
1820
  if (configChanged) {
1545
1821
  console.log(
1546
- ` ${import_chalk10.default.yellow("!")} Config updated \u2014 review ${import_chalk10.default.cyan(CONFIG_FILE5)} for changes`
1822
+ ` ${import_chalk9.default.yellow("!")} Config updated \u2014 review ${import_chalk9.default.cyan(CONFIG_FILE5)} for changes`
1547
1823
  );
1548
1824
  }
1549
1825
  fs13.writeFileSync(configPath, `${mergedJson}
1550
1826
  `);
1551
1827
  writeGeneratedFiles(projectRoot, merged, scanResult);
1552
1828
  console.log(`
1553
- ${import_chalk10.default.bold("Synced:")}`);
1829
+ ${import_chalk9.default.bold("Synced:")}`);
1554
1830
  if (configChanged) {
1555
- console.log(` ${import_chalk10.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
1831
+ console.log(` ${import_chalk9.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
1556
1832
  } else {
1557
- console.log(` ${import_chalk10.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
1833
+ console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
1558
1834
  }
1559
- console.log(` ${import_chalk10.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1560
- console.log(` ${import_chalk10.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1835
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1836
+ console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1561
1837
  }
1562
1838
 
1563
1839
  // src/index.ts
1564
- var VERSION = "0.3.1";
1840
+ var VERSION = "0.3.2";
1565
1841
  var program = new import_commander.Command();
1566
1842
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1567
1843
  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) => {
@@ -1569,7 +1845,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1569
1845
  await initCommand(options);
1570
1846
  } catch (err) {
1571
1847
  const message = err instanceof Error ? err.message : String(err);
1572
- console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1848
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1573
1849
  process.exit(1);
1574
1850
  }
1575
1851
  });
@@ -1578,7 +1854,7 @@ program.command("sync").description("Re-scan and update generated files").action
1578
1854
  await syncCommand();
1579
1855
  } catch (err) {
1580
1856
  const message = err instanceof Error ? err.message : String(err);
1581
- console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1857
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1582
1858
  process.exit(1);
1583
1859
  }
1584
1860
  });
@@ -1593,7 +1869,7 @@ program.command("check").description("Check files against enforced rules").optio
1593
1869
  process.exit(exitCode);
1594
1870
  } catch (err) {
1595
1871
  const message = err instanceof Error ? err.message : String(err);
1596
- console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1872
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1597
1873
  process.exit(1);
1598
1874
  }
1599
1875
  }
@@ -1604,7 +1880,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
1604
1880
  process.exit(exitCode);
1605
1881
  } catch (err) {
1606
1882
  const message = err instanceof Error ? err.message : String(err);
1607
- console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1883
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1608
1884
  process.exit(1);
1609
1885
  }
1610
1886
  });
@@ -1613,7 +1889,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1613
1889
  await boundariesCommand(options);
1614
1890
  } catch (err) {
1615
1891
  const message = err instanceof Error ? err.message : String(err);
1616
- console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1892
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1617
1893
  process.exit(1);
1618
1894
  }
1619
1895
  });