viberails 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,14 +34,14 @@ __export(index_exports, {
34
34
  VERSION: () => VERSION
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
- var import_chalk10 = __toESM(require("chalk"), 1);
37
+ var import_chalk11 = __toESM(require("chalk"), 1);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/boundaries.ts
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_chalk = __toESM(require("chalk"), 1);
44
+ var import_chalk2 = __toESM(require("chalk"), 1);
45
45
 
46
46
  // src/utils/find-project-root.ts
47
47
  var fs = __toESM(require("fs"), 1);
@@ -62,6 +62,7 @@ function findProjectRoot(startDir) {
62
62
 
63
63
  // src/utils/prompt.ts
64
64
  var readline = __toESM(require("readline"), 1);
65
+ var import_chalk = __toESM(require("chalk"), 1);
65
66
  async function confirm(message) {
66
67
  const rl = readline.createInterface({
67
68
  input: process.stdin,
@@ -75,6 +76,29 @@ async function confirm(message) {
75
76
  });
76
77
  });
77
78
  }
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
86
+ });
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
+ });
94
+ });
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;
101
+ }
78
102
 
79
103
  // src/utils/resolve-workspace-packages.ts
80
104
  var fs2 = __toESM(require("fs"), 1);
@@ -131,52 +155,52 @@ async function boundariesCommand(options, cwd) {
131
155
  }
132
156
  function displayRules(config) {
133
157
  if (!config.boundaries || config.boundaries.length === 0) {
134
- console.log(import_chalk.default.yellow("No boundary rules configured."));
135
- console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
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.`);
136
160
  return;
137
161
  }
138
162
  const allowRules = config.boundaries.filter((r) => r.allow);
139
163
  const denyRules = config.boundaries.filter((r) => !r.allow);
140
164
  console.log(`
141
- ${import_chalk.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
165
+ ${import_chalk2.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
142
166
  `);
143
167
  for (const r of allowRules) {
144
- console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
168
+ console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
145
169
  }
146
170
  for (const r of denyRules) {
147
- const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
148
- console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
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}`);
149
173
  }
150
174
  console.log(
151
175
  `
152
- Enforcement: ${config.rules.enforceBoundaries ? import_chalk.default.green("on") : import_chalk.default.yellow("off")}`
176
+ Enforcement: ${config.rules.enforceBoundaries ? import_chalk2.default.green("on") : import_chalk2.default.yellow("off")}`
153
177
  );
154
178
  }
155
179
  async function inferAndDisplay(projectRoot, config, configPath) {
156
- console.log(import_chalk.default.dim("Analyzing imports..."));
180
+ console.log(import_chalk2.default.dim("Analyzing imports..."));
157
181
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
158
182
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
159
183
  const graph = await buildImportGraph(projectRoot, {
160
184
  packages,
161
185
  ignore: config.ignore
162
186
  });
163
- console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
187
+ console.log(import_chalk2.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
164
188
  const inferred = inferBoundaries(graph);
165
189
  if (inferred.length === 0) {
166
- console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
190
+ console.log(import_chalk2.default.yellow("No boundary rules could be inferred."));
167
191
  return;
168
192
  }
169
193
  const allow = inferred.filter((r) => r.allow);
170
194
  const deny = inferred.filter((r) => !r.allow);
171
195
  console.log(`
172
- ${import_chalk.default.bold("Inferred boundary rules:")}
196
+ ${import_chalk2.default.bold("Inferred boundary rules:")}
173
197
  `);
174
198
  for (const r of allow) {
175
- console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
199
+ console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
176
200
  }
177
201
  for (const r of deny) {
178
- const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
179
- console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
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}`);
180
204
  }
181
205
  console.log(`
182
206
  ${allow.length} allowed, ${deny.length} denied`);
@@ -186,11 +210,11 @@ ${import_chalk.default.bold("Inferred boundary rules:")}
186
210
  config.rules.enforceBoundaries = true;
187
211
  fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
188
212
  `);
189
- console.log(`${import_chalk.default.green("\u2713")} Saved ${inferred.length} rules`);
213
+ console.log(`${import_chalk2.default.green("\u2713")} Saved ${inferred.length} rules`);
190
214
  }
191
215
  }
192
216
  async function showGraph(projectRoot, config) {
193
- console.log(import_chalk.default.dim("Building import graph..."));
217
+ console.log(import_chalk2.default.dim("Building import graph..."));
194
218
  const { buildImportGraph } = await import("@viberails/graph");
195
219
  const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
196
220
  const graph = await buildImportGraph(projectRoot, {
@@ -198,20 +222,20 @@ async function showGraph(projectRoot, config) {
198
222
  ignore: config.ignore
199
223
  });
200
224
  console.log(`
201
- ${import_chalk.default.bold("Import dependency graph:")}
225
+ ${import_chalk2.default.bold("Import dependency graph:")}
202
226
  `);
203
227
  console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
204
228
  `);
