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.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
  }
@@ -3765,18 +3947,19 @@ function setupLintHook(projectRoot, linter) {
3765
3947
  let command;
3766
3948
  let lefthookExtra;
3767
3949
  if (isLefthook) {
3768
- command = linter === "biome" ? "npx biome check {staged_files}" : "npx eslint {staged_files}";
3950
+ command = linter === "biome" ? "npx biome check --write {staged_files}" : "npx eslint --fix {staged_files}";
3769
3951
  lefthookExtra = {
3770
- glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}"
3952
+ glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}",
3953
+ stage_fixed: "true"
3771
3954
  };
3772
3955
  } else {
3773
3956
  const exts = linter === "biome" ? "'*.js' '*.ts' '*.jsx' '*.tsx' '*.json' '*.css'" : "'*.js' '*.ts' '*.jsx' '*.tsx'";
3774
- const lintCmd = linter === "biome" ? "biome check" : "eslint";
3957
+ const lintCmd = linter === "biome" ? "biome check --write" : "eslint --fix";
3775
3958
  command = `git diff --cached --name-only --diff-filter=ACMR -- ${exts} | xargs npx ${lintCmd}`;
3776
3959
  }
3777
3960
  const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
3778
3961
  if (target) {
3779
- 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`);
3780
3963
  }
3781
3964
  return target;
3782
3965
  }
@@ -3785,7 +3968,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3785
3968
  if (integrations.preCommitHook) {
3786
3969
  const t = setupPreCommitHook(projectRoot);
3787
3970
  if (t && opts.lefthookExpected && !t.includes("lefthook")) {
3788
- 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}`);
3789
3972
  }
3790
3973
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
3791
3974
  }
@@ -3818,10 +4001,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3818
4001
  // src/commands/init-non-interactive.ts
3819
4002
  var fs20 = __toESM(require("fs"), 1);
3820
4003
  var path20 = __toESM(require("path"), 1);
3821
- var clack12 = __toESM(require("@clack/prompts"), 1);
4004
+ var clack13 = __toESM(require("@clack/prompts"), 1);
3822
4005
  var import_config8 = require("@viberails/config");
3823
4006
  var import_scanner2 = require("@viberails/scanner");
3824
- var import_chalk15 = __toESM(require("chalk"), 1);
4007
+ var import_chalk17 = __toESM(require("chalk"), 1);
3825
4008
 
3826
4009
  // src/utils/filter-confidence.ts
