viberails 0.6.10 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,7 +34,7 @@ __export(index_exports, {
34
34
  VERSION: () => VERSION
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
- var import_chalk18 = __toESM(require("chalk"), 1);
37
+ var import_chalk20 = __toESM(require("chalk"), 1);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/boundaries.ts
@@ -162,12 +162,12 @@ async function promptNamingMenu(state) {
162
162
  },
163
163
  {
164
164
  value: "componentNaming",
165
- label: `${state.componentNaming ? ok : unset} Component naming`,
165
+ label: `${state.componentNaming ? ok : unset} Component exports`,
166
166
  hint: state.componentNaming ?? HINT_NOT_SET
167
167
  },
168
168
  {
169
169
  value: "hookNaming",
170
- label: `${state.hookNaming ? ok : unset} Hook naming`,
170
+ label: `${state.hookNaming ? ok : unset} Hook exports`,
171
171
  hint: state.hookNaming ?? HINT_NOT_SET
172
172
  },
173
173
  {
@@ -204,7 +204,7 @@ async function promptNamingMenu(state) {
204
204
  }
205
205
  if (choice === "componentNaming") {
206
206
  const selected = await clack.select({
207
- message: "Component naming convention",
207
+ message: "Component export naming (e.g. UserProfile)",
208
208
  options: [
209
209
  ...COMPONENT_NAMING_OPTIONS,
210
210
  { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
@@ -216,7 +216,7 @@ async function promptNamingMenu(state) {
216
216
  }
217
217
  if (choice === "hookNaming") {
218
218
  const selected = await clack.select({
219
- message: "Hook naming convention",
219
+ message: "Hook export naming (e.g. useAuth)",
220
220
  options: [
221
221
  ...HOOK_NAMING_OPTIONS,
222
222
  { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
@@ -1452,9 +1452,9 @@ async function checkCommand(options, cwd) {
1452
1452
  }
1453
1453
  const violations = [];
1454
1454
  const severity = options.enforce ? "error" : "warn";
1455
- const log9 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk5.default.dim(msg)) : () => {
1455
+ const log10 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk5.default.dim(msg)) : () => {
1456
1456
  };
1457
- log9(" Checking files...");
1457
+ log10(" Checking files...");
1458
1458
  for (const file of filesToCheck) {
1459
1459
  const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
1460
1460
  const relPath = path7.relative(projectRoot, absPath);
@@ -1487,9 +1487,9 @@ async function checkCommand(options, cwd) {
1487
1487
  }
1488
1488
  }
1489
1489
  }
1490
- log9(" done\n");
1490
+ log10(" done\n");
1491
1491
  if (!options.files) {
1492
- log9(" Checking missing tests...");
1492
+ log10(" Checking missing tests...");
1493
1493
  const testViolations = checkMissingTests(projectRoot, config, severity);
1494
1494
  if (options.staged) {
1495
1495
  const stagedSet = new Set(filesToCheck);
@@ -1502,14 +1502,14 @@ async function checkCommand(options, cwd) {
1502
1502
  } else {
1503
1503
  violations.push(...testViolations);
1504
1504
  }
1505
- log9(" done\n");
1505
+ log10(" done\n");
1506
1506
  }
1507
1507
  if (!options.files && !options.staged && !options.diffBase) {
1508
- log9(" Running test coverage...\n");
1508
+ log10(" Running test coverage...\n");
1509
1509
  const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
1510
1510
  staged: options.staged,
1511
1511
  enforce: options.enforce,
1512
- onProgress: (pkg) => log9(` Coverage: ${pkg}...
1512
+ onProgress: (pkg) => log10(` Coverage: ${pkg}...
1513
1513
  `)
1514
1514
  });
1515
1515
  violations.push(...coverageViolations);
@@ -1534,7 +1534,7 @@ async function checkCommand(options, cwd) {
1534
1534
  severity
1535
1535
  });
1536
1536
  }
1537
- log9(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1537
+ log10(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1538
1538
  `);
1539
1539
  }
1540
1540
  if (options.format === "json") {
@@ -2816,10 +2816,10 @@ ${import_chalk10.default.yellow("!")} No safe fixes to apply. Resolve aliased im
2816
2816
  // src/commands/init.ts
2817
2817
  var fs21 = __toESM(require("fs"), 1);
2818
2818
  var path21 = __toESM(require("path"), 1);
2819
- var clack13 = __toESM(require("@clack/prompts"), 1);
2819
+ var clack14 = __toESM(require("@clack/prompts"), 1);
2820
2820
  var import_config9 = require("@viberails/config");
2821
2821
  var import_scanner3 = require("@viberails/scanner");
2822
- var import_chalk16 = __toESM(require("chalk"), 1);
2822
+ var import_chalk18 = __toESM(require("chalk"), 1);
2823
2823
 
2824
2824
  // src/utils/check-prerequisites.ts
2825
2825
  var fs14 = __toESM(require("fs"), 1);
@@ -2926,15 +2926,7 @@ async function executeDeferredInstalls(projectRoot, installs) {
2926
2926
  return successCount;
2927
2927
  }
2928
2928
 
2929
- // src/utils/prompt-main-menu.ts
2930
- var clack11 = __toESM(require("@clack/prompts"), 1);
2931
-
2932
- // src/utils/prompt-main-menu-handlers.ts
2933
- var clack10 = __toESM(require("@clack/prompts"), 1);
2934
-
2935
2929
  // src/utils/prompt-integrations.ts
2936
- var fs15 = __toESM(require("fs"), 1);
2937
- var path15 = __toESM(require("path"), 1);
2938
2930
  var clack9 = __toESM(require("@clack/prompts"), 1);
2939
2931
  function buildLefthookInstallCommand(pm, isWorkspace) {
2940
2932
  if (pm === "yarn") return "yarn add -D lefthook";
@@ -2942,26 +2934,24 @@ function buildLefthookInstallCommand(pm, isWorkspace) {
2942
2934
  if (pm === "npm") return "npm install -D lefthook";
2943
2935
  return `${pm} add -D lefthook`;
2944
2936
  }
2945
- async function promptIntegrationsDeferred(hookManager, tools, packageManager, isWorkspace, projectRoot) {
2937
+ async function promptIntegrationsDeferred(hookManager, tools) {
2938
+ const hasHookManager = !!hookManager;
2946
2939
  const options = [];
2947
- const needsLefthook = !hookManager;
2948
- if (needsLefthook) {
2949
- const pm = packageManager ?? "npm";
2950
- options.push({
2951
- value: "installLefthook",
2952
- label: "Install Lefthook",
2953
- hint: `after final confirmation \u2014 ${buildLefthookInstallCommand(pm, isWorkspace)}`
2954
- });
2955
- }
2956
- const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
2957
- const hookHint = needsLefthook ? "uses Lefthook if installed above, otherwise local git hook" : "runs viberails checks when you commit";
2940
+ const hookLabel = hasHookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
2941
+ const hookHint = hasHookManager ? "runs viberails checks when you commit" : "local hook only \u2014 use lefthook or husky to commit hooks to repo";
2958
2942
  options.push({ value: "preCommit", label: hookLabel, hint: hookHint });
2959
- if (tools?.isTypeScript) {
2943
+ if (tools?.typecheckLabel) {
2960
2944
  options.push({
2961
2945
  value: "typecheck",
2962
- label: "Typecheck (tsc --noEmit)",
2946
+ label: `Typecheck (${tools.typecheckLabel})`,
2963
2947
  hint: "pre-commit hook + CI check"
2964
2948
  });
2949
+ } else if (tools?.isTypeScript) {
2950
+ options.push({
2951
+ value: "typecheck",
2952
+ label: "Typecheck",
2953
+ hint: "needs root tsconfig.json, typecheck script, or turbo task"
2954
+ });
2965
2955
  }
2966
2956
  if (tools?.linter) {
2967
2957
  const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
@@ -2988,7 +2978,12 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2988
2978
  hint: "blocks PRs that fail viberails check"
2989
2979
  }
2990
2980
  );
2991
- const initialValues = options.map((o) => o.value);
2981
+ const hasTypecheck = !!tools?.typecheckLabel;
2982
+ const initialValues = options.filter((o) => {
2983
+ if (o.value === "preCommit" && !hasHookManager) return false;
2984
+ if (o.value === "typecheck" && !hasTypecheck) return false;
2985
+ return true;
2986
+ }).map((o) => o.value);
2992
2987
  const result = await clack9.multiselect({
2993
2988
  message: "Integrations",
2994
2989
  options,
@@ -2996,20 +2991,6 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2996
2991
  required: false
2997
2992
  });
2998
2993
  assertNotCancelled(result);
2999
- let lefthookInstall;
3000
- if (needsLefthook && result.includes("installLefthook")) {
3001
- const pm = packageManager ?? "npm";
3002
- lefthookInstall = {
3003
- label: "Lefthook",
3004
- command: buildLefthookInstallCommand(pm, isWorkspace),
3005
- onSuccess: projectRoot ? () => {
3006
- const ymlPath = path15.join(projectRoot, "lefthook.yml");
3007
- if (!fs15.existsSync(ymlPath)) {
3008
- fs15.writeFileSync(ymlPath, "# Generated by viberails\n");
3009
- }
3010
- } : void 0
3011
- };
3012
- }
3013
2994
  return {
3014
2995
  choice: {
3015
2996
  preCommitHook: result.includes("preCommit"),
@@ -3018,39 +2999,16 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
3018
2999
  githubAction: result.includes("githubAction"),
3019
3000
  typecheckHook: result.includes("typecheck"),
3020
3001
  lintHook: result.includes("lint")
3021
- },
3022
- lefthookInstall
3002
+ }
3023
3003
  };
3024
3004
  }
3025
3005
 
3006
+ // src/utils/prompt-main-menu.ts
3007
+ var clack11 = __toESM(require("@clack/prompts"), 1);
3008
+
3026
3009
  // src/utils/prompt-main-menu-handlers.ts
3027
- async function handleAdvancedNaming(config) {
3028
- const rootPkg = getRootPackage(config.packages);
3029
- const state = {
3030
- maxFileLines: config.rules.maxFileLines,
3031
- maxTestFileLines: config.rules.maxTestFileLines,
3032
- testCoverage: config.rules.testCoverage,
3033
- enforceMissingTests: config.rules.enforceMissingTests,
3034
- enforceNaming: config.rules.enforceNaming,
3035
- fileNamingValue: rootPkg.conventions?.fileNaming,
3036
- componentNaming: rootPkg.conventions?.componentNaming,
3037
- hookNaming: rootPkg.conventions?.hookNaming,
3038
- importAlias: rootPkg.conventions?.importAlias,
3039
- coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
3040
- coverageCommand: config.defaults?.coverage?.command
3041
- };
3042
- await promptNamingMenu(state);
3043
- rootPkg.conventions = rootPkg.conventions ?? {};
3044
- config.rules.enforceNaming = state.enforceNaming;
3045
- if (state.fileNamingValue) {
3046
- rootPkg.conventions.fileNaming = state.fileNamingValue;
3047
- } else {
3048
- delete rootPkg.conventions.fileNaming;
3049
- }
3050
- rootPkg.conventions.componentNaming = state.componentNaming || void 0;
3051
- rootPkg.conventions.hookNaming = state.hookNaming || void 0;
3052
- rootPkg.conventions.importAlias = state.importAlias || void 0;
3053
- }
3010
+ var clack10 = __toESM(require("@clack/prompts"), 1);
3011
+ var import_chalk12 = __toESM(require("chalk"), 1);
3054
3012
  async function handleFileNaming(config, scanResult) {
3055
3013
  const isMonorepo = config.packages.length > 1;
3056
3014
  if (isMonorepo) {
@@ -3190,24 +3148,95 @@ async function handleBoundaries(config, state, opts) {
3190
3148
  clack10.log.warn(`Boundary inference failed: ${err instanceof Error ? err.message : err}`);
3191
3149
  }
3192
3150
  }
3193
- async function handleIntegrations(state, opts) {
3194
- const result = await promptIntegrationsDeferred(
3195
- state.hookManager,
3196
- opts.tools,
3197
- opts.tools.packageManager,
3198
- opts.tools.isWorkspace,
3199
- opts.projectRoot
3200
- );
3201
- state.visited.integrations = true;
3202
- state.integrations = result.choice;
3203
- state.deferredInstalls = state.deferredInstalls.filter((d) => !d.command.includes("lefthook"));
3204
- if (result.lefthookInstall) {
3205
- state.deferredInstalls.push(result.lefthookInstall);
3151
+ async function handleAiContext(config) {
3152
+ const rootPkg = getRootPackage(config.packages);
3153
+ rootPkg.conventions = rootPkg.conventions ?? {};
3154
+ while (true) {
3155
+ const ok = import_chalk12.default.green("\u2713");
3156
+ const unset = import_chalk12.default.dim("-");
3157
+ const options = [
3158
+ {
3159
+ value: "componentNaming",
3160
+ label: `${rootPkg.conventions.componentNaming ? ok : unset} Component exports`,
3161
+ hint: rootPkg.conventions.componentNaming ?? "not set"
3162
+ },
3163
+ {
3164
+ value: "hookNaming",
3165
+ label: `${rootPkg.conventions.hookNaming ? ok : unset} Hook exports`,
3166
+ hint: rootPkg.conventions.hookNaming ?? "not set"
3167
+ },
3168
+ {
3169
+ value: "importAlias",
3170
+ label: `${rootPkg.conventions.importAlias ? ok : unset} Import alias`,
3171
+ hint: rootPkg.conventions.importAlias ?? "not set"
3172
+ },
3173
+ { value: "back", label: " Back" }
3174
+ ];
3175
+ const choice = await clack10.select({
3176
+ message: "AI context \u2014 conventions written to context.md for AI tools",
3177
+ options
3178
+ });
3179
+ if (isCancelled(choice) || choice === "back") return;
3180
+ if (choice === "componentNaming") {
3181
+ const selected = await clack10.select({
3182
+ message: "Component export naming (e.g. UserProfile)",
3183
+ options: [
3184
+ ...COMPONENT_NAMING_OPTIONS,
3185
+ { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
3186
+ ],
3187
+ initialValue: rootPkg.conventions.componentNaming ?? SENTINEL_CLEAR
3188
+ });
3189
+ if (isCancelled(selected)) continue;
3190
+ rootPkg.conventions.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
3191
+ }
3192
+ if (choice === "hookNaming") {
3193
+ const selected = await clack10.select({
3194
+ message: "Hook export naming (e.g. useAuth)",
3195
+ options: [
3196
+ ...HOOK_NAMING_OPTIONS,
3197
+ { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
3198
+ ],
3199
+ initialValue: rootPkg.conventions.hookNaming ?? SENTINEL_CLEAR
3200
+ });
3201
+ if (isCancelled(selected)) continue;
3202
+ rootPkg.conventions.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
3203
+ }
3204
+ if (choice === "importAlias") {
3205
+ const selected = await clack10.select({
3206
+ message: "Import alias pattern",
3207
+ options: [
3208
+ { value: "@/*", label: "@/*", hint: "import { x } from '@/utils'" },
3209
+ { value: "~/*", label: "~/*", hint: "import { x } from '~/utils'" },
3210
+ { value: SENTINEL_CUSTOM, label: "Custom..." },
3211
+ { value: SENTINEL_CLEAR, label: "Clear (no alias)" }
3212
+ ],
3213
+ initialValue: rootPkg.conventions.importAlias ?? SENTINEL_CLEAR
3214
+ });
3215
+ if (isCancelled(selected)) continue;
3216
+ if (selected === SENTINEL_CLEAR) {
3217
+ rootPkg.conventions.importAlias = void 0;
3218
+ } else if (selected === SENTINEL_CUSTOM) {
3219
+ const result = await clack10.text({
3220
+ message: "Custom import alias (e.g. #/*)?",
3221
+ initialValue: rootPkg.conventions.importAlias ?? "",
3222
+ placeholder: "e.g. #/*",
3223
+ validate: (v) => {
3224
+ if (typeof v !== "string" || !v.trim()) return "Alias cannot be empty";
3225
+ if (!/^[a-zA-Z@~#$][a-zA-Z0-9@~#$_-]*\/\*$/.test(v.trim()))
3226
+ return "Must match pattern like @/*, ~/*, or #src/*";
3227
+ }
3228
+ });
3229
+ if (isCancelled(result)) continue;
3230
+ rootPkg.conventions.importAlias = result.trim();
3231
+ } else {
3232
+ rootPkg.conventions.importAlias = selected;
3233
+ }
3234
+ }
3206
3235
  }
3207
3236
  }
3208
3237
 
3209
3238
  // src/utils/prompt-main-menu-hints.ts
3210
- var import_chalk12 = __toESM(require("chalk"), 1);
3239
+ var import_chalk13 = __toESM(require("chalk"), 1);
3211
3240
  function fileLimitsHint(config) {
3212
3241
  const max = config.rules.maxFileLines;
3213
3242
  const test = config.rules.maxTestFileLines;
@@ -3223,7 +3252,7 @@ function fileNamingHint(config, scanResult) {
3223
3252
  );
3224
3253
  return detected ? `${naming} (detected)` : naming;
3225
3254
  }
3226
- return "mixed \u2014 will not enforce if skipped";
3255
+ return "not set \u2014 select to configure";
3227
3256
  }
3228
3257
  function fileNamingStatus(config) {
3229
3258
  if (!config.rules.enforceNaming) return "unconfigured";
@@ -3250,30 +3279,27 @@ function coverageHint(config, hasTestRunner) {
3250
3279
  }
3251
3280
  return `${config.rules.testCoverage}%`;
3252
3281
  }
3253
- function advancedNamingHint(config) {
3282
+ function aiContextHint(config) {
3254
3283
  const rootPkg = getRootPackage(config.packages);
3255
- if (!config.rules.enforceNaming) return "not enforced";
3256
- const ok = import_chalk12.default.green("\u2713");
3257
- const no = import_chalk12.default.dim("\u2717");
3258
- const parts = [
3259
- `${rootPkg.conventions?.fileNaming ? ok : no} file naming`,
3260
- `${rootPkg.conventions?.componentNaming ? ok : no} components`,
3261
- `${rootPkg.conventions?.hookNaming ? ok : no} hooks`,
3262
- `${rootPkg.conventions?.importAlias ? ok : no} alias`
3263
- ];
3264
- return parts.join(import_chalk12.default.dim(", "));
3265
- }
3266
- function integrationsHint(state) {
3267
- if (!state.visited.integrations || !state.integrations)
3268
- return "not configured \u2014 select to set up";
3269
- const items = [];
3270
- if (state.integrations.preCommitHook) items.push(import_chalk12.default.green("pre-commit"));
3271
- if (state.integrations.typecheckHook) items.push(import_chalk12.default.green("typecheck"));
3272
- if (state.integrations.lintHook) items.push(import_chalk12.default.green("lint"));
3273
- if (state.integrations.claudeCodeHook) items.push(import_chalk12.default.green("Claude"));
3274
- if (state.integrations.claudeMdRef) items.push(import_chalk12.default.green("CLAUDE.md"));
3275
- if (state.integrations.githubAction) items.push(import_chalk12.default.green("CI"));
3276
- return items.length > 0 ? items.join(import_chalk12.default.dim(" \xB7 ")) : "none selected";
3284
+ const count = [
3285
+ rootPkg.conventions?.componentNaming,
3286
+ rootPkg.conventions?.hookNaming,
3287
+ rootPkg.conventions?.importAlias
3288
+ ].filter(Boolean).length;
3289
+ if (count === 3) return "all set";
3290
+ if (count > 0) return `${count} of 3 conventions`;
3291
+ return "none set \u2014 optional AI guidelines";
3292
+ }
3293
+ function aiContextStatus(config) {
3294
+ const rootPkg = getRootPackage(config.packages);
3295
+ const count = [
3296
+ rootPkg.conventions?.componentNaming,
3297
+ rootPkg.conventions?.hookNaming,
3298
+ rootPkg.conventions?.importAlias
3299
+ ].filter(Boolean).length;
3300
+ if (count === 3) return "ok";
3301
+ if (count > 0) return "partial";
3302
+ return "unconfigured";
3277
3303
  }
3278
3304
  function packageOverridesHint(config) {
3279
3305
  const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
@@ -3291,17 +3317,6 @@ function boundariesHint(config, state) {
3291
3317
  const pkgCount = Object.keys(deny).length;
3292
3318
  return `${ruleCount} rules across ${pkgCount} packages`;
3293
3319
  }
3294
- function advancedNamingStatus(config) {
3295
- if (!config.rules.enforceNaming) return "unconfigured";
3296
- const rootPkg = getRootPackage(config.packages);
3297
- const hasFile = !!rootPkg.conventions?.fileNaming;
3298
- const hasComp = !!rootPkg.conventions?.componentNaming;
3299
- const hasHook = !!rootPkg.conventions?.hookNaming;
3300
- const hasAlias = !!rootPkg.conventions?.importAlias;
3301
- if (hasFile && hasComp && hasHook && hasAlias) return "ok";
3302
- if (hasFile || hasComp || hasHook || hasAlias) return "partial";
3303
- return "unconfigured";
3304
- }
3305
3320
  function packageOverridesStatus(config) {
3306
3321
  const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
3307
3322
  const editable = config.packages.filter((p) => p.path !== ".");
@@ -3311,10 +3326,10 @@ function packageOverridesStatus(config) {
3311
3326
  return customized ? "ok" : "unconfigured";
3312
3327
  }
3313
3328
  function statusIcon(status) {
3314
- if (status === "ok") return import_chalk12.default.green("\u2713");
3315
- if (status === "needs-input") return import_chalk12.default.yellow("?");
3316
- if (status === "unconfigured") return import_chalk12.default.dim("-");
3317
- return import_chalk12.default.yellow("~");
3329
+ if (status === "ok") return import_chalk13.default.green("\u2713");
3330
+ if (status === "needs-input") return import_chalk13.default.yellow("?");
3331
+ if (status === "unconfigured") return import_chalk13.default.dim("-");
3332
+ return import_chalk13.default.yellow("~");
3318
3333
  }
3319
3334
  function buildMainMenuOptions(config, scanResult, state) {
3320
3335
  const namingStatus = fileNamingStatus(config);
@@ -3328,7 +3343,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3328
3343
  },
3329
3344
  {
3330
3345
  value: "fileNaming",
3331
- label: `${statusIcon(namingStatus)} Default file naming`,
3346
+ label: `${statusIcon(namingStatus)} File naming`,
3332
3347
  hint: fileNamingHint(config, scanResult)
3333
3348
  },
3334
3349
  {
@@ -3342,9 +3357,9 @@ function buildMainMenuOptions(config, scanResult, state) {
3342
3357
  hint: coverageHint(config, state.hasTestRunner)
3343
3358
  },
3344
3359
  {
3345
- value: "advancedNaming",
3346
- label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
3347
- hint: advancedNamingHint(config)
3360
+ value: "aiContext",
3361
+ label: `${statusIcon(aiContextStatus(config))} AI context`,
3362
+ hint: aiContextHint(config)
3348
3363
  }
3349
3364
  ];
3350
3365
  if (config.packages.length > 1) {
@@ -3361,9 +3376,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3361
3376
  { value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
3362
3377
  );
3363
3378
  }
3364
- const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
3365
3379
  options.push(
3366
- { value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
3367
3380
  { value: "reset", label: " Reset all to defaults" },
3368
3381
  { value: "review", label: " Review scan details", hint: "detected stack & conventions" },
3369
3382
  { value: "done", label: " Done \u2014 write config" }
@@ -3375,7 +3388,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3375
3388
  async function promptMainMenu(config, scanResult, opts) {
3376
3389
  const originalConfig = structuredClone(config);
3377
3390
  const state = {
3378
- visited: { integrations: false, boundaries: false },
3391
+ visited: { boundaries: false },
3379
3392
  deferredInstalls: [],
3380
3393
  hasTestRunner: opts.hasTestRunner,
3381
3394
  hookManager: opts.hookManager
@@ -3402,10 +3415,9 @@ async function promptMainMenu(config, scanResult, opts) {
3402
3415
  if (choice === "fileNaming") await handleFileNaming(config, scanResult);
3403
3416
  if (choice === "missingTests") await handleMissingTests(config);
3404
3417
  if (choice === "coverage") await handleCoverage(config, state, opts);
3405
- if (choice === "advancedNaming") await handleAdvancedNaming(config);
3418
+ if (choice === "aiContext") await handleAiContext(config);
3406
3419
  if (choice === "packageOverrides") await handlePackageOverrides(config);
3407
3420
  if (choice === "boundaries") await handleBoundaries(config, state, opts);
3408
- if (choice === "integrations") await handleIntegrations(state, opts);
3409
3421
  if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
3410
3422
  if (choice === "reset") {
3411
3423
  const confirmed = await clack11.confirm({
@@ -3416,8 +3428,7 @@ async function promptMainMenu(config, scanResult, opts) {
3416
3428
  if (confirmed) {
3417
3429
  Object.assign(config, structuredClone(originalConfig));
3418
3430
  state.deferredInstalls = [];
3419
- state.visited = { integrations: false, boundaries: false };
3420
- state.integrations = void 0;
3431
+ state.visited = { boundaries: false };
3421
3432
  clack11.log.info("Reset all settings to scan-detected defaults.");
3422
3433
  }
3423
3434
  }
@@ -3425,37 +3436,26 @@ async function promptMainMenu(config, scanResult, opts) {
3425
3436
  return state;
3426
3437
  }
3427
3438
 
3428
- // src/utils/update-gitignore.ts
3429
- var fs16 = __toESM(require("fs"), 1);
3430
- var path16 = __toESM(require("path"), 1);
3431
- function updateGitignore(projectRoot) {
3432
- const gitignorePath = path16.join(projectRoot, ".gitignore");
3433
- let content = "";
3434
- if (fs16.existsSync(gitignorePath)) {
3435
- content = fs16.readFileSync(gitignorePath, "utf-8");
3436
- }
3437
- if (!content.includes(".viberails/scan-result.json")) {
3438
- const block = "\n# viberails\n.viberails/scan-result.json\n";
3439
- const prefix = content.length === 0 ? "" : `${content.trimEnd()}
3440
- `;
3441
- fs16.writeFileSync(gitignorePath, `${prefix}${block}`);
3442
- }
3443
- }
3439
+ // src/utils/prompt-prereqs.ts
3440
+ var fs17 = __toESM(require("fs"), 1);
3441
+ var path17 = __toESM(require("path"), 1);
3442
+ var clack12 = __toESM(require("@clack/prompts"), 1);
3443
+ var import_chalk15 = __toESM(require("chalk"), 1);
3444
3444
 
3445
3445
  // src/commands/init-hooks.ts
3446
- var fs18 = __toESM(require("fs"), 1);
3447
- var path18 = __toESM(require("path"), 1);
3448
- var import_chalk13 = __toESM(require("chalk"), 1);
3446
+ var fs16 = __toESM(require("fs"), 1);
3447
+ var path16 = __toESM(require("path"), 1);
3448
+ var import_chalk14 = __toESM(require("chalk"), 1);
3449
3449
  var import_yaml = require("yaml");
3450
3450
 
3451
3451
  // src/commands/resolve-typecheck.ts
3452
- var fs17 = __toESM(require("fs"), 1);
3453
- var path17 = __toESM(require("path"), 1);
3452
+ var fs15 = __toESM(require("fs"), 1);
3453
+ var path15 = __toESM(require("path"), 1);
3454
3454
  function hasTurboTask(projectRoot, taskName) {
3455
- const turboPath = path17.join(projectRoot, "turbo.json");
3456
- if (!fs17.existsSync(turboPath)) return false;
3455
+ const turboPath = path15.join(projectRoot, "turbo.json");
3456
+ if (!fs15.existsSync(turboPath)) return false;
3457
3457
  try {
3458
- const turbo = JSON.parse(fs17.readFileSync(turboPath, "utf-8"));
3458
+ const turbo = JSON.parse(fs15.readFileSync(turboPath, "utf-8"));
3459
3459
  const tasks = turbo.tasks ?? turbo.pipeline ?? {};
3460
3460
  return taskName in tasks;
3461
3461
  } catch {
@@ -3466,10 +3466,10 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3466
3466
  if (hasTurboTask(projectRoot, "typecheck")) {
3467
3467
  return { command: "npx turbo typecheck", label: "turbo typecheck" };
3468
3468
  }
3469
- const pkgJsonPath = path17.join(projectRoot, "package.json");
3470
- if (fs17.existsSync(pkgJsonPath)) {
3469
+ const pkgJsonPath = path15.join(projectRoot, "package.json");
3470
+ if (fs15.existsSync(pkgJsonPath)) {
3471
3471
  try {
3472
- const pkg = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
3472
+ const pkg = JSON.parse(fs15.readFileSync(pkgJsonPath, "utf-8"));
3473
3473
  if (pkg.scripts?.typecheck) {
3474
3474
  const pm = packageManager ?? "npm";
3475
3475
  return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
@@ -3477,7 +3477,7 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3477
3477
  } catch {
3478
3478
  }
3479
3479
  }
3480
- if (fs17.existsSync(path17.join(projectRoot, "tsconfig.json"))) {
3480
+ if (fs15.existsSync(path15.join(projectRoot, "tsconfig.json"))) {
3481
3481
  return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
3482
3482
  }
3483
3483
  return {
@@ -3487,36 +3487,36 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3487
3487
 
3488
3488
  // src/commands/init-hooks.ts
3489
3489
  function setupPreCommitHook(projectRoot) {
3490
- const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3491
- if (fs18.existsSync(lefthookPath)) {
3490
+ const lefthookPath = path16.join(projectRoot, "lefthook.yml");
3491
+ if (fs16.existsSync(lefthookPath)) {
3492
3492
  addLefthookPreCommit(lefthookPath);
3493
- console.log(` ${import_chalk13.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3493
+ console.log(` ${import_chalk14.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3494
3494
  return "lefthook.yml";
3495
3495
  }
3496
- const huskyDir = path18.join(projectRoot, ".husky");
3497
- if (fs18.existsSync(huskyDir)) {
3496
+ const huskyDir = path16.join(projectRoot, ".husky");
3497
+ if (fs16.existsSync(huskyDir)) {
3498
3498
  writeHuskyPreCommit(huskyDir);
3499
- console.log(` ${import_chalk13.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3499
+ console.log(` ${import_chalk14.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3500
3500
  return ".husky/pre-commit";
3501
3501
  }
3502
- const gitDir = path18.join(projectRoot, ".git");
3503
- if (fs18.existsSync(gitDir)) {
3504
- const hooksDir = path18.join(gitDir, "hooks");
3505
- if (!fs18.existsSync(hooksDir)) {
3506
- fs18.mkdirSync(hooksDir, { recursive: true });
3502
+ const gitDir = path16.join(projectRoot, ".git");
3503
+ if (fs16.existsSync(gitDir)) {
3504
+ const hooksDir = path16.join(gitDir, "hooks");
3505
+ if (!fs16.existsSync(hooksDir)) {
3506
+ fs16.mkdirSync(hooksDir, { recursive: true });
3507
3507
  }
3508
3508
  writeGitHookPreCommit(hooksDir);
3509
- console.log(` ${import_chalk13.default.green("\u2713")} .git/hooks/pre-commit`);
3509
+ console.log(` ${import_chalk14.default.green("\u2713")} .git/hooks/pre-commit`);
3510
3510
  return ".git/hooks/pre-commit";
3511
3511
  }
3512
3512
  return void 0;
3513
3513
  }
3514
3514
  function writeGitHookPreCommit(hooksDir) {
3515
- const hookPath = path18.join(hooksDir, "pre-commit");
3516
- if (fs18.existsSync(hookPath)) {
3517
- const existing = fs18.readFileSync(hookPath, "utf-8");
3515
+ const hookPath = path16.join(hooksDir, "pre-commit");
3516
+ if (fs16.existsSync(hookPath)) {
3517
+ const existing = fs16.readFileSync(hookPath, "utf-8");
3518
3518
  if (existing.includes("viberails")) return;
3519
- fs18.writeFileSync(
3519
+ fs16.writeFileSync(
3520
3520
  hookPath,
3521
3521
  `${existing.trimEnd()}
3522
3522
 
@@ -3533,10 +3533,10 @@ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails chec
3533
3533
  "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
3534
3534
  ""
3535
3535
  ].join("\n");
3536
- fs18.writeFileSync(hookPath, script, { mode: 493 });
3536
+ fs16.writeFileSync(hookPath, script, { mode: 493 });
3537
3537
  }
3538
3538
  function addLefthookPreCommit(lefthookPath) {
3539
- const content = fs18.readFileSync(lefthookPath, "utf-8");
3539
+ const content = fs16.readFileSync(lefthookPath, "utf-8");
3540
3540
  if (content.includes("viberails")) return;
3541
3541
  const doc = (0, import_yaml.parse)(content) ?? {};
3542
3542
  if (!doc["pre-commit"]) {
@@ -3548,28 +3548,28 @@ function addLefthookPreCommit(lefthookPath) {
3548
3548
  doc["pre-commit"].commands.viberails = {
3549
3549
  run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
3550
3550
  };
3551
- fs18.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
3551
+ fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
3552
3552
  }
3553
3553
  function detectHookManager(projectRoot) {
3554
- if (fs18.existsSync(path18.join(projectRoot, "lefthook.yml"))) return "Lefthook";
3555
- if (fs18.existsSync(path18.join(projectRoot, ".husky"))) return "Husky";
3554
+ if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
3555
+ if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
3556
3556
  return void 0;
3557
3557
  }
3558
3558
  function setupClaudeCodeHook(projectRoot) {
3559
- const claudeDir = path18.join(projectRoot, ".claude");
3560
- if (!fs18.existsSync(claudeDir)) {
3561
- fs18.mkdirSync(claudeDir, { recursive: true });
3559
+ const claudeDir = path16.join(projectRoot, ".claude");
3560
+ if (!fs16.existsSync(claudeDir)) {
3561
+ fs16.mkdirSync(claudeDir, { recursive: true });
3562
3562
  }
3563
- const settingsPath = path18.join(claudeDir, "settings.json");
3563
+ const settingsPath = path16.join(claudeDir, "settings.json");
3564
3564
  let settings = {};
3565
- if (fs18.existsSync(settingsPath)) {
3565
+ if (fs16.existsSync(settingsPath)) {
3566
3566
  try {
3567
- settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
3567
+ settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
3568
3568
  } catch {
3569
3569
  console.warn(
3570
- ` ${import_chalk13.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3570
+ ` ${import_chalk14.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3571
3571
  );
3572
- console.warn(` Fix the JSON manually, then re-run ${import_chalk13.default.cyan("viberails init --force")}`);
3572
+ console.warn(` Fix the JSON manually, then re-run ${import_chalk14.default.cyan("viberails init --force")}`);
3573
3573
  return;
3574
3574
  }
3575
3575
  }
@@ -3590,30 +3590,30 @@ function setupClaudeCodeHook(projectRoot) {
3590
3590
  }
3591
3591
  ];
3592
3592
  settings.hooks = hooks;
3593
- fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3593
+ fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3594
3594
  `);
3595
- console.log(` ${import_chalk13.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3595
+ console.log(` ${import_chalk14.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3596
3596
  }
3597
3597
  function setupClaudeMdReference(projectRoot) {
3598
- const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
3598
+ const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
3599
3599
  let content = "";
3600
- if (fs18.existsSync(claudeMdPath)) {
3601
- content = fs18.readFileSync(claudeMdPath, "utf-8");
3600
+ if (fs16.existsSync(claudeMdPath)) {
3601
+ content = fs16.readFileSync(claudeMdPath, "utf-8");
3602
3602
  }
3603
3603
  if (content.includes("@.viberails/context.md")) return;
3604
3604
  const ref = "\n@.viberails/context.md\n";
3605
3605
  const prefix = content.length === 0 ? "" : content.trimEnd();
3606
- fs18.writeFileSync(claudeMdPath, prefix + ref);
3607
- console.log(` ${import_chalk13.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3606
+ fs16.writeFileSync(claudeMdPath, prefix + ref);
3607
+ console.log(` ${import_chalk14.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3608
3608
  }
3609
3609
  function setupGithubAction(projectRoot, packageManager, options) {
3610
- const workflowDir = path18.join(projectRoot, ".github", "workflows");
3611
- const workflowPath = path18.join(workflowDir, "viberails.yml");
3612
- if (fs18.existsSync(workflowPath)) {
3613
- const existing = fs18.readFileSync(workflowPath, "utf-8");
3610
+ const workflowDir = path16.join(projectRoot, ".github", "workflows");
3611
+ const workflowPath = path16.join(workflowDir, "viberails.yml");
3612
+ if (fs16.existsSync(workflowPath)) {
3613
+ const existing = fs16.readFileSync(workflowPath, "utf-8");
3614
3614
  if (existing.includes("viberails")) return void 0;
3615
3615
  }
3616
- fs18.mkdirSync(workflowDir, { recursive: true });
3616
+ fs16.mkdirSync(workflowDir, { recursive: true });
3617
3617
  const pm = packageManager || "npm";
3618
3618
  const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
3619
3619
  const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
@@ -3667,30 +3667,212 @@ function setupGithubAction(projectRoot, packageManager, options) {
3667
3667
  ""
3668
3668
  );
3669
3669
  const content = lines.filter((l) => l !== void 0).join("\n");
3670
- fs18.writeFileSync(workflowPath, content);
3670
+ fs16.writeFileSync(workflowPath, content);
3671
3671
  return ".github/workflows/viberails.yml";
3672
3672
  }
3673
3673
  function writeHuskyPreCommit(huskyDir) {
3674
- const hookPath = path18.join(huskyDir, "pre-commit");
3674
+ const hookPath = path16.join(huskyDir, "pre-commit");
3675
3675
  const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
3676
- if (fs18.existsSync(hookPath)) {
3677
- const existing = fs18.readFileSync(hookPath, "utf-8");
3676
+ if (fs16.existsSync(hookPath)) {
3677
+ const existing = fs16.readFileSync(hookPath, "utf-8");
3678
3678
  if (!existing.includes("viberails")) {
3679
- fs18.writeFileSync(hookPath, `${existing.trimEnd()}
3679
+ fs16.writeFileSync(hookPath, `${existing.trimEnd()}
3680
3680
  ${cmd}
3681
3681
  `);
3682
3682
  }
3683
3683
  return;
3684
3684
  }
3685
- fs18.writeFileSync(hookPath, `#!/bin/sh
3685
+ fs16.writeFileSync(hookPath, `#!/bin/sh
3686
3686
  ${cmd}
3687
3687
  `, { mode: 493 });
3688
3688
  }
3689
3689
 
3690
+ // src/utils/prompt-prereqs.ts
3691
+ function buildVitestInstallCommand(pm, isWorkspace) {
3692
+ if (pm === "yarn") return "yarn add -D vitest";
3693
+ if (pm === "npm") return "npm install -D vitest";
3694
+ return isWorkspace ? "pnpm add -D -w vitest" : "pnpm add -D vitest";
3695
+ }
3696
+ function statusIcon2(status) {
3697
+ if (status === "ok") return import_chalk15.default.green("\u2713");
3698
+ if (status === "missing") return import_chalk15.default.yellow("!");
3699
+ if (status === "skipped") return import_chalk15.default.dim("\u2717");
3700
+ return import_chalk15.default.dim("-");
3701
+ }
3702
+ function buildReadinessNote(state) {
3703
+ const lines = [];
3704
+ const tr = state.testRunner;
3705
+ lines.push(
3706
+ `${statusIcon2(tr.status)} Test runner ${tr.label ?? (tr.status === "skipped" ? "skipped" : "not detected")}`
3707
+ );
3708
+ const hm = state.hookManager;
3709
+ lines.push(
3710
+ `${statusIcon2(hm.status)} Hook manager ${hm.label ?? (hm.status === "skipped" ? "skipped" : "not detected")}`
3711
+ );
3712
+ const li = state.linter;
3713
+ lines.push(`${statusIcon2(li.status)} Linter ${li.label ?? "none"}`);
3714
+ const tc = state.typecheck;
3715
+ if (tc.status === "ok") {
3716
+ lines.push(`${statusIcon2("ok")} Typecheck ${tc.label}`);
3717
+ } else if (tc.status === "skipped") {
3718
+ lines.push(`${statusIcon2("skipped")} Typecheck skipped`);
3719
+ } else {
3720
+ lines.push(
3721
+ `${statusIcon2("missing")} Typecheck needs root tsconfig.json, typecheck script, or turbo task`
3722
+ );
3723
+ }
3724
+ return lines.join("\n");
3725
+ }
3726
+ function hasMissing(state) {
3727
+ return state.testRunner.status === "missing" || state.hookManager.status === "missing" || state.typecheck.status === "missing";
3728
+ }
3729
+ async function promptPrereqs(projectRoot, scanResult, hookManager, packageManager, isWorkspace) {
3730
+ let hasTestRunner = !!scanResult.stack.testRunner;
3731
+ let currentHookManager = hookManager;
3732
+ let skipCoverage = false;
3733
+ let skipHooks = false;
3734
+ const linterName = scanResult.stack.linter?.name;
3735
+ const linterLabel = linterName === "biome" ? "Biome" : linterName === "eslint" ? "ESLint" : linterName;
3736
+ const typecheckResolved = resolveTypecheckCommand(projectRoot, packageManager);
3737
+ const state = {
3738
+ testRunner: hasTestRunner ? { status: "ok", label: scanResult.stack.testRunner?.name } : { status: "missing" },
3739
+ hookManager: currentHookManager ? { status: "ok", label: currentHookManager } : { status: "missing" },
3740
+ linter: linterName ? { status: "ok", label: linterLabel } : { status: "none" },
3741
+ typecheck: typecheckResolved.label ? { status: "ok", label: typecheckResolved.label } : { status: "missing", reason: typecheckResolved.reason }
3742
+ };
3743
+ if (!hasMissing(state)) {
3744
+ return {
3745
+ hasTestRunner,
3746
+ hookManager: currentHookManager,
3747
+ skipCoverage,
3748
+ skipHooks,
3749
+ typecheckLabel: typecheckResolved.label
3750
+ };
3751
+ }
3752
+ if (state.testRunner.status === "missing") {
3753
+ clack12.note(buildReadinessNote(state), "Project readiness");
3754
+ const cmd = buildVitestInstallCommand(packageManager, isWorkspace);
3755
+ const choice = await clack12.select({
3756
+ message: "No test runner detected. Coverage checks require one.",
3757
+ options: [
3758
+ { value: "install", label: "Install vitest", hint: cmd },
3759
+ { value: "skip", label: "Skip \u2014 disable coverage checks" },
3760
+ { value: "exit", label: "Exit" }
3761
+ ]
3762
+ });
3763
+ assertNotCancelled(choice);
3764
+ if (choice === "install") {
3765
+ const s = clack12.spinner();
3766
+ s.start("Installing vitest...");
3767
+ const result = await spawnAsync(cmd, projectRoot);
3768
+ if (result.status === 0) {
3769
+ s.stop("Installed vitest");
3770
+ hasTestRunner = true;
3771
+ state.testRunner = { status: "ok", label: "vitest" };
3772
+ } else {
3773
+ s.stop("Failed to install vitest");
3774
+ clack12.log.warn(`Install manually: ${cmd}`);
3775
+ skipCoverage = true;
3776
+ state.testRunner = { status: "skipped" };
3777
+ }
3778
+ } else if (choice === "skip") {
3779
+ skipCoverage = true;
3780
+ state.testRunner = { status: "skipped" };
3781
+ } else {
3782
+ clack12.outro("Aborted.");
3783
+ process.exit(0);
3784
+ }
3785
+ }
3786
+ if (state.hookManager.status === "missing") {
3787
+ clack12.note(buildReadinessNote(state), "Project readiness");
3788
+ const cmd = buildLefthookInstallCommand(packageManager, isWorkspace);
3789
+ const choice = await clack12.select({
3790
+ message: "No git hook manager detected. Pre-commit integration requires one.",
3791
+ options: [
3792
+ { value: "install", label: "Install lefthook", hint: cmd },
3793
+ { value: "skip", label: "Skip \u2014 no pre-commit integration" },
3794
+ { value: "exit", label: "Exit" }
3795
+ ]
3796
+ });
3797
+ assertNotCancelled(choice);
3798
+ if (choice === "install") {
3799
+ const s = clack12.spinner();
3800
+ s.start("Installing lefthook...");
3801
+ const result = await spawnAsync(cmd, projectRoot);
3802
+ if (result.status === 0) {
3803
+ s.stop("Installed lefthook");
3804
+ const ymlPath = path17.join(projectRoot, "lefthook.yml");
3805
+ if (!fs17.existsSync(ymlPath)) {
3806
+ fs17.writeFileSync(ymlPath, "# Managed by viberails\npre-commit:\n commands: {}\n");
3807
+ }
3808
+ currentHookManager = detectHookManager(projectRoot);
3809
+ state.hookManager = { status: "ok", label: currentHookManager ?? "lefthook" };
3810
+ } else {
3811
+ s.stop("Failed to install lefthook");
3812
+ clack12.log.warn(`Install manually: ${cmd}`);
3813
+ skipHooks = true;
3814
+ state.hookManager = { status: "skipped" };
3815
+ }
3816
+ } else if (choice === "skip") {
3817
+ skipHooks = true;
3818
+ state.hookManager = { status: "skipped" };
3819
+ } else {
3820
+ clack12.outro("Aborted.");
3821
+ process.exit(0);
3822
+ }
3823
+ }
3824
+ if (state.typecheck.status === "missing") {
3825
+ clack12.note(buildReadinessNote(state), "Project readiness");
3826
+ const choice = await clack12.select({
3827
+ message: "No typecheck command found. Without this, pre-commit and CI typecheck hooks will be unavailable.",
3828
+ options: [
3829
+ {
3830
+ value: "continue",
3831
+ label: "Continue without typecheck",
3832
+ hint: "add a root tsconfig.json or typecheck script later, then re-run viberails"
3833
+ },
3834
+ { value: "exit", label: "Exit \u2014 fix this first" }
3835
+ ]
3836
+ });
3837
+ assertNotCancelled(choice);
3838
+ if (choice === "exit") {
3839
+ clack12.outro(
3840
+ "Add a root tsconfig.json, a typecheck script, or a turbo typecheck task, then re-run viberails."
3841
+ );
3842
+ process.exit(0);
3843
+ }
3844
+ state.typecheck = { status: "skipped" };
3845
+ }
3846
+ return {
3847
+ hasTestRunner,
3848
+ hookManager: currentHookManager,
3849
+ skipCoverage,
3850
+ skipHooks,
3851
+ typecheckLabel: typecheckResolved.label
3852
+ };
3853
+ }
3854
+
3855
+ // src/utils/update-gitignore.ts
3856
+ var fs18 = __toESM(require("fs"), 1);
3857
+ var path18 = __toESM(require("path"), 1);
3858
+ function updateGitignore(projectRoot) {
3859
+ const gitignorePath = path18.join(projectRoot, ".gitignore");
3860
+ let content = "";
3861
+ if (fs18.existsSync(gitignorePath)) {
3862
+ content = fs18.readFileSync(gitignorePath, "utf-8");
3863
+ }
3864
+ if (!content.includes(".viberails/scan-result.json")) {
3865
+ const block = "\n# viberails\n.viberails/scan-result.json\n";
3866
+ const prefix = content.length === 0 ? "" : `${content.trimEnd()}
3867
+ `;
3868
+ fs18.writeFileSync(gitignorePath, `${prefix}${block}`);
3869
+ }
3870
+ }
3871
+
3690
3872
  // src/commands/init-hooks-extra.ts
3691
3873
  var fs19 = __toESM(require("fs"), 1);
3692
3874
  var path19 = __toESM(require("path"), 1);
3693
- var import_chalk14 = __toESM(require("chalk"), 1);
3875
+ var import_chalk16 = __toESM(require("chalk"), 1);
3694
3876
  var import_yaml2 = require("yaml");
3695
3877
  function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
3696
3878
  const lefthookPath = path19.join(projectRoot, "lefthook.yml");
@@ -3750,12 +3932,12 @@ ${command}
3750
3932
  function setupTypecheckHook(projectRoot, packageManager) {
3751
3933
  const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3752
3934
  if (!resolved.command) {
3753
- console.log(` ${import_chalk14.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3935
+ console.log(` ${import_chalk16.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3754
3936
  return void 0;
3755
3937
  }
3756
3938
  const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
3757
3939
  if (target) {
3758
- console.log(` ${import_chalk14.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3940
+ console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3759
3941
  }
3760
3942
  return target;
3761
3943
  }
@@ -3777,7 +3959,7 @@ function setupLintHook(projectRoot, linter) {
3777
3959
  }
3778
3960
  const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
3779
3961
  if (target) {
3780
- console.log(` ${import_chalk14.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3962
+ console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3781
3963
  }
3782
3964
  return target;
3783
3965
  }
@@ -3786,7 +3968,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3786
3968
  if (integrations.preCommitHook) {
3787
3969
  const t = setupPreCommitHook(projectRoot);
3788
3970
  if (t && opts.lefthookExpected && !t.includes("lefthook")) {
3789
- console.log(` ${import_chalk14.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3971
+ console.log(` ${import_chalk16.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3790
3972
  }
3791
3973
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
3792
3974
  }
@@ -3819,10 +4001,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3819
4001
  // src/commands/init-non-interactive.ts
3820
4002
  var fs20 = __toESM(require("fs"), 1);
3821
4003
  var path20 = __toESM(require("path"), 1);
3822
- var clack12 = __toESM(require("@clack/prompts"), 1);
4004
+ var clack13 = __toESM(require("@clack/prompts"), 1);
3823
4005
  var import_config8 = require("@viberails/config");
3824
4006
  var import_scanner2 = require("@viberails/scanner");
3825
- var import_chalk15 = __toESM(require("chalk"), 1);
4007
+ var import_chalk17 = __toESM(require("chalk"), 1);
3826
4008
 
3827
4009
  // src/utils/filter-confidence.ts
3828
4010
  function filterHighConfidence(conventions, meta) {
@@ -3843,7 +4025,7 @@ function getExemptedPackages(config) {
3843
4025
  return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3844
4026
  }
3845
4027
  async function initNonInteractive(projectRoot, configPath) {
3846
- const s = clack12.spinner();
4028
+ const s = clack13.spinner();
3847
4029
  s.start("Scanning project...");
3848
4030
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
3849
4031
  const config = (0, import_config8.generateConfig)(scanResult);
@@ -3858,11 +4040,11 @@ async function initNonInteractive(projectRoot, configPath) {
3858
4040
  const exempted = getExemptedPackages(config);
3859
4041
  if (exempted.length > 0) {
3860
4042
  console.log(
3861
- ` ${import_chalk15.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk15.default.dim("(types-only)")}`
4043
+ ` ${import_chalk17.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk17.default.dim("(types-only)")}`
3862
4044
  );
3863
4045
  }
3864
4046
  if (config.packages.length > 1) {
3865
- const bs = clack12.spinner();
4047
+ const bs = clack13.spinner();
3866
4048
  bs.start("Building import graph...");
3867
4049
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3868
4050
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3895,14 +4077,14 @@ async function initNonInteractive(projectRoot, configPath) {
3895
4077
  const hookManager = detectHookManager(projectRoot);
3896
4078
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3897
4079
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3898
- const ok = import_chalk15.default.green("\u2713");
4080
+ const ok = import_chalk17.default.green("\u2713");
3899
4081
  const created = [
3900
4082
  `${ok} ${path20.basename(configPath)}`,
3901
4083
  `${ok} .viberails/context.md`,
3902
4084
  `${ok} .viberails/scan-result.json`,
3903
4085
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3904
4086
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3905
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk15.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
4087
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk17.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3906
4088
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3907
4089
  ].filter(Boolean);
3908
4090
  if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
@@ -3927,8 +4109,8 @@ async function initCommand(options, cwd) {
3927
4109
  return initInteractive(projectRoot, configPath, options);
3928
4110
  }
3929
4111
  console.log(
3930
- `${import_chalk16.default.yellow("!")} viberails is already initialized.
3931
- Run ${import_chalk16.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk16.default.cyan("viberails sync")} to update generated files, or ${import_chalk16.default.cyan("viberails init --force")} to replace it.`
4112
+ `${import_chalk18.default.yellow("!")} viberails is already initialized.
4113
+ Run ${import_chalk18.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk18.default.cyan("viberails sync")} to update generated files, or ${import_chalk18.default.cyan("viberails init --force")} to replace it.`
3932
4114
  );
3933
4115
  return;
3934
4116
  }
@@ -3936,11 +4118,11 @@ async function initCommand(options, cwd) {
3936
4118
  await initInteractive(projectRoot, configPath, options);
3937
4119
  }
3938
4120
  async function initInteractive(projectRoot, configPath, options) {
3939
- clack13.intro("viberails");
4121
+ clack14.intro("viberails");
3940
4122
  if (fs21.existsSync(configPath) && !options.force) {
3941
4123
  const action = await promptExistingConfigAction(path21.basename(configPath));
3942
4124
  if (action === "cancel") {
3943
- clack13.outro("Aborted. No files were written.");
4125
+ clack14.outro("Aborted. No files were written.");
3944
4126
  return;
3945
4127
  }
3946
4128
  if (action === "edit") {
@@ -3954,45 +4136,60 @@ async function initInteractive(projectRoot, configPath, options) {
3954
4136
  `${path21.basename(configPath)} already exists and will be replaced. Continue?`
3955
4137
  );
3956
4138
  if (!replace) {
3957
- clack13.outro("Aborted. No files were written.");
4139
+ clack14.outro("Aborted. No files were written.");
3958
4140
  return;
3959
4141
  }
3960
4142
  }
3961
- const s = clack13.spinner();
4143
+ const s = clack14.spinner();
3962
4144
  s.start("Scanning project...");
3963
4145
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3964
4146
  const config = (0, import_config9.generateConfig)(scanResult);
3965
4147
  s.stop("Scan complete");
3966
4148
  if (scanResult.statistics.totalFiles === 0) {
3967
- clack13.log.warn(
4149
+ clack14.log.warn(
3968
4150
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3969
4151
  );
3970
4152
  }
3971
- const hasTestRunner = !!scanResult.stack.testRunner;
3972
- const hookManager = detectHookManager(projectRoot);
3973
- const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3974
4153
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
4154
+ const packageManager = rootPkgStack?.packageManager?.split("@")[0] ?? "npm";
4155
+ const isWorkspace = config.packages.length > 1;
4156
+ const prereqs = await promptPrereqs(
4157
+ projectRoot,
4158
+ scanResult,
4159
+ detectHookManager(projectRoot),
4160
+ packageManager,
4161
+ isWorkspace
4162
+ );
4163
+ if (prereqs.skipCoverage) config.rules.testCoverage = 0;
4164
+ const coveragePrereqs = prereqs.hasTestRunner ? checkCoveragePrereqs(projectRoot, scanResult) : [];
3975
4165
  const state = await promptMainMenu(config, scanResult, {
3976
- hasTestRunner,
3977
- hookManager,
4166
+ hasTestRunner: prereqs.hasTestRunner,
4167
+ hookManager: prereqs.hookManager,
3978
4168
  coveragePrereqs,
3979
4169
  projectRoot,
3980
4170
  tools: {
3981
4171
  isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3982
4172
  linter: rootPkgStack?.linter?.split("@")[0],
3983
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
3984
- isWorkspace: config.packages.length > 1
4173
+ packageManager,
4174
+ isWorkspace
3985
4175
  }
3986
4176
  });
3987
4177
  const shouldWrite = await confirm3("Apply this setup?");
3988
4178
  if (!shouldWrite) {
3989
- clack13.outro("Aborted. No files were written.");
4179
+ clack14.outro("Aborted. No files were written.");
3990
4180
  return;
3991
4181
  }
4182
+ const integrations = await promptIntegrationsDeferred(prereqs.hookManager, {
4183
+ isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
4184
+ typecheckLabel: prereqs.typecheckLabel,
4185
+ linter: rootPkgStack?.linter?.split("@")[0],
4186
+ packageManager,
4187
+ isWorkspace
4188
+ });
3992
4189
  if (state.deferredInstalls.length > 0) {
3993
4190
  await executeDeferredInstalls(projectRoot, state.deferredInstalls);
3994
4191
  }
3995
- const ws = clack13.spinner();
4192
+ const ws = clack14.spinner();
3996
4193
  ws.start("Writing configuration...");
3997
4194
  const compacted = (0, import_config9.compactConfig)(config);
3998
4195
  fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
@@ -4000,31 +4197,28 @@ async function initInteractive(projectRoot, configPath, options) {
4000
4197
  writeGeneratedFiles(projectRoot, config, scanResult);
4001
4198
  updateGitignore(projectRoot);
4002
4199
  ws.stop("Configuration written");
4003
- const ok = import_chalk16.default.green("\u2713");
4004
- clack13.log.step(`${ok} ${path21.basename(configPath)}`);
4005
- clack13.log.step(`${ok} .viberails/context.md`);
4006
- clack13.log.step(`${ok} .viberails/scan-result.json`);
4007
- if (state.visited.integrations && state.integrations) {
4008
- const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
4009
- setupSelectedIntegrations(projectRoot, state.integrations, {
4010
- linter: rootPkgStack?.linter?.split("@")[0],
4011
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
4012
- lefthookExpected
4013
- });
4014
- }
4015
- clack13.outro(
4200
+ const ok = import_chalk18.default.green("\u2713");
4201
+ clack14.log.step(`${ok} ${path21.basename(configPath)}`);
4202
+ clack14.log.step(`${ok} .viberails/context.md`);
4203
+ clack14.log.step(`${ok} .viberails/scan-result.json`);
4204
+ setupSelectedIntegrations(projectRoot, integrations.choice, {
4205
+ linter: rootPkgStack?.linter?.split("@")[0],
4206
+ packageManager,
4207
+ lefthookExpected: prereqs.hookManager === "lefthook"
4208
+ });
4209
+ clack14.outro(
4016
4210
  `Done! Next: review viberails.config.json, then run viberails check
4017
- ${import_chalk16.default.dim("Tip: use")} ${import_chalk16.default.cyan("viberails check --enforce")} ${import_chalk16.default.dim("in CI to block PRs on violations.")}`
4211
+ ${import_chalk18.default.dim("Tip: use")} ${import_chalk18.default.cyan("viberails check --enforce")} ${import_chalk18.default.dim("in CI to block PRs on violations.")}`
4018
4212
  );
4019
4213
  }
4020
4214
 
4021
4215
  // src/commands/sync.ts
4022
4216
  var fs22 = __toESM(require("fs"), 1);
4023
4217
  var path22 = __toESM(require("path"), 1);
4024
- var clack14 = __toESM(require("@clack/prompts"), 1);
4218
+ var clack15 = __toESM(require("@clack/prompts"), 1);
4025
4219
  var import_config11 = require("@viberails/config");
4026
4220
  var import_scanner4 = require("@viberails/scanner");
4027
- var import_chalk17 = __toESM(require("chalk"), 1);
4221
+ var import_chalk19 = __toESM(require("chalk"), 1);
4028
4222
  var CONFIG_FILE6 = "viberails.config.json";
4029
4223
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
4030
4224
  function loadPreviousStats(projectRoot) {
@@ -4050,7 +4244,7 @@ async function syncCommand(options, cwd) {
4050
4244
  const configPath = path22.join(projectRoot, CONFIG_FILE6);
4051
4245
  const existing = await (0, import_config11.loadConfig)(configPath);
4052
4246
  const previousStats = loadPreviousStats(projectRoot);
4053
- const s = clack14.spinner();
4247
+ const s = clack15.spinner();
4054
4248
  s.start("Scanning project...");
4055
4249
  const scanResult = await (0, import_scanner4.scan)(projectRoot);
4056
4250
  s.stop("Scan complete");
@@ -4065,19 +4259,19 @@ async function syncCommand(options, cwd) {
4065
4259
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
4066
4260
  if (changes.length > 0 || statsDelta) {
4067
4261
  console.log(`
4068
- ${import_chalk17.default.bold("Changes:")}`);
4262
+ ${import_chalk19.default.bold("Changes:")}`);
4069
4263
  for (const change of changes) {
4070
- const icon = change.type === "removed" ? import_chalk17.default.red("-") : import_chalk17.default.green("+");
4264
+ const icon = change.type === "removed" ? import_chalk19.default.red("-") : import_chalk19.default.green("+");
4071
4265
  console.log(` ${icon} ${change.description}`);
4072
4266
  }
4073
4267
  if (statsDelta) {
4074
- console.log(` ${import_chalk17.default.dim(statsDelta)}`);
4268
+ console.log(` ${import_chalk19.default.dim(statsDelta)}`);
4075
4269
  }
4076
4270
  }
4077
4271
  if (options?.interactive) {
4078
- clack14.intro("viberails sync (interactive)");
4079
- clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
4080
- const decision = await clack14.select({
4272
+ clack15.intro("viberails sync (interactive)");
4273
+ clack15.note(formatRulesText(merged).join("\n"), "Rules after sync");
4274
+ const decision = await clack15.select({
4081
4275
  message: "How would you like to proceed?",
4082
4276
  options: [
4083
4277
  { value: "accept", label: "Accept changes" },
@@ -4087,7 +4281,7 @@ ${import_chalk17.default.bold("Changes:")}`);
4087
4281
  });
4088
4282
  assertNotCancelled(decision);
4089
4283
  if (decision === "cancel") {
4090
- clack14.outro("Sync cancelled. No files were written.");
4284
+ clack15.outro("Sync cancelled. No files were written.");
4091
4285
  return;
4092
4286
  }
4093
4287
  if (decision === "customize") {
@@ -4111,8 +4305,8 @@ ${import_chalk17.default.bold("Changes:")}`);
4111
4305
  fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
4112
4306
  `);
4113
4307
  writeGeneratedFiles(projectRoot, merged, scanResult);
4114
- clack14.log.success("Updated config with your customizations.");
4115
- clack14.outro("Done! Run viberails check to verify.");
4308
+ clack15.log.success("Updated config with your customizations.");
4309
+ clack15.outro("Done! Run viberails check to verify.");
4116
4310
  return;
4117
4311
  }
4118
4312
  }
@@ -4120,18 +4314,18 @@ ${import_chalk17.default.bold("Changes:")}`);
4120
4314
  `);
4121
4315
  writeGeneratedFiles(projectRoot, merged, scanResult);
4122
4316
  console.log(`
4123
- ${import_chalk17.default.bold("Synced:")}`);
4317
+ ${import_chalk19.default.bold("Synced:")}`);
4124
4318
  if (configChanged) {
4125
- console.log(` ${import_chalk17.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4319
+ console.log(` ${import_chalk19.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4126
4320
  } else {
4127
- console.log(` ${import_chalk17.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4321
+ console.log(` ${import_chalk19.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4128
4322
  }
4129
- console.log(` ${import_chalk17.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4130
- console.log(` ${import_chalk17.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4323
+ console.log(` ${import_chalk19.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4324
+ console.log(` ${import_chalk19.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4131
4325
  }
4132
4326
 
4133
4327
  // src/index.ts
4134
- var VERSION = "0.6.10";
4328
+ var VERSION = "0.6.11";
4135
4329
  var program = new import_commander.Command();
4136
4330
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
4137
4331
  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) => {
@@ -4139,7 +4333,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
4139
4333
  await initCommand(options);
4140
4334
  } catch (err) {
4141
4335
  const message = err instanceof Error ? err.message : String(err);
4142
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4336
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4143
4337
  process.exit(1);
4144
4338
  }
4145
4339
  });
@@ -4148,7 +4342,7 @@ program.command("sync").description("Re-scan and update generated files").option
4148
4342
  await syncCommand(options);
4149
4343
  } catch (err) {
4150
4344
  const message = err instanceof Error ? err.message : String(err);
4151
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4345
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4152
4346
  process.exit(1);
4153
4347
  }
4154
4348
  });
@@ -4157,7 +4351,7 @@ program.command("config").description("Interactively edit existing config rules"
4157
4351
  await configCommand(options);
4158
4352
  } catch (err) {
4159
4353
  const message = err instanceof Error ? err.message : String(err);
4160
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4354
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4161
4355
  process.exit(1);
4162
4356
  }
4163
4357
  });
@@ -4178,7 +4372,7 @@ program.command("check").description("Check files against enforced rules").optio
4178
4372
  process.exit(exitCode);
4179
4373
  } catch (err) {
4180
4374
  const message = err instanceof Error ? err.message : String(err);
4181
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4375
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4182
4376
  process.exit(1);
4183
4377
  }
4184
4378
  }
@@ -4189,7 +4383,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
4189
4383
  process.exit(exitCode);
4190
4384
  } catch (err) {
4191
4385
  const message = err instanceof Error ? err.message : String(err);
4192
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4386
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4193
4387
  process.exit(1);
4194
4388
  }
4195
4389
  });
@@ -4198,7 +4392,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
4198
4392
  await boundariesCommand(options);
4199
4393
  } catch (err) {
4200
4394
  const message = err instanceof Error ? err.message : String(err);
4201
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4395
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4202
4396
  process.exit(1);
4203
4397
  }
4204
4398
  });