205
229
  if (graph.packages.length > 0) {
206
230
  for (const pkg of graph.packages) {
207
231
  const deps = pkg.internalDeps.length > 0 ? `
208
- ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk.default.dim(" (no internal deps)");
232
+ ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk2.default.dim(" (no internal deps)");
209
233
  console.log(` ${pkg.name}${deps}`);
210
234
  }
211
235
  }
212
236
  if (graph.cycles.length > 0) {
213
237
  console.log(`
214
- ${import_chalk.default.yellow("Cycles detected:")}`);
238
+ ${import_chalk2.default.yellow("Cycles detected:")}`);
215
239
  for (const cycle of graph.cycles) {
216
240
  const paths = cycle.map((f) => path3.relative(projectRoot, f));
217
241
  console.log(` ${paths.join(" \u2192 ")}`);
@@ -223,7 +247,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
223
247
  var fs6 = __toESM(require("fs"), 1);
224
248
  var path6 = __toESM(require("path"), 1);
225
249
  var import_config2 = require("@viberails/config");
226
- var import_chalk2 = __toESM(require("chalk"), 1);
250
+ var import_chalk3 = __toESM(require("chalk"), 1);
227
251
 
228
252
  // src/commands/check-config.ts
229
253
  function resolveConfigForFile(relPath, config) {
@@ -256,6 +280,7 @@ function resolveIgnoreForFile(relPath, config) {
256
280
  var import_node_child_process = require("child_process");
257
281
  var fs4 = __toESM(require("fs"), 1);
258
282
  var path4 = __toESM(require("path"), 1);
283
+ var import_picomatch = __toESM(require("picomatch"), 1);
259
284
  var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
260
285
  "node_modules",
261
286
  ".git",
@@ -291,25 +316,9 @@ var NAMING_PATTERNS = {
291
316
  snake_case: /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/
292
317
  };
293
318
  function isIgnored(relPath, ignorePatterns) {
294
- for (const pattern of ignorePatterns) {
295
- const startsGlob = pattern.startsWith("**/");
296
- const endsGlob = pattern.endsWith("/**");
297
- if (startsGlob && endsGlob) {
298
- const middle = pattern.slice(3, -3);
299
- if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
300
- return true;
301
- }
302
- } else if (endsGlob) {
303
- const prefix = pattern.slice(0, -3);
304
- if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
305
- } else if (startsGlob) {
306
- const suffix = pattern.slice(3);
307
- if (relPath.endsWith(suffix) || relPath === suffix) return true;
308
- } else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
309
- return true;
310
- }
311
- }
312
- return false;
319
+ if (ignorePatterns.length === 0) return false;
320
+ const isMatch = (0, import_picomatch.default)(ignorePatterns, { dot: true });
321
+ return isMatch(relPath);
313
322
  }
314
323
  function countFileLines(filePath) {
315
324
  try {
@@ -476,12 +485,12 @@ function printGroupedViolations(violations, limit) {
476
485
  const toShow = group.slice(0, remaining);
477
486
  const hidden = group.length - toShow.length;
478
487
  for (const v of toShow) {
479
- const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
480
- console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
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}`);
481
490
  }
482
491
  totalShown += toShow.length;
483
492
  if (hidden > 0) {
484
- console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
493
+ console.log(import_chalk3.default.dim(` ... and ${hidden} more ${rule} violations`));
485
494
  }
486
495
  }
487
496
  }
@@ -499,13 +508,13 @@ async function checkCommand(options, cwd) {
499
508
  const startDir = cwd ?? process.cwd();
500
509
  const projectRoot = findProjectRoot(startDir);
501
510
  if (!projectRoot) {
502
- console.error(`${import_chalk2.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
511
+ console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
503
512
  return 1;
504
513
  }
505
514
  const configPath = path6.join(projectRoot, CONFIG_FILE2);
506
515
  if (!fs6.existsSync(configPath)) {
507
516
  console.error(
508
- `${import_chalk2.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
517
+ `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
509
518
  );
510
519
  return 1;
511
520
  }
@@ -519,7 +528,7 @@ async function checkCommand(options, cwd) {
519
528
  filesToCheck = getAllSourceFiles(projectRoot, config);
520
529
  }
521
530
  if (filesToCheck.length === 0) {
522
- console.log(`${import_chalk2.default.green("\u2713")} No files to check.`);
531
+ console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
523
532
  return 0;
524
533
  }
525
534
  const violations = [];
@@ -581,10 +590,20 @@ async function checkCommand(options, cwd) {
581
590
  });
582
591
  }
583
592
  const elapsed = Date.now() - startTime;
584
- console.log(import_chalk2.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
593
+ console.log(import_chalk3.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
594
+ }
595
+ if (options.format === "json") {
596
+ console.log(
597
+ JSON.stringify({
598
+ violations,
599
+ checkedFiles: filesToCheck.length,
600
+ enforcement: config.enforcement
601
+ })
602
+ );
603
+ return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
585
604
  }
586
605
  if (violations.length === 0) {
587
- console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
606
+ console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
588
607
  return 0;
589
608
  }
590
609
  if (!options.quiet) {
@@ -592,7 +611,7 @@ async function checkCommand(options, cwd) {
592
611
  }
593
612
  printSummary(violations);
594
613
  if (config.enforcement === "enforce") {
595
- console.log(import_chalk2.default.red("Fix violations before committing."));
614
+ console.log(import_chalk3.default.red("Fix violations before committing."));
596
615
  return 1;
597
616
  }
598
617
  return 0;
@@ -602,23 +621,23 @@ async function checkCommand(options, cwd) {
602
621
  var fs9 = __toESM(require("fs"), 1);
603
622
  var path10 = __toESM(require("path"), 1);
604
623
  var import_config3 = require("@viberails/config");
605
- var import_chalk4 = __toESM(require("chalk"), 1);
624
+ var import_chalk5 = __toESM(require("chalk"), 1);
606
625
 
607
626
  // src/commands/fix-helpers.ts
608
627
  var import_node_child_process2 = require("child_process");
609
628
  var import_node_readline = require("readline");
610
- var import_chalk3 = __toESM(require("chalk"), 1);
629
+ var import_chalk4 = __toESM(require("chalk"), 1);
611
630
  function printPlan(renames, stubs) {
612
631
  if (renames.length > 0) {
613
- console.log(import_chalk3.default.bold("\nFile renames:"));
632
+ console.log(import_chalk4.default.bold("\nFile renames:"));
614
633
  for (const r of renames) {
615
- console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
634
+ console.log(` ${import_chalk4.default.red(r.oldPath)} \u2192 ${import_chalk4.default.green(r.newPath)}`);
616
635
  }
617
636
  }
618
637
  if (stubs.length > 0) {
619
- console.log(import_chalk3.default.bold("\nTest stubs to create:"));
638
+ console.log(import_chalk4.default.bold("\nTest stubs to create:"));
620
639
  for (const s of stubs) {
621
- console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
640
+ console.log(` ${import_chalk4.default.green("+")} ${s.path}`);
622
641
  }
623
642
  }
624
643
  }
@@ -682,7 +701,8 @@ async function updateImportsAfterRenames(renames, projectRoot) {
682
701
  const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
683
702
  for (const sourceFile of project.getSourceFiles()) {
684
703
  const filePath = sourceFile.getFilePath();
685
- if (filePath.includes("/node_modules/") || filePath.includes("/dist/")) continue;
704
+ const segments = filePath.split(path7.sep);
705
+ if (segments.includes("node_modules") || segments.includes("dist")) continue;
686
706
  const fileDir = path7.dirname(filePath);
687
707
  for (const decl of sourceFile.getImportDeclarations()) {
688
708
  const specifier = decl.getModuleSpecifierValue();
@@ -868,13 +888,13 @@ async function fixCommand(options, cwd) {
868
888
  const startDir = cwd ?? process.cwd();
869
889
  const projectRoot = findProjectRoot(startDir);
870
890
  if (!projectRoot) {
871
- console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
891
+ console.error(`${import_chalk5.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
872
892
  return 1;
873
893
  }
874
894
  const configPath = path10.join(projectRoot, CONFIG_FILE3);
875
895
  if (!fs9.existsSync(configPath)) {
876
896
  console.error(
877
- `${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
897
+ `${import_chalk5.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
878
898
  );
879
899
  return 1;
880
900
  }
@@ -883,7 +903,7 @@ async function fixCommand(options, cwd) {
883
903
  const isDirty = checkGitDirty(projectRoot);
884
904
  if (isDirty) {
885
905
  console.log(
886
- import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
906
+ import_chalk5.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
887
907
  );
888
908
  }
889
909
  }
@@ -913,12 +933,12 @@ async function fixCommand(options, cwd) {
913
933
  }
914
934
  }
915
935
  if (dedupedRenames.length === 0 && testStubs.length === 0) {
916
- console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
936
+ console.log(`${import_chalk5.default.green("\u2713")} No fixable violations found.`);
917
937
  return 0;
918
938
  }
919
939
  printPlan(dedupedRenames, testStubs);
920
940
  if (options.dryRun) {
921
- console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
941
+ console.log(import_chalk5.default.dim("\nDry run \u2014 no changes applied."));
922
942
  return 0;
923
943
  }
924
944
  if (!options.yes) {
@@ -949,15 +969,15 @@ async function fixCommand(options, cwd) {
949
969
  }
950
970
  console.log("");
951
971
  if (renameCount > 0) {
952
- console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
972
+ console.log(`${import_chalk5.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
953
973
  }
954
974
  if (importUpdateCount > 0) {
955
975
  console.log(
956
- `${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
976
+ `${import_chalk5.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
957
977
  );
958
978
  }
959
979
  if (stubCount > 0) {
960
- console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
980
+ console.log(`${import_chalk5.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
961
981
  }
962
982
  return 0;
963
983
  }
@@ -967,11 +987,11 @@ var fs12 = __toESM(require("fs"), 1);
967
987
  var path13 = __toESM(require("path"), 1);
968
988
  var import_config4 = require("@viberails/config");
969
989
  var import_scanner = require("@viberails/scanner");
970
- var import_chalk8 = __toESM(require("chalk"), 1);
990
+ var import_chalk9 = __toESM(require("chalk"), 1);
971
991
 
972
992
  // src/display.ts
973
993
  var import_types3 = require("@viberails/types");
974
- var import_chalk6 = __toESM(require("chalk"), 1);
994
+ var import_chalk7 = __toESM(require("chalk"), 1);
975
995
 
976
996
  // src/display-helpers.ts
977
997
  var import_types = require("@viberails/types");
@@ -1024,7 +1044,7 @@ function formatRoleGroup(group) {
1024
1044
 
1025
1045
  // src/display-monorepo.ts
1026
1046
  var import_types2 = require("@viberails/types");
1027
- var import_chalk5 = __toESM(require("chalk"), 1);
1047
+ var import_chalk6 = __toESM(require("chalk"), 1);
1028
1048
  function formatPackageSummary(pkg) {
1029
1049
  const parts = [];
1030
1050
  if (pkg.stack.framework) {
@@ -1040,19 +1060,19 @@ function formatPackageSummary(pkg) {
1040
1060
  function displayMonorepoResults(scanResult) {
1041
1061
  const { stack, packages } = scanResult;
1042
1062
  console.log(`
1043
- ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1044
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1063
+ ${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1064
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1045
1065
  if (stack.packageManager) {
1046
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1066
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1047
1067
  }
1048
1068
  if (stack.linter) {
1049
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1069
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1050
1070
  }
1051
1071
  if (stack.formatter) {
1052
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1072
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1053
1073
  }
1054
1074
  if (stack.testRunner) {
1055
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1075
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1056
1076
  }
1057
1077
  console.log("");
1058
1078
  for (const pkg of packages) {
@@ -1063,13 +1083,13 @@ ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`
1063
1083
  );
1064
1084
  if (packagesWithDirs.length > 0) {
1065
1085
  console.log(`
1066
- ${import_chalk5.default.bold("Structure:")}`);
1086
+ ${import_chalk6.default.bold("Structure:")}`);
1067
1087
  for (const pkg of packagesWithDirs) {
1068
1088
  const groups = groupByRole(pkg.structure.directories);
1069
1089
  if (groups.length === 0) continue;
1070
1090
  console.log(` ${pkg.relativePath}:`);
1071
1091
  for (const group of groups) {
1072
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1092
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1073
1093
  }
1074
1094
  }
1075
1095
  }
@@ -1100,7 +1120,7 @@ function displayConventions(scanResult) {
1100
1120
  const conventionEntries = Object.entries(scanResult.conventions);
1101
1121
  if (conventionEntries.length === 0) return;
1102
1122
  console.log(`
1103
- ${import_chalk6.default.bold("Conventions:")}`);
1123
+ ${import_chalk7.default.bold("Conventions:")}`);
1104
1124
  for (const [key, convention] of conventionEntries) {
1105
1125
  if (convention.confidence === "low") continue;
1106
1126
  const label = CONVENTION_LABELS[key] ?? key;
@@ -1108,19 +1128,19 @@ ${import_chalk6.default.bold("Conventions:")}`);
1108
1128
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1109
1129
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1110
1130
  if (allSame || pkgValues.length <= 1) {
1111
- const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1112
- const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1131
+ const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
1132
+ const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
1113
1133
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1114
1134
  } else {
1115
- console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
1135
+ console.log(` ${import_chalk7.default.yellow("~")} ${label}: varies by package`);
1116
1136
  for (const pv of pkgValues) {
1117
1137
  const pct = Math.round(pv.convention.consistency);
1118
1138
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1119
1139
  }
1120
1140
  }
1121
1141
  } else {
1122
- const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1123
- const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1142
+ const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
1143
+ const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
1124
1144
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1125
1145
  }
1126
1146
  }
@@ -1128,7 +1148,7 @@ ${import_chalk6.default.bold("Conventions:")}`);
1128
1148
  function displaySummarySection(scanResult) {
1129
1149
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1130
1150
  console.log(`
1131
- ${import_chalk6.default.bold("Summary:")}`);
1151
+ ${import_chalk7.default.bold("Summary:")}`);
1132
1152
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1133
1153
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1134
1154
  if (ext) {
@@ -1142,46 +1162,84 @@ function displayScanResults(scanResult) {
1142
1162
  }
1143
1163
  const { stack } = scanResult;
1144
1164
  console.log(`
1145
- ${import_chalk6.default.bold("Detected:")}`);
1165
+ ${import_chalk7.default.bold("Detected:")}`);
1146
1166
  if (stack.framework) {
1147
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1167
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1148
1168
  }
1149
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1169
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.language)}`);
1150
1170
  if (stack.styling) {
1151
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1171
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1152
1172
  }
1153
1173
  if (stack.backend) {
1154
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1174
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1175
+ }
1176
+ if (stack.orm) {
1177
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1155
1178
  }
1156
1179
  if (stack.linter) {
1157
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1180
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.linter)}`);
1158
1181
  }
1159
1182
  if (stack.formatter) {
1160
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1183
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1161
1184
  }
1162
1185
  if (stack.testRunner) {
1163
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1186
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1164
1187
  }
1165
1188
  if (stack.packageManager) {
1166
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1189
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1167
1190
  }
1168
1191
  if (stack.libraries.length > 0) {
1169
1192
  for (const lib of stack.libraries) {
1170
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1193
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1171
1194
  }
1172
1195
  }
1173
1196
  const groups = groupByRole(scanResult.structure.directories);
1174
1197
  if (groups.length > 0) {
1175
1198
  console.log(`
1176
- ${import_chalk6.default.bold("Structure:")}`);
1199
+ ${import_chalk7.default.bold("Structure:")}`);
1177
1200
  for (const group of groups) {
1178
- console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1201
+ console.log(` ${import_chalk7.default.green("\u2713")} ${formatRoleGroup(group)}`);
1179
1202
  }
1180
1203
  }
1181
1204
  displayConventions(scanResult);
1182
1205
  displaySummarySection(scanResult);
1183
1206
  console.log("");
1184
1207
  }
1208
+ function getConventionStr(cv) {
1209
+ return typeof cv === "string" ? cv : cv.value;
1210
+ }
1211
+ 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`);
1214
+ if (config.rules.requireTests && config.structure.testPattern) {
1215
+ console.log(
1216
+ ` ${import_chalk7.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
1217
+ );
1218
+ } else if (config.rules.requireTests) {
1219
+ console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: yes`);
1220
+ } else {
1221
+ console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: no`);
1222
+ }
1223
+ if (config.rules.enforceNaming && config.conventions.fileNaming) {
1224
+ console.log(
1225
+ ` ${import_chalk7.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
1226
+ );
1227
+ } else {
1228
+ console.log(` ${import_chalk7.default.dim("\u2022")} Enforce file naming: no`);
1229
+ }
1230
+ console.log(
1231
+ ` ${import_chalk7.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1232
+ );
1233
+ console.log("");
1234
+ if (config.enforcement === "enforce") {
1235
+ console.log(`${import_chalk7.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
1236
+ } else {
1237
+ console.log(
1238
+ `${import_chalk7.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
1239
+ );
1240
+ }
1241
+ console.log("");
1242
+ }
1185
1243
 
1186
1244
  // src/utils/write-generated-files.ts
1187
1245
  var fs10 = __toESM(require("fs"), 1);
@@ -1212,18 +1270,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
1212
1270
  // src/commands/init-hooks.ts
1213
1271
  var fs11 = __toESM(require("fs"), 1);
1214
1272
  var path12 = __toESM(require("path"), 1);
1215
- var import_chalk7 = __toESM(require("chalk"), 1);
1273
+ var import_chalk8 = __toESM(require("chalk"), 1);
1216
1274
  function setupPreCommitHook(projectRoot) {
1217
1275
  const lefthookPath = path12.join(projectRoot, "lefthook.yml");
1218
1276
  if (fs11.existsSync(lefthookPath)) {
1219
1277
  addLefthookPreCommit(lefthookPath);
1220
- console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1278
+ console.log(` ${import_chalk8.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
1221
1279
  return;
1222
1280
  }
1223
1281
  const huskyDir = path12.join(projectRoot, ".husky");
1224
1282
  if (fs11.existsSync(huskyDir)) {
1225
1283
  writeHuskyPreCommit(huskyDir);
1226
- console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1284
+ console.log(` ${import_chalk8.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
1227
1285
  return;
1228
1286
  }
1229
1287
  const gitDir = path12.join(projectRoot, ".git");
@@ -1233,7 +1291,7 @@ function setupPreCommitHook(projectRoot) {
1233
1291
  fs11.mkdirSync(hooksDir, { recursive: true });
1234
1292
  }
1235
1293
  writeGitHookPreCommit(hooksDir);
1236
- console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
1294
+ console.log(` ${import_chalk8.default.green("\u2713")} .git/hooks/pre-commit`);
1237
1295
  }
1238
1296
  }
1239
1297
  function writeGitHookPreCommit(hooksDir) {
@@ -1263,10 +1321,72 @@ npx viberails check --staged
1263
1321
  function addLefthookPreCommit(lefthookPath) {
1264
1322
  const content = fs11.readFileSync(lefthookPath, "utf-8");
1265
1323
  if (content.includes("viberails")) return;
1266
- const addition = ["", " viberails:", " run: npx viberails check --staged"].join("\n");
1267
- fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1268
- ${addition}
1324
+ const hasPreCommit = /^pre-commit:/m.test(content);
1325
+ if (hasPreCommit) {
1326
+ const commandBlock = ["", " viberails:", " run: npx viberails check --staged"].join(
1327
+ "\n"
1328
+ );
1329
+ const updated = `${content.trimEnd()}
1330
+ ${commandBlock}
1331
+ `;
1332
+ fs11.writeFileSync(lefthookPath, updated);
1333
+ } else {
1334
+ const section = [
1335
+ "",
1336
+ "pre-commit:",
1337
+ " commands:",
1338
+ " viberails:",
1339
+ " run: npx viberails check --staged"
1340
+ ].join("\n");
1341
+ fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
1342
+ ${section}
1269
1343
  `);
1344
+ }
1345
+ }
1346
+ function detectHookManager(projectRoot) {
1347
+ if (fs11.existsSync(path12.join(projectRoot, "lefthook.yml"))) return "Lefthook";
1348
+ if (fs11.existsSync(path12.join(projectRoot, ".husky"))) return "Husky";
1349
+ if (fs11.existsSync(path12.join(projectRoot, ".git"))) return "git hook";
1350
+ return void 0;
1351
+ }
1352
+ function setupClaudeCodeHook(projectRoot) {
1353
+ const claudeDir = path12.join(projectRoot, ".claude");
1354
+ if (!fs11.existsSync(claudeDir)) {
1355
+ fs11.mkdirSync(claudeDir, { recursive: true });
1356
+ }
1357
+ const settingsPath = path12.join(claudeDir, "settings.json");
1358
+ let settings = {};
1359
+ if (fs11.existsSync(settingsPath)) {
1360
+ try {
1361
+ settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
1362
+ } catch {
1363
+ console.warn(
1364
+ ` ${import_chalk8.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
1365
+ );
1366
+ settings = {};
1367
+ }
1368
+ }
1369
+ const hooks = settings.hooks ?? {};
1370
+ const existing = hooks.PostToolUse ?? [];
1371
+ if (existing.some((h) => JSON.stringify(h).includes("viberails"))) return;
1372
+ const extractFile = `node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input?.file_path??'')}catch{}"`;
1373
+ const hookCommand = `FILE=$(${extractFile}) && [ -n "$FILE" ] && npx viberails check --files "$FILE" --format json; exit 0`;
1374
+ hooks.PostToolUse = [
1375
+ ...existing,
1376
+ {
1377
+ matcher: "Edit|Write",
1378
+ hooks: [
1379
+ {
1380
+ type: "command",
1381
+ command: hookCommand
1382
+ }
1383
+ ]
1384
+ }
1385
+ ];
1386
+ settings.hooks = hooks;
1387
+ fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
1388
+ `);
1389
+ console.log(` ${import_chalk8.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
1270
1390
  }
1271
1391
  function writeHuskyPreCommit(huskyDir) {
1272
1392
  const hookPath = path12.join(huskyDir, "pre-commit");
@@ -1307,36 +1427,38 @@ async function initCommand(options, cwd) {
1307
1427
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
1308
1428
  if (fs12.existsSync(configPath)) {
1309
1429
  console.log(
1310
- import_chalk8.default.yellow("!") + " viberails is already initialized in this project.\n Run " + import_chalk8.default.cyan("viberails sync") + " to update the generated files."
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."
1311
1431
  );
1312
1432
  return;
1313
1433
  }
1314
- console.log(import_chalk8.default.dim("Scanning project..."));
1434
+ console.log(import_chalk9.default.dim("Scanning project..."));
1315
1435
  const scanResult = await (0, import_scanner.scan)(projectRoot);
1436
+ const config = (0, import_config4.generateConfig)(scanResult);
1437
+ if (options.yes) {
1438
+ config.conventions = filterHighConfidence(config.conventions);
1439
+ }
1316
1440
  displayScanResults(scanResult);
1317
1441
  if (scanResult.statistics.totalFiles === 0) {
1318
1442
  console.log(
1319
- import_chalk8.default.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + import_chalk8.default.cyan("viberails sync") + " after adding source files.\n"
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"
1320
1444
  );
1321
1445
  }
1446
+ displayRulesPreview(config);
1322
1447
  if (!options.yes) {
1323
- const accepted = await confirm("Does this look right?");
1448
+ const accepted = await confirm("Proceed with these settings?");
1324
1449
  if (!accepted) {
1325
1450
  console.log("Aborted.");
1326
1451
  return;
1327
1452
  }
1328
1453
  }
1329
- const config = (0, import_config4.generateConfig)(scanResult);
1330
- if (options.yes) {
1331
- config.conventions = filterHighConfidence(config.conventions);
1332
- }
1333
1454
  if (config.workspace && config.workspace.packages.length > 0) {
1334
1455
  let shouldInfer = options.yes;
1335
1456
  if (!options.yes) {
1457
+ console.log(import_chalk9.default.dim(" Scans imports between packages to suggest dependency rules"));
1336
1458
  shouldInfer = await confirm("Infer boundary rules from import patterns?");
1337
1459
  }
1338
1460
  if (shouldInfer) {
1339
- console.log(import_chalk8.default.dim("Building import graph..."));
1461
+ console.log(import_chalk9.default.dim("Building import graph..."));
1340
1462
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
1341
1463
  const packages = resolveWorkspacePackages(projectRoot, config.workspace);
1342
1464
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -1344,27 +1466,43 @@ async function initCommand(options, cwd) {
1344
1466
  if (inferred.length > 0) {
1345
1467
  config.boundaries = inferred;
1346
1468
  config.rules.enforceBoundaries = true;
1347
- console.log(` ${import_chalk8.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1469
+ console.log(` ${import_chalk9.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
1348
1470
  }
1349
1471
  }
1350
1472
  }
1473
+ const hookManager = detectHookManager(projectRoot);
1474
+ let integrations = { preCommitHook: true, claudeCodeHook: true };
1475
+ if (!options.yes) {
1476
+ console.log("");
1477
+ integrations = await selectIntegrations(hookManager);
1478
+ }
1351
1479
  fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
1352
1480
  `);
1353
1481
  writeGeneratedFiles(projectRoot, config, scanResult);
1354
1482
  updateGitignore(projectRoot);
1355
- setupPreCommitHook(projectRoot);
1356
1483
  console.log(`
1357
- ${import_chalk8.default.bold("Created:")}`);
1358
- console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
1359
- console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
1360
- console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
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`);
1488
+ if (integrations.preCommitHook) {
1489
+ setupPreCommitHook(projectRoot);
1490
+ }
1491
+ if (integrations.claudeCodeHook) {
1492
+ setupClaudeCodeHook(projectRoot);
1493
+ }
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
+ }
1361
1501
  console.log(`
1362
- ${import_chalk8.default.bold("Next steps:")}`);
1363
- console.log(` 1. Review ${import_chalk8.default.cyan("viberails.config.json")} and adjust rules`);
1364
- console.log(
1365
- ` 2. Commit ${import_chalk8.default.cyan("viberails.config.json")} and ${import_chalk8.default.cyan(".viberails/context.md")}`
1366
- );
1367
- console.log(` 3. Run ${import_chalk8.default.cyan("viberails check")} to verify your project passes`);
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`);
1368
1506
  }
1369
1507
  function updateGitignore(projectRoot) {
1370
1508
  const gitignorePath = path13.join(projectRoot, ".gitignore");
@@ -1374,8 +1512,9 @@ function updateGitignore(projectRoot) {
1374
1512
  }
1375
1513
  if (!content.includes(".viberails/scan-result.json")) {
1376
1514
  const block = "\n# viberails\n.viberails/scan-result.json\n";
1377
- fs12.writeFileSync(gitignorePath, `${content.trimEnd()}
1378
- ${block}`);
1515
+ const prefix = content.length === 0 ? "" : `${content.trimEnd()}
1516
+ `;
1517
+ fs12.writeFileSync(gitignorePath, `${prefix}${block}`);
1379
1518
  }
1380
1519
  }
1381
1520
 
@@ -1384,7 +1523,7 @@ var fs13 = __toESM(require("fs"), 1);
1384
1523
  var path14 = __toESM(require("path"), 1);
1385
1524
  var import_config5 = require("@viberails/config");
1386
1525
  var import_scanner2 = require("@viberails/scanner");
1387
- var import_chalk9 = __toESM(require("chalk"), 1);
1526
+ var import_chalk10 = __toESM(require("chalk"), 1);
1388
1527
  var CONFIG_FILE5 = "viberails.config.json";
1389
1528
  async function syncCommand(cwd) {
1390
1529
  const startDir = cwd ?? process.cwd();
@@ -1396,21 +1535,33 @@ async function syncCommand(cwd) {
1396
1535
  }
1397
1536
  const configPath = path14.join(projectRoot, CONFIG_FILE5);
1398
1537
  const existing = await (0, import_config5.loadConfig)(configPath);
1399
- console.log(import_chalk9.default.dim("Scanning project..."));
1538
+ console.log(import_chalk10.default.dim("Scanning project..."));
1400
1539
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
1401
1540
  const merged = (0, import_config5.mergeConfig)(existing, scanResult);
1402
- fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
1541
+ const existingJson = JSON.stringify(existing, null, 2);
1542
+ const mergedJson = JSON.stringify(merged, null, 2);
1543
+ const configChanged = existingJson !== mergedJson;
1544
+ if (configChanged) {
1545
+ console.log(
1546
+ ` ${import_chalk10.default.yellow("!")} Config updated \u2014 review ${import_chalk10.default.cyan(CONFIG_FILE5)} for changes`
1547
+ );
1548
+ }
1549
+ fs13.writeFileSync(configPath, `${mergedJson}
1403
1550
  `);
1404
1551
  writeGeneratedFiles(projectRoot, merged, scanResult);
1405
1552
  console.log(`
1406
- ${import_chalk9.default.bold("Synced:")}`);
1407
- console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
1408
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
1409
- console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
1553
+ ${import_chalk10.default.bold("Synced:")}`);
1554
+ if (configChanged) {
1555
+ console.log(` ${import_chalk10.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
1556
+ } else {
1557
+ console.log(` ${import_chalk10.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
1558
+ }
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`);
1410
1561
  }
1411
1562
 
1412
1563
  // src/index.ts
1413
- var VERSION = "0.2.3";
1564
+ var VERSION = "0.3.1";
1414
1565
  var program = new import_commander.Command();
1415
1566
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1416
1567
  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) => {
@@ -1418,7 +1569,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
1418
1569
  await initCommand(options);
1419
1570
  } catch (err) {
1420
1571
  const message = err instanceof Error ? err.message : String(err);
1421
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1572
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1422
1573
  process.exit(1);
1423
1574
  }
1424
1575
  });
@@ -1427,21 +1578,22 @@ program.command("sync").description("Re-scan and update generated files").action
1427
1578
  await syncCommand();
1428
1579
  } catch (err) {
1429
1580
  const message = err instanceof Error ? err.message : String(err);
1430
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1581
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1431
1582
  process.exit(1);
1432
1583
  }
1433
1584
  });
1434
- program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).action(
1585
+ program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).option("--format <format>", "Output format: text (default) or json").action(
1435
1586
  async (options) => {
1436
1587
  try {
1437
1588
  const exitCode = await checkCommand({
1438
1589
  ...options,
1439
- noBoundaries: options.boundaries === false
1590
+ noBoundaries: options.boundaries === false,
1591
+ format: options.format === "json" ? "json" : "text"
1440
1592
  });
1441
1593
  process.exit(exitCode);
1442
1594
  } catch (err) {
1443
1595
  const message = err instanceof Error ? err.message : String(err);
1444
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1596
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1445
1597
  process.exit(1);
1446
1598
  }
1447
1599
  }
@@ -1452,7 +1604,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
1452
1604
  process.exit(exitCode);
1453
1605
  } catch (err) {
1454
1606
  const message = err instanceof Error ? err.message : String(err);
1455
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1607
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1456
1608
  process.exit(1);
1457
1609
  }
1458
1610
  });
@@ -1461,7 +1613,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
1461
1613
  await boundariesCommand(options);
1462
1614
  } catch (err) {
1463
1615
  const message = err instanceof Error ? err.message : String(err);
1464
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1616
+ console.error(`${import_chalk11.default.red("Error:")} ${message}`);
1465
1617
  process.exit(1);
1466
1618
  }
1467
1619
  });