viberails 0.6.6 → 0.6.8

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_chalk15 = __toESM(require("chalk"), 1);
37
+ var import_chalk17 = __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);
@@ -84,9 +84,13 @@ var SENTINEL_CUSTOM = "__custom__";
84
84
  var SENTINEL_NONE = "__none__";
85
85
  var SENTINEL_INHERIT = "__inherit__";
86
86
  var SENTINEL_SKIP = "__skip__";
87
+ var HINT_NOT_SET = "not set";
88
+ var HINT_NO_OVERRIDES = "no overrides";
89
+ var HINT_AUTO_DETECT = "auto-detect";
87
90
 
88
91
  // src/utils/prompt-submenus.ts
89
92
  var clack = __toESM(require("@clack/prompts"), 1);
93
+ var import_chalk = __toESM(require("chalk"), 1);
90
94
  var FILE_NAMING_OPTIONS = [
91
95
  { value: "kebab-case", label: "kebab-case" },
92
96
  { value: "camelCase", label: "camelCase" },
@@ -115,8 +119,7 @@ async function promptFileLimitsMenu(state) {
115
119
  { value: "back", label: "Back" }
116
120
  ]
117
121
  });
118
- assertNotCancelled(choice);
119
- if (choice === "back") return;
122
+ if (isCancelled(choice) || choice === "back") return;
120
123
  if (choice === "maxFileLines") {
121
124
  const result = await clack.text({
122
125
  message: "Maximum lines per source file?",
@@ -127,7 +130,7 @@ async function promptFileLimitsMenu(state) {
127
130
  if (Number.isNaN(n) || n < 1) return "Enter a positive number";
128
131
  }
129
132
  });
130
- assertNotCancelled(result);
133
+ if (isCancelled(result)) continue;
131
134
  state.maxFileLines = Number.parseInt(result, 10);
132
135
  }
133
136
  if (choice === "maxTestFileLines") {
@@ -140,7 +143,7 @@ async function promptFileLimitsMenu(state) {
140
143
  if (Number.isNaN(n) || n < 0) return "Enter a number (0 or positive)";
141
144
  }
142
145
  });
143
- assertNotCancelled(result);
146
+ if (isCancelled(result)) continue;
144
147
  state.maxTestFileLines = Number.parseInt(result, 10);
145
148
  }
146
149
  }
@@ -151,49 +154,48 @@ async function promptNamingMenu(state) {
151
154
  {
152
155
  value: "enforceNaming",
153
156
  label: "Enforce file naming",
154
- hint: state.enforceNaming ? "yes" : "no"
157
+ hint: state.enforceNaming ? import_chalk.default.green("yes") : import_chalk.default.dim("no")
155
158
  }
156
159
  ];
157
160
  if (state.enforceNaming) {
158
161
  options.push({
159
162
  value: "fileNaming",
160
163
  label: "File naming convention",
161
- hint: state.fileNamingValue ?? "(not set)"
164
+ hint: state.fileNamingValue ?? HINT_NOT_SET
162
165
  });
163
166
  }
164
167
  options.push(
165
168
  {
166
169
  value: "componentNaming",
167
170
  label: "Component naming",
168
- hint: state.componentNaming ?? "(not set)"
171
+ hint: state.componentNaming ?? HINT_NOT_SET
169
172
  },
170
173
  {
171
174
  value: "hookNaming",
172
175
  label: "Hook naming",
173
- hint: state.hookNaming ?? "(not set)"
176
+ hint: state.hookNaming ?? HINT_NOT_SET
174
177
  },
175
178
  {
176
179
  value: "importAlias",
177
180
  label: "Import alias",
178
- hint: state.importAlias ?? "(not set)"
181
+ hint: state.importAlias ?? HINT_NOT_SET
179
182
  },
180
183
  { value: "back", label: "Back" }
181
184
  );
182
185
  const choice = await clack.select({ message: "Naming & conventions", options });
183
- assertNotCancelled(choice);
184
- if (choice === "back") return;
186
+ if (isCancelled(choice) || choice === "back") return;
185
187
  if (choice === "enforceNaming") {
186
188
  const result = await clack.confirm({
187
189
  message: state.fileNamingValue ? `Enforce file naming? (detected: ${state.fileNamingValue})` : "Enforce file naming?",
188
190
  initialValue: state.enforceNaming
189
191
  });
190
- assertNotCancelled(result);
192
+ if (isCancelled(result)) continue;
191
193
  if (result && !state.fileNamingValue) {
192
194
  const selected = await clack.select({
193
195
  message: "Which file naming convention should be enforced?",
194
196
  options: [...FILE_NAMING_OPTIONS]
195
197
  });
196
- assertNotCancelled(selected);
198
+ if (isCancelled(selected)) continue;
197
199
  state.fileNamingValue = selected;
198
200
  }
199
201
  state.enforceNaming = result;
@@ -204,7 +206,7 @@ async function promptNamingMenu(state) {
204
206
  options: [...FILE_NAMING_OPTIONS],
205
207
  initialValue: state.fileNamingValue
206
208
  });
207
- assertNotCancelled(selected);
209
+ if (isCancelled(selected)) continue;
208
210
  state.fileNamingValue = selected;
209
211
  }
210
212
  if (choice === "componentNaming") {
@@ -216,7 +218,7 @@ async function promptNamingMenu(state) {
216
218
  ],
217
219
  initialValue: state.componentNaming ?? SENTINEL_CLEAR
218
220
  });
219
- assertNotCancelled(selected);
221
+ if (isCancelled(selected)) continue;
220
222
  state.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
221
223
  }
222
224
  if (choice === "hookNaming") {
@@ -228,7 +230,7 @@ async function promptNamingMenu(state) {
228
230
  ],
229
231
  initialValue: state.hookNaming ?? SENTINEL_CLEAR
230
232
  });
231
- assertNotCancelled(selected);
233
+ if (isCancelled(selected)) continue;
232
234
  state.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
233
235
  }
234
236
  if (choice === "importAlias") {
@@ -242,7 +244,7 @@ async function promptNamingMenu(state) {
242
244
  ],
243
245
  initialValue: state.importAlias ?? SENTINEL_CLEAR
244
246
  });
245
- assertNotCancelled(selected);
247
+ if (isCancelled(selected)) continue;
246
248
  if (selected === SENTINEL_CLEAR) {
247
249
  state.importAlias = void 0;
248
250
  } else if (selected === SENTINEL_CUSTOM) {
@@ -256,7 +258,7 @@ async function promptNamingMenu(state) {
256
258
  return "Must match pattern like @/*, ~/*, or #src/*";
257
259
  }
258
260
  });
259
- assertNotCancelled(result);
261
+ if (isCancelled(result)) continue;
260
262
  state.importAlias = result.trim();
261
263
  } else {
262
264
  state.importAlias = selected;
@@ -270,7 +272,7 @@ async function promptTestingMenu(state) {
270
272
  {
271
273
  value: "enforceMissingTests",
272
274
  label: "Enforce missing tests",
273
- hint: state.enforceMissingTests ? "yes" : "no"
275
+ hint: state.enforceMissingTests ? import_chalk.default.green("yes") : import_chalk.default.dim("no")
274
276
  },
275
277
  {
276
278
  value: "testCoverage",
@@ -294,14 +296,13 @@ async function promptTestingMenu(state) {
294
296
  }
295
297
  options.push({ value: "back", label: "Back" });
296
298
  const choice = await clack.select({ message: "Testing & coverage", options });
297
- assertNotCancelled(choice);
298
- if (choice === "back") return;
299
+ if (isCancelled(choice) || choice === "back") return;
299
300
  if (choice === "enforceMissingTests") {
300
301
  const result = await clack.confirm({
301
302
  message: "Require every source file to have a corresponding test file?",
302
303
  initialValue: state.enforceMissingTests
303
304
  });
304
- assertNotCancelled(result);
305
+ if (isCancelled(result)) continue;
305
306
  state.enforceMissingTests = result;
306
307
  }
307
308
  if (choice === "testCoverage") {
@@ -314,7 +315,7 @@ async function promptTestingMenu(state) {
314
315
  if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
315
316
  }
316
317
  });
317
- assertNotCancelled(result);
318
+ if (isCancelled(result)) continue;
318
319
  state.testCoverage = Number.parseInt(result, 10);
319
320
  }
320
321
  if (choice === "coverageSummaryPath") {
@@ -325,7 +326,7 @@ async function promptTestingMenu(state) {
325
326
  if (typeof v !== "string" || v.trim().length === 0) return "Path cannot be empty";
326
327
  }
327
328
  });
328
- assertNotCancelled(result);
329
+ if (isCancelled(result)) continue;
329
330
  state.coverageSummaryPath = result.trim();
330
331
  }
331
332
  if (choice === "coverageCommand") {
@@ -334,7 +335,7 @@ async function promptTestingMenu(state) {
334
335
  initialValue: state.coverageCommand ?? "",
335
336
  placeholder: "(auto-detect from package.json test runner)"
336
337
  });
337
- assertNotCancelled(result);
338
+ if (isCancelled(result)) continue;
338
339
  const trimmed = result.trim();
339
340
  state.coverageCommand = trimmed.length > 0 ? trimmed : void 0;
340
341
  }
@@ -378,7 +379,7 @@ function packageOverrideHint(pkg, defaults) {
378
379
  const hasCommandOverride = pkg.coverage?.command !== void 0 && pkg.coverage.command !== defaultCommand;
379
380
  if (hasSummaryOverride) tags.push("summary override");
380
381
  if (hasCommandOverride) tags.push("command override");
381
- return tags.length > 0 ? tags.join(", ") : "(no overrides)";
382
+ return tags.length > 0 ? tags.join(", ") : HINT_NO_OVERRIDES;
382
383
  }
383
384
  async function promptPackageOverrides(packages, defaults) {
384
385
  const editablePackages = packages.filter((pkg) => pkg.path !== ".");
@@ -395,8 +396,7 @@ async function promptPackageOverrides(packages, defaults) {
395
396
  { value: SENTINEL_DONE, label: "Done" }
396
397
  ]
397
398
  });
398
- assertNotCancelled(selectedPath);
399
- if (selectedPath === SENTINEL_DONE) break;
399
+ if (isCancelled(selectedPath) || selectedPath === SENTINEL_DONE) break;
400
400
  const target = editablePackages.find((pkg) => pkg.path === selectedPath);
401
401
  if (!target) continue;
402
402
  await promptSinglePackageOverrides(target, defaults);