3827
4010
  function filterHighConfidence(conventions, meta) {
@@ -3842,7 +4025,7 @@ function getExemptedPackages(config) {
3842
4025
  return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3843
4026
  }
3844
4027
  async function initNonInteractive(projectRoot, configPath) {
3845
- const s = clack12.spinner();
4028
+ const s = clack13.spinner();
3846
4029
  s.start("Scanning project...");
3847
4030
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
3848
4031
  const config = (0, import_config8.generateConfig)(scanResult);
@@ -3857,11 +4040,11 @@ async function initNonInteractive(projectRoot, configPath) {
3857
4040
  const exempted = getExemptedPackages(config);
3858
4041
  if (exempted.length > 0) {
3859
4042
  console.log(
3860
- ` ${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)")}`
3861
4044
  );
3862
4045
  }
3863
4046
  if (config.packages.length > 1) {
3864
- const bs = clack12.spinner();
4047
+ const bs = clack13.spinner();
3865
4048
  bs.start("Building import graph...");
3866
4049
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3867
4050
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3894,14 +4077,14 @@ async function initNonInteractive(projectRoot, configPath) {
3894
4077
  const hookManager = detectHookManager(projectRoot);
3895
4078
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3896
4079
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3897
- const ok = import_chalk15.default.green("\u2713");
4080
+ const ok = import_chalk17.default.green("\u2713");
3898
4081
  const created = [
3899
4082
  `${ok} ${path20.basename(configPath)}`,
3900
4083
  `${ok} .viberails/context.md`,
3901
4084
  `${ok} .viberails/scan-result.json`,
3902
4085
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3903
4086
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3904
- 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)`,
3905
4088
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3906
4089
  ].filter(Boolean);
3907
4090
  if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
@@ -3926,8 +4109,8 @@ async function initCommand(options, cwd) {
3926
4109
  return initInteractive(projectRoot, configPath, options);
3927
4110
  }
3928
4111
  console.log(
3929
- `${import_chalk16.default.yellow("!")} viberails is already initialized.
3930
- 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.`
3931
4114
  );
3932
4115
  return;
3933
4116
  }
@@ -3935,11 +4118,11 @@ async function initCommand(options, cwd) {
3935
4118
  await initInteractive(projectRoot, configPath, options);
3936
4119
  }
3937
4120
  async function initInteractive(projectRoot, configPath, options) {
3938
- clack13.intro("viberails");
4121
+ clack14.intro("viberails");
3939
4122
  if (fs21.existsSync(configPath) && !options.force) {
3940
4123
  const action = await promptExistingConfigAction(path21.basename(configPath));
3941
4124
  if (action === "cancel") {
3942
- clack13.outro("Aborted. No files were written.");
4125
+ clack14.outro("Aborted. No files were written.");
3943
4126
  return;
3944
4127
  }
3945
4128
  if (action === "edit") {
@@ -3953,45 +4136,60 @@ async function initInteractive(projectRoot, configPath, options) {
3953
4136
  `${path21.basename(configPath)} already exists and will be replaced. Continue?`
3954
4137
  );
3955
4138
  if (!replace) {
3956
- clack13.outro("Aborted. No files were written.");
4139
+ clack14.outro("Aborted. No files were written.");
3957
4140
  return;
3958
4141
  }
3959
4142
  }
3960
- const s = clack13.spinner();
4143
+ const s = clack14.spinner();
3961
4144
  s.start("Scanning project...");
3962
4145
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3963
4146
  const config = (0, import_config9.generateConfig)(scanResult);
3964
4147
  s.stop("Scan complete");
3965
4148
  if (scanResult.statistics.totalFiles === 0) {
3966
- clack13.log.warn(
4149
+ clack14.log.warn(
3967
4150
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3968
4151
  );
3969
4152
  }
3970
- const hasTestRunner = !!scanResult.stack.testRunner;
3971
- const hookManager = detectHookManager(projectRoot);
3972
- const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3973
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) : [];
3974
4165
  const state = await promptMainMenu(config, scanResult, {
3975
- hasTestRunner,
3976
- hookManager,
4166
+ hasTestRunner: prereqs.hasTestRunner,
4167
+ hookManager: prereqs.hookManager,
3977
4168
  coveragePrereqs,
3978
4169
  projectRoot,
3979
4170
  tools: {
3980
4171
  isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3981
4172
  linter: rootPkgStack?.linter?.split("@")[0],
3982
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
3983
- isWorkspace: config.packages.length > 1
4173
+ packageManager,
4174
+ isWorkspace
3984
4175
  }
3985
4176
  });
3986
4177
  const shouldWrite = await confirm3("Apply this setup?");
3987
4178
  if (!shouldWrite) {
3988
- clack13.outro("Aborted. No files were written.");
4179
+ clack14.outro("Aborted. No files were written.");
3989
4180
  return;
3990
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
+ });
3991
4189
  if (state.deferredInstalls.length > 0) {
3992
4190
  await executeDeferredInstalls(projectRoot, state.deferredInstalls);
3993
4191
  }
3994
- const ws = clack13.spinner();
4192
+ const ws = clack14.spinner();
3995
4193
  ws.start("Writing configuration...");
3996
4194
  const compacted = (0, import_config9.compactConfig)(config);
3997
4195
  fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
@@ -3999,31 +4197,28 @@ async function initInteractive(projectRoot, configPath, options) {
3999
4197
  writeGeneratedFiles(projectRoot, config, scanResult);
4000
4198
  updateGitignore(projectRoot);
4001
4199
  ws.stop("Configuration written");
4002
- const ok = import_chalk16.default.green("\u2713");
4003
- clack13.log.step(`${ok} ${path21.basename(configPath)}`);
4004
- clack13.log.step(`${ok} .viberails/context.md`);
4005
- clack13.log.step(`${ok} .viberails/scan-result.json`);
4006
- if (state.visited.integrations && state.integrations) {
4007
- const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
4008
- setupSelectedIntegrations(projectRoot, state.integrations, {
4009
- linter: rootPkgStack?.linter?.split("@")[0],
4010
- packageManager: rootPkgStack?.packageManager?.split("@")[0],
4011
- lefthookExpected
4012
- });
4013
- }
4014
- 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(
4015
4210
  `Done! Next: review viberails.config.json, then run viberails check
4016
- ${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.")}`
4017
4212
  );
4018
4213
  }
4019
4214
 
4020
4215
  // src/commands/sync.ts
4021
4216
  var fs22 = __toESM(require("fs"), 1);
4022
4217
  var path22 = __toESM(require("path"), 1);
4023
- var clack14 = __toESM(require("@clack/prompts"), 1);
4218
+ var clack15 = __toESM(require("@clack/prompts"), 1);
4024
4219
  var import_config11 = require("@viberails/config");
4025
4220
  var import_scanner4 = require("@viberails/scanner");
4026
- var import_chalk17 = __toESM(require("chalk"), 1);
4221
+ var import_chalk19 = __toESM(require("chalk"), 1);
4027
4222
  var CONFIG_FILE6 = "viberails.config.json";
4028
4223
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
4029
4224
  function loadPreviousStats(projectRoot) {
@@ -4049,7 +4244,7 @@ async function syncCommand(options, cwd) {
4049
4244
  const configPath = path22.join(projectRoot, CONFIG_FILE6);
4050
4245
  const existing = await (0, import_config11.loadConfig)(configPath);
4051
4246
  const previousStats = loadPreviousStats(projectRoot);
4052
- const s = clack14.spinner();
4247
+ const s = clack15.spinner();
4053
4248
  s.start("Scanning project...");
4054
4249
  const scanResult = await (0, import_scanner4.scan)(projectRoot);
4055
4250
  s.stop("Scan complete");
@@ -4064,19 +4259,19 @@ async function syncCommand(options, cwd) {
4064
4259
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
4065
4260
  if (changes.length > 0 || statsDelta) {
4066
4261
  console.log(`
4067
- ${import_chalk17.default.bold("Changes:")}`);
4262
+ ${import_chalk19.default.bold("Changes:")}`);
4068
4263
  for (const change of changes) {
4069
- 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("+");
4070
4265
  console.log(` ${icon} ${change.description}`);
4071
4266
  }
4072
4267
  if (statsDelta) {
4073
- console.log(` ${import_chalk17.default.dim(statsDelta)}`);
4268
+ console.log(` ${import_chalk19.default.dim(statsDelta)}`);
4074
4269
  }
4075
4270
  }
4076
4271
  if (options?.interactive) {
4077
- clack14.intro("viberails sync (interactive)");
4078
- clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
4079
- 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({
4080
4275
  message: "How would you like to proceed?",
4081
4276
  options: [
4082
4277
  { value: "accept", label: "Accept changes" },
@@ -4086,7 +4281,7 @@ ${import_chalk17.default.bold("Changes:")}`);
4086
4281
  });
4087
4282
  assertNotCancelled(decision);
4088
4283
  if (decision === "cancel") {
4089
- clack14.outro("Sync cancelled. No files were written.");
4284
+ clack15.outro("Sync cancelled. No files were written.");
4090
4285
  return;
4091
4286
  }
4092
4287
  if (decision === "customize") {
@@ -4110,8 +4305,8 @@ ${import_chalk17.default.bold("Changes:")}`);
4110
4305
  fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
4111
4306
  `);
4112
4307
  writeGeneratedFiles(projectRoot, merged, scanResult);
4113
- clack14.log.success("Updated config with your customizations.");
4114
- 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.");
4115
4310
  return;
4116
4311
  }
4117
4312
  }
@@ -4119,18 +4314,18 @@ ${import_chalk17.default.bold("Changes:")}`);
4119
4314
  `);
4120
4315
  writeGeneratedFiles(projectRoot, merged, scanResult);
4121
4316
  console.log(`
4122
- ${import_chalk17.default.bold("Synced:")}`);
4317
+ ${import_chalk19.default.bold("Synced:")}`);
4123
4318
  if (configChanged) {
4124
- 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)`);
4125
4320
  } else {
4126
- console.log(` ${import_chalk17.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4321
+ console.log(` ${import_chalk19.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
4127
4322
  }
4128
- console.log(` ${import_chalk17.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
4129
- 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`);
4130
4325
  }
4131
4326
 
4132
4327
  // src/index.ts
4133
- var VERSION = "0.6.9";
4328
+ var VERSION = "0.6.11";
4134
4329
  var program = new import_commander.Command();
4135
4330
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
4136
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) => {
@@ -4138,7 +4333,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
4138
4333
  await initCommand(options);
4139
4334
  } catch (err) {
4140
4335
  const message = err instanceof Error ? err.message : String(err);
4141
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4336
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4142
4337
  process.exit(1);
4143
4338
  }
