viberails 0.6.9 → 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import chalk18 from "chalk";
4
+ import chalk20 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/boundaries.ts
@@ -129,12 +129,12 @@ async function promptNamingMenu(state) {
129
129
  },
130
130
  {
131
131
  value: "componentNaming",
132
- label: `${state.componentNaming ? ok : unset} Component naming`,
132
+ label: `${state.componentNaming ? ok : unset} Component exports`,
133
133
  hint: state.componentNaming ?? HINT_NOT_SET
134
134
  },
135
135
  {
136
136
  value: "hookNaming",
137
- label: `${state.hookNaming ? ok : unset} Hook naming`,
137
+ label: `${state.hookNaming ? ok : unset} Hook exports`,
138
138
  hint: state.hookNaming ?? HINT_NOT_SET
139
139
  },
140
140
  {
@@ -171,7 +171,7 @@ async function promptNamingMenu(state) {
171
171
  }
172
172
  if (choice === "componentNaming") {
173
173
  const selected = await clack.select({
174
- message: "Component naming convention",
174
+ message: "Component export naming (e.g. UserProfile)",
175
175
  options: [
176
176
  ...COMPONENT_NAMING_OPTIONS,
177
177
  { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
@@ -183,7 +183,7 @@ async function promptNamingMenu(state) {
183
183
  }
184
184
  if (choice === "hookNaming") {
185
185
  const selected = await clack.select({
186
- message: "Hook naming convention",
186
+ message: "Hook export naming (e.g. useAuth)",
187
187
  options: [
188
188
  ...HOOK_NAMING_OPTIONS,
189
189
  { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
@@ -1419,9 +1419,9 @@ async function checkCommand(options, cwd) {
1419
1419
  }
1420
1420
  const violations = [];
1421
1421
  const severity = options.enforce ? "error" : "warn";
1422
- const log9 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(chalk5.dim(msg)) : () => {
1422
+ const log10 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(chalk5.dim(msg)) : () => {
1423
1423
  };
1424
- log9(" Checking files...");
1424
+ log10(" Checking files...");
1425
1425
  for (const file of filesToCheck) {
1426
1426
  const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
1427
1427
  const relPath = path7.relative(projectRoot, absPath);
@@ -1454,9 +1454,9 @@ async function checkCommand(options, cwd) {
1454
1454
  }
1455
1455
  }
1456
1456
  }
1457
- log9(" done\n");
1457
+ log10(" done\n");
1458
1458
  if (!options.files) {
1459
- log9(" Checking missing tests...");
1459
+ log10(" Checking missing tests...");
1460
1460
  const testViolations = checkMissingTests(projectRoot, config, severity);
1461
1461
  if (options.staged) {
1462
1462
  const stagedSet = new Set(filesToCheck);
@@ -1469,14 +1469,14 @@ async function checkCommand(options, cwd) {
1469
1469
  } else {
1470
1470
  violations.push(...testViolations);
1471
1471
  }
1472
- log9(" done\n");
1472
+ log10(" done\n");
1473
1473
  }
1474
1474
  if (!options.files && !options.staged && !options.diffBase) {
1475
- log9(" Running test coverage...\n");
1475
+ log10(" Running test coverage...\n");
1476
1476
  const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
1477
1477
  staged: options.staged,
1478
1478
  enforce: options.enforce,
1479
- onProgress: (pkg) => log9(` Coverage: ${pkg}...
1479
+ onProgress: (pkg) => log10(` Coverage: ${pkg}...
1480
1480
  `)
1481
1481
  });
1482
1482
  violations.push(...coverageViolations);
@@ -1501,7 +1501,7 @@ async function checkCommand(options, cwd) {
1501
1501
  severity
1502
1502
  });
1503
1503
  }
1504
- log9(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1504
+ log10(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1505
1505
  `);
1506
1506
  }
1507
1507
  if (options.format === "json") {
@@ -2795,10 +2795,10 @@ ${chalk10.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2795
2795
  // src/commands/init.ts
2796
2796
  import * as fs21 from "fs";
2797
2797
  import * as path21 from "path";
2798
- import * as clack13 from "@clack/prompts";
2798
+ import * as clack14 from "@clack/prompts";
2799
2799
  import { compactConfig as compactConfig4, generateConfig as generateConfig2 } from "@viberails/config";
2800
2800
  import { scan as scan3 } from "@viberails/scanner";
2801
- import chalk16 from "chalk";
2801
+ import chalk18 from "chalk";
2802
2802
 
2803
2803
  // src/utils/check-prerequisites.ts
2804
2804
  import * as fs14 from "fs";
@@ -2905,15 +2905,7 @@ async function executeDeferredInstalls(projectRoot, installs) {
2905
2905
  return successCount;
2906
2906
  }
2907
2907
 
2908
- // src/utils/prompt-main-menu.ts
2909
- import * as clack11 from "@clack/prompts";
2910
-
2911
- // src/utils/prompt-main-menu-handlers.ts
2912
- import * as clack10 from "@clack/prompts";
2913
-
2914
2908
  // src/utils/prompt-integrations.ts
2915
- import * as fs15 from "fs";
2916
- import * as path15 from "path";
2917
2909
  import * as clack9 from "@clack/prompts";
2918
2910
  function buildLefthookInstallCommand(pm, isWorkspace) {
2919
2911
  if (pm === "yarn") return "yarn add -D lefthook";
@@ -2921,26 +2913,24 @@ function buildLefthookInstallCommand(pm, isWorkspace) {
2921
2913
  if (pm === "npm") return "npm install -D lefthook";
2922
2914
  return `${pm} add -D lefthook`;
2923
2915
  }
2924
- async function promptIntegrationsDeferred(hookManager, tools, packageManager, isWorkspace, projectRoot) {
2916
+ async function promptIntegrationsDeferred(hookManager, tools) {
2917
+ const hasHookManager = !!hookManager;
2925
2918
  const options = [];
2926
- const needsLefthook = !hookManager;
2927
- if (needsLefthook) {
2928
- const pm = packageManager ?? "npm";
2929
- options.push({
2930
- value: "installLefthook",
2931
- label: "Install Lefthook",
2932
- hint: `after final confirmation \u2014 ${buildLefthookInstallCommand(pm, isWorkspace)}`
2933
- });
2934
- }
2935
- const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
2936
- const hookHint = needsLefthook ? "uses Lefthook if installed above, otherwise local git hook" : "runs viberails checks when you commit";
2919
+ const hookLabel = hasHookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
2920
+ const hookHint = hasHookManager ? "runs viberails checks when you commit" : "local hook only \u2014 use lefthook or husky to commit hooks to repo";
2937
2921
  options.push({ value: "preCommit", label: hookLabel, hint: hookHint });
2938
- if (tools?.isTypeScript) {
2922
+ if (tools?.typecheckLabel) {
2939
2923
  options.push({
2940
2924
  value: "typecheck",
2941
- label: "Typecheck (tsc --noEmit)",
2925
+ label: `Typecheck (${tools.typecheckLabel})`,
2942
2926
  hint: "pre-commit hook + CI check"
2943
2927
  });
2928
+ } else if (tools?.isTypeScript) {
2929
+ options.push({
2930
+ value: "typecheck",
2931
+ label: "Typecheck",
2932
+ hint: "needs root tsconfig.json, typecheck script, or turbo task"
2933
+ });
2944
2934
  }
2945
2935
  if (tools?.linter) {
2946
2936
  const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
@@ -2967,7 +2957,12 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2967
2957
  hint: "blocks PRs that fail viberails check"
2968
2958
  }
2969
2959
  );
2970
- const initialValues = options.map((o) => o.value);
2960
+ const hasTypecheck = !!tools?.typecheckLabel;
2961
+ const initialValues = options.filter((o) => {
2962
+ if (o.value === "preCommit" && !hasHookManager) return false;
2963
+ if (o.value === "typecheck" && !hasTypecheck) return false;
2964
+ return true;
2965
+ }).map((o) => o.value);
2971
2966
  const result = await clack9.multiselect({
2972
2967
  message: "Integrations",
2973
2968
  options,
@@ -2975,20 +2970,6 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2975
2970
  required: false
2976
2971
  });
2977
2972
  assertNotCancelled(result);
2978
- let lefthookInstall;
2979
- if (needsLefthook && result.includes("installLefthook")) {
2980
- const pm = packageManager ?? "npm";
2981
- lefthookInstall = {
2982
- label: "Lefthook",
2983
- command: buildLefthookInstallCommand(pm, isWorkspace),
2984
- onSuccess: projectRoot ? () => {
2985
- const ymlPath = path15.join(projectRoot, "lefthook.yml");
2986
- if (!fs15.existsSync(ymlPath)) {
2987
- fs15.writeFileSync(ymlPath, "# Generated by viberails\n");
2988
- }
2989
- } : void 0
2990
- };
2991
- }
2992
2973
  return {
2993
2974
  choice: {
2994
2975
  preCommitHook: result.includes("preCommit"),
@@ -2997,39 +2978,16 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
2997
2978
  githubAction: result.includes("githubAction"),
2998
2979
  typecheckHook: result.includes("typecheck"),
2999
2980
  lintHook: result.includes("lint")
3000
- },
3001
- lefthookInstall
2981
+ }
3002
2982
  };
3003
2983
  }
3004
2984
 
2985
+ // src/utils/prompt-main-menu.ts
2986
+ import * as clack11 from "@clack/prompts";
2987
+
3005
2988
  // src/utils/prompt-main-menu-handlers.ts
3006
- async function handleAdvancedNaming(config) {
3007
- const rootPkg = getRootPackage(config.packages);
3008
- const state = {
3009
- maxFileLines: config.rules.maxFileLines,
3010
- maxTestFileLines: config.rules.maxTestFileLines,
3011
- testCoverage: config.rules.testCoverage,
3012
- enforceMissingTests: config.rules.enforceMissingTests,
3013
- enforceNaming: config.rules.enforceNaming,
3014
- fileNamingValue: rootPkg.conventions?.fileNaming,
3015
- componentNaming: rootPkg.conventions?.componentNaming,
3016
- hookNaming: rootPkg.conventions?.hookNaming,
3017
- importAlias: rootPkg.conventions?.importAlias,
3018
- coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
3019
- coverageCommand: config.defaults?.coverage?.command
3020
- };
3021
- await promptNamingMenu(state);
3022
- rootPkg.conventions = rootPkg.conventions ?? {};
3023
- config.rules.enforceNaming = state.enforceNaming;
3024
- if (state.fileNamingValue) {
3025
- rootPkg.conventions.fileNaming = state.fileNamingValue;
3026
- } else {
3027
- delete rootPkg.conventions.fileNaming;
3028
- }
3029
- rootPkg.conventions.componentNaming = state.componentNaming || void 0;
3030
- rootPkg.conventions.hookNaming = state.hookNaming || void 0;
3031
- rootPkg.conventions.importAlias = state.importAlias || void 0;
3032
- }
2989
+ import * as clack10 from "@clack/prompts";
2990
+ import chalk12 from "chalk";
3033
2991
  async function handleFileNaming(config, scanResult) {
3034
2992
  const isMonorepo = config.packages.length > 1;
3035
2993
  if (isMonorepo) {
@@ -3169,24 +3127,95 @@ async function handleBoundaries(config, state, opts) {
3169
3127
  clack10.log.warn(`Boundary inference failed: ${err instanceof Error ? err.message : err}`);
3170
3128
  }
3171
3129
  }
3172
- async function handleIntegrations(state, opts) {
3173
- const result = await promptIntegrationsDeferred(
3174
- state.hookManager,
3175
- opts.tools,
3176
- opts.tools.packageManager,
3177
- opts.tools.isWorkspace,
3178
- opts.projectRoot
3179
- );
3180
- state.visited.integrations = true;
3181
- state.integrations = result.choice;
3182
- state.deferredInstalls = state.deferredInstalls.filter((d) => !d.command.includes("lefthook"));
3183
- if (result.lefthookInstall) {
3184
- state.deferredInstalls.push(result.lefthookInstall);
3130
+ async function handleAiContext(config) {
3131
+ const rootPkg = getRootPackage(config.packages);
3132
+ rootPkg.conventions = rootPkg.conventions ?? {};
3133
+ while (true) {
3134
+ const ok = chalk12.green("\u2713");
3135
+ const unset = chalk12.dim("-");
3136
+ const options = [
3137
+ {
3138
+ value: "componentNaming",
3139
+ label: `${rootPkg.conventions.componentNaming ? ok : unset} Component exports`,
3140
+ hint: rootPkg.conventions.componentNaming ?? "not set"
3141
+ },
3142
+ {
3143
+ value: "hookNaming",
3144
+ label: `${rootPkg.conventions.hookNaming ? ok : unset} Hook exports`,
3145
+ hint: rootPkg.conventions.hookNaming ?? "not set"
3146
+ },
3147
+ {
3148
+ value: "importAlias",
3149
+ label: `${rootPkg.conventions.importAlias ? ok : unset} Import alias`,
3150
+ hint: rootPkg.conventions.importAlias ?? "not set"
3151
+ },
3152
+ { value: "back", label: " Back" }
3153
+ ];
3154
+ const choice = await clack10.select({
3155
+ message: "AI context \u2014 conventions written to context.md for AI tools",
3156
+ options
3157
+ });
3158
+ if (isCancelled(choice) || choice === "back") return;
3159
+ if (choice === "componentNaming") {
3160
+ const selected = await clack10.select({
3161
+ message: "Component export naming (e.g. UserProfile)",
3162
+ options: [
3163
+ ...COMPONENT_NAMING_OPTIONS,
3164
+ { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
3165
+ ],
3166
+ initialValue: rootPkg.conventions.componentNaming ?? SENTINEL_CLEAR
3167
+ });
3168
+ if (isCancelled(selected)) continue;
3169
+ rootPkg.conventions.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
3170
+ }
3171
+ if (choice === "hookNaming") {
3172
+ const selected = await clack10.select({
3173
+ message: "Hook export naming (e.g. useAuth)",
3174
+ options: [
3175
+ ...HOOK_NAMING_OPTIONS,
3176
+ { value: SENTINEL_CLEAR, label: "Clear (no convention)" }
3177
+ ],
3178
+ initialValue: rootPkg.conventions.hookNaming ?? SENTINEL_CLEAR
3179
+ });
3180
+ if (isCancelled(selected)) continue;
3181
+ rootPkg.conventions.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
3182
+ }
3183
+ if (choice === "importAlias") {
3184
+ const selected = await clack10.select({
3185
+ message: "Import alias pattern",
3186
+ options: [
3187
+ { value: "@/*", label: "@/*", hint: "import { x } from '@/utils'" },
3188
+ { value: "~/*", label: "~/*", hint: "import { x } from '~/utils'" },
3189
+ { value: SENTINEL_CUSTOM, label: "Custom..." },
3190
+ { value: SENTINEL_CLEAR, label: "Clear (no alias)" }
3191
+ ],
3192
+ initialValue: rootPkg.conventions.importAlias ?? SENTINEL_CLEAR
3193
+ });
3194
+ if (isCancelled(selected)) continue;
3195
+ if (selected === SENTINEL_CLEAR) {
3196
+ rootPkg.conventions.importAlias = void 0;
3197
+ } else if (selected === SENTINEL_CUSTOM) {
3198
+ const result = await clack10.text({
3199
+ message: "Custom import alias (e.g. #/*)?",
3200
+ initialValue: rootPkg.conventions.importAlias ?? "",
3201
+ placeholder: "e.g. #/*",
3202
+ validate: (v) => {
3203
+ if (typeof v !== "string" || !v.trim()) return "Alias cannot be empty";
3204
+ if (!/^[a-zA-Z@~#$][a-zA-Z0-9@~#$_-]*\/\*$/.test(v.trim()))
3205
+ return "Must match pattern like @/*, ~/*, or #src/*";
3206
+ }
3207
+ });
3208
+ if (isCancelled(result)) continue;
3209
+ rootPkg.conventions.importAlias = result.trim();
3210
+ } else {
3211
+ rootPkg.conventions.importAlias = selected;
3212
+ }
3213
+ }
3185
3214
  }
3186
3215
  }
3187
3216
 
3188
3217
  // src/utils/prompt-main-menu-hints.ts
3189
- import chalk12 from "chalk";
3218
+ import chalk13 from "chalk";
3190
3219
  function fileLimitsHint(config) {
3191
3220
  const max = config.rules.maxFileLines;
3192
3221
  const test = config.rules.maxTestFileLines;
@@ -3202,7 +3231,7 @@ function fileNamingHint(config, scanResult) {
3202
3231
  );
3203
3232
  return detected ? `${naming} (detected)` : naming;
3204
3233
  }
3205
- return "mixed \u2014 will not enforce if skipped";
3234
+ return "not set \u2014 select to configure";
3206
3235
  }
3207
3236
  function fileNamingStatus(config) {
3208
3237
  if (!config.rules.enforceNaming) return "unconfigured";
@@ -3229,30 +3258,27 @@ function coverageHint(config, hasTestRunner) {
3229
3258
  }
3230
3259
  return `${config.rules.testCoverage}%`;
3231
3260
  }
3232
- function advancedNamingHint(config) {
3261
+ function aiContextHint(config) {
3233
3262
  const rootPkg = getRootPackage(config.packages);
3234
- if (!config.rules.enforceNaming) return "not enforced";
3235
- const ok = chalk12.green("\u2713");
3236
- const no = chalk12.dim("\u2717");
3237
- const parts = [
3238
- `${rootPkg.conventions?.fileNaming ? ok : no} file naming`,
3239
- `${rootPkg.conventions?.componentNaming ? ok : no} components`,
3240
- `${rootPkg.conventions?.hookNaming ? ok : no} hooks`,
3241
- `${rootPkg.conventions?.importAlias ? ok : no} alias`
3242
- ];
3243
- return parts.join(chalk12.dim(", "));
3244
- }
3245
- function integrationsHint(state) {
3246
- if (!state.visited.integrations || !state.integrations)
3247
- return "not configured \u2014 select to set up";
3248
- const items = [];
3249
- if (state.integrations.preCommitHook) items.push(chalk12.green("pre-commit"));
3250
- if (state.integrations.typecheckHook) items.push(chalk12.green("typecheck"));
3251
- if (state.integrations.lintHook) items.push(chalk12.green("lint"));
3252
- if (state.integrations.claudeCodeHook) items.push(chalk12.green("Claude"));
3253
- if (state.integrations.claudeMdRef) items.push(chalk12.green("CLAUDE.md"));
3254
- if (state.integrations.githubAction) items.push(chalk12.green("CI"));
3255
- return items.length > 0 ? items.join(chalk12.dim(" \xB7 ")) : "none selected";
3263
+ const count = [
3264
+ rootPkg.conventions?.componentNaming,
3265
+ rootPkg.conventions?.hookNaming,
3266
+ rootPkg.conventions?.importAlias
3267
+ ].filter(Boolean).length;
3268
+ if (count === 3) return "all set";
3269
+ if (count > 0) return `${count} of 3 conventions`;
3270
+ return "none set \u2014 optional AI guidelines";
3271
+ }
3272
+ function aiContextStatus(config) {
3273
+ const rootPkg = getRootPackage(config.packages);
3274
+ const count = [
3275
+ rootPkg.conventions?.componentNaming,
3276
+ rootPkg.conventions?.hookNaming,
3277
+ rootPkg.conventions?.importAlias
3278
+ ].filter(Boolean).length;
3279
+ if (count === 3) return "ok";
3280
+ if (count > 0) return "partial";
3281
+ return "unconfigured";
3256
3282
  }
3257
3283
  function packageOverridesHint(config) {
3258
3284
  const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
@@ -3270,17 +3296,6 @@ function boundariesHint(config, state) {
3270
3296
  const pkgCount = Object.keys(deny).length;
3271
3297
  return `${ruleCount} rules across ${pkgCount} packages`;
3272
3298
  }
3273
- function advancedNamingStatus(config) {
3274
- if (!config.rules.enforceNaming) return "unconfigured";
3275
- const rootPkg = getRootPackage(config.packages);
3276
- const hasFile = !!rootPkg.conventions?.fileNaming;
3277
- const hasComp = !!rootPkg.conventions?.componentNaming;
3278
- const hasHook = !!rootPkg.conventions?.hookNaming;
3279
- const hasAlias = !!rootPkg.conventions?.importAlias;
3280
- if (hasFile && hasComp && hasHook && hasAlias) return "ok";
3281
- if (hasFile || hasComp || hasHook || hasAlias) return "partial";
3282
- return "unconfigured";
3283
- }
3284
3299
  function packageOverridesStatus(config) {
3285
3300
  const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
3286
3301
  const editable = config.packages.filter((p) => p.path !== ".");
@@ -3290,10 +3305,10 @@ function packageOverridesStatus(config) {
3290
3305
  return customized ? "ok" : "unconfigured";
3291
3306
  }
3292
3307
  function statusIcon(status) {
3293
- if (status === "ok") return chalk12.green("\u2713");
3294
- if (status === "needs-input") return chalk12.yellow("?");
3295
- if (status === "unconfigured") return chalk12.dim("-");
3296
- return chalk12.yellow("~");
3308
+ if (status === "ok") return chalk13.green("\u2713");
3309
+ if (status === "needs-input") return chalk13.yellow("?");
3310
+ if (status === "unconfigured") return chalk13.dim("-");
3311
+ return chalk13.yellow("~");
3297
3312
  }
3298
3313
  function buildMainMenuOptions(config, scanResult, state) {
3299
3314
  const namingStatus = fileNamingStatus(config);
@@ -3307,7 +3322,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3307
3322
  },
3308
3323
  {
3309
3324
  value: "fileNaming",
3310
- label: `${statusIcon(namingStatus)} Default file naming`,
3325
+ label: `${statusIcon(namingStatus)} File naming`,
3311
3326
  hint: fileNamingHint(config, scanResult)
3312
3327
  },
3313
3328
  {
@@ -3321,9 +3336,9 @@ function buildMainMenuOptions(config, scanResult, state) {
3321
3336
  hint: coverageHint(config, state.hasTestRunner)
3322
3337
  },
3323
3338
  {
3324
- value: "advancedNaming",
3325
- label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
3326
- hint: advancedNamingHint(config)
3339
+ value: "aiContext",
3340
+ label: `${statusIcon(aiContextStatus(config))} AI context`,
3341
+ hint: aiContextHint(config)
3327
3342
  }
3328
3343
  ];
3329
3344
  if (config.packages.length > 1) {
@@ -3340,9 +3355,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3340
3355
  { value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
3341
3356
  );
3342
3357
  }
3343
- const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
3344
3358
  options.push(
3345
- { value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
3346
3359
  { value: "reset", label: " Reset all to defaults" },
3347
3360
  { value: "review", label: " Review scan details", hint: "detected stack & conventions" },
3348
3361
  { value: "done", label: " Done \u2014 write config" }
@@ -3354,7 +3367,7 @@ function buildMainMenuOptions(config, scanResult, state) {
3354
3367
  async function promptMainMenu(config, scanResult, opts) {
3355
3368
  const originalConfig = structuredClone(config);
3356
3369
  const state = {
3357
- visited: { integrations: false, boundaries: false },
3370
+ visited: { boundaries: false },
3358
3371
  deferredInstalls: [],
3359
3372
  hasTestRunner: opts.hasTestRunner,
3360
3373
  hookManager: opts.hookManager
@@ -3381,10 +3394,9 @@ async function promptMainMenu(config, scanResult, opts) {
3381
3394
  if (choice === "fileNaming") await handleFileNaming(config, scanResult);
3382
3395
  if (choice === "missingTests") await handleMissingTests(config);
3383
3396
  if (choice === "coverage") await handleCoverage(config, state, opts);
3384
- if (choice === "advancedNaming") await handleAdvancedNaming(config);
3397
+ if (choice === "aiContext") await handleAiContext(config);
3385
3398
  if (choice === "packageOverrides") await handlePackageOverrides(config);
3386
3399
  if (choice === "boundaries") await handleBoundaries(config, state, opts);
3387
- if (choice === "integrations") await handleIntegrations(state, opts);
3388
3400
  if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
3389
3401
  if (choice === "reset") {
3390
3402
  const confirmed = await clack11.confirm({
@@ -3395,8 +3407,7 @@ async function promptMainMenu(config, scanResult, opts) {
3395
3407
  if (confirmed) {
3396
3408
  Object.assign(config, structuredClone(originalConfig));
3397
3409
  state.deferredInstalls = [];
3398
- state.visited = { integrations: false, boundaries: false };
3399
- state.integrations = void 0;
3410
+ state.visited = { boundaries: false };
3400
3411
  clack11.log.info("Reset all settings to scan-detected defaults.");
3401
3412
  }
3402
3413
  }
@@ -3404,37 +3415,26 @@ async function promptMainMenu(config, scanResult, opts) {
3404
3415
  return state;
3405
3416
  }
3406
3417
 
3407
- // src/utils/update-gitignore.ts
3408
- import * as fs16 from "fs";
3409
- import * as path16 from "path";
3410
- function updateGitignore(projectRoot) {
3411
- const gitignorePath = path16.join(projectRoot, ".gitignore");
3412
- let content = "";
3413
- if (fs16.existsSync(gitignorePath)) {
3414
- content = fs16.readFileSync(gitignorePath, "utf-8");
3415
- }
3416
- if (!content.includes(".viberails/scan-result.json")) {
3417
- const block = "\n# viberails\n.viberails/scan-result.json\n";
3418
- const prefix = content.length === 0 ? "" : `${content.trimEnd()}
3419
- `;
3420
- fs16.writeFileSync(gitignorePath, `${prefix}${block}`);
3421
- }
3422
- }
3418
+ // src/utils/prompt-prereqs.ts
3419
+ import * as fs17 from "fs";
3420
+ import * as path17 from "path";
3421
+ import * as clack12 from "@clack/prompts";
3422
+ import chalk15 from "chalk";
3423
3423
 
3424
3424
  // src/commands/init-hooks.ts
3425
- import * as fs18 from "fs";
3426
- import * as path18 from "path";
3427
- import chalk13 from "chalk";
3425
+ import * as fs16 from "fs";
3426
+ import * as path16 from "path";
3427
+ import chalk14 from "chalk";
3428
3428
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3429
3429
 
3430
3430
  // src/commands/resolve-typecheck.ts
3431
- import * as fs17 from "fs";
3432
- import * as path17 from "path";
3431
+ import * as fs15 from "fs";
3432
+ import * as path15 from "path";
3433
3433
  function hasTurboTask(projectRoot, taskName) {
3434
- const turboPath = path17.join(projectRoot, "turbo.json");
3435
- if (!fs17.existsSync(turboPath)) return false;
3434
+ const turboPath = path15.join(projectRoot, "turbo.json");
3435
+ if (!fs15.existsSync(turboPath)) return false;
3436
3436
  try {
3437
- const turbo = JSON.parse(fs17.readFileSync(turboPath, "utf-8"));
3437
+ const turbo = JSON.parse(fs15.readFileSync(turboPath, "utf-8"));
3438
3438
  const tasks = turbo.tasks ?? turbo.pipeline ?? {};
3439
3439
  return taskName in tasks;
3440
3440
  } catch {
@@ -3445,10 +3445,10 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3445
3445
  if (hasTurboTask(projectRoot, "typecheck")) {
3446
3446
  return { command: "npx turbo typecheck", label: "turbo typecheck" };
3447
3447
  }
3448
- const pkgJsonPath = path17.join(projectRoot, "package.json");
3449
- if (fs17.existsSync(pkgJsonPath)) {
3448
+ const pkgJsonPath = path15.join(projectRoot, "package.json");
3449
+ if (fs15.existsSync(pkgJsonPath)) {
3450
3450
  try {
3451
- const pkg = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
3451
+ const pkg = JSON.parse(fs15.readFileSync(pkgJsonPath, "utf-8"));
3452
3452
  if (pkg.scripts?.typecheck) {
3453
3453
  const pm = packageManager ?? "npm";
3454
3454
  return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
@@ -3456,7 +3456,7 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3456
3456
  } catch {
3457
3457
  }
3458
3458
  }
3459
- if (fs17.existsSync(path17.join(projectRoot, "tsconfig.json"))) {
3459
+ if (fs15.existsSync(path15.join(projectRoot, "tsconfig.json"))) {
3460
3460
  return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
3461
3461
  }
3462
3462
  return {
@@ -3466,36 +3466,36 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
3466
3466
 
3467
3467
  // src/commands/init-hooks.ts
3468
3468
  function setupPreCommitHook(projectRoot) {
3469
- const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3470
- if (fs18.existsSync(lefthookPath)) {
3469
+ const lefthookPath = path16.join(projectRoot, "lefthook.yml");
3470
+ if (fs16.existsSync(lefthookPath)) {
3471
3471
  addLefthookPreCommit(lefthookPath);
3472
- console.log(` ${chalk13.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3472
+ console.log(` ${chalk14.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3473
3473
  return "lefthook.yml";
3474
3474
  }
3475
- const huskyDir = path18.join(projectRoot, ".husky");
3476
- if (fs18.existsSync(huskyDir)) {
3475
+ const huskyDir = path16.join(projectRoot, ".husky");
3476
+ if (fs16.existsSync(huskyDir)) {
3477
3477
  writeHuskyPreCommit(huskyDir);
3478
- console.log(` ${chalk13.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3478
+ console.log(` ${chalk14.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3479
3479
  return ".husky/pre-commit";
3480
3480
  }
3481
- const gitDir = path18.join(projectRoot, ".git");
3482
- if (fs18.existsSync(gitDir)) {
3483
- const hooksDir = path18.join(gitDir, "hooks");
3484
- if (!fs18.existsSync(hooksDir)) {
3485
- fs18.mkdirSync(hooksDir, { recursive: true });
3481
+ const gitDir = path16.join(projectRoot, ".git");
3482
+ if (fs16.existsSync(gitDir)) {
3483
+ const hooksDir = path16.join(gitDir, "hooks");
3484
+ if (!fs16.existsSync(hooksDir)) {
3485
+ fs16.mkdirSync(hooksDir, { recursive: true });
3486
3486
  }
3487
3487
  writeGitHookPreCommit(hooksDir);
3488
- console.log(` ${chalk13.green("\u2713")} .git/hooks/pre-commit`);
3488
+ console.log(` ${chalk14.green("\u2713")} .git/hooks/pre-commit`);
3489
3489
  return ".git/hooks/pre-commit";
3490
3490
  }
3491
3491
  return void 0;
3492
3492
  }
3493
3493
  function writeGitHookPreCommit(hooksDir) {
3494
- const hookPath = path18.join(hooksDir, "pre-commit");
3495
- if (fs18.existsSync(hookPath)) {
3496
- const existing = fs18.readFileSync(hookPath, "utf-8");
3494
+ const hookPath = path16.join(hooksDir, "pre-commit");
3495
+ if (fs16.existsSync(hookPath)) {
3496
+ const existing = fs16.readFileSync(hookPath, "utf-8");
3497
3497
  if (existing.includes("viberails")) return;
3498
- fs18.writeFileSync(
3498
+ fs16.writeFileSync(
3499
3499
  hookPath,
3500
3500
  `${existing.trimEnd()}
3501
3501
 
@@ -3512,10 +3512,10 @@ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails chec
3512
3512
  "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
3513
3513
  ""
3514
3514
  ].join("\n");
3515
- fs18.writeFileSync(hookPath, script, { mode: 493 });
3515
+ fs16.writeFileSync(hookPath, script, { mode: 493 });
3516
3516
  }
3517
3517
  function addLefthookPreCommit(lefthookPath) {
3518
- const content = fs18.readFileSync(lefthookPath, "utf-8");
3518
+ const content = fs16.readFileSync(lefthookPath, "utf-8");
3519
3519
  if (content.includes("viberails")) return;
3520
3520
  const doc = parseYaml(content) ?? {};
3521
3521
  if (!doc["pre-commit"]) {
@@ -3527,28 +3527,28 @@ function addLefthookPreCommit(lefthookPath) {
3527
3527
  doc["pre-commit"].commands.viberails = {
3528
3528
  run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
3529
3529
  };
3530
- fs18.writeFileSync(lefthookPath, stringifyYaml(doc));
3530
+ fs16.writeFileSync(lefthookPath, stringifyYaml(doc));
3531
3531
  }
3532
3532
  function detectHookManager(projectRoot) {
3533
- if (fs18.existsSync(path18.join(projectRoot, "lefthook.yml"))) return "Lefthook";
3534
- if (fs18.existsSync(path18.join(projectRoot, ".husky"))) return "Husky";
3533
+ if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
3534
+ if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
3535
3535
  return void 0;
3536
3536
  }
3537
3537
  function setupClaudeCodeHook(projectRoot) {
3538
- const claudeDir = path18.join(projectRoot, ".claude");
3539
- if (!fs18.existsSync(claudeDir)) {
3540
- fs18.mkdirSync(claudeDir, { recursive: true });
3538
+ const claudeDir = path16.join(projectRoot, ".claude");
3539
+ if (!fs16.existsSync(claudeDir)) {
3540
+ fs16.mkdirSync(claudeDir, { recursive: true });
3541
3541
  }
3542
- const settingsPath = path18.join(claudeDir, "settings.json");
3542
+ const settingsPath = path16.join(claudeDir, "settings.json");
3543
3543
  let settings = {};
3544
- if (fs18.existsSync(settingsPath)) {
3544
+ if (fs16.existsSync(settingsPath)) {
3545
3545
  try {
3546
- settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
3546
+ settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
3547
3547
  } catch {
3548
3548
  console.warn(
3549
- ` ${chalk13.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3549
+ ` ${chalk14.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3550
3550
  );
3551
- console.warn(` Fix the JSON manually, then re-run ${chalk13.cyan("viberails init --force")}`);
3551
+ console.warn(` Fix the JSON manually, then re-run ${chalk14.cyan("viberails init --force")}`);
3552
3552
  return;
3553
3553
  }
3554
3554
  }
@@ -3569,30 +3569,30 @@ function setupClaudeCodeHook(projectRoot) {
3569
3569
  }
3570
3570
  ];
3571
3571
  settings.hooks = hooks;
3572
- fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3572
+ fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3573
3573
  `);
3574
- console.log(` ${chalk13.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3574
+ console.log(` ${chalk14.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3575
3575
  }
3576
3576
  function setupClaudeMdReference(projectRoot) {
3577
- const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
3577
+ const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
3578
3578
  let content = "";
3579
- if (fs18.existsSync(claudeMdPath)) {
3580
- content = fs18.readFileSync(claudeMdPath, "utf-8");
3579
+ if (fs16.existsSync(claudeMdPath)) {
3580
+ content = fs16.readFileSync(claudeMdPath, "utf-8");
3581
3581
  }
3582
3582
  if (content.includes("@.viberails/context.md")) return;
3583
3583
  const ref = "\n@.viberails/context.md\n";
3584
3584
  const prefix = content.length === 0 ? "" : content.trimEnd();
3585
- fs18.writeFileSync(claudeMdPath, prefix + ref);
3586
- console.log(` ${chalk13.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3585
+ fs16.writeFileSync(claudeMdPath, prefix + ref);
3586
+ console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3587
3587
  }
3588
3588
  function setupGithubAction(projectRoot, packageManager, options) {
3589
- const workflowDir = path18.join(projectRoot, ".github", "workflows");
3590
- const workflowPath = path18.join(workflowDir, "viberails.yml");
3591
- if (fs18.existsSync(workflowPath)) {
3592
- const existing = fs18.readFileSync(workflowPath, "utf-8");
3589
+ const workflowDir = path16.join(projectRoot, ".github", "workflows");
3590
+ const workflowPath = path16.join(workflowDir, "viberails.yml");
3591
+ if (fs16.existsSync(workflowPath)) {
3592
+ const existing = fs16.readFileSync(workflowPath, "utf-8");
3593
3593
  if (existing.includes("viberails")) return void 0;
3594
3594
  }
3595
- fs18.mkdirSync(workflowDir, { recursive: true });
3595
+ fs16.mkdirSync(workflowDir, { recursive: true });
3596
3596
  const pm = packageManager || "npm";
3597
3597
  const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
3598
3598
  const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
@@ -3646,30 +3646,212 @@ function setupGithubAction(projectRoot, packageManager, options) {
3646
3646
  ""
3647
3647
  );
3648
3648
  const content = lines.filter((l) => l !== void 0).join("\n");
3649
- fs18.writeFileSync(workflowPath, content);
3649
+ fs16.writeFileSync(workflowPath, content);
3650
3650
  return ".github/workflows/viberails.yml";
3651
3651
  }
3652
3652
  function writeHuskyPreCommit(huskyDir) {
3653
- const hookPath = path18.join(huskyDir, "pre-commit");
3653
+ const hookPath = path16.join(huskyDir, "pre-commit");
3654
3654
  const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
3655
- if (fs18.existsSync(hookPath)) {
3656
- const existing = fs18.readFileSync(hookPath, "utf-8");
3655
+ if (fs16.existsSync(hookPath)) {
3656
+ const existing = fs16.readFileSync(hookPath, "utf-8");
3657
3657
  if (!existing.includes("viberails")) {
3658
- fs18.writeFileSync(hookPath, `${existing.trimEnd()}
3658
+ fs16.writeFileSync(hookPath, `${existing.trimEnd()}
3659
3659
  ${cmd}
3660
3660
  `);
3661
3661
  }
3662
3662
  return;
3663
3663
  }
3664
- fs18.writeFileSync(hookPath, `#!/bin/sh
3664
+ fs16.writeFileSync(hookPath, `#!/bin/sh
3665
3665
  ${cmd}
3666
3666
  `, { mode: 493 });
3667
3667
  }
3668
3668
 
3669
+ // src/utils/prompt-prereqs.ts
3670
+ function buildVitestInstallCommand(pm, isWorkspace) {
3671
+ if (pm === "yarn") return "yarn add -D vitest";
3672
+ if (pm === "npm") return "npm install -D vitest";
3673
+ return isWorkspace ? "pnpm add -D -w vitest" : "pnpm add -D vitest";
3674
+ }
3675
+ function statusIcon2(status) {
3676
+ if (status === "ok") return chalk15.green("\u2713");
3677
+ if (status === "missing") return chalk15.yellow("!");
3678
+ if (status === "skipped") return chalk15.dim("\u2717");
3679
+ return chalk15.dim("-");
3680
+ }
3681
+ function buildReadinessNote(state) {
3682
+ const lines = [];
3683
+ const tr = state.testRunner;
3684
+ lines.push(
3685
+ `${statusIcon2(tr.status)} Test runner ${tr.label ?? (tr.status === "skipped" ? "skipped" : "not detected")}`
3686
+ );
3687
+ const hm = state.hookManager;
3688
+ lines.push(
3689
+ `${statusIcon2(hm.status)} Hook manager ${hm.label ?? (hm.status === "skipped" ? "skipped" : "not detected")}`
3690
+ );
3691
+ const li = state.linter;
3692
+ lines.push(`${statusIcon2(li.status)} Linter ${li.label ?? "none"}`);
3693
+ const tc = state.typecheck;
3694
+ if (tc.status === "ok") {
3695
+ lines.push(`${statusIcon2("ok")} Typecheck ${tc.label}`);
3696
+ } else if (tc.status === "skipped") {
3697
+ lines.push(`${statusIcon2("skipped")} Typecheck skipped`);
3698
+ } else {
3699
+ lines.push(
3700
+ `${statusIcon2("missing")} Typecheck needs root tsconfig.json, typecheck script, or turbo task`
3701
+ );
3702
+ }
3703
+ return lines.join("\n");
3704
+ }
3705
+ function hasMissing(state) {
3706
+ return state.testRunner.status === "missing" || state.hookManager.status === "missing" || state.typecheck.status === "missing";
3707
+ }
3708
+ async function promptPrereqs(projectRoot, scanResult, hookManager, packageManager, isWorkspace) {
3709
+ let hasTestRunner = !!scanResult.stack.testRunner;
3710
+ let currentHookManager = hookManager;
3711
+ let skipCoverage = false;
3712
+ let skipHooks = false;
3713
+ const linterName = scanResult.stack.linter?.name;
3714
+ const linterLabel = linterName === "biome" ? "Biome" : linterName === "eslint" ? "ESLint" : linterName;
3715
+ const typecheckResolved = resolveTypecheckCommand(projectRoot, packageManager);
3716
+ const state = {
3717
+ testRunner: hasTestRunner ? { status: "ok", label: scanResult.stack.testRunner?.name } : { status: "missing" },
3718
+ hookManager: currentHookManager ? { status: "ok", label: currentHookManager } : { status: "missing" },
3719
+ linter: linterName ? { status: "ok", label: linterLabel } : { status: "none" },
3720
+ typecheck: typecheckResolved.label ? { status: "ok", label: typecheckResolved.label } : { status: "missing", reason: typecheckResolved.reason }
3721
+ };
3722
+ if (!hasMissing(state)) {
3723
+ return {
3724
+ hasTestRunner,
3725
+ hookManager: currentHookManager,
3726
+ skipCoverage,
3727
+ skipHooks,
3728
+ typecheckLabel: typecheckResolved.label
3729
+ };
3730
+ }
3731
+ if (state.testRunner.status === "missing") {
3732
+ clack12.note(buildReadinessNote(state), "Project readiness");
3733
+ const cmd = buildVitestInstallCommand(packageManager, isWorkspace);
3734
+ const choice = await clack12.select({
3735
+ message: "No test runner detected. Coverage checks require one.",
3736
+ options: [
3737
+ { value: "install", label: "Install vitest", hint: cmd },
3738
+ { value: "skip", label: "Skip \u2014 disable coverage checks" },
3739
+ { value: "exit", label: "Exit" }
3740
+ ]
3741
+ });
3742
+ assertNotCancelled(choice);
3743
+ if (choice === "install") {
3744
+ const s = clack12.spinner();
3745
+ s.start("Installing vitest...");
3746
+ const result = await spawnAsync(cmd, projectRoot);
3747
+ if (result.status === 0) {
3748
+ s.stop("Installed vitest");
3749
+ hasTestRunner = true;
3750
+ state.testRunner = { status: "ok", label: "vitest" };
3751
+ } else {
3752
+ s.stop("Failed to install vitest");
3753
+ clack12.log.warn(`Install manually: ${cmd}`);
3754
+ skipCoverage = true;
3755
+ state.testRunner = { status: "skipped" };
3756
+ }
3757
+ } else if (choice === "skip") {
3758
+ skipCoverage = true;
3759
+ state.testRunner = { status: "skipped" };
3760
+ } else {
3761
+ clack12.outro("Aborted.");
3762
+ process.exit(0);
3763
+ }
3764
+ }
3765
+ if (state.hookManager.status === "missing") {
3766
+ clack12.note(buildReadinessNote(state), "Project readiness");
3767
+ const cmd = buildLefthookInstallCommand(packageManager, isWorkspace);
3768
+ const choice = await clack12.select({
3769
+ message: "No git hook manager detected. Pre-commit integration requires one.",
3770
+ options: [
3771
+ { value: "install", label: "Install lefthook", hint: cmd },
3772
+ { value: "skip", label: "Skip \u2014 no pre-commit integration" },
3773
+ { value: "exit", label: "Exit" }
3774
+ ]
3775
+ });
3776
+ assertNotCancelled(choice);
3777
+ if (choice === "install") {
3778
+ const s = clack12.spinner();
3779
+ s.start("Installing lefthook...");
3780
+ const result = await spawnAsync(cmd, projectRoot);
3781
+ if (result.status === 0) {
3782
+ s.stop("Installed lefthook");
3783
+ const ymlPath = path17.join(projectRoot, "lefthook.yml");
3784
+ if (!fs17.existsSync(ymlPath)) {
3785
+ fs17.writeFileSync(ymlPath, "# Managed by viberails\npre-commit:\n commands: {}\n");
3786
+ }
3787
+ currentHookManager = detectHookManager(projectRoot);
3788
+ state.hookManager = { status: "ok", label: currentHookManager ?? "lefthook" };
3789
+ } else {
3790
+ s.stop("Failed to install lefthook");
3791
+ clack12.log.warn(`Install manually: ${cmd}`);
3792
+ skipHooks = true;
3793
+ state.hookManager = { status: "skipped" };
3794
+ }
3795
+ } else if (choice === "skip") {
3796
+ skipHooks = true;
3797
+ state.hookManager = { status: "skipped" };
3798
+ } else {
3799
+ clack12.outro("Aborted.");
3800
+ process.exit(0);
3801
+ }
3802
+ }
3803
+ if (state.typecheck.status === "missing") {
3804
+ clack12.note(buildReadinessNote(state), "Project readiness");
3805
+ const choice = await clack12.select({
3806
+ message: "No typecheck command found. Without this, pre-commit and CI typecheck hooks will be unavailable.",
3807
+ options: [
3808
+ {
3809
+ value: "continue",
3810
+ label: "Continue without typecheck",
3811
+ hint: "add a root tsconfig.json or typecheck script later, then re-run viberails"
3812
+ },
3813
+ { value: "exit", label: "Exit \u2014 fix this first" }
3814
+ ]
3815
+ });
3816
+ assertNotCancelled(choice);
3817
+ if (choice === "exit") {
3818
+ clack12.outro(
3819
+ "Add a root tsconfig.json, a typecheck script, or a turbo typecheck task, then re-run viberails."
3820
+ );
3821
+ process.exit(0);
3822
+ }
3823
+ state.typecheck = { status: "skipped" };
3824
+ }
3825
+ return {
3826
+ hasTestRunner,
3827
+ hookManager: currentHookManager,
3828
+ skipCoverage,
3829
+ skipHooks,
3830
+ typecheckLabel: typecheckResolved.label
3831
+ };
3832
+ }
3833
+
3834
+ // src/utils/update-gitignore.ts
3835
+ import * as fs18 from "fs";
3836
+ import * as path18 from "path";
3837
+ function updateGitignore(projectRoot) {
3838
+ const gitignorePath = path18.join(projectRoot, ".gitignore");
3839
+ let content = "";
3840
+ if (fs18.existsSync(gitignorePath)) {
3841
+ content = fs18.readFileSync(gitignorePath, "utf-8");
3842
+ }
3843
+ if (!content.includes(".viberails/scan-result.json")) {
3844
+ const block = "\n# viberails\n.viberails/scan-result.json\n";
3845
+ const prefix = content.length === 0 ? "" : `${content.trimEnd()}
3846
+ `;
3847
+ fs18.writeFileSync(gitignorePath, `${prefix}${block}`);
3848
+ }
3849
+ }
3850
+
3669
3851
  // src/commands/init-hooks-extra.ts
3670
3852
  import * as fs19 from "fs";
3671
3853
  import * as path19 from "path";
3672
- import chalk14 from "chalk";
3854
+ import chalk16 from "chalk";
3673
3855
  import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
3674
3856
  function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
3675
3857
  const lefthookPath = path19.join(projectRoot, "lefthook.yml");
@@ -3729,12 +3911,12 @@ ${command}
3729
3911
  function setupTypecheckHook(projectRoot, packageManager) {
3730
3912
  const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3731
3913
  if (!resolved.command) {
3732
- console.log(` ${chalk14.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3914
+ console.log(` ${chalk16.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3733
3915
  return void 0;
3734
3916
  }
3735
3917
  const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
3736
3918
  if (target) {
3737
- console.log(` ${chalk14.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3919
+ console.log(` ${chalk16.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3738
3920
  }
3739
3921
  return target;
3740
3922
  }
@@ -3744,18 +3926,19 @@ function setupLintHook(projectRoot, linter) {
3744
3926
  let command;
3745
3927
  let lefthookExtra;
3746
3928
  if (isLefthook) {
3747
- command = linter === "biome" ? "npx biome check {staged_files}" : "npx eslint {staged_files}";
3929
+ command = linter === "biome" ? "npx biome check --write {staged_files}" : "npx eslint --fix {staged_files}";
3748
3930
  lefthookExtra = {
3749
- glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}"
3931
+ glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}",
3932
+ stage_fixed: "true"
3750
3933
  };
3751
3934
  } else {
3752
3935
  const exts = linter === "biome" ? "'*.js' '*.ts' '*.jsx' '*.tsx' '*.json' '*.css'" : "'*.js' '*.ts' '*.jsx' '*.tsx'";
3753
- const lintCmd = linter === "biome" ? "biome check" : "eslint";
3936
+ const lintCmd = linter === "biome" ? "biome check --write" : "eslint --fix";
3754
3937
  command = `git diff --cached --name-only --diff-filter=ACMR -- ${exts} | xargs npx ${lintCmd}`;
3755
3938
  }
3756
3939
  const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
3757
3940
  if (target) {
3758
- console.log(` ${chalk14.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3941
+ console.log(` ${chalk16.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3759
3942
  }
3760
3943
  return target;
3761
3944
  }
@@ -3764,7 +3947,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3764
3947
  if (integrations.preCommitHook) {
3765
3948
  const t = setupPreCommitHook(projectRoot);
3766
3949
  if (t && opts.lefthookExpected && !t.includes("lefthook")) {
3767
- console.log(` ${chalk14.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3950
+ console.log(` ${chalk16.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
3768
3951
  }
3769
3952
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
3770
3953
  }
@@ -3797,10 +3980,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3797
3980
  // src/commands/init-non-interactive.ts
3798
3981
  import * as fs20 from "fs";
3799
3982
  import * as path20 from "path";
3800
- import * as clack12 from "@clack/prompts";
3983
+ import * as clack13 from "@clack/prompts";
3801
3984
  import { compactConfig as compactConfig3, generateConfig } from "@viberails/config";
3802
3985
  import { scan as scan2 } from "@viberails/scanner";
3803
- import chalk15 from "chalk";
3986
+ import chalk17 from "chalk";
3804
3987
 
3805
3988
  // src/utils/filter-confidence.ts
3806
3989
  function filterHighConfidence(conventions, meta) {
@@ -3821,7 +4004,7 @@ function getExemptedPackages(config) {
3821
4004
  return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3822
4005
  }
3823
4006
  async function initNonInteractive(projectRoot, configPath) {
3824
- const s = clack12.spinner();
4007
+ const s = clack13.spinner();
3825
4008
  s.start("Scanning project...");
3826
4009
  const scanResult = await scan2(projectRoot);
3827
4010
  const config = generateConfig(scanResult);
@@ -3836,11 +4019,11 @@ async function initNonInteractive(projectRoot, configPath) {
3836
4019
  const exempted = getExemptedPackages(config);
3837
4020
  if (exempted.length > 0) {
3838
4021
  console.log(
3839
- ` ${chalk15.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk15.dim("(types-only)")}`
4022
+ ` ${chalk17.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk17.dim("(types-only)")}`
3840
4023
  );
3841
4024
  }
3842
4025
  if (config.packages.length > 1) {
3843
- const bs = clack12.spinner();
4026
+ const bs = clack13.spinner();
3844
4027
  bs.start("Building import graph...");
3845
4028
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3846
4029
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3873,14 +4056,14 @@ async function initNonInteractive(projectRoot, configPath) {
3873
4056
  const hookManager = detectHookManager(projectRoot);
3874
4057
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3875
4058
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3876
- const ok = chalk15.green("\u2713");
4059
+ const ok = chalk17.green("\u2713");
3877
4060
  const created = [
3878
4061
  `${ok} ${path20.basename(configPath)}`,
3879
4062
  `${ok} .viberails/context.md`,
3880
4063
  `${ok} .viberails/scan-result.json`,
3881
4064
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3882
4065
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3883
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk15.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
4066
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk17.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3884
4067
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3885
4068
  ].filter(Boolean);
3886
4069
  if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
@@ -3905,8 +4088,8 @@ async function initCommand(options, cwd) {
3905
4088
  return initInteractive(projectRoot, configPath, options);
3906
4089
  }
3907
4090
  console.log(
3908
- `${chalk16.yellow("!")} viberails is already initialized.
3909
- Run ${chalk16.cyan("viberails")} to review or edit the existing setup, ${chalk16.cyan("viberails sync")} to update generated files, or ${chalk16.cyan("viberails init --force")} to replace it.`
4091
+ `${chalk18.yellow("!")} viberails is already initialized.
4092
+ Run ${chalk18.cyan("viberails")} to review or edit the existing setup, ${chalk18.cyan("viberails sync")} to update generated files, or ${chalk18.cyan("viberails init --force")} to replace it.`
3910
4093
  );
3911
4094
  return;
3912
4095
  }
@@ -3914,11 +4097,11 @@ async function initCommand(options, cwd) {
3914
4097
  await initInteractive(projectRoot, configPath, options);
3915
4098
  }
3916
4099
  async function initInteractive(projectRoot, configPath, options) {
3917
- clack13.intro("viberails");
4100
+ clack14.intro("viberails");
3918
4101
  if (fs21.existsSync(configPath) && !options.force) {
3919
4102
  const action = await promptExistingConfigAction(path21.basename(configPath));
3920
4103
  if (action === "cancel") {
3921
- clack13.outro("Aborted. No files were written.");
4104
+ clack14.outro("Aborted. No files were written.");
3922
4105
  return;
3923
4106
  }
3924
4107
  if (action === "edit") {
@@ -3932,45 +4115,60 @@ async function initInteractive(projectRoot, configPath, options) {
3932
4115
  `${path21.basename(configPath)} already exists and will be replaced. Continue?`
3933
4116
  );
3934
4117
  if (!replace) {
3935
- clack13.outro("Aborted. No files were written.");
4118
+ clack14.outro("Aborted. No files were written.");
3936
4119
  return;
3937
4120
  }
3938
4121
  }
3939
- const s = clack13.spinner();
4122
+ const s = clack14.spinner();
3940
4123
  s.start("Scanning project...");
3941
4124
  const scanResult = await scan3(projectRoot);
3942
4125
  const config = generateConfig2(scanResult);
3943
4126
  s.stop("Scan complete");
3944
4127
  if (scanResult.statistics.totalFiles === 0) {
3945
- clack13.log.warn(
4128
+ clack14.log.warn(
3946
4129
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3947
4130
  );
3948
4131
  }
3949
- const hasTestRunner = !!scanResult.stack.testRunner;
3950
- const hookManager = detectHookManager(projectRoot);
3951
- const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3952
4132
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
4133
+ const packageManager = rootPkgStack?.packageManager?.split("@")[0] ?? "npm";
4134
+ const isWorkspace = config.packages.length > 1;
4135
+ const prereqs = await promptPrereqs(
4136
+ projectRoot,
4137
+ scanResult,
4138
+ detectHookManager(projectRoot),
4139
+ packageManager,
4140
+ isWorkspace
4141
+ );
4142
+ if (prereqs.skipCoverage) config.rules.testCoverage = 0;
4143
+ const coveragePrereqs = prereqs.hasTestRunner ? checkCoveragePrereqs(projectRoot, scanResult) : [];
3953
4144
  const state = await promptMainMenu(config, scanResult, {
3954
- hasTestRunner,
3955
- hookManager,
4145
+ hasTestRunner: prereqs.hasTestRunner,
4146
+ hookManager: prereqs.hookManager,
3956
4147
  coveragePrereqs,
3957
4148
  projectRoot,
3958
4149
  tools: {
3959
4150
  isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3960
4151
  linter: rootPkgStack?.linter?.split("@")[0],
3961
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
3962
- isWorkspace: config.packages.length > 1
4152
+ packageManager,
4153
+ isWorkspace
3963
4154
  }
3964
4155
  });
3965
4156
  const shouldWrite = await confirm3("Apply this setup?");
3966
4157
  if (!shouldWrite) {
3967
- clack13.outro("Aborted. No files were written.");
4158
+ clack14.outro("Aborted. No files were written.");
3968
4159
  return;
3969
4160
  }
4161
+ const integrations = await promptIntegrationsDeferred(prereqs.hookManager, {
4162
+ isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
4163
+ typecheckLabel: prereqs.typecheckLabel,
4164
+ linter: rootPkgStack?.linter?.split("@")[0],
4165
+ packageManager,
4166
+ isWorkspace
4167
+ });
3970
4168
  if (state.deferredInstalls.length > 0) {
3971
4169
  await executeDeferredInstalls(projectRoot, state.deferredInstalls);
3972
4170
  }
3973
- const ws = clack13.spinner();
4171
+ const ws = clack14.spinner();
3974
4172
  ws.start("Writing configuration...");
3975
4173
  const compacted = compactConfig4(config);
3976
4174
  fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
@@ -3978,31 +4176,28 @@ async function initInteractive(projectRoot, configPath, options) {
3978
4176
  writeGeneratedFiles(projectRoot, config, scanResult);
3979
4177
  updateGitignore(projectRoot);
3980
4178
  ws.stop("Configuration written");
3981
- const ok = chalk16.green("\u2713");
3982
- clack13.log.step(`${ok} ${path21.basename(configPath)}`);
3983
- clack13.log.step(`${ok} .viberails/context.md`);
3984
- clack13.log.step(`${ok} .viberails/scan-result.json`);
3985
- if (state.visited.integrations && state.integrations) {
3986
- const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
3987
- setupSelectedIntegrations(projectRoot, state.integrations, {
3988
- linter: rootPkgStack?.linter?.split("@")[0],
3989
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
3990
- lefthookExpected
3991
- });
3992
- }
3993
- clack13.outro(
4179
+ const ok = chalk18.green("\u2713");
4180
+ clack14.log.step(`${ok} ${path21.basename(configPath)}`);
4181
+ clack14.log.step(`${ok} .viberails/context.md`);
4182
+ clack14.log.step(`${ok} .viberails/scan-result.json`);
4183
+ setupSelectedIntegrations(projectRoot, integrations.choice, {
4184
+ linter: rootPkgStack?.linter?.split("@")[0],
4185
+ packageManager,
4186
+ lefthookExpected: prereqs.hookManager === "lefthook"
4187
+ });
4188
+ clack14.outro(
3994
4189
  `Done! Next: review viberails.config.json, then run viberails check
3995
- ${chalk16.dim("Tip: use")} ${chalk16.cyan("viberails check --enforce")} ${chalk16.dim("in CI to block PRs on violations.")}`
4190
+ ${chalk18.dim("Tip: use")} ${chalk18.cyan("viberails check --enforce")} ${chalk18.dim("in CI to block PRs on violations.")}`
3996
4191
  );
3997
4192
  }
3998
4193
 
3999
4194
  // src/commands/sync.ts
4000
4195
  import * as fs22 from "fs";
4001
4196
  import * as path22 from "path";
4002
- import * as clack14 from "@clack/prompts";
4197
+ import * as clack15 from "@clack/prompts";
4003
4198
  import { compactConfig as compactConfig5, loadConfig as loadConfig5, mergeConfig as mergeConfig2 } from "@viberails/config";
4004
4199
  import { scan as scan4 } from "@viberails/scanner";
4005
- import chalk17 from "chalk";
4200
+ import chalk19 from "chalk";
4006
4201
  var CONFIG_FILE6 = "viberails.config.json";
4007
4202
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
4008
4203
  function loadPreviousStats(projectRoot) {
@@ -4028,7 +4223,7 @@ async function syncCommand(options, cwd) {
4028
4223
  const configPath = path22.join(projectRoot, CONFIG_FILE6);
4029
4224
  const existing = await loadConfig5(configPath);
4030
4225
  const previousStats = loadPreviousStats(projectRoot);
4031
- const s = clack14.spinner();
4226
+ const s = clack15.spinner();
4032
4227
  s.start("Scanning project...");
4033
4228
  const scanResult = await scan4(projectRoot);
4034
4229
  s.stop("Scan complete");
@@ -4043,19 +4238,19 @@ async function syncCommand(options, cwd) {
4043
4238
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
4044
4239
  if (changes.length > 0 || statsDelta) {
4045
4240
  console.log(`
4046
- ${chalk17.bold("Changes:")}`);
4241
+ ${chalk19.bold("Changes:")}`);
4047
4242
  for (const change of changes) {
4048
- const icon = change.type === "removed" ? chalk17.red("-") : chalk17.green("+");
4243
+ const icon = change.type === "removed" ? chalk19.red("-") : chalk19.green("+");
4049
4244
  console.log(` ${icon} ${change.description}`);
4050
4245
  }
4051
4246
  if (statsDelta) {
4052
- console.log(` ${chalk17.dim(statsDelta)}`);
4247
+ console.log(` ${chalk19.dim(statsDelta)}`);
4053
4248
  }
4054
4249
  }
4055
4250
  if (options?.interactive) {
4056
- clack14.intro("viberails sync (interactive)");
4057
- clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
4058
- const decision = await clack14.select({
4251
+ clack15.intro("viberails sync (interactive)");
4252
+ clack15.note(formatRulesText(merged).join("\n"), "Rules after sync");
4253
+ const decision = await clack15.select({
4059
4254
  message: "How would you like to proceed?",
4060
4255
  options: [
4061
4256
  { value: "accept", label: "Accept changes" },
@@ -4065,7 +4260,7 @@ ${chalk17.bold("Changes:")}`);
4065
4260
  });
4066
4261
  assertNotCancelled(decision);
4067
4262
  if (decision === "cancel") {
4068
- clack14.outro("Sync cancelled. No files were written.");
4263
+ clack15.outro("Sync cancelled. No files were written.");
4069
4264
  return;
4070
4265
  }
4071
4266
  if (decision === "customize") {
@@ -4089,8 +4284,8 @@ ${chalk17.bold("Changes:")}`);
4089
4284
  fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
4090
4285
  `);
4091
4286
  writeGeneratedFiles(projectRoot, merged, scanResult);
4092
- clack14.log.success("Updated config with your customizations.");
4093
- clack14.outro("Done! Run viberails check to verify.");
4287
+ clack15.log.success("Updated config with your customizations.");
4288
+ clack15.outro("Done! Run viberails check to verify.");
4094
4289
  return;
4095
4290
  }
4096
4291
  }
@@ -4098,18 +4293,18 @@ ${chalk17.bold("Changes:")}`);
4098
4293
  `);
4099
4294
  writeGeneratedFiles(projectRoot, merged, scanResult);
4100
4295
  console.log(`
4101
- ${chalk17.bold("Synced:")}`);
4296
+ ${chalk19.bold("Synced:")}`);
4102
4297
  if (configChanged) {
4103
- console.log(` ${chalk17.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4298
+ console.log(` ${chalk19.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
4104
4299
  } else {
4105
- console.log(` ${chalk17.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4300
+ console.log(` ${chalk19.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4106
4301
  }
4107
- console.log(` ${chalk17.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4108
- console.log(` ${chalk17.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4302
+ console.log(` ${chalk19.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4303
+ console.log(` ${chalk19.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
4109
4304
  }
4110
4305
 
4111
4306
  // src/index.ts
4112
- var VERSION = "0.6.9";
4307
+ var VERSION = "0.6.11";
4113
4308
  var program = new Command();
4114
4309
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
4115
4310
  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) => {
@@ -4117,7 +4312,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
4117
4312
  await initCommand(options);
4118
4313
  } catch (err) {
4119
4314
  const message = err instanceof Error ? err.message : String(err);
4120
- console.error(`${chalk18.red("Error:")} ${message}`);
4315
+ console.error(`${chalk20.red("Error:")} ${message}`);
4121
4316
  process.exit(1);
4122
4317
  }
4123
4318
  });
@@ -4126,7 +4321,7 @@ program.command("sync").description("Re-scan and update generated files").option
4126
4321
  await syncCommand(options);
4127
4322
  } catch (err) {
4128
4323
  const message = err instanceof Error ? err.message : String(err);
4129
- console.error(`${chalk18.red("Error:")} ${message}`);
4324
+ console.error(`${chalk20.red("Error:")} ${message}`);
4130
4325
  process.exit(1);
4131
4326
  }
4132
4327
  });
@@ -4135,7 +4330,7 @@ program.command("config").description("Interactively edit existing config rules"
4135
4330
  await configCommand(options);
4136
4331
  } catch (err) {
4137
4332
  const message = err instanceof Error ? err.message : String(err);
4138
- console.error(`${chalk18.red("Error:")} ${message}`);
4333
+ console.error(`${chalk20.red("Error:")} ${message}`);
4139
4334
  process.exit(1);
4140
4335
  }
4141
4336
  });
@@ -4156,7 +4351,7 @@ program.command("check").description("Check files against enforced rules").optio
4156
4351
  process.exit(exitCode);
4157
4352
  } catch (err) {
4158
4353
  const message = err instanceof Error ? err.message : String(err);
4159
- console.error(`${chalk18.red("Error:")} ${message}`);
4354
+ console.error(`${chalk20.red("Error:")} ${message}`);
4160
4355
  process.exit(1);
4161
4356
  }
4162
4357
  }
@@ -4167,7 +4362,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
4167
4362
  process.exit(exitCode);
4168
4363
  } catch (err) {
4169
4364
  const message = err instanceof Error ? err.message : String(err);
4170
- console.error(`${chalk18.red("Error:")} ${message}`);
4365
+ console.error(`${chalk20.red("Error:")} ${message}`);
4171
4366
  process.exit(1);
4172
4367
  }
4173
4368
  });
@@ -4176,7 +4371,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
4176
4371
  await boundariesCommand(options);
4177
4372
  } catch (err) {
4178
4373
  const message = err instanceof Error ? err.message : String(err);
4179
- console.error(`${chalk18.red("Error:")} ${message}`);
4374
+ console.error(`${chalk20.red("Error:")} ${message}`);
4180
4375
  process.exit(1);
4181
4376
  }
4182
4377
  });