@@ -410,11 +410,11 @@ async function promptSinglePackageOverrides(target, defaults) {
410
410
  const effectiveMaxLines = target.rules?.maxFileLines ?? defaults.maxFileLines;
411
411
  const effectiveCoverage = target.rules?.testCoverage ?? defaults.testCoverage;
412
412
  const effectiveSummary = target.coverage?.summaryPath ?? defaults.coverageSummaryPath;
413
- const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ?? "(auto-detect)";
413
+ const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ?? HINT_AUTO_DETECT;
414
414
  const hasNamingOverride = target.conventions?.fileNaming !== void 0 && target.conventions.fileNaming !== defaults.fileNamingValue;
415
415
  const hasMaxLinesOverride = target.rules?.maxFileLines !== void 0 && target.rules.maxFileLines !== defaults.maxFileLines;
416
- const namingHint = hasNamingOverride ? String(effectiveNaming) : `(inherits: ${effectiveNaming ?? "not set"})`;
417
- const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `(inherits: ${effectiveMaxLines})`;
416
+ const namingHint = hasNamingOverride ? String(effectiveNaming) : `inherits: ${effectiveNaming ?? "not set"}`;
417
+ const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `inherits: ${effectiveMaxLines}`;
418
418
  const choice = await clack2.select({
419
419
  message: `Edit overrides for ${target.path}`,
420
420
  options: [
@@ -427,8 +427,7 @@ async function promptSinglePackageOverrides(target, defaults) {
427
427
  { value: "back", label: "Back to package list" }
428
428
  ]
429
429
  });
430
- assertNotCancelled(choice);
431
- if (choice === "back") break;
430
+ if (isCancelled(choice) || choice === "back") break;
432
431
  if (choice === "fileNaming") {
433
432
  const selected = await clack2.select({
434
433
  message: `File naming for ${target.path}`,
@@ -442,7 +441,7 @@ async function promptSinglePackageOverrides(target, defaults) {
442
441
  ],
443
442
  initialValue: target.conventions?.fileNaming ?? SENTINEL_INHERIT
444
443
  });
445
- assertNotCancelled(selected);
444
+ if (isCancelled(selected)) continue;
446
445
  if (selected === SENTINEL_INHERIT) {
447
446
  if (target.conventions) delete target.conventions.fileNaming;
448
447
  } else if (selected === SENTINEL_NONE) {
@@ -457,7 +456,7 @@ async function promptSinglePackageOverrides(target, defaults) {
457
456
  initialValue: target.rules?.maxFileLines !== void 0 ? String(target.rules.maxFileLines) : "",
458
457
  placeholder: String(defaults.maxFileLines)
459
458
  });
460
- assertNotCancelled(result);
459
+ if (isCancelled(result)) continue;
461
460
  const value = result.trim();
462
461
  if (value.length === 0 || Number.parseInt(value, 10) === defaults.maxFileLines) {
463
462
  if (target.rules) delete target.rules.maxFileLines;
@@ -475,7 +474,7 @@ async function promptSinglePackageOverrides(target, defaults) {
475
474
  if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
476
475
  }
477
476
  });
478
- assertNotCancelled(result);
477
+ if (isCancelled(result)) continue;
479
478
  const nextCoverage = Number.parseInt(result, 10);
480
479
  if (nextCoverage === defaults.testCoverage) {
481
480
  if (target.rules) delete target.rules.testCoverage;
@@ -489,7 +488,7 @@ async function promptSinglePackageOverrides(target, defaults) {
489
488
  initialValue: target.coverage?.summaryPath !== void 0 ? target.coverage.summaryPath : "",
490
489
  placeholder: defaults.coverageSummaryPath
491
490
  });
492
- assertNotCancelled(result);
491
+ if (isCancelled(result)) continue;
493
492
  const value = result.trim();
494
493
  if (value.length === 0 || value === defaults.coverageSummaryPath) {
495
494
  if (target.coverage) delete target.coverage.summaryPath;
@@ -503,7 +502,7 @@ async function promptSinglePackageOverrides(target, defaults) {
503
502
  initialValue: target.coverage?.command !== void 0 ? target.coverage.command : "",
504
503
  placeholder: defaults.coverageCommand ?? "(auto-detect from package.json test runner)"
505
504
  });
506
- assertNotCancelled(result);
505
+ if (isCancelled(result)) continue;
507
506
  const value = result.trim();
508
507
  const defaultCommand = defaults.coverageCommand ?? "";
509
508
  if (value.length === 0 || value === defaultCommand) {
@@ -674,6 +673,9 @@ function assertNotCancelled(value) {
674
673
  process.exit(0);
675
674
  }
676
675
  }
676
+ function isCancelled(value) {
677
+ return clack5.isCancel(value);
678
+ }
677
679
  async function confirm3(message) {
678
680
  const result = await clack5.confirm({ message, initialValue: true });
679
681
  assertNotCancelled(result);
@@ -766,48 +768,48 @@ async function boundariesCommand(options, cwd) {
766
768
  }
767
769
  function displayRules(config) {
768
770
  if (!config.boundaries || Object.keys(config.boundaries.deny).length === 0) {
769
- console.log(import_chalk.default.yellow("No boundary rules configured."));
770
- console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
771
+ console.log(import_chalk2.default.yellow("No boundary rules configured."));
772
+ console.log(`Run ${import_chalk2.default.cyan("viberails boundaries --infer")} to generate rules.`);
771
773
  return;
772
774
  }
773
775
  const { deny } = config.boundaries;
774
776
  const sources = Object.keys(deny).filter((k) => deny[k].length > 0);
775
777
  const totalRules = sources.reduce((sum, k) => sum + deny[k].length, 0);
776
778
  console.log(`
777
- ${import_chalk.default.bold(`Boundary rules (${totalRules} deny rules):`)}
779
+ ${import_chalk2.default.bold(`Boundary rules (${totalRules} deny rules):`)}
778
780
  `);
779
781
  for (const source of sources) {
780
782
  for (const target of deny[source]) {
781
- console.log(` ${import_chalk.default.red("\u2717")} ${source} \u2192 ${target}`);
783
+ console.log(` ${import_chalk2.default.red("\u2717")} ${source} \u2192 ${target}`);
782
784
  }
783
785
  }
784
786
  console.log(
785
787
  `
786
- Enforcement: ${config.rules.enforceBoundaries ? import_chalk.default.green("on") : import_chalk.default.yellow("off")}`
788
+ Enforcement: ${config.rules.enforceBoundaries ? import_chalk2.default.green("on") : import_chalk2.default.yellow("off")}`
787
789
  );
788
790
  }
789
791
  async function inferAndDisplay(projectRoot, config, configPath) {
790
- console.log(import_chalk.default.dim("Analyzing imports..."));
792
+ console.log(import_chalk2.default.dim("Analyzing imports..."));
791
793
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
792
794
  const packages = config.packages.length > 1 ? resolveWorkspacePackages(projectRoot, config.packages) : void 0;
793
795
  const graph = await buildImportGraph(projectRoot, {
794
796
  packages,
795
797
  ignore: config.ignore
796
798
  });
797
- console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
799
+ console.log(import_chalk2.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
798
800
  const inferred = inferBoundaries(graph);
799
801
  const sources = Object.keys(inferred.deny).filter((k) => inferred.deny[k].length > 0);
800
802
  const totalRules = sources.reduce((sum, k) => sum + inferred.deny[k].length, 0);
801
803
  if (totalRules === 0) {
802
- console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
804
+ console.log(import_chalk2.default.yellow("No boundary rules could be inferred."));
803
805
  return;
804
806
  }
805
807
  console.log(`
806
- ${import_chalk.default.bold("Inferred boundary rules:")}
808
+ ${import_chalk2.default.bold("Inferred boundary rules:")}
807
809
  `);
808
810
  for (const source of sources) {
809
811
  for (const target of inferred.deny[source]) {
810
- console.log(` ${import_chalk.default.red("\u2717")} ${source} \u2192 ${target}`);
812
+ console.log(` ${import_chalk2.default.red("\u2717")} ${source} \u2192 ${target}`);
811
813
  }
812
814
  }
813
815
  console.log(`
@@ -819,11 +821,11 @@ ${import_chalk.default.bold("Inferred boundary rules:")}
819
821
  config.rules.enforceBoundaries = true;
820
822
  fs3.writeFileSync(configPath, `${JSON.stringify((0, import_config.compactConfig)(config), null, 2)}
821
823
  `);
822
- console.log(`${import_chalk.default.green("\u2713")} Saved ${totalRules} rules`);
824
+ console.log(`${import_chalk2.default.green("\u2713")} Saved ${totalRules} rules`);
823
825
  }
824
826
  }
825
827
  async function showGraph(projectRoot, config) {
826
- console.log(import_chalk.default.dim("Building import graph..."));
828
+ console.log(import_chalk2.default.dim("Building import graph..."));
827
829
  const { buildImportGraph } = await import("@viberails/graph");
828
830
  const packages = config.packages.length > 1 ? resolveWorkspacePackages(projectRoot, config.packages) : void 0;
829
831
  const graph = await buildImportGraph(projectRoot, {
@@ -831,20 +833,20 @@ async function showGraph(projectRoot, config) {
831
833
  ignore: config.ignore
832
834
  });
833
835
  console.log(`
834
- ${import_chalk.default.bold("Import dependency graph:")}
836
+ ${import_chalk2.default.bold("Import dependency graph:")}
835
837
  `);
836
838
  console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
837
839
  `);
838
840
  if (graph.packages.length > 0) {
839
841
  for (const pkg of graph.packages) {
840
842
  const deps = pkg.internalDeps.length > 0 ? `
841
- ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk.default.dim(" (no internal deps)");
843
+ ${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk2.default.dim(" (no internal deps)");
842
844
  console.log(` ${pkg.name}${deps}`);
843
845
  }
844
846
  }
845
847
  if (graph.cycles.length > 0) {
846
848
  console.log(`
847
- ${import_chalk.default.yellow("Cycles detected:")}`);
849
+ ${import_chalk2.default.yellow("Cycles detected:")}`);
848
850
  for (const cycle of graph.cycles) {
849
851
  const paths = cycle.map((f) => path3.relative(projectRoot, f));
850
852
  console.log(` ${paths.join(" \u2192 ")}`);
@@ -856,7 +858,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
856
858
  var fs7 = __toESM(require("fs"), 1);
857
859
  var path7 = __toESM(require("path"), 1);
858
860
  var import_config5 = require("@viberails/config");
859
- var import_chalk3 = __toESM(require("chalk"), 1);
861
+ var import_chalk4 = __toESM(require("chalk"), 1);
860
862
 
861
863
  // src/commands/check-config.ts
862
864
  var import_config2 = require("@viberails/config");
@@ -1248,7 +1250,7 @@ function collectSourceFiles(dir, projectRoot) {
1248
1250
  }
1249
1251
 
1250
1252
  // src/commands/check-print.ts
1251
- var import_chalk2 = __toESM(require("chalk"), 1);
1253
+ var import_chalk3 = __toESM(require("chalk"), 1);
1252
1254
  function printGroupedViolations(violations, limit) {
1253
1255
  const groups = /* @__PURE__ */ new Map();
1254
1256
  for (const v of violations) {
@@ -1276,12 +1278,12 @@ function printGroupedViolations(violations, limit) {
1276
1278
  const toShow = group.slice(0, remaining);
1277
1279
  const hidden = group.length - toShow.length;
1278
1280
  for (const v of toShow) {
1279
- const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
1280
- console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
1281
+ const icon = v.severity === "error" ? import_chalk3.default.red("\u2717") : import_chalk3.default.yellow("!");
1282
+ console.log(`${icon} ${import_chalk3.default.dim(v.rule)} ${v.file}: ${v.message}`);
1281
1283
  }
1282
1284
  totalShown += toShow.length;
1283
1285
  if (hidden > 0) {
1284
- console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
1286
+ console.log(import_chalk3.default.dim(` ... and ${hidden} more ${rule} violations`));
1285
1287
  }
1286
1288
  }
1287
1289
  }
@@ -1371,13 +1373,13 @@ async function checkCommand(options, cwd) {
1371
1373
  const startDir = cwd ?? process.cwd();
1372
1374
  const projectRoot = findProjectRoot(startDir);
1373
1375
  if (!projectRoot) {
1374
- console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1376
+ console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1375
1377
  return 1;
1376
1378
  }
1377
1379
  const configPath = path7.join(projectRoot, CONFIG_FILE2);
1378
1380
  if (!fs7.existsSync(configPath)) {
1379
1381
  console.error(
1380
- `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1382
+ `${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1381
1383
  );
1382
1384
  return 1;
1383
1385
  }
@@ -1391,7 +1393,7 @@ async function checkCommand(options, cwd) {
1391
1393
  } else if (options.diffBase) {
1392
1394
  const diff = getDiffFiles(projectRoot, options.diffBase);
1393
1395
  if (diff.error && options.enforce) {
1394
- console.error(`${import_chalk3.default.red("Error:")} ${diff.error}`);
1396
+ console.error(`${import_chalk4.default.red("Error:")} ${diff.error}`);
1395
1397
  return 1;
1396
1398
  }
1397
1399
  filesToCheck = diff.all.filter((f) => SOURCE_EXTS.has(path7.extname(f)));
@@ -1406,15 +1408,15 @@ async function checkCommand(options, cwd) {
1406
1408
  if (options.format === "json") {
1407
1409
  console.log(JSON.stringify({ violations: [], checkedFiles: 0 }));
1408
1410
  } else {
1409
- console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
1411
+ console.log(`${import_chalk4.default.green("\u2713")} No files to check.`);
1410
1412
  }
1411
1413
  return 0;
1412
1414
  }
1413
1415
  const violations = [];
1414
1416
  const severity = options.enforce ? "error" : "warn";
1415
- const log8 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk3.default.dim(msg)) : () => {
1417
+ const log9 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk4.default.dim(msg)) : () => {
1416
1418
  };
1417
- log8(" Checking files...");
1419
+ log9(" Checking files...");
1418
1420
  for (const file of filesToCheck) {
1419
1421
  const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
1420
1422
  const relPath = path7.relative(projectRoot, absPath);
@@ -1447,9 +1449,9 @@ async function checkCommand(options, cwd) {
1447
1449
  }
1448
1450
  }
1449
1451
  }
1450
- log8(" done\n");
1452
+ log9(" done\n");
1451
1453
  if (!options.files) {
1452
- log8(" Checking missing tests...");
1454
+ log9(" Checking missing tests...");
1453
1455
  const testViolations = checkMissingTests(projectRoot, config, severity);
1454
1456
  if (options.staged) {
1455
1457
  const stagedSet = new Set(filesToCheck);
@@ -1462,14 +1464,14 @@ async function checkCommand(options, cwd) {
1462
1464
  } else {
1463
1465
  violations.push(...testViolations);
1464
1466
  }
1465
- log8(" done\n");
1467
+ log9(" done\n");
1466
1468
  }
1467
1469
  if (!options.files && !options.staged && !options.diffBase) {
1468
- log8(" Running test coverage...\n");
1470
+ log9(" Running test coverage...\n");
1469
1471
  const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
1470
1472
  staged: options.staged,
1471
1473
  enforce: options.enforce,
1472
- onProgress: (pkg) => log8(` Coverage: ${pkg}...
1474
+ onProgress: (pkg) => log9(` Coverage: ${pkg}...
1473
1475
  `)
1474
1476
  });
1475
1477
  violations.push(...coverageViolations);
@@ -1494,7 +1496,7 @@ async function checkCommand(options, cwd) {
1494
1496
  severity
1495
1497
  });
1496
1498
  }
1497
- log8(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1499
+ log9(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1498
1500
  `);
1499
1501
  }
1500
1502
  if (options.format === "json") {
@@ -1507,7 +1509,7 @@ async function checkCommand(options, cwd) {
1507
1509
  return options.enforce && violations.length > 0 ? 1 : 0;
1508
1510
  }
1509
1511
  if (violations.length === 0) {
1510
- console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1512
+ console.log(`${import_chalk4.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1511
1513
  return 0;
1512
1514
  }
1513
1515
  if (!options.quiet) {
@@ -1515,7 +1517,7 @@ async function checkCommand(options, cwd) {
1515
1517
  }
1516
1518
  printSummary(violations);
1517
1519
  if (options.enforce) {
1518
- console.log(import_chalk3.default.red("Fix violations before committing."));
1520
+ console.log(import_chalk4.default.red("Fix violations before committing."));
1519
1521
  return 1;
1520
1522
  }
1521
1523
  return 0;
@@ -1573,14 +1575,14 @@ var path9 = __toESM(require("path"), 1);
1573
1575
  var clack6 = __toESM(require("@clack/prompts"), 1);
1574
1576
  var import_config6 = require("@viberails/config");
1575
1577
  var import_scanner = require("@viberails/scanner");
1576
- var import_chalk6 = __toESM(require("chalk"), 1);
1578
+ var import_chalk7 = __toESM(require("chalk"), 1);
1577
1579
 
1578
1580
  // src/display-text.ts
1579
1581
  var import_types4 = require("@viberails/types");
1580
1582
 
1581
1583
  // src/display.ts
1582
1584
  var import_types3 = require("@viberails/types");
1583
- var import_chalk5 = __toESM(require("chalk"), 1);
1585
+ var import_chalk6 = __toESM(require("chalk"), 1);
1584
1586
 
1585
1587
  // src/display-helpers.ts
1586
1588
  var import_types = require("@viberails/types");
@@ -1633,7 +1635,7 @@ function formatRoleGroup(group) {
1633
1635
 
1634
1636
  // src/display-monorepo.ts
1635
1637
  var import_types2 = require("@viberails/types");
1636
- var import_chalk4 = __toESM(require("chalk"), 1);
1638
+ var import_chalk5 = __toESM(require("chalk"), 1);
1637
1639
  function formatPackageSummary(pkg) {
1638
1640
  const parts = [];
1639
1641
  if (pkg.stack.framework) {
@@ -1650,23 +1652,23 @@ function formatPackageSummary(pkg) {
1650
1652
  function displayMonorepoResults(scanResult) {
1651
1653
  const { stack, packages } = scanResult;
1652
1654
  console.log(`
1653
- ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1654
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1655
+ ${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1656
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1655
1657
  if (stack.packageManager) {
1656
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1658
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1657
1659
  }
1658
1660
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1659
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1661
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1660
1662
  } else {
1661
1663
  if (stack.linter) {
1662
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1664
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1663
1665
  }
1664
1666
  if (stack.formatter) {
1665
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1667
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1666
1668
  }
1667
1669
  }
1668
1670
  if (stack.testRunner) {
1669
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1671
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1670
1672
  }
1671
1673
  console.log("");
1672
1674
  for (const pkg of packages) {
@@ -1677,13 +1679,13 @@ ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`
1677
1679
  );
1678
1680
  if (packagesWithDirs.length > 0) {
1679
1681
  console.log(`
1680
- ${import_chalk4.default.bold("Structure:")}`);
1682
+ ${import_chalk5.default.bold("Structure:")}`);
1681
1683
  for (const pkg of packagesWithDirs) {
1682
1684
  const groups = groupByRole(pkg.structure.directories);
1683
1685
  if (groups.length === 0) continue;
1684
1686
  console.log(` ${pkg.relativePath}:`);
1685
1687
  for (const group of groups) {
1686
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
1688
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1687
1689
  }
1688
1690
  }
1689
1691
  }
@@ -1764,7 +1766,7 @@ function displayConventions(scanResult) {
1764
1766
  const conventionEntries = Object.entries(scanResult.conventions);
1765
1767
  if (conventionEntries.length === 0) return;
1766
1768
  console.log(`
1767
- ${import_chalk5.default.bold("Conventions:")}`);
1769
+ ${import_chalk6.default.bold("Conventions:")}`);
1768
1770
  for (const [key, convention] of conventionEntries) {
1769
1771
  if (convention.confidence === "low") continue;
1770
1772
  const label = import_types3.CONVENTION_LABELS[key] ?? key;
@@ -1772,19 +1774,19 @@ ${import_chalk5.default.bold("Conventions:")}`);
1772
1774
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1773
1775
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1774
1776
  if (allSame || pkgValues.length <= 1) {
1775
- const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
1776
- const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
1777
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1778
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1777
1779
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1778
1780
  } else {
1779
- console.log(` ${import_chalk5.default.yellow("~")} ${label}: varies by package`);
1781
+ console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
1780
1782
  for (const pv of pkgValues) {
1781
1783
  const pct = Math.round(pv.convention.consistency);
1782
1784
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1783
1785
  }
1784
1786
  }
1785
1787
  } else {
1786
- const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
1787
- const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
1788
+ const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
1789
+ const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
1788
1790
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1789
1791
  }
1790
1792
  }
@@ -1792,7 +1794,7 @@ ${import_chalk5.default.bold("Conventions:")}`);
1792
1794
  function displaySummarySection(scanResult) {
1793
1795
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1794
1796
  console.log(`
1795
- ${import_chalk5.default.bold("Summary:")}`);
1797
+ ${import_chalk6.default.bold("Summary:")}`);
1796
1798
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1797
1799
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1798
1800
  if (ext) {
@@ -1806,47 +1808,47 @@ function displayScanResults(scanResult) {
1806
1808
  }
1807
1809
  const { stack } = scanResult;
1808
1810
  console.log(`
1809
- ${import_chalk5.default.bold("Detected:")}`);
1811
+ ${import_chalk6.default.bold("Detected:")}`);
1810
1812
  if (stack.framework) {
1811
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1813
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1812
1814
  }
1813
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1815
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
1814
1816
  if (stack.styling) {
1815
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1817
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1816
1818
  }
1817
1819
  if (stack.backend) {
1818
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1820
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1819
1821
  }
1820
1822
  if (stack.orm) {
1821
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1823
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1822
1824
  }
1823
1825
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1824
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1826
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1825
1827
  } else {
1826
1828
  if (stack.linter) {
1827
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1829
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
1828
1830
  }
1829
1831
  if (stack.formatter) {
1830
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1832
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1831
1833
  }
1832
1834
  }
1833
1835
  if (stack.testRunner) {
1834
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1836
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1835
1837
  }
1836
1838
  if (stack.packageManager) {
1837
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1839
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1838
1840
  }
1839
1841
  if (stack.libraries.length > 0) {
1840
1842
  for (const lib of stack.libraries) {
1841
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1843
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1842
1844
  }
1843
1845
  }
1844
1846
  const groups = groupByRole(scanResult.structure.directories);
1845
1847
  if (groups.length > 0) {
1846
1848
  console.log(`
1847
- ${import_chalk5.default.bold("Structure:")}`);
1849
+ ${import_chalk6.default.bold("Structure:")}`);
1848
1850
  for (const group of groups) {
1849
- console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1851
+ console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
1850
1852
  }
1851
1853
  }
1852
1854
  displayConventions(scanResult);
@@ -1856,25 +1858,25 @@ ${import_chalk5.default.bold("Structure:")}`);
1856
1858
  function displayRulesPreview(config) {
1857
1859
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1858
1860
  console.log(
1859
- `${import_chalk5.default.bold("Rules:")} ${import_chalk5.default.dim("(warns on violation; use --enforce in CI to block)")}`
1861
+ `${import_chalk6.default.bold("Rules:")} ${import_chalk6.default.dim("(warns on violation; use --enforce in CI to block)")}`
1860
1862
  );
1861
- console.log(` ${import_chalk5.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1863
+ console.log(` ${import_chalk6.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1862
1864
  if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
1863
1865
  console.log(
1864
- ` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1866
+ ` ${import_chalk6.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1865
1867
  );
1866
1868
  } else if (config.rules.testCoverage > 0) {
1867
- console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1869
+ console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1868
1870
  } else {
1869
- console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: disabled`);
1871
+ console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: disabled`);
1870
1872
  }
1871
1873
  if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1872
- console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1874
+ console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1873
1875
  } else {
1874
- console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: no`);
1876
+ console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
1875
1877
  }
1876
1878
  console.log(
1877
- ` ${import_chalk5.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1879
+ ` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1878
1880
  );
1879
1881
  console.log("");
1880
1882
  }
@@ -2199,7 +2201,7 @@ async function configCommand(options, cwd) {
2199
2201
  }
2200
2202
  const configPath = path9.join(projectRoot, CONFIG_FILE3);
2201
2203
  if (!fs10.existsSync(configPath)) {
2202
- console.log(`${import_chalk6.default.yellow("!")} No config found. Run ${import_chalk6.default.cyan("viberails")} first.`);
2204
+ console.log(`${import_chalk7.default.yellow("!")} No config found. Run ${import_chalk7.default.cyan("viberails")} first.`);
2203
2205
  return;
2204
2206
  }
2205
2207
  if (!options.suppressIntro) {
@@ -2290,22 +2292,22 @@ async function rescanAndMerge(projectRoot, config) {
2290
2292
  var fs13 = __toESM(require("fs"), 1);
2291
2293
  var path13 = __toESM(require("path"), 1);
2292
2294
  var import_config7 = require("@viberails/config");
2293
- var import_chalk8 = __toESM(require("chalk"), 1);
2295
+ var import_chalk9 = __toESM(require("chalk"), 1);
2294
2296
 
2295
2297
  // src/commands/fix-helpers.ts
2296
2298
  var import_node_child_process3 = require("child_process");
2297
- var import_chalk7 = __toESM(require("chalk"), 1);
2299
+ var import_chalk8 = __toESM(require("chalk"), 1);
2298
2300
  function printPlan(renames, stubs) {
2299
2301
  if (renames.length > 0) {
2300
- console.log(import_chalk7.default.bold("\nFile renames:"));
2302
+ console.log(import_chalk8.default.bold("\nFile renames:"));
2301
2303
  for (const r of renames) {
2302
- console.log(` ${import_chalk7.default.red(r.oldPath)} \u2192 ${import_chalk7.default.green(r.newPath)}`);
2304
+ console.log(` ${import_chalk8.default.red(r.oldPath)} \u2192 ${import_chalk8.default.green(r.newPath)}`);
2303
2305
  }
2304
2306
  }
2305
2307
  if (stubs.length > 0) {
2306
- console.log(import_chalk7.default.bold("\nTest stubs to create:"));
2308
+ console.log(import_chalk8.default.bold("\nTest stubs to create:"));
2307
2309
  for (const s of stubs) {
2308
- console.log(` ${import_chalk7.default.green("+")} ${s.path}`);
2310
+ console.log(` ${import_chalk8.default.green("+")} ${s.path}`);
2309
2311
  }
2310
2312
  }
2311
2313
  }
@@ -2633,13 +2635,13 @@ async function fixCommand(options, cwd) {
2633
2635
  const startDir = cwd ?? process.cwd();
2634
2636
  const projectRoot = findProjectRoot(startDir);
2635
2637
  if (!projectRoot) {
2636
- console.error(`${import_chalk8.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2638
+ console.error(`${import_chalk9.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2637
2639
  return 1;
2638
2640
  }
2639
2641
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
2640
2642
  if (!fs13.existsSync(configPath)) {
2641
2643
  console.error(
2642
- `${import_chalk8.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2644
+ `${import_chalk9.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2643
2645
  );
2644
2646
  return 1;
2645
2647
  }
@@ -2648,7 +2650,7 @@ async function fixCommand(options, cwd) {
2648
2650
  const isDirty = checkGitDirty(projectRoot);
2649
2651
  if (isDirty) {
2650
2652
  console.log(
2651
- import_chalk8.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
2653
+ import_chalk9.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
2652
2654
  );
2653
2655
  }
2654
2656
  }
@@ -2695,41 +2697,41 @@ async function fixCommand(options, cwd) {
2695
2697
  return blockedOldBareNames.has(bare);
2696
2698
  });
2697
2699
  if (safeRenames.length === 0 && testStubs.length === 0 && skippedRenames.length === 0) {
2698
- console.log(`${import_chalk8.default.green("\u2713")} No fixable violations found.`);
2700
+ console.log(`${import_chalk9.default.green("\u2713")} No fixable violations found.`);
2699
2701
  return 0;
2700
2702
  }
2701
2703
  printPlan(safeRenames, testStubs);
2702
2704
  if (skippedRenames.length > 0) {
2703
2705
  console.log("");
2704
2706
  console.log(
2705
- import_chalk8.default.yellow(
2707
+ import_chalk9.default.yellow(
2706
2708
  `Skipping ${skippedRenames.length} rename${skippedRenames.length > 1 ? "s" : ""} \u2014 aliased imports would break:`
2707
2709
  )
2708
2710
  );
2709
2711
  for (const r of skippedRenames.slice(0, 5)) {
2710
- console.log(import_chalk8.default.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
2712
+ console.log(import_chalk9.default.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
2711
2713
  }
2712
2714
  if (skippedRenames.length > 5) {
2713
- console.log(import_chalk8.default.dim(` ... and ${skippedRenames.length - 5} more`));
2715
+ console.log(import_chalk9.default.dim(` ... and ${skippedRenames.length - 5} more`));
2714
2716
  }
2715
2717
  console.log("");
2716
- console.log(import_chalk8.default.yellow("Affected aliased imports:"));
2718
+ console.log(import_chalk9.default.yellow("Affected aliased imports:"));
2717
2719
  for (const alias of aliasImports.slice(0, 5)) {
2718
2720
  const relFile = path13.relative(projectRoot, alias.file);
2719
- console.log(import_chalk8.default.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
2721
+ console.log(import_chalk9.default.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
2720
2722
  }
2721
2723
  if (aliasImports.length > 5) {
2722
- console.log(import_chalk8.default.dim(` ... and ${aliasImports.length - 5} more`));
2724
+ console.log(import_chalk9.default.dim(` ... and ${aliasImports.length - 5} more`));
2723
2725
  }
2724
- console.log(import_chalk8.default.dim(" Update these imports to relative paths first, then re-run fix."));
2726
+ console.log(import_chalk9.default.dim(" Update these imports to relative paths first, then re-run fix."));
2725
2727
  }
2726
2728
  if (safeRenames.length === 0 && testStubs.length === 0) {
2727
2729
  console.log(`
2728
- ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2730
+ ${import_chalk9.default.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2729
2731
  return 0;
2730
2732
  }
2731
2733
  if (options.dryRun) {
2732
- console.log(import_chalk8.default.dim("\nDry run \u2014 no changes applied."));
2734
+ console.log(import_chalk9.default.dim("\nDry run \u2014 no changes applied."));
2733
2735
  return 0;
2734
2736
  }
2735
2737
  if (!options.yes) {
@@ -2760,15 +2762,15 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
2760
2762
  }
2761
2763
  console.log("");
2762
2764
  if (renameCount > 0) {
2763
- console.log(`${import_chalk8.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2765
+ console.log(`${import_chalk9.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2764
2766
  }
2765
2767
  if (importUpdateCount > 0) {
2766
2768
  console.log(
2767
- `${import_chalk8.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2769
+ `${import_chalk9.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2768
2770
  );
2769
2771
  }
2770
2772
  if (stubCount > 0) {
2771
- console.log(`${import_chalk8.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2773
+ console.log(`${import_chalk9.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2772
2774
  }
2773
2775
  return 0;
2774
2776
  }
@@ -2776,16 +2778,16 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
2776
2778
  // src/commands/init.ts
2777
2779
  var fs21 = __toESM(require("fs"), 1);
2778
2780
  var path21 = __toESM(require("path"), 1);
2779
- var clack12 = __toESM(require("@clack/prompts"), 1);
2781
+ var clack13 = __toESM(require("@clack/prompts"), 1);
2780
2782
  var import_config9 = require("@viberails/config");
2781
2783
  var import_scanner3 = require("@viberails/scanner");
2782
- var import_chalk13 = __toESM(require("chalk"), 1);
2784
+ var import_chalk15 = __toESM(require("chalk"), 1);
2783
2785
 
2784
2786
  // src/utils/check-prerequisites.ts
2785
2787
  var fs14 = __toESM(require("fs"), 1);
2786
2788
  var path14 = __toESM(require("path"), 1);
2787
2789
  var clack7 = __toESM(require("@clack/prompts"), 1);
2788
- var import_chalk9 = __toESM(require("chalk"), 1);
2790
+ var import_chalk10 = __toESM(require("chalk"), 1);
2789
2791
 
2790
2792
  // src/utils/spawn-async.ts
2791
2793
  var import_node_child_process4 = require("child_process");
@@ -2840,9 +2842,9 @@ function displayMissingPrereqs(prereqs) {
2840
2842
  const missing = prereqs.filter((p) => !p.installed);
2841
2843
  for (const m of missing) {
2842
2844
  const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2843
- console.log(` ${import_chalk9.default.yellow("!")} ${m.label} not installed${suffix}`);
2845
+ console.log(` ${import_chalk10.default.yellow("!")} ${m.label} not installed${suffix}`);
2844
2846
  if (m.installCommand) {
2845
- console.log(` Install: ${import_chalk9.default.cyan(m.installCommand)}`);
2847
+ console.log(` Install: ${import_chalk10.default.cyan(m.installCommand)}`);
2846
2848
  }
2847
2849
  }
2848
2850
  }
@@ -2887,6 +2889,9 @@ async function executeDeferredInstalls(projectRoot, installs) {
2887
2889
  }
2888
2890
 
2889
2891
  // src/utils/prompt-main-menu.ts
2892
+ var clack11 = __toESM(require("@clack/prompts"), 1);
2893
+
2894
+ // src/utils/prompt-main-menu-handlers.ts
2890
2895
  var clack10 = __toESM(require("@clack/prompts"), 1);
2891
2896
 
2892
2897
  // src/utils/prompt-integrations.ts
@@ -2980,140 +2985,7 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2980
2985
  };
2981
2986
  }
2982
2987
 
2983
- // src/utils/prompt-main-menu-hints.ts
2984
- function fileLimitsHint(config) {
2985
- const max = config.rules.maxFileLines;
2986
- const test = config.rules.maxTestFileLines;
2987
- return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
2988
- }
2989
- function fileNamingHint(config, scanResult) {
2990
- const rootPkg = getRootPackage(config.packages);
2991
- const naming = rootPkg.conventions?.fileNaming;
2992
- if (!config.rules.enforceNaming) return "not enforced";
2993
- if (naming) {
2994
- const detected = scanResult.packages.some(
2995
- (p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
2996
- );
2997
- return detected ? `${naming} (detected)` : naming;
2998
- }
2999
- return "mixed \u2014 will not enforce if skipped";
3000
- }
3001
- function fileNamingStatus(config) {
3002
- if (!config.rules.enforceNaming) return "disabled";
3003
- const rootPkg = getRootPackage(config.packages);
3004
- return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
3005
- }
3006
- function missingTestsHint(config) {
3007
- if (!config.rules.enforceMissingTests) return "not enforced";
3008
- const rootPkg = getRootPackage(config.packages);
3009
- const pattern = rootPkg.structure?.testPattern;
3010
- return pattern ? `enforced (${pattern})` : "enforced";
3011
- }
3012
- function coverageHint(config, hasTestRunner) {
3013
- if (config.rules.testCoverage === 0) return "disabled";
3014
- if (!hasTestRunner)
3015
- return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
3016
- const isMonorepo = config.packages.length > 1;
3017
- if (isMonorepo) {
3018
- const withCov = config.packages.filter(
3019
- (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
3020
- );
3021
- const exempt = config.packages.length - withCov.length;
3022
- return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
3023
- }
3024
- return `${config.rules.testCoverage}%`;
3025
- }
3026
- function advancedNamingHint(config) {
3027
- const rootPkg = getRootPackage(config.packages);
3028
- const parts = [];
3029
- if (rootPkg.conventions?.componentNaming)
3030
- parts.push(`${rootPkg.conventions.componentNaming} components`);
3031
- if (rootPkg.conventions?.hookNaming) parts.push(`${rootPkg.conventions.hookNaming} hooks`);
3032
- if (rootPkg.conventions?.importAlias) parts.push(rootPkg.conventions.importAlias);
3033
- return parts.length > 0 ? parts.join(", ") : "component, hook, and alias conventions";
3034
- }
3035
- function integrationsHint(state) {
3036
- if (!state.visited.integrations || !state.integrations)
3037
- return "not configured \u2014 select to set up";
3038
- const items = [];
3039
- if (state.integrations.preCommitHook) items.push("pre-commit");
3040
- if (state.integrations.typecheckHook) items.push("typecheck");
3041
- if (state.integrations.lintHook) items.push("lint");
3042
- if (state.integrations.claudeCodeHook) items.push("Claude");
3043
- if (state.integrations.claudeMdRef) items.push("CLAUDE.md");
3044
- if (state.integrations.githubAction) items.push("CI");
3045
- return items.length > 0 ? items.join(" \xB7 ") : "none selected";
3046
- }
3047
- function packageOverridesHint(config) {
3048
- const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
3049
- const editable = config.packages.filter((p) => p.path !== ".");
3050
- const customized = editable.filter(
3051
- (p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
3052
- ).length;
3053
- return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
3054
- }
3055
- function boundariesHint(config, state) {
3056
- if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
3057
- const deny = config.boundaries?.deny;
3058
- if (!deny) return "enabled";
3059
- const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
3060
- const pkgCount = Object.keys(deny).length;
3061
- return `${ruleCount} rules across ${pkgCount} packages`;
3062
- }
3063
- function statusIcon(status) {
3064
- if (status === "ok") return "\u2713";
3065
- if (status === "needs-input") return "?";
3066
- return "~";
3067
- }
3068
- function buildMainMenuOptions(config, scanResult, state) {
3069
- const namingStatus = fileNamingStatus(config);
3070
- const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
3071
- const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
3072
- const options = [
3073
- {
3074
- value: "fileLimits",
3075
- label: `${statusIcon("ok")} Max file size`,
3076
- hint: fileLimitsHint(config)
3077
- },
3078
- {
3079
- value: "fileNaming",
3080
- label: `${statusIcon(namingStatus)} File naming`,
3081
- hint: fileNamingHint(config, scanResult)
3082
- },
3083
- {
3084
- value: "missingTests",
3085
- label: `${statusIcon(missingTestsStatus)} Missing tests`,
3086
- hint: missingTestsHint(config)
3087
- },
3088
- {
3089
- value: "coverage",
3090
- label: `${statusIcon(coverageStatus)} Coverage`,
3091
- hint: coverageHint(config, state.hasTestRunner)
3092
- },
3093
- { value: "advancedNaming", label: " Advanced naming", hint: advancedNamingHint(config) }
3094
- ];
3095
- if (config.packages.length > 1) {
3096
- const bIcon = state.visited.boundaries && config.rules.enforceBoundaries ? statusIcon("ok") : " ";
3097
- options.push(
3098
- {
3099
- value: "packageOverrides",
3100
- label: " Per-package overrides",
3101
- hint: packageOverridesHint(config)
3102
- },
3103
- { value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
3104
- );
3105
- }
3106
- const iIcon = state.visited.integrations ? statusIcon("ok") : " ";
3107
- options.push(
3108
- { value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
3109
- { value: "reset", label: " Reset all to defaults" },
3110
- { value: "review", label: " Review scan details" },
3111
- { value: "done", label: " Done \u2014 write config" }
3112
- );
3113
- return options;
3114
- }
3115
-
3116
- // src/utils/prompt-main-menu.ts
2988
+ // src/utils/prompt-main-menu-handlers.ts
3117
2989
  async function handleAdvancedNaming(config) {
3118
2990
  const rootPkg = getRootPackage(config.packages);
3119
2991
  const state = {
@@ -3141,51 +3013,6 @@ async function handleAdvancedNaming(config) {
3141
3013
  rootPkg.conventions.hookNaming = state.hookNaming || void 0;
3142
3014
  rootPkg.conventions.importAlias = state.importAlias || void 0;
3143
3015
  }
3144
- async function promptMainMenu(config, scanResult, opts) {
3145
- const originalConfig = structuredClone(config);
3146
- const state = {
3147
- visited: { integrations: false, boundaries: false },
3148
- deferredInstalls: [],
3149
- hasTestRunner: opts.hasTestRunner,
3150
- hookManager: opts.hookManager
3151
- };
3152
- while (true) {
3153
- const options = buildMainMenuOptions(config, scanResult, state);
3154
- const choice = await clack10.select({ message: "Configure viberails", options });
3155
- assertNotCancelled(choice);
3156
- if (choice === "done") {
3157
- if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
3158
- config.rules.enforceNaming = false;
3159
- }
3160
- break;
3161
- }
3162
- if (choice === "fileLimits") {
3163
- const s = {
3164
- maxFileLines: config.rules.maxFileLines,
3165
- maxTestFileLines: config.rules.maxTestFileLines
3166
- };
3167
- await promptFileLimitsMenu(s);
3168
- config.rules.maxFileLines = s.maxFileLines;
3169
- config.rules.maxTestFileLines = s.maxTestFileLines;
3170
- }
3171
- if (choice === "fileNaming") await handleFileNaming(config, scanResult);
3172
- if (choice === "missingTests") await handleMissingTests(config);
3173
- if (choice === "coverage") await handleCoverage(config, state, opts);
3174
- if (choice === "advancedNaming") await handleAdvancedNaming(config);
3175
- if (choice === "packageOverrides") await handlePackageOverrides(config);
3176
- if (choice === "boundaries") await handleBoundaries(config, state, opts);
3177
- if (choice === "integrations") await handleIntegrations(state, opts);
3178
- if (choice === "review") clack10.note(formatScanResultsText(scanResult), "Scan details");
3179
- if (choice === "reset") {
3180
- Object.assign(config, structuredClone(originalConfig));
3181
- state.deferredInstalls = [];
3182
- state.visited = { integrations: false, boundaries: false };
3183
- state.integrations = void 0;
3184
- clack10.log.info("Reset all settings to scan-detected defaults.");
3185
- }
3186
- }
3187
- return state;
3188
- }
3189
3016
  async function handleFileNaming(config, scanResult) {
3190
3017
  const isMonorepo = config.packages.length > 1;
3191
3018
  if (isMonorepo) {
@@ -3214,7 +3041,7 @@ async function handleFileNaming(config, scanResult) {
3214
3041
  options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
3215
3042
  initialValue: rootPkg.conventions?.fileNaming ?? SENTINEL_SKIP
3216
3043
  });
3217
- assertNotCancelled(selected);
3044
+ if (isCancelled(selected)) return;
3218
3045
  if (selected === SENTINEL_SKIP) {
3219
3046
  config.rules.enforceNaming = false;
3220
3047
  if (rootPkg.conventions) delete rootPkg.conventions.fileNaming;
@@ -3229,12 +3056,15 @@ async function handleMissingTests(config) {
3229
3056
  message: "Require every source file to have a test file?",
3230
3057
  initialValue: config.rules.enforceMissingTests
3231
3058
  });
3232
- assertNotCancelled(result);
3059
+ if (isCancelled(result)) return;
3233
3060
  config.rules.enforceMissingTests = result;
3234
3061
  }
3235
3062
  async function handleCoverage(config, state, opts) {
3236
3063
  if (!opts.hasTestRunner) {
3237
- clack10.log.info("Coverage checks are inactive \u2014 no test runner detected.");
3064
+ clack10.note(
3065
+ "No test runner (vitest, jest, etc.) was detected.\nInstall one, then re-run viberails init to configure coverage.",
3066
+ "Coverage inactive"
3067
+ );
3238
3068
  return;
3239
3069
  }
3240
3070
  const planned = planCoverageInstall(opts.coveragePrereqs);
@@ -3255,7 +3085,7 @@ async function handleCoverage(config, state, opts) {
3255
3085
  }
3256
3086
  ]
3257
3087
  });
3258
- assertNotCancelled(choice);
3088
+ if (isCancelled(choice)) return;
3259
3089
  state.deferredInstalls = state.deferredInstalls.filter((d) => d.command !== planned.command);
3260
3090
  if (choice === "install") {
3261
3091
  planned.onFailure = () => {
@@ -3276,7 +3106,7 @@ async function handleCoverage(config, state, opts) {
3276
3106
  if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
3277
3107
  }
3278
3108
  });
3279
- assertNotCancelled(result);
3109
+ if (isCancelled(result)) return;
3280
3110
  config.rules.testCoverage = Number.parseInt(result, 10);
3281
3111
  }
3282
3112
  async function handlePackageOverrides(config) {
@@ -3295,7 +3125,7 @@ async function handleBoundaries(config, state, opts) {
3295
3125
  message: "Infer boundary rules from current import patterns?",
3296
3126
  initialValue: false
3297
3127
  });
3298
- assertNotCancelled(shouldInfer);
3128
+ if (isCancelled(shouldInfer)) return;
3299
3129
  state.visited.boundaries = true;
3300
3130
  if (!shouldInfer) {
3301
3131
  config.rules.enforceBoundaries = false;
@@ -3338,6 +3168,221 @@ async function handleIntegrations(state, opts) {
3338
3168
  }
3339
3169
  }
3340
3170
 
3171
+ // src/utils/prompt-main-menu-hints.ts
3172
+ var import_chalk11 = __toESM(require("chalk"), 1);
3173
+ function fileLimitsHint(config) {
3174
+ const max = config.rules.maxFileLines;
3175
+ const test = config.rules.maxTestFileLines;
3176
+ return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
3177
+ }
3178
+ function fileNamingHint(config, scanResult) {
3179
+ const rootPkg = getRootPackage(config.packages);
3180
+ const naming = rootPkg.conventions?.fileNaming;
3181
+ if (!config.rules.enforceNaming) return "not enforced";
3182
+ if (naming) {
3183
+ const detected = scanResult.packages.some(
3184
+ (p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
3185
+ );
3186
+ return detected ? `${naming} (detected)` : naming;
3187
+ }
3188
+ return "mixed \u2014 will not enforce if skipped";
3189
+ }
3190
+ function fileNamingStatus(config) {
3191
+ if (!config.rules.enforceNaming) return "disabled";
3192
+ const rootPkg = getRootPackage(config.packages);
3193
+ return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
3194
+ }
3195
+ function missingTestsHint(config) {
3196
+ if (!config.rules.enforceMissingTests) return "not enforced";
3197
+ const rootPkg = getRootPackage(config.packages);
3198
+ const pattern = rootPkg.structure?.testPattern;
3199
+ return pattern ? `enforced (${pattern})` : "enforced";
3200
+ }
3201
+ function coverageHint(config, hasTestRunner) {
3202
+ if (config.rules.testCoverage === 0) return "disabled";
3203
+ if (!hasTestRunner)
3204
+ return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
3205
+ const isMonorepo = config.packages.length > 1;
3206
+ if (isMonorepo) {
3207
+ const withCov = config.packages.filter(
3208
+ (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
3209
+ );
3210
+ const exempt = config.packages.length - withCov.length;
3211
+ return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
3212
+ }
3213
+ return `${config.rules.testCoverage}%`;
3214
+ }
3215
+ function advancedNamingHint(config) {
3216
+ const rootPkg = getRootPackage(config.packages);
3217
+ if (!config.rules.enforceNaming) return "not enforced";
3218
+ const parts = [];
3219
+ const naming = rootPkg.conventions?.fileNaming;
3220
+ if (naming) parts.push(import_chalk11.default.green(naming));
3221
+ const comp = rootPkg.conventions?.componentNaming;
3222
+ parts.push(comp ? import_chalk11.default.green(`${comp} components`) : import_chalk11.default.dim("components"));
3223
+ const hook = rootPkg.conventions?.hookNaming;
3224
+ parts.push(hook ? import_chalk11.default.green(`${hook} hooks`) : import_chalk11.default.dim("hooks"));
3225
+ const alias = rootPkg.conventions?.importAlias;
3226
+ parts.push(alias ? import_chalk11.default.green(alias) : import_chalk11.default.dim("alias"));
3227
+ return parts.join(import_chalk11.default.dim(", "));
3228
+ }
3229
+ function integrationsHint(state) {
3230
+ if (!state.visited.integrations || !state.integrations)
3231
+ return "not configured \u2014 select to set up";
3232
+ const items = [];
3233
+ if (state.integrations.preCommitHook) items.push(import_chalk11.default.green("pre-commit"));
3234
+ if (state.integrations.typecheckHook) items.push(import_chalk11.default.green("typecheck"));
3235
+ if (state.integrations.lintHook) items.push(import_chalk11.default.green("lint"));
3236
+ if (state.integrations.claudeCodeHook) items.push(import_chalk11.default.green("Claude"));
3237
+ if (state.integrations.claudeMdRef) items.push(import_chalk11.default.green("CLAUDE.md"));
3238
+ if (state.integrations.githubAction) items.push(import_chalk11.default.green("CI"));
3239
+ return items.length > 0 ? items.join(import_chalk11.default.dim(" \xB7 ")) : "none selected";
3240
+ }
3241
+ function packageOverridesHint(config) {
3242
+ const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
3243
+ const editable = config.packages.filter((p) => p.path !== ".");
3244
+ const customized = editable.filter(
3245
+ (p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
3246
+ ).length;
3247
+ return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
3248
+ }
3249
+ function boundariesHint(config, state) {
3250
+ if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
3251
+ const deny = config.boundaries?.deny;
3252
+ if (!deny) return "enabled";
3253
+ const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
3254
+ const pkgCount = Object.keys(deny).length;
3255
+ return `${ruleCount} rules across ${pkgCount} packages`;
3256
+ }
3257
+ function advancedNamingStatus(config) {
3258
+ if (!config.rules.enforceNaming) return "disabled";
3259
+ const rootPkg = getRootPackage(config.packages);
3260
+ const hasAny = !!rootPkg.conventions?.fileNaming || !!rootPkg.conventions?.componentNaming || !!rootPkg.conventions?.hookNaming || !!rootPkg.conventions?.importAlias;
3261
+ return hasAny ? "ok" : "unconfigured";
3262
+ }
3263
+ function packageOverridesStatus(config) {
3264
+ const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
3265
+ const editable = config.packages.filter((p) => p.path !== ".");
3266
+ const customized = editable.some(
3267
+ (p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
3268
+ );
3269
+ return customized ? "ok" : "unconfigured";
3270
+ }
3271
+ function statusIcon(status) {
3272
+ if (status === "ok") return import_chalk11.default.green("\u2713");
3273
+ if (status === "needs-input") return import_chalk11.default.yellow("?");
3274
+ if (status === "unconfigured") return import_chalk11.default.dim("-");
3275
+ return import_chalk11.default.yellow("~");
3276
+ }
3277
+ function buildMainMenuOptions(config, scanResult, state) {
3278
+ const namingStatus = fileNamingStatus(config);
3279
+ const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
3280
+ const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
3281
+ const options = [
3282
+ {
3283
+ value: "fileLimits",
3284
+ label: `${statusIcon("ok")} Max file size`,
3285
+ hint: fileLimitsHint(config)
3286
+ },
3287
+ {
3288
+ value: "fileNaming",
3289
+ label: `${statusIcon(namingStatus)} Default file naming`,
3290
+ hint: fileNamingHint(config, scanResult)
3291
+ },
3292
+ {
3293
+ value: "missingTests",
3294
+ label: `${statusIcon(missingTestsStatus)} Missing tests`,
3295
+ hint: missingTestsHint(config)
3296
+ },
3297
+ {
3298
+ value: "coverage",
3299
+ label: `${statusIcon(coverageStatus)} Coverage`,
3300
+ hint: coverageHint(config, state.hasTestRunner)
3301
+ },
3302
+ {
3303
+ value: "advancedNaming",
3304
+ label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
3305
+ hint: advancedNamingHint(config)
3306
+ }
3307
+ ];
3308
+ if (config.packages.length > 1) {
3309
+ const bIcon = statusIcon(
3310
+ state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
3311
+ );
3312
+ const poIcon = statusIcon(packageOverridesStatus(config));
3313
+ options.push(
3314
+ {
3315
+ value: "packageOverrides",
3316
+ label: `${poIcon} Per-package overrides`,
3317
+ hint: packageOverridesHint(config)
3318
+ },
3319
+ { value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
3320
+ );
3321
+ }
3322
+ const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
3323
+ options.push(
3324
+ { value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
3325
+ { value: "reset", label: " Reset all to defaults" },
3326
+ { value: "review", label: " Review scan details", hint: "detected stack & conventions" },
3327
+ { value: "done", label: " Done \u2014 write config" }
3328
+ );
3329
+ return options;
3330
+ }
3331
+
3332
+ // src/utils/prompt-main-menu.ts
3333
+ async function promptMainMenu(config, scanResult, opts) {
3334
+ const originalConfig = structuredClone(config);
3335
+ const state = {
3336
+ visited: { integrations: false, boundaries: false },
3337
+ deferredInstalls: [],
3338
+ hasTestRunner: opts.hasTestRunner,
3339
+ hookManager: opts.hookManager
3340
+ };
3341
+ while (true) {
3342
+ const options = buildMainMenuOptions(config, scanResult, state);
3343
+ const choice = await clack11.select({ message: "Configure viberails", options });
3344
+ assertNotCancelled(choice);
3345
+ if (choice === "done") {
3346
+ if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
3347
+ config.rules.enforceNaming = false;
3348
+ }
3349
+ break;
3350
+ }
3351
+ if (choice === "fileLimits") {
3352
+ const s = {
3353
+ maxFileLines: config.rules.maxFileLines,
3354
+ maxTestFileLines: config.rules.maxTestFileLines
3355
+ };
3356
+ await promptFileLimitsMenu(s);
3357
+ config.rules.maxFileLines = s.maxFileLines;
3358
+ config.rules.maxTestFileLines = s.maxTestFileLines;
3359
+ }
3360
+ if (choice === "fileNaming") await handleFileNaming(config, scanResult);
3361
+ if (choice === "missingTests") await handleMissingTests(config);
3362
+ if (choice === "coverage") await handleCoverage(config, state, opts);
3363
+ if (choice === "advancedNaming") await handleAdvancedNaming(config);
3364
+ if (choice === "packageOverrides") await handlePackageOverrides(config);
3365
+ if (choice === "boundaries") await handleBoundaries(config, state, opts);
3366
+ if (choice === "integrations") await handleIntegrations(state, opts);
3367
+ if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
3368
+ if (choice === "reset") {
3369
+ const confirmed = await clack11.confirm({
3370
+ message: "Reset all settings to scan-detected defaults?",
3371
+ initialValue: false
3372
+ });
3373
+ assertNotCancelled(confirmed);
3374
+ if (confirmed) {
3375
+ Object.assign(config, structuredClone(originalConfig));
3376
+ state.deferredInstalls = [];
3377
+ state.visited = { integrations: false, boundaries: false };
3378
+ state.integrations = void 0;
3379
+ clack11.log.info("Reset all settings to scan-detected defaults.");
3380
+ }
3381
+ }
3382
+ }
3383
+ return state;
3384
+ }
3385
+
3341
3386
  // src/utils/update-gitignore.ts
3342
3387
  var fs16 = __toESM(require("fs"), 1);
3343
3388
  var path16 = __toESM(require("path"), 1);
@@ -3358,7 +3403,7 @@ function updateGitignore(projectRoot) {
3358
3403
  // src/commands/init-hooks.ts
3359
3404
  var fs18 = __toESM(require("fs"), 1);
3360
3405
  var path18 = __toESM(require("path"), 1);
3361
- var import_chalk10 = __toESM(require("chalk"), 1);
3406
+ var import_chalk12 = __toESM(require("chalk"), 1);
3362
3407
  var import_yaml = require("yaml");
3363
3408
 
3364
3409
  // src/commands/resolve-typecheck.ts
@@ -3403,13 +3448,13 @@ function setupPreCommitHook(projectRoot) {
3403
3448
  const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3404
3449
  if (fs18.existsSync(lefthookPath)) {
3405
3450
  addLefthookPreCommit(lefthookPath);
3406
- console.log(` ${import_chalk10.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3451
+ console.log(` ${import_chalk12.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3407
3452
  return "lefthook.yml";
3408
3453
  }
3409
3454
  const huskyDir = path18.join(projectRoot, ".husky");
3410
3455
  if (fs18.existsSync(huskyDir)) {
3411
3456
  writeHuskyPreCommit(huskyDir);
3412
- console.log(` ${import_chalk10.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3457
+ console.log(` ${import_chalk12.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3413
3458
  return ".husky/pre-commit";
3414
3459
  }
3415
3460
  const gitDir = path18.join(projectRoot, ".git");
@@ -3419,7 +3464,7 @@ function setupPreCommitHook(projectRoot) {
3419
3464
  fs18.mkdirSync(hooksDir, { recursive: true });
3420
3465
  }
3421
3466
  writeGitHookPreCommit(hooksDir);
3422
- console.log(` ${import_chalk10.default.green("\u2713")} .git/hooks/pre-commit`);
3467
+ console.log(` ${import_chalk12.default.green("\u2713")} .git/hooks/pre-commit`);
3423
3468
  return ".git/hooks/pre-commit";
3424
3469
  }
3425
3470
  return void 0;
@@ -3480,9 +3525,9 @@ function setupClaudeCodeHook(projectRoot) {
3480
3525
  settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
3481
3526
  } catch {
3482
3527
  console.warn(
3483
- ` ${import_chalk10.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3528
+ ` ${import_chalk12.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3484
3529
  );
3485
- console.warn(` Fix the JSON manually, then re-run ${import_chalk10.default.cyan("viberails init --force")}`);
3530
+ console.warn(` Fix the JSON manually, then re-run ${import_chalk12.default.cyan("viberails init --force")}`);
3486
3531
  return;
3487
3532
  }
3488
3533
  }
@@ -3505,7 +3550,7 @@ function setupClaudeCodeHook(projectRoot) {
3505
3550
  settings.hooks = hooks;
3506
3551
  fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3507
3552
  `);
3508
- console.log(` ${import_chalk10.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3553
+ console.log(` ${import_chalk12.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3509
3554
  }
3510
3555
  function setupClaudeMdReference(projectRoot) {
3511
3556
  const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
@@ -3517,7 +3562,7 @@ function setupClaudeMdReference(projectRoot) {
3517
3562
  const ref = "\n@.viberails/context.md\n";
3518
3563
  const prefix = content.length === 0 ? "" : content.trimEnd();
3519
3564
  fs18.writeFileSync(claudeMdPath, prefix + ref);
3520
- console.log(` ${import_chalk10.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3565
+ console.log(` ${import_chalk12.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3521
3566
  }
3522
3567
  function setupGithubAction(projectRoot, packageManager, options) {
3523
3568
  const workflowDir = path18.join(projectRoot, ".github", "workflows");
@@ -3603,7 +3648,7 @@ ${cmd}
3603
3648
  // src/commands/init-hooks-extra.ts
3604
3649
  var fs19 = __toESM(require("fs"), 1);
3605
3650
  var path19 = __toESM(require("path"), 1);
3606
- var import_chalk11 = __toESM(require("chalk"), 1);
3651
+ var import_chalk13 = __toESM(require("chalk"), 1);
3607
3652
  var import_yaml2 = require("yaml");
3608
3653
  function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
3609
3654
  const lefthookPath = path19.join(projectRoot, "lefthook.yml");
@@ -3663,12 +3708,12 @@ ${command}
3663
3708
  function setupTypecheckHook(projectRoot, packageManager) {
3664
3709
  const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3665
3710
  if (!resolved.command) {
3666
- console.log(` ${import_chalk11.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3711
+ console.log(` ${import_chalk13.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3667
3712
  return void 0;
3668
3713
  }
3669
3714
  const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
3670
3715
  if (target) {
3671
- console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3716
+ console.log(` ${import_chalk13.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3672
3717
  }
3673
3718
  return target;
3674
3719
  }
@@ -3689,7 +3734,7 @@ function setupLintHook(projectRoot, linter) {
3689
3734
  }
3690
3735
  const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
3691
3736
  if (target) {
3692
- console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3737
+ console.log(` ${import_chalk13.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3693
3738
  }
3694
3739
  return target;
3695
3740
  }
@@ -3698,7 +3743,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3698
3743
  if (integrations.preCommitHook) {
3699
3744
  const t = setupPreCommitHook(projectRoot);
3700
3745
  if (t && opts.lefthookExpected && !t.includes("lefthook")) {
3701
- console.log(` ${import_chalk11.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3746
+ console.log(` ${import_chalk13.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3702
3747
  }
3703
3748
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
3704
3749
  }
@@ -3731,10 +3776,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3731
3776
  // src/commands/init-non-interactive.ts
3732
3777
  var fs20 = __toESM(require("fs"), 1);
3733
3778
  var path20 = __toESM(require("path"), 1);
3734
- var clack11 = __toESM(require("@clack/prompts"), 1);
3779
+ var clack12 = __toESM(require("@clack/prompts"), 1);
3735
3780
  var import_config8 = require("@viberails/config");
3736
3781
  var import_scanner2 = require("@viberails/scanner");
3737
- var import_chalk12 = __toESM(require("chalk"), 1);
3782
+ var import_chalk14 = __toESM(require("chalk"), 1);
3738
3783
 
3739
3784
  // src/utils/filter-confidence.ts
3740
3785
  function filterHighConfidence(conventions, meta) {
@@ -3755,7 +3800,7 @@ function getExemptedPackages(config) {
3755
3800
  return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3756
3801
  }
3757
3802
  async function initNonInteractive(projectRoot, configPath) {
3758
- const s = clack11.spinner();
3803
+ const s = clack12.spinner();
3759
3804
  s.start("Scanning project...");
3760
3805
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
3761
3806
  const config = (0, import_config8.generateConfig)(scanResult);
@@ -3770,11 +3815,11 @@ async function initNonInteractive(projectRoot, configPath) {
3770
3815
  const exempted = getExemptedPackages(config);
3771
3816
  if (exempted.length > 0) {
3772
3817
  console.log(
3773
- ` ${import_chalk12.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk12.default.dim("(types-only)")}`
3818
+ ` ${import_chalk14.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk14.default.dim("(types-only)")}`
3774
3819
  );
3775
3820
  }
3776
3821
  if (config.packages.length > 1) {
3777
- const bs = clack11.spinner();
3822
+ const bs = clack12.spinner();
3778
3823
  bs.start("Building import graph...");
3779
3824
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3780
3825
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3807,14 +3852,14 @@ async function initNonInteractive(projectRoot, configPath) {
3807
3852
  const hookManager = detectHookManager(projectRoot);
3808
3853
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3809
3854
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3810
- const ok = import_chalk12.default.green("\u2713");
3855
+ const ok = import_chalk14.default.green("\u2713");
3811
3856
  const created = [
3812
3857
  `${ok} ${path20.basename(configPath)}`,
3813
3858
  `${ok} .viberails/context.md`,
3814
3859
  `${ok} .viberails/scan-result.json`,
3815
3860
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3816
3861
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3817
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk12.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3862
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk14.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3818
3863
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3819
3864
  ].filter(Boolean);
3820
3865
  if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
@@ -3839,8 +3884,8 @@ async function initCommand(options, cwd) {
3839
3884
  return initInteractive(projectRoot, configPath, options);
3840
3885
  }
3841
3886
  console.log(
3842
- `${import_chalk13.default.yellow("!")} viberails is already initialized.
3843
- Run ${import_chalk13.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk13.default.cyan("viberails sync")} to update generated files, or ${import_chalk13.default.cyan("viberails init --force")} to replace it.`
3887
+ `${import_chalk15.default.yellow("!")} viberails is already initialized.
3888
+ Run ${import_chalk15.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk15.default.cyan("viberails sync")} to update generated files, or ${import_chalk15.default.cyan("viberails init --force")} to replace it.`
3844
3889
  );
3845
3890
  return;
3846
3891
  }
@@ -3848,11 +3893,11 @@ async function initCommand(options, cwd) {
3848
3893
  await initInteractive(projectRoot, configPath, options);
3849
3894
  }
3850
3895
  async function initInteractive(projectRoot, configPath, options) {
3851
- clack12.intro("viberails");
3896
+ clack13.intro("viberails");
3852
3897
  if (fs21.existsSync(configPath) && !options.force) {
3853
3898
  const action = await promptExistingConfigAction(path21.basename(configPath));
3854
3899
  if (action === "cancel") {
3855
- clack12.outro("Aborted. No files were written.");
3900
+ clack13.outro("Aborted. No files were written.");
3856
3901
  return;
3857
3902
  }
3858
3903
  if (action === "edit") {
@@ -3866,23 +3911,23 @@ async function initInteractive(projectRoot, configPath, options) {
3866
3911
  `${path21.basename(configPath)} already exists and will be replaced. Continue?`
3867
3912
  );
3868
3913
  if (!replace) {
3869
- clack12.outro("Aborted. No files were written.");
3914
+ clack13.outro("Aborted. No files were written.");
3870
3915
  return;
3871
3916
  }
3872
3917
  }
3873
- const s = clack12.spinner();
3918
+ const s = clack13.spinner();
3874
3919
  s.start("Scanning project...");
3875
3920
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3876
3921
  const config = (0, import_config9.generateConfig)(scanResult);
3877
3922
  s.stop("Scan complete");
3878
3923
  if (scanResult.statistics.totalFiles === 0) {
3879
- clack12.log.warn(
3924
+ clack13.log.warn(
3880
3925
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3881
3926
  );
3882
3927
  }
3883
3928
  const hasTestRunner = !!scanResult.stack.testRunner;
3884
3929
  if (!hasTestRunner) {
3885
- clack12.log.info(
3930
+ clack13.log.info(
3886
3931
  "No test runner detected. Coverage checks are inactive until a test runner is installed.\nInstall a test runner (e.g. vitest) and re-run viberails init."
3887
3932
  );
3888
3933
  }
@@ -3903,13 +3948,13 @@ async function initInteractive(projectRoot, configPath, options) {
3903
3948
  });
3904
3949
  const shouldWrite = await confirm3("Apply this setup?");
3905
3950
  if (!shouldWrite) {
3906
- clack12.outro("Aborted. No files were written.");
3951
+ clack13.outro("Aborted. No files were written.");
3907
3952
  return;
3908
3953
  }
3909
3954
  if (state.deferredInstalls.length > 0) {
3910
3955
  await executeDeferredInstalls(projectRoot, state.deferredInstalls);
3911
3956
  }
3912
- const ws = clack12.spinner();
3957
+ const ws = clack13.spinner();
3913
3958
  ws.start("Writing configuration...");
3914
3959
  const compacted = (0, import_config9.compactConfig)(config);
3915
3960
  fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
@@ -3917,10 +3962,10 @@ async function initInteractive(projectRoot, configPath, options) {
3917
3962
  writeGeneratedFiles(projectRoot, config, scanResult);
3918
3963
  updateGitignore(projectRoot);
3919
3964
  ws.stop("Configuration written");
3920
- const ok = import_chalk13.default.green("\u2713");
3921
- clack12.log.step(`${ok} ${path21.basename(configPath)}`);
3922
- clack12.log.step(`${ok} .viberails/context.md`);
3923
- clack12.log.step(`${ok} .viberails/scan-result.json`);
3965
+ const ok = import_chalk15.default.green("\u2713");
3966
+ clack13.log.step(`${ok} ${path21.basename(configPath)}`);
3967
+ clack13.log.step(`${ok} .viberails/context.md`);
3968
+ clack13.log.step(`${ok} .viberails/scan-result.json`);
3924
3969
  if (state.visited.integrations && state.integrations) {
3925
3970
  const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
3926
3971
  setupSelectedIntegrations(projectRoot, state.integrations, {
@@ -3929,19 +3974,19 @@ async function initInteractive(projectRoot, configPath, options) {
3929
3974
  lefthookExpected
3930
3975
  });
3931
3976
  }
3932
- clack12.outro(
3977
+ clack13.outro(
3933
3978
  `Done! Next: review viberails.config.json, then run viberails check
3934
- ${import_chalk13.default.dim("Tip: use")} ${import_chalk13.default.cyan("viberails check --enforce")} ${import_chalk13.default.dim("in CI to block PRs on violations.")}`
3979
+ ${import_chalk15.default.dim("Tip: use")} ${import_chalk15.default.cyan("viberails check --enforce")} ${import_chalk15.default.dim("in CI to block PRs on violations.")}`
3935
3980
  );
3936
3981
  }
3937
3982
 
3938
3983
  // src/commands/sync.ts
3939
3984
  var fs22 = __toESM(require("fs"), 1);
3940
3985
  var path22 = __toESM(require("path"), 1);
3941
- var clack13 = __toESM(require("@clack/prompts"), 1);
3986
+ var clack14 = __toESM(require("@clack/prompts"), 1);
3942
3987
  var import_config11 = require("@viberails/config");
3943
3988
  var import_scanner4 = require("@viberails/scanner");
3944
- var import_chalk14 = __toESM(require("chalk"), 1);
3989
+ var import_chalk16 = __toESM(require("chalk"), 1);
3945
3990
  var CONFIG_FILE6 = "viberails.config.json";
3946
3991
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
3947
3992
  function loadPreviousStats(projectRoot) {
@@ -3967,7 +4012,7 @@ async function syncCommand(options, cwd) {
3967
4012
  const configPath = path22.join(projectRoot, CONFIG_FILE6);
3968
4013
  const existing = await (0, import_config11.loadConfig)(configPath);
3969
4014
  const previousStats = loadPreviousStats(projectRoot);
3970
- const s = clack13.spinner();
4015
+ const s = clack14.spinner();
3971
4016
  s.start("Scanning project...");
3972
4017
  const scanResult = await (0, import_scanner4.scan)(projectRoot);
3973
4018
  s.stop("Scan complete");
@@ -3982,19 +4027,19 @@ async function syncCommand(options, cwd) {
3982
4027
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
3983
4028
  if (changes.length > 0 || statsDelta) {
3984
4029
  console.log(`
3985
- ${import_chalk14.default.bold("Changes:")}`);
4030
+ ${import_chalk16.default.bold("Changes:")}`);
3986
4031
  for (const change of changes) {
3987
- const icon = change.type === "removed" ? import_chalk14.default.red("-") : import_chalk14.default.green("+");
4032
+ const icon = change.type === "removed" ? import_chalk16.default.red("-") : import_chalk16.default.green("+");
3988
4033
  console.log(` ${icon} ${change.description}`);
3989
4034
  }
3990
4035
  if (statsDelta) {
3991
- console.log(` ${import_chalk14.default.dim(statsDelta)}`);
4036
+ console.log(` ${import_chalk16.default.dim(statsDelta)}`);
3992
4037
  }
3993
4038
  }
3994
4039
  if (options?.interactive) {
3995
- clack13.intro("viberails sync (interactive)");
3996
- clack13.note(formatRulesText(merged).join("\n"), "Rules after sync");
3997
- const decision = await clack13.select({
4040
+ clack14.intro("viberails sync (interactive)");
4041
+ clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
4042
+ const decision = await clack14.select({
3998
4043
  message: "How would you like to proceed?",
3999
4044
  options: [
4000
4045
  { value: "accept", label: "Accept changes" },
@@ -4004,7 +4049,7 @@ ${import_chalk14.default.bold("Changes:")}`);
4004
4049
  });
4005
4050
  assertNotCancelled(decision);
4006
4051
  if (decision === "cancel") {
4007
- clack13.outro("Sync cancelled. No files were written.");
4052
+ clack14.outro("Sync cancelled. No files were written.");
4008
4053
  return;
4009
4054
  }
4010
4055
  if (decision === "customize") {
@@ -4028,8 +4073,8 @@ ${import_chalk14.default.bold("Changes:")}`);
4028
4073
  fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
4029
4074
  `);
4030
4075
  writeGeneratedFiles(projectRoot, merged, scanResult);
4031
- clack13.log.success("Updated config with your customizations.");
4032
- clack13.outro("Done! Run viberails check to verify.");
4076
+ clack14.log.success("Updated config with your customizations.");
4077
+ clack14.outro("Done! Run viberails check to verify.");
4033
4078
  return;
4034
4079
  }
4035
4080
  }
@@ -4037,18 +4082,18 @@ ${import_chalk14.default.bold("Changes:")}`);
4037
4082
  `);
4038
4083
  writeGeneratedFiles(projectRoot, merged, scanResult);
4039
4084
  console.log(`
4040
- ${import_chalk14.default.bold("Synced:")}`);
4085
+ ${import_chalk16.default.bold("Synced:")}`);
4041
4086
  if (configChanged) {
4042
- console.log(` ${import_chalk14.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4087
+ console.log(` ${import_chalk16.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4043
4088
  } else {
4044
- console.log(` ${import_chalk14.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4089
+ console.log(` ${import_chalk16.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4045
4090
  }
4046
- console.log(` ${import_chalk14.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4047
- console.log(` ${import_chalk14.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4091
+ console.log(` ${import_chalk16.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4092
+ console.log(` ${import_chalk16.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4048
4093
  }
4049
4094
 
4050
4095
  // src/index.ts
4051
- var VERSION = "0.6.6";
4096
+ var VERSION = "0.6.8";
4052
4097
  var program = new import_commander.Command();
4053
4098
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
4054
4099
  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)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {
@@ -4056,7 +4101,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
4056
4101
  await initCommand(options);
4057
4102
  } catch (err) {
4058
4103
  const message = err instanceof Error ? err.message : String(err);
4059
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4104
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4060
4105
  process.exit(1);
4061
4106
  }
4062
4107
  });
@@ -4065,7 +4110,7 @@ program.command("sync").description("Re-scan and update generated files").option
4065
4110
  await syncCommand(options);
4066
4111
  } catch (err) {
4067
4112
  const message = err instanceof Error ? err.message : String(err);
4068
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4113
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4069
4114
  process.exit(1);
4070
4115
  }
4071
4116
  });
@@ -4074,7 +4119,7 @@ program.command("config").description("Interactively edit existing config rules"
4074
4119
  await configCommand(options);
4075
4120
  } catch (err) {
4076
4121
  const message = err instanceof Error ? err.message : String(err);
4077
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4122
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4078
4123
  process.exit(1);
4079
4124
  }
4080
4125
  });
@@ -4095,7 +4140,7 @@ program.command("check").description("Check files against enforced rules").optio
4095
4140
  process.exit(exitCode);
4096
4141
  } catch (err) {
4097
4142
  const message = err instanceof Error ? err.message : String(err);
4098
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4143
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4099
4144
  process.exit(1);
4100
4145
  }
4101
4146
  }
@@ -4106,7 +4151,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
4106
4151
  process.exit(exitCode);
4107
4152
  } catch (err) {
4108
4153
  const message = err instanceof Error ? err.message : String(err);
4109
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4154
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4110
4155
  process.exit(1);
4111
4156
  }
4112
4157
  });
@@ -4115,7 +4160,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
4115
4160
  await boundariesCommand(options);
4116
4161
  } catch (err) {
4117
4162
  const message = err instanceof Error ? err.message : String(err);
4118
- console.error(`${import_chalk15.default.red("Error:")} ${message}`);
4163
+ console.error(`${import_chalk17.default.red("Error:")} ${message}`);
4119
4164
  process.exit(1);
4120
4165
  }
4121
4166
  });