4144
4339
  });
@@ -4147,7 +4342,7 @@ program.command("sync").description("Re-scan and update generated files").option
4147
4342
  await syncCommand(options);
4148
4343
  } catch (err) {
4149
4344
  const message = err instanceof Error ? err.message : String(err);
4150
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4345
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4151
4346
  process.exit(1);
4152
4347
  }
4153
4348
  });
@@ -4156,7 +4351,7 @@ program.command("config").description("Interactively edit existing config rules"
4156
4351
  await configCommand(options);
4157
4352
  } catch (err) {
4158
4353
  const message = err instanceof Error ? err.message : String(err);
4159
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4354
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4160
4355
  process.exit(1);
4161
4356
  }
4162
4357
  });
@@ -4177,7 +4372,7 @@ program.command("check").description("Check files against enforced rules").optio
4177
4372
  process.exit(exitCode);
4178
4373
  } catch (err) {
4179
4374
  const message = err instanceof Error ? err.message : String(err);
4180
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4375
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4181
4376
  process.exit(1);
4182
4377
  }
4183
4378
  }
@@ -4188,7 +4383,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
4188
4383
  process.exit(exitCode);
4189
4384
  } catch (err) {
4190
4385
  const message = err instanceof Error ? err.message : String(err);
4191
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4386
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4192
4387
  process.exit(1);
4193
4388
  }
4194
4389
  });
@@ -4197,7 +4392,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
4197
4392
  await boundariesCommand(options);
4198
4393
  } catch (err) {
4199
4394
  const message = err instanceof Error ? err.message : String(err);
4200
- console.error(`${import_chalk18.default.red("Error:")} ${message}`);
4395
+ console.error(`${import_chalk20.default.red("Error:")} ${message}`);
4201
4396
  process.exit(1);
4202
4397
  }
4203
4398
  });