viberails 0.5.4 → 0.6.0

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 chalk13 from "chalk";
4
+ import chalk14 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/boundaries.ts
@@ -62,11 +62,11 @@ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace
62
62
  stdio: "pipe"
63
63
  });
64
64
  if (result.status === 0) {
65
- const fs20 = await import("fs");
66
- const path20 = await import("path");
67
- const lefthookPath = path20.join(projectRoot, "lefthook.yml");
68
- if (!fs20.existsSync(lefthookPath)) {
69
- fs20.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
65
+ const fs21 = await import("fs");
66
+ const path21 = await import("path");
67
+ const lefthookPath = path21.join(projectRoot, "lefthook.yml");
68
+ if (!fs21.existsSync(lefthookPath)) {
69
+ fs21.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
70
70
  }
71
71
  s.stop("Installed Lefthook");
72
72
  return "Lefthook";
@@ -98,7 +98,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
98
98
  options.push({
99
99
  value: "typecheck",
100
100
  label: "Typecheck (tsc --noEmit)",
101
- hint: "catches type errors before commit"
101
+ hint: "pre-commit hook + CI check"
102
102
  });
103
103
  }
104
104
  if (tools?.linter) {
@@ -106,7 +106,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
106
106
  options.push({
107
107
  value: "lint",
108
108
  label: `Lint check (${linterName})`,
109
- hint: "runs linter on staged files before commit"
109
+ hint: "pre-commit hook + CI check"
110
110
  });
111
111
  }
112
112
  options.push(
@@ -698,7 +698,7 @@ ${chalk.yellow("Cycles detected:")}`);
698
698
  import * as fs7 from "fs";
699
699
  import * as path7 from "path";
700
700
  import { loadConfig as loadConfig2 } from "@viberails/config";
701
- import chalk2 from "chalk";
701
+ import chalk3 from "chalk";
702
702
 
703
703
  // src/commands/check-config.ts
704
704
  import { BUILTIN_IGNORE } from "@viberails/config";
@@ -789,7 +789,8 @@ function runCoverageCommand(pkgRoot, command) {
789
789
  cwd: pkgRoot,
790
790
  shell: true,
791
791
  encoding: "utf-8",
792
- stdio: "pipe"
792
+ stdio: "pipe",
793
+ timeout: 3e5
793
794
  });
794
795
  if (result.status === 0) return { ok: true };
795
796
  const stderr = result.stderr?.trim() ?? "";
@@ -952,7 +953,7 @@ function checkNaming(relPath, conventions) {
952
953
  }
953
954
  function getStagedFiles(projectRoot) {
954
955
  try {
955
- const output = execSync("git diff --cached --name-only --diff-filter=ACM", {
956
+ const output = execSync("git diff --cached --name-only --diff-filter=ACMR", {
956
957
  cwd: projectRoot,
957
958
  encoding: "utf-8",
958
959
  stdio: ["ignore", "pipe", "ignore"]
@@ -979,7 +980,62 @@ function getDiffFiles(projectRoot, base) {
979
980
  added: addedOutput.trim().split("\n").filter(Boolean)
980
981
  };
981
982
  } catch {
982
- return { all: [], added: [] };
983
+ const msg = `git diff failed for base '${base}' \u2014 no files will be checked`;
984
+ process.stderr.write(`Warning: ${msg}
985
+ `);
986
+ return { all: [], added: [], error: msg };
987
+ }
988
+ }
989
+ function testFileToSourceFile(testFile) {
990
+ const match = testFile.match(/^(.+)\.(test|spec)(\.[^.]+)$/);
991
+ if (!match) return null;
992
+ return `${match[1]}${match[3]}`;
993
+ }
994
+ function deletedTestFileToSourceFile(deletedTestFile, config) {
995
+ const normalized = deletedTestFile.replaceAll("\\", "/");
996
+ const sortedPackages = [...config.packages].sort((a, b) => b.path.length - a.path.length);
997
+ for (const pkg of sortedPackages) {
998
+ const relInPkg = pkg.path === "." ? normalized : normalized.startsWith(`${pkg.path}/`) ? normalized.slice(pkg.path.length + 1) : null;
999
+ if (relInPkg === null) continue;
1000
+ const srcDir = pkg.structure?.srcDir;
1001
+ if (!srcDir) continue;
1002
+ const testsDir = pkg.structure?.tests;
1003
+ if (testsDir && relInPkg.startsWith(`${testsDir}/`)) {
1004
+ const relWithinTests = relInPkg.slice(testsDir.length + 1);
1005
+ const relWithinSrc = testFileToSourceFile(relWithinTests);
1006
+ if (relWithinSrc) {
1007
+ return pkg.path === "." ? path5.posix.join(srcDir, relWithinSrc) : path5.posix.join(pkg.path, srcDir, relWithinSrc);
1008
+ }
1009
+ }
1010
+ const colocated = testFileToSourceFile(relInPkg);
1011
+ if (colocated) {
1012
+ return pkg.path === "." ? colocated : path5.posix.join(pkg.path, colocated);
1013
+ }
1014
+ }
1015
+ return null;
1016
+ }
1017
+ function getStagedDeletedTestSourceFiles(projectRoot, config) {
1018
+ try {
1019
+ const output = execSync("git diff --cached --name-only --diff-filter=D", {
1020
+ cwd: projectRoot,
1021
+ encoding: "utf-8",
1022
+ stdio: ["ignore", "pipe", "ignore"]
1023
+ });
1024
+ return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
1025
+ } catch {
1026
+ return [];
1027
+ }
1028
+ }
1029
+ function getDiffDeletedTestSourceFiles(projectRoot, base, config) {
1030
+ try {
1031
+ const output = execSync(`git diff --name-only --diff-filter=D ${base}...HEAD`, {
1032
+ cwd: projectRoot,
1033
+ encoding: "utf-8",
1034
+ stdio: ["ignore", "pipe", "ignore"]
1035
+ });
1036
+ return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
1037
+ } catch {
1038
+ return [];
983
1039
  }
984
1040
  }
985
1041
  function getAllSourceFiles(projectRoot, config) {
@@ -1022,7 +1078,7 @@ function collectSourceFiles(dir, projectRoot) {
1022
1078
  }
1023
1079
  for (const entry of entries) {
1024
1080
  if (entry.isDirectory()) {
1025
- if (entry.name === "node_modules") continue;
1081
+ if (ALWAYS_SKIP_DIRS.has(entry.name)) continue;
1026
1082
  walk(path5.join(d, entry.name));
1027
1083
  } else if (entry.isFile()) {
1028
1084
  files.push(path5.relative(projectRoot, path5.join(d, entry.name)));
@@ -1033,6 +1089,55 @@ function collectSourceFiles(dir, projectRoot) {
1033
1089
  return files;
1034
1090
  }
1035
1091
 
1092
+ // src/commands/check-print.ts
1093
+ import chalk2 from "chalk";
1094
+ function printGroupedViolations(violations, limit) {
1095
+ const groups = /* @__PURE__ */ new Map();
1096
+ for (const v of violations) {
1097
+ const existing = groups.get(v.rule) ?? [];
1098
+ existing.push(v);
1099
+ groups.set(v.rule, existing);
1100
+ }
1101
+ const ruleOrder = [
1102
+ "file-size",
1103
+ "file-naming",
1104
+ "missing-test",
1105
+ "test-coverage",
1106
+ "boundary-violation"
1107
+ ];
1108
+ const sortedKeys = [...groups.keys()].sort(
1109
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
1110
+ );
1111
+ let totalShown = 0;
1112
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
1113
+ for (const rule of sortedKeys) {
1114
+ const group = groups.get(rule);
1115
+ if (!group) continue;
1116
+ const remaining = totalLimit - totalShown;
1117
+ if (remaining <= 0) break;
1118
+ const toShow = group.slice(0, remaining);
1119
+ const hidden = group.length - toShow.length;
1120
+ for (const v of toShow) {
1121
+ const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
1122
+ console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
1123
+ }
1124
+ totalShown += toShow.length;
1125
+ if (hidden > 0) {
1126
+ console.log(chalk2.dim(` ... and ${hidden} more ${rule} violations`));
1127
+ }
1128
+ }
1129
+ }
1130
+ function printSummary(violations) {
1131
+ const counts = /* @__PURE__ */ new Map();
1132
+ for (const v of violations) {
1133
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
1134
+ }
1135
+ const word = violations.length === 1 ? "violation" : "violations";
1136
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
1137
+ console.log(`
1138
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
1139
+ }
1140
+
1036
1141
  // src/commands/check-tests.ts
1037
1142
  import * as fs6 from "fs";
1038
1143
  import * as path6 from "path";
@@ -1062,18 +1167,19 @@ function checkMissingTests(projectRoot, config, severity) {
1062
1167
  const testSuffix = testPattern.replace("*", "");
1063
1168
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
1064
1169
  for (const relFile of sourceFiles) {
1065
- const basename8 = path6.basename(relFile);
1066
- if (basename8.includes(".test.") || basename8.includes(".spec.") || basename8.startsWith("index.") || basename8.endsWith(".d.ts")) {
1170
+ const basename9 = path6.basename(relFile);
1171
+ if (basename9.includes(".test.") || basename9.includes(".spec.") || basename9.startsWith("index.") || basename9.endsWith(".d.ts")) {
1067
1172
  continue;
1068
1173
  }
1069
- const ext = path6.extname(basename8);
1174
+ const ext = path6.extname(basename9);
1070
1175
  if (!SOURCE_EXTS2.has(ext)) continue;
1071
- const stem = basename8.slice(0, -ext.length);
1176
+ const stem = basename9.slice(0, -ext.length);
1072
1177
  const expectedTestFile = `${stem}${testSuffix}`;
1073
1178
  const dir = path6.dirname(path6.join(projectRoot, relFile));
1074
1179
  const colocatedTest = path6.join(dir, expectedTestFile);
1075
1180
  const testsDir = pkg.structure?.tests;
1076
- const dedicatedTest = testsDir ? path6.join(packageRoot2, testsDir, expectedTestFile) : null;
1181
+ const relToSrc = path6.relative(srcPath, path6.join(projectRoot, path6.dirname(relFile)));
1182
+ const dedicatedTest = testsDir ? path6.join(packageRoot2, testsDir, relToSrc, expectedTestFile) : null;
1077
1183
  const hasTest = fs6.existsSync(colocatedTest) || dedicatedTest !== null && fs6.existsSync(dedicatedTest);
1078
1184
  if (!hasTest) {
1079
1185
  violations.push({
@@ -1103,91 +1209,52 @@ function isTestFile(relPath) {
1103
1209
  const filename = path7.basename(relPath);
1104
1210
  return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
1105
1211
  }
1106
- function printGroupedViolations(violations, limit) {
1107
- const groups = /* @__PURE__ */ new Map();
1108
- for (const v of violations) {
1109
- const existing = groups.get(v.rule) ?? [];
1110
- existing.push(v);
1111
- groups.set(v.rule, existing);
1112
- }
1113
- const ruleOrder = [
1114
- "file-size",
1115
- "file-naming",
1116
- "missing-test",
1117
- "test-coverage",
1118
- "boundary-violation"
1119
- ];
1120
- const sortedKeys = [...groups.keys()].sort(
1121
- (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
1122
- );
1123
- let totalShown = 0;
1124
- const totalLimit = limit ?? Number.POSITIVE_INFINITY;
1125
- for (const rule of sortedKeys) {
1126
- const group = groups.get(rule);
1127
- if (!group) continue;
1128
- const remaining = totalLimit - totalShown;
1129
- if (remaining <= 0) break;
1130
- const toShow = group.slice(0, remaining);
1131
- const hidden = group.length - toShow.length;
1132
- for (const v of toShow) {
1133
- const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
1134
- console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
1135
- }
1136
- totalShown += toShow.length;
1137
- if (hidden > 0) {
1138
- console.log(chalk2.dim(` ... and ${hidden} more ${rule} violations`));
1139
- }
1140
- }
1141
- }
1142
- function printSummary(violations) {
1143
- const counts = /* @__PURE__ */ new Map();
1144
- for (const v of violations) {
1145
- counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
1146
- }
1147
- const word = violations.length === 1 ? "violation" : "violations";
1148
- const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
1149
- console.log(`
1150
- ${violations.length} ${word} found (${parts.join(", ")}).`);
1151
- }
1152
1212
  async function checkCommand(options, cwd) {
1153
1213
  const startDir = cwd ?? process.cwd();
1154
1214
  const projectRoot = findProjectRoot(startDir);
1155
1215
  if (!projectRoot) {
1156
- console.error(`${chalk2.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1216
+ console.error(`${chalk3.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1157
1217
  return 1;
1158
1218
  }
1159
1219
  const configPath = path7.join(projectRoot, CONFIG_FILE2);
1160
1220
  if (!fs7.existsSync(configPath)) {
1161
1221
  console.error(
1162
- `${chalk2.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1222
+ `${chalk3.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1163
1223
  );
1164
1224
  return 1;
1165
1225
  }
1166
1226
  const config = await loadConfig2(configPath);
1167
1227
  let filesToCheck;
1168
1228
  let diffAddedFiles = null;
1229
+ let deletedTestSourceFiles = [];
1169
1230
  if (options.staged) {
1170
- filesToCheck = getStagedFiles(projectRoot);
1231
+ filesToCheck = getStagedFiles(projectRoot).filter((f) => SOURCE_EXTS.has(path7.extname(f)));
1232
+ deletedTestSourceFiles = getStagedDeletedTestSourceFiles(projectRoot, config);
1171
1233
  } else if (options.diffBase) {
1172
1234
  const diff = getDiffFiles(projectRoot, options.diffBase);
1235
+ if (diff.error && options.enforce) {
1236
+ console.error(`${chalk3.red("Error:")} ${diff.error}`);
1237
+ return 1;
1238
+ }
1173
1239
  filesToCheck = diff.all.filter((f) => SOURCE_EXTS.has(path7.extname(f)));
1174
1240
  diffAddedFiles = new Set(diff.added);
1241
+ deletedTestSourceFiles = getDiffDeletedTestSourceFiles(projectRoot, options.diffBase, config);
1175
1242
  } else if (options.files && options.files.length > 0) {
1176
1243
  filesToCheck = options.files;
1177
1244
  } else {
1178
1245
  filesToCheck = getAllSourceFiles(projectRoot, config);
1179
1246
  }
1180
- if (filesToCheck.length === 0) {
1247
+ if (filesToCheck.length === 0 && deletedTestSourceFiles.length === 0) {
1181
1248
  if (options.format === "json") {
1182
1249
  console.log(JSON.stringify({ violations: [], checkedFiles: 0 }));
1183
1250
  } else {
1184
- console.log(`${chalk2.green("\u2713")} No files to check.`);
1251
+ console.log(`${chalk3.green("\u2713")} No files to check.`);
1185
1252
  }
1186
1253
  return 0;
1187
1254
  }
1188
1255
  const violations = [];
1189
1256
  const severity = options.enforce ? "error" : "warn";
1190
- const log7 = options.format !== "json" && !options.hook ? (msg) => process.stderr.write(chalk2.dim(msg)) : () => {
1257
+ const log7 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(chalk3.dim(msg)) : () => {
1191
1258
  };
1192
1259
  log7(" Checking files...");
1193
1260
  for (const file of filesToCheck) {
@@ -1223,12 +1290,20 @@ async function checkCommand(options, cwd) {
1223
1290
  }
1224
1291
  }
1225
1292
  log7(" done\n");
1226
- if (!options.staged && !options.files) {
1293
+ if (!options.files) {
1227
1294
  log7(" Checking missing tests...");
1228
1295
  const testViolations = checkMissingTests(projectRoot, config, severity);
1229
- violations.push(
1230
- ...diffAddedFiles ? testViolations.filter((v) => diffAddedFiles.has(v.file)) : testViolations
1231
- );
1296
+ if (options.staged) {
1297
+ const stagedSet = new Set(filesToCheck);
1298
+ for (const f of deletedTestSourceFiles) stagedSet.add(f);
1299
+ violations.push(...testViolations.filter((v) => stagedSet.has(v.file)));
1300
+ } else if (diffAddedFiles) {
1301
+ const checkSet = new Set(diffAddedFiles);
1302
+ for (const f of deletedTestSourceFiles) checkSet.add(f);
1303
+ violations.push(...testViolations.filter((v) => checkSet.has(v.file)));
1304
+ } else {
1305
+ violations.push(...testViolations);
1306
+ }
1232
1307
  log7(" done\n");
1233
1308
  }
1234
1309
  if (!options.files && !options.staged && !options.diffBase) {
@@ -1274,7 +1349,7 @@ async function checkCommand(options, cwd) {
1274
1349
  return options.enforce && violations.length > 0 ? 1 : 0;
1275
1350
  }
1276
1351
  if (violations.length === 0) {
1277
- console.log(`${chalk2.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1352
+ console.log(`${chalk3.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1278
1353
  return 0;
1279
1354
  }
1280
1355
  if (!options.quiet) {
@@ -1282,7 +1357,7 @@ async function checkCommand(options, cwd) {
1282
1357
  }
1283
1358
  printSummary(violations);
1284
1359
  if (options.enforce) {
1285
- console.log(chalk2.red("Fix violations before committing."));
1360
+ console.log(chalk3.red("Fix violations before committing."));
1286
1361
  return 1;
1287
1362
  }
1288
1363
  return 0;
@@ -1340,7 +1415,7 @@ import * as path9 from "path";
1340
1415
  import * as clack6 from "@clack/prompts";
1341
1416
  import { compactConfig as compactConfig2, loadConfig as loadConfig3, mergeConfig } from "@viberails/config";
1342
1417
  import { scan } from "@viberails/scanner";
1343
- import chalk5 from "chalk";
1418
+ import chalk6 from "chalk";
1344
1419
 
1345
1420
  // src/display-text.ts
1346
1421
  import {
@@ -1359,7 +1434,7 @@ import {
1359
1434
  ORM_NAMES,
1360
1435
  STYLING_NAMES as STYLING_NAMES2
1361
1436
  } from "@viberails/types";
1362
- import chalk4 from "chalk";
1437
+ import chalk5 from "chalk";
1363
1438
 
1364
1439
  // src/display-helpers.ts
1365
1440
  import { ROLE_DESCRIPTIONS } from "@viberails/types";
@@ -1412,7 +1487,7 @@ function formatRoleGroup(group) {
1412
1487
 
1413
1488
  // src/display-monorepo.ts
1414
1489
  import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
1415
- import chalk3 from "chalk";
1490
+ import chalk4 from "chalk";
1416
1491
  function formatPackageSummary(pkg) {
1417
1492
  const parts = [];
1418
1493
  if (pkg.stack.framework) {
@@ -1421,30 +1496,31 @@ function formatPackageSummary(pkg) {
1421
1496
  if (pkg.stack.styling) {
1422
1497
  parts.push(formatItem(pkg.stack.styling, STYLING_NAMES));
1423
1498
  }
1424
- const files = `${pkg.statistics.totalFiles} files`;
1499
+ const n = pkg.statistics.totalFiles;
1500
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1425
1501
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1426
1502
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1427
1503
  }
1428
1504
  function displayMonorepoResults(scanResult) {
1429
1505
  const { stack, packages } = scanResult;
1430
1506
  console.log(`
1431
- ${chalk3.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1432
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.language)}`);
1507
+ ${chalk4.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1508
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.language)}`);
1433
1509
  if (stack.packageManager) {
1434
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.packageManager)}`);
1510
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.packageManager)}`);
1435
1511
  }
1436
1512
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1437
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1513
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1438
1514
  } else {
1439
1515
  if (stack.linter) {
1440
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.linter)}`);
1516
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)}`);
1441
1517
  }
1442
1518
  if (stack.formatter) {
1443
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.formatter)}`);
1519
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.formatter)}`);
1444
1520
  }
1445
1521
  }
1446
1522
  if (stack.testRunner) {
1447
- console.log(` ${chalk3.green("\u2713")} ${formatItem(stack.testRunner)}`);
1523
+ console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.testRunner)}`);
1448
1524
  }
1449
1525
  console.log("");
1450
1526
  for (const pkg of packages) {
@@ -1455,13 +1531,13 @@ ${chalk3.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1455
1531
  );
1456
1532
  if (packagesWithDirs.length > 0) {
1457
1533
  console.log(`
1458
- ${chalk3.bold("Structure:")}`);
1534
+ ${chalk4.bold("Structure:")}`);
1459
1535
  for (const pkg of packagesWithDirs) {
1460
1536
  const groups = groupByRole(pkg.structure.directories);
1461
1537
  if (groups.length === 0) continue;
1462
1538
  console.log(` ${pkg.relativePath}:`);
1463
1539
  for (const group of groups) {
1464
- console.log(` ${chalk3.green("\u2713")} ${formatRoleGroup(group)}`);
1540
+ console.log(` ${chalk4.green("\u2713")} ${formatRoleGroup(group)}`);
1465
1541
  }
1466
1542
  }
1467
1543
  }
@@ -1477,7 +1553,8 @@ function formatPackageSummaryPlain(pkg) {
1477
1553
  if (pkg.stack.styling) {
1478
1554
  parts.push(formatItem(pkg.stack.styling, STYLING_NAMES));
1479
1555
  }
1480
- const files = `${pkg.statistics.totalFiles} files`;
1556
+ const n = pkg.statistics.totalFiles;
1557
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1481
1558
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1482
1559
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1483
1560
  }
@@ -1541,7 +1618,7 @@ function displayConventions(scanResult) {
1541
1618
  const conventionEntries = Object.entries(scanResult.conventions);
1542
1619
  if (conventionEntries.length === 0) return;
1543
1620
  console.log(`
1544
- ${chalk4.bold("Conventions:")}`);
1621
+ ${chalk5.bold("Conventions:")}`);
1545
1622
  for (const [key, convention] of conventionEntries) {
1546
1623
  if (convention.confidence === "low") continue;
1547
1624
  const label = CONVENTION_LABELS[key] ?? key;
@@ -1549,19 +1626,19 @@ ${chalk4.bold("Conventions:")}`);
1549
1626
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1550
1627
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1551
1628
  if (allSame || pkgValues.length <= 1) {
1552
- const ind = convention.confidence === "high" ? chalk4.green("\u2713") : chalk4.yellow("~");
1553
- const detail = chalk4.dim(`(${confidenceLabel(convention)})`);
1629
+ const ind = convention.confidence === "high" ? chalk5.green("\u2713") : chalk5.yellow("~");
1630
+ const detail = chalk5.dim(`(${confidenceLabel(convention)})`);
1554
1631
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1555
1632
  } else {
1556
- console.log(` ${chalk4.yellow("~")} ${label}: varies by package`);
1633
+ console.log(` ${chalk5.yellow("~")} ${label}: varies by package`);
1557
1634
  for (const pv of pkgValues) {
1558
1635
  const pct = Math.round(pv.convention.consistency);
1559
1636
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1560
1637
  }
1561
1638
  }
1562
1639
  } else {
1563
- const ind = convention.confidence === "high" ? chalk4.green("\u2713") : chalk4.yellow("~");
1564
- const detail = chalk4.dim(`(${confidenceLabel(convention)})`);
1640
+ const ind = convention.confidence === "high" ? chalk5.green("\u2713") : chalk5.yellow("~");
1641
+ const detail = chalk5.dim(`(${confidenceLabel(convention)})`);
1565
1642
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1566
1643
  }
1567
1644
  }
@@ -1569,7 +1646,7 @@ ${chalk4.bold("Conventions:")}`);
1569
1646
  function displaySummarySection(scanResult) {
1570
1647
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1571
1648
  console.log(`
1572
- ${chalk4.bold("Summary:")}`);
1649
+ ${chalk5.bold("Summary:")}`);
1573
1650
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1574
1651
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1575
1652
  if (ext) {
@@ -1583,47 +1660,47 @@ function displayScanResults(scanResult) {
1583
1660
  }
1584
1661
  const { stack } = scanResult;
1585
1662
  console.log(`
1586
- ${chalk4.bold("Detected:")}`);
1663
+ ${chalk5.bold("Detected:")}`);
1587
1664
  if (stack.framework) {
1588
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
1665
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
1589
1666
  }
1590
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.language)}`);
1667
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.language)}`);
1591
1668
  if (stack.styling) {
1592
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
1669
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
1593
1670
  }
1594
1671
  if (stack.backend) {
1595
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
1672
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
1596
1673
  }
1597
1674
  if (stack.orm) {
1598
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.orm, ORM_NAMES)}`);
1675
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.orm, ORM_NAMES)}`);
1599
1676
  }
1600
1677
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1601
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1678
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1602
1679
  } else {
1603
1680
  if (stack.linter) {
1604
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.linter)}`);
1681
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)}`);
1605
1682
  }
1606
1683
  if (stack.formatter) {
1607
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.formatter)}`);
1684
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.formatter)}`);
1608
1685
  }
1609
1686
  }
1610
1687
  if (stack.testRunner) {
1611
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.testRunner)}`);
1688
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.testRunner)}`);
1612
1689
  }
1613
1690
  if (stack.packageManager) {
1614
- console.log(` ${chalk4.green("\u2713")} ${formatItem(stack.packageManager)}`);
1691
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.packageManager)}`);
1615
1692
  }
1616
1693
  if (stack.libraries.length > 0) {
1617
1694
  for (const lib of stack.libraries) {
1618
- console.log(` ${chalk4.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
1695
+ console.log(` ${chalk5.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
1619
1696
  }
1620
1697
  }
1621
1698
  const groups = groupByRole(scanResult.structure.directories);
1622
1699
  if (groups.length > 0) {
1623
1700
  console.log(`
1624
- ${chalk4.bold("Structure:")}`);
1701
+ ${chalk5.bold("Structure:")}`);
1625
1702
  for (const group of groups) {
1626
- console.log(` ${chalk4.green("\u2713")} ${formatRoleGroup(group)}`);
1703
+ console.log(` ${chalk5.green("\u2713")} ${formatRoleGroup(group)}`);
1627
1704
  }
1628
1705
  }
1629
1706
  displayConventions(scanResult);
@@ -1633,49 +1710,49 @@ ${chalk4.bold("Structure:")}`);
1633
1710
  function displayRulesPreview(config) {
1634
1711
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1635
1712
  console.log(
1636
- `${chalk4.bold("Rules:")} ${chalk4.dim("(warns on violation; use --enforce in CI to block)")}`
1713
+ `${chalk5.bold("Rules:")} ${chalk5.dim("(warns on violation; use --enforce in CI to block)")}`
1637
1714
  );
1638
- console.log(` ${chalk4.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1715
+ console.log(` ${chalk5.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1639
1716
  if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
1640
1717
  console.log(
1641
- ` ${chalk4.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1718
+ ` ${chalk5.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1642
1719
  );
1643
1720
  } else if (config.rules.testCoverage > 0) {
1644
- console.log(` ${chalk4.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1721
+ console.log(` ${chalk5.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1645
1722
  } else {
1646
- console.log(` ${chalk4.dim("\u2022")} Test coverage target: disabled`);
1723
+ console.log(` ${chalk5.dim("\u2022")} Test coverage target: disabled`);
1647
1724
  }
1648
1725
  if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1649
- console.log(` ${chalk4.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1726
+ console.log(` ${chalk5.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1650
1727
  } else {
1651
- console.log(` ${chalk4.dim("\u2022")} Enforce file naming: no`);
1728
+ console.log(` ${chalk5.dim("\u2022")} Enforce file naming: no`);
1652
1729
  }
1653
1730
  console.log(
1654
- ` ${chalk4.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1731
+ ` ${chalk5.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1655
1732
  );
1656
1733
  console.log("");
1657
1734
  }
1658
1735
  function displayInitSummary(config, exemptedPackages) {
1659
1736
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1660
1737
  const isMonorepo = config.packages.length > 1;
1661
- const ok = chalk4.green("\u2713");
1662
- const off = chalk4.dim("\u25CB");
1738
+ const ok = chalk5.green("\u2713");
1739
+ const off = chalk5.dim("\u25CB");
1663
1740
  console.log("");
1664
- console.log(` ${chalk4.bold("Rules to apply:")}`);
1665
- console.log(` ${ok} Max file size: ${chalk4.cyan(`${config.rules.maxFileLines} lines`)}`);
1741
+ console.log(` ${chalk5.bold("Rules to apply:")}`);
1742
+ console.log(` ${ok} Max file size: ${chalk5.cyan(`${config.rules.maxFileLines} lines`)}`);
1666
1743
  const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1667
1744
  if (config.rules.enforceNaming && fileNaming) {
1668
- console.log(` ${ok} File naming: ${chalk4.cyan(fileNaming)}`);
1745
+ console.log(` ${ok} File naming: ${chalk5.cyan(fileNaming)}`);
1669
1746
  } else {
1670
- console.log(` ${off} File naming: ${chalk4.dim("not enforced")}`);
1747
+ console.log(` ${off} File naming: ${chalk5.dim("not enforced")}`);
1671
1748
  }
1672
1749
  const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1673
1750
  if (config.rules.enforceMissingTests && testPattern) {
1674
- console.log(` ${ok} Missing tests: ${chalk4.cyan(`enforced (${testPattern})`)}`);
1751
+ console.log(` ${ok} Missing tests: ${chalk5.cyan(`enforced (${testPattern})`)}`);
1675
1752
  } else if (config.rules.enforceMissingTests) {
1676
- console.log(` ${ok} Missing tests: ${chalk4.cyan("enforced")}`);
1753
+ console.log(` ${ok} Missing tests: ${chalk5.cyan("enforced")}`);
1677
1754
  } else {
1678
- console.log(` ${off} Missing tests: ${chalk4.dim("not enforced")}`);
1755
+ console.log(` ${off} Missing tests: ${chalk5.dim("not enforced")}`);
1679
1756
  }
1680
1757
  if (config.rules.testCoverage > 0) {
1681
1758
  if (isMonorepo) {
@@ -1683,27 +1760,27 @@ function displayInitSummary(config, exemptedPackages) {
1683
1760
  (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
1684
1761
  );
1685
1762
  console.log(
1686
- ` ${ok} Coverage: ${chalk4.cyan(`${config.rules.testCoverage}%`)} default ${chalk4.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1763
+ ` ${ok} Coverage: ${chalk5.cyan(`${config.rules.testCoverage}%`)} default ${chalk5.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1687
1764
  );
1688
1765
  } else {
1689
- console.log(` ${ok} Coverage: ${chalk4.cyan(`${config.rules.testCoverage}%`)}`);
1766
+ console.log(` ${ok} Coverage: ${chalk5.cyan(`${config.rules.testCoverage}%`)}`);
1690
1767
  }
1691
1768
  } else {
1692
- console.log(` ${off} Coverage: ${chalk4.dim("disabled")}`);
1769
+ console.log(` ${off} Coverage: ${chalk5.dim("disabled")}`);
1693
1770
  }
1694
1771
  if (exemptedPackages.length > 0) {
1695
1772
  console.log(
1696
- ` ${chalk4.dim(" exempted:")} ${chalk4.dim(exemptedPackages.join(", "))} ${chalk4.dim("(types-only)")}`
1773
+ ` ${chalk5.dim(" exempted:")} ${chalk5.dim(exemptedPackages.join(", "))} ${chalk5.dim("(types-only)")}`
1697
1774
  );
1698
1775
  }
1699
1776
  if (isMonorepo) {
1700
1777
  console.log(
1701
1778
  `
1702
- ${chalk4.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1779
+ ${chalk5.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1703
1780
  );
1704
1781
  } else {
1705
1782
  console.log(`
1706
- ${chalk4.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1783
+ ${chalk5.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1707
1784
  }
1708
1785
  console.log("");
1709
1786
  }
@@ -2015,7 +2092,7 @@ async function configCommand(options, cwd) {
2015
2092
  }
2016
2093
  const configPath = path9.join(projectRoot, CONFIG_FILE3);
2017
2094
  if (!fs10.existsSync(configPath)) {
2018
- console.log(`${chalk5.yellow("!")} No config found. Run ${chalk5.cyan("viberails init")} first.`);
2095
+ console.log(`${chalk6.yellow("!")} No config found. Run ${chalk6.cyan("viberails init")} first.`);
2019
2096
  return;
2020
2097
  }
2021
2098
  clack6.intro("viberails config");
@@ -2100,22 +2177,22 @@ async function rescanAndMerge(projectRoot, config) {
2100
2177
  import * as fs13 from "fs";
2101
2178
  import * as path13 from "path";
2102
2179
  import { loadConfig as loadConfig4 } from "@viberails/config";
2103
- import chalk7 from "chalk";
2180
+ import chalk8 from "chalk";
2104
2181
 
2105
2182
  // src/commands/fix-helpers.ts
2106
2183
  import { execSync as execSync2 } from "child_process";
2107
- import chalk6 from "chalk";
2184
+ import chalk7 from "chalk";
2108
2185
  function printPlan(renames, stubs) {
2109
2186
  if (renames.length > 0) {
2110
- console.log(chalk6.bold("\nFile renames:"));
2187
+ console.log(chalk7.bold("\nFile renames:"));
2111
2188
  for (const r of renames) {
2112
- console.log(` ${chalk6.red(r.oldPath)} \u2192 ${chalk6.green(r.newPath)}`);
2189
+ console.log(` ${chalk7.red(r.oldPath)} \u2192 ${chalk7.green(r.newPath)}`);
2113
2190
  }
2114
2191
  }
2115
2192
  if (stubs.length > 0) {
2116
- console.log(chalk6.bold("\nTest stubs to create:"));
2193
+ console.log(chalk7.bold("\nTest stubs to create:"));
2117
2194
  for (const s of stubs) {
2118
- console.log(` ${chalk6.green("+")} ${s.path}`);
2195
+ console.log(` ${chalk7.green("+")} ${s.path}`);
2119
2196
  }
2120
2197
  }
2121
2198
  }
@@ -2149,8 +2226,62 @@ function computeNewSpecifier(oldSpecifier, newBare) {
2149
2226
  const newSpec = prefix + newBare;
2150
2227
  return hasJsExt ? `${newSpec}.js` : newSpec;
2151
2228
  }
2152
- async function updateImportsAfterRenames(renames, projectRoot) {
2229
+ async function scanForAliasImports(renames, projectRoot) {
2153
2230
  if (renames.length === 0) return [];
2231
+ const { readFile, readdir } = await import("fs/promises");
2232
+ const oldBareNames = /* @__PURE__ */ new Set();
2233
+ for (const r of renames) {
2234
+ const oldFilename = path10.basename(r.oldPath);
2235
+ oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
2236
+ }
2237
+ const importPattern = /(?:import|export)\s+.*?from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2238
+ const skipDirs = /* @__PURE__ */ new Set([
2239
+ "node_modules",
2240
+ "dist",
2241
+ "build",
2242
+ ".next",
2243
+ ".expo",
2244
+ ".turbo",
2245
+ "coverage"
2246
+ ]);
2247
+ const sourceExts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2248
+ const allEntries = await readdir(projectRoot, { recursive: true, withFileTypes: true });
2249
+ const files = allEntries.filter((e) => {
2250
+ if (!e.isFile()) return false;
2251
+ const ext = path10.extname(e.name);
2252
+ if (!sourceExts.has(ext)) return false;
2253
+ const rel = path10.join(e.parentPath, e.name);
2254
+ const segments = path10.relative(projectRoot, rel).split(path10.sep);
2255
+ return !segments.some((s) => skipDirs.has(s));
2256
+ }).map((e) => path10.join(e.parentPath, e.name));
2257
+ const aliases = [];
2258
+ for (const file of files) {
2259
+ let content;
2260
+ try {
2261
+ content = await readFile(file, "utf-8");
2262
+ } catch {
2263
+ continue;
2264
+ }
2265
+ const lines = content.split("\n");
2266
+ for (let i = 0; i < lines.length; i++) {
2267
+ const line = lines[i];
2268
+ importPattern.lastIndex = 0;
2269
+ for (let match = importPattern.exec(line); match !== null; match = importPattern.exec(line)) {
2270
+ const specifier = match[1] ?? match[2];
2271
+ if (!specifier || specifier.startsWith(".")) continue;
2272
+ if (!specifier.includes("/")) continue;
2273
+ const lastSegment = specifier.split("/").pop() ?? "";
2274
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2275
+ if (oldBareNames.has(bare)) {
2276
+ aliases.push({ file, specifier, line: i + 1 });
2277
+ }
2278
+ }
2279
+ }
2280
+ }
2281
+ return aliases;
2282
+ }
2283
+ async function updateImportsAfterRenames(renames, projectRoot) {
2284
+ if (renames.length === 0) return { updates: [], skippedAliases: [] };
2154
2285
  const { Project, SyntaxKind } = await import("ts-morph");
2155
2286
  const renameMap = /* @__PURE__ */ new Map();
2156
2287
  for (const r of renames) {
@@ -2165,15 +2296,44 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2165
2296
  });
2166
2297
  project.addSourceFilesAtPaths(path10.join(projectRoot, "**/*.{ts,tsx,js,jsx,mjs,cjs}"));
2167
2298
  const updates = [];
2299
+ const skippedAliases = [];
2168
2300
  const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
2301
+ const oldBareNames = /* @__PURE__ */ new Set();
2302
+ for (const r of renames) {
2303
+ const oldFilename = path10.basename(r.oldPath);
2304
+ oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
2305
+ }
2169
2306
  for (const sourceFile of project.getSourceFiles()) {
2170
2307
  const filePath = sourceFile.getFilePath();
2171
2308
  const segments = filePath.split(path10.sep);
2172
- if (segments.includes("node_modules") || segments.includes("dist")) continue;
2309
+ const skipDirs = /* @__PURE__ */ new Set([
2310
+ "node_modules",
2311
+ "dist",
2312
+ "build",
2313
+ ".next",
2314
+ ".expo",
2315
+ ".svelte-kit",
2316
+ ".turbo",
2317
+ "coverage"
2318
+ ]);
2319
+ if (segments.some((s) => skipDirs.has(s))) continue;
2173
2320
  const fileDir = path10.dirname(filePath);
2174
2321
  for (const decl of sourceFile.getImportDeclarations()) {
2175
2322
  const specifier = decl.getModuleSpecifierValue();
2176
- if (!specifier.startsWith(".")) continue;
2323
+ if (!specifier.startsWith(".")) {
2324
+ if (specifier.includes("/")) {
2325
+ const lastSegment = specifier.split("/").pop() ?? "";
2326
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2327
+ if (oldBareNames.has(bare)) {
2328
+ skippedAliases.push({
2329
+ file: filePath,
2330
+ specifier,
2331
+ line: decl.getStartLineNumber()
2332
+ });
2333
+ }
2334
+ }
2335
+ continue;
2336
+ }
2177
2337
  const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
2178
2338
  if (!match) continue;
2179
2339
  const newSpec = computeNewSpecifier(specifier, match.newBare);
@@ -2223,7 +2383,7 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2223
2383
  if (updates.length > 0) {
2224
2384
  await project.save();
2225
2385
  }
2226
- return updates;
2386
+ return { updates, skippedAliases };
2227
2387
  }
2228
2388
  function resolveToRenamedFile(specifier, fromDir, renameMap, extensions) {
2229
2389
  const cleanSpec = specifier.endsWith(".js") ? specifier.slice(0, -3) : specifier;
@@ -2326,10 +2486,10 @@ function generateTestStub(sourceRelPath, config, projectRoot) {
2326
2486
  const pkg = resolvePackageForFile(sourceRelPath, config);
2327
2487
  const testPattern = pkg?.structure?.testPattern;
2328
2488
  if (!testPattern) return null;
2329
- const basename8 = path12.basename(sourceRelPath);
2330
- const ext = path12.extname(basename8);
2489
+ const basename9 = path12.basename(sourceRelPath);
2490
+ const ext = path12.extname(basename9);
2331
2491
  if (!ext) return null;
2332
- const stem = basename8.slice(0, -ext.length);
2492
+ const stem = basename9.slice(0, -ext.length);
2333
2493
  const testSuffix = testPattern.replace("*", "");
2334
2494
  const testFilename = `${stem}${testSuffix}`;
2335
2495
  const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
@@ -2360,13 +2520,13 @@ async function fixCommand(options, cwd) {
2360
2520
  const startDir = cwd ?? process.cwd();
2361
2521
  const projectRoot = findProjectRoot(startDir);
2362
2522
  if (!projectRoot) {
2363
- console.error(`${chalk7.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2523
+ console.error(`${chalk8.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2364
2524
  return 1;
2365
2525
  }
2366
2526
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
2367
2527
  if (!fs13.existsSync(configPath)) {
2368
2528
  console.error(
2369
- `${chalk7.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2529
+ `${chalk8.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2370
2530
  );
2371
2531
  return 1;
2372
2532
  }
@@ -2375,7 +2535,7 @@ async function fixCommand(options, cwd) {
2375
2535
  const isDirty = checkGitDirty(projectRoot);
2376
2536
  if (isDirty) {
2377
2537
  console.log(
2378
- chalk7.yellow("Warning: You have uncommitted changes. Consider committing first.")
2538
+ chalk8.yellow("Warning: You have uncommitted changes. Consider committing first.")
2379
2539
  );
2380
2540
  }
2381
2541
  }
@@ -2404,13 +2564,59 @@ async function fixCommand(options, cwd) {
2404
2564
  if (stub) testStubs.push(stub);
2405
2565
  }
2406
2566
  }
2407
- if (dedupedRenames.length === 0 && testStubs.length === 0) {
2408
- console.log(`${chalk7.green("\u2713")} No fixable violations found.`);
2567
+ const aliasImports = await scanForAliasImports(dedupedRenames, projectRoot);
2568
+ const blockedOldBareNames = /* @__PURE__ */ new Set();
2569
+ for (const alias of aliasImports) {
2570
+ const lastSegment = alias.specifier.split("/").pop() ?? "";
2571
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2572
+ blockedOldBareNames.add(bare);
2573
+ }
2574
+ const safeRenames = dedupedRenames.filter((r) => {
2575
+ const oldFilename = path13.basename(r.oldPath);
2576
+ const bare = oldFilename.slice(0, oldFilename.indexOf("."));
2577
+ return !blockedOldBareNames.has(bare);
2578
+ });
2579
+ const skippedRenames = dedupedRenames.filter((r) => {
2580
+ const oldFilename = path13.basename(r.oldPath);
2581
+ const bare = oldFilename.slice(0, oldFilename.indexOf("."));
2582
+ return blockedOldBareNames.has(bare);
2583
+ });
2584
+ if (safeRenames.length === 0 && testStubs.length === 0 && skippedRenames.length === 0) {
2585
+ console.log(`${chalk8.green("\u2713")} No fixable violations found.`);
2586
+ return 0;
2587
+ }
2588
+ printPlan(safeRenames, testStubs);
2589
+ if (skippedRenames.length > 0) {
2590
+ console.log("");
2591
+ console.log(
2592
+ chalk8.yellow(
2593
+ `Skipping ${skippedRenames.length} rename${skippedRenames.length > 1 ? "s" : ""} \u2014 aliased imports would break:`
2594
+ )
2595
+ );
2596
+ for (const r of skippedRenames.slice(0, 5)) {
2597
+ console.log(chalk8.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
2598
+ }
2599
+ if (skippedRenames.length > 5) {
2600
+ console.log(chalk8.dim(` ... and ${skippedRenames.length - 5} more`));
2601
+ }
2602
+ console.log("");
2603
+ console.log(chalk8.yellow("Affected aliased imports:"));
2604
+ for (const alias of aliasImports.slice(0, 5)) {
2605
+ const relFile = path13.relative(projectRoot, alias.file);
2606
+ console.log(chalk8.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
2607
+ }
2608
+ if (aliasImports.length > 5) {
2609
+ console.log(chalk8.dim(` ... and ${aliasImports.length - 5} more`));
2610
+ }
2611
+ console.log(chalk8.dim(" Update these imports to relative paths first, then re-run fix."));
2612
+ }
2613
+ if (safeRenames.length === 0 && testStubs.length === 0) {
2614
+ console.log(`
2615
+ ${chalk8.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2409
2616
  return 0;
2410
2617
  }
2411
- printPlan(dedupedRenames, testStubs);
2412
2618
  if (options.dryRun) {
2413
- console.log(chalk7.dim("\nDry run \u2014 no changes applied."));
2619
+ console.log(chalk8.dim("\nDry run \u2014 no changes applied."));
2414
2620
  return 0;
2415
2621
  }
2416
2622
  if (!options.yes) {
@@ -2421,15 +2627,15 @@ async function fixCommand(options, cwd) {
2421
2627
  }
2422
2628
  }
2423
2629
  let renameCount = 0;
2424
- for (const rename of dedupedRenames) {
2630
+ for (const rename of safeRenames) {
2425
2631
  if (executeRename(rename)) {
2426
2632
  renameCount++;
2427
2633
  }
2428
2634
  }
2429
2635
  let importUpdateCount = 0;
2430
2636
  if (renameCount > 0) {
2431
- const appliedRenames = dedupedRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2432
- const updates = await updateImportsAfterRenames(appliedRenames, projectRoot);
2637
+ const appliedRenames = safeRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2638
+ const { updates } = await updateImportsAfterRenames(appliedRenames, projectRoot);
2433
2639
  importUpdateCount = updates.length;
2434
2640
  }
2435
2641
  let stubCount = 0;
@@ -2441,33 +2647,33 @@ async function fixCommand(options, cwd) {
2441
2647
  }
2442
2648
  console.log("");
2443
2649
  if (renameCount > 0) {
2444
- console.log(`${chalk7.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2650
+ console.log(`${chalk8.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2445
2651
  }
2446
2652
  if (importUpdateCount > 0) {
2447
2653
  console.log(
2448
- `${chalk7.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2654
+ `${chalk8.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2449
2655
  );
2450
2656
  }
2451
2657
  if (stubCount > 0) {
2452
- console.log(`${chalk7.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2658
+ console.log(`${chalk8.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2453
2659
  }
2454
2660
  return 0;
2455
2661
  }
2456
2662
 
2457
2663
  // src/commands/init.ts
2458
- import * as fs18 from "fs";
2459
- import * as path18 from "path";
2664
+ import * as fs19 from "fs";
2665
+ import * as path19 from "path";
2460
2666
  import * as clack8 from "@clack/prompts";
2461
2667
  import { compactConfig as compactConfig3, generateConfig } from "@viberails/config";
2462
2668
  import { scan as scan2 } from "@viberails/scanner";
2463
- import chalk11 from "chalk";
2669
+ import chalk12 from "chalk";
2464
2670
 
2465
2671
  // src/utils/check-prerequisites.ts
2466
2672
  import { spawnSync as spawnSync3 } from "child_process";
2467
2673
  import * as fs14 from "fs";
2468
2674
  import * as path14 from "path";
2469
2675
  import * as clack7 from "@clack/prompts";
2470
- import chalk8 from "chalk";
2676
+ import chalk9 from "chalk";
2471
2677
  function checkCoveragePrereqs(projectRoot, scanResult) {
2472
2678
  const pm = scanResult.stack.packageManager.name;
2473
2679
  const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
@@ -2498,9 +2704,9 @@ function displayMissingPrereqs(prereqs) {
2498
2704
  const missing = prereqs.filter((p) => !p.installed);
2499
2705
  for (const m of missing) {
2500
2706
  const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2501
- console.log(` ${chalk8.yellow("!")} ${m.label} not installed${suffix}`);
2707
+ console.log(` ${chalk9.yellow("!")} ${m.label} not installed${suffix}`);
2502
2708
  if (m.installCommand) {
2503
- console.log(` Install: ${chalk8.cyan(m.installCommand)}`);
2709
+ console.log(` Install: ${chalk9.cyan(m.installCommand)}`);
2504
2710
  }
2505
2711
  }
2506
2712
  }
@@ -2611,46 +2817,85 @@ function updateGitignore(projectRoot) {
2611
2817
  }
2612
2818
 
2613
2819
  // src/commands/init-hooks.ts
2820
+ import * as fs17 from "fs";
2821
+ import * as path17 from "path";
2822
+ import chalk10 from "chalk";
2823
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2824
+
2825
+ // src/commands/resolve-typecheck.ts
2614
2826
  import * as fs16 from "fs";
2615
2827
  import * as path16 from "path";
2616
- import chalk9 from "chalk";
2617
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2828
+ function hasTurboTask(projectRoot, taskName) {
2829
+ const turboPath = path16.join(projectRoot, "turbo.json");
2830
+ if (!fs16.existsSync(turboPath)) return false;
2831
+ try {
2832
+ const turbo = JSON.parse(fs16.readFileSync(turboPath, "utf-8"));
2833
+ const tasks = turbo.tasks ?? turbo.pipeline ?? {};
2834
+ return taskName in tasks;
2835
+ } catch {
2836
+ return false;
2837
+ }
2838
+ }
2839
+ function resolveTypecheckCommand(projectRoot, packageManager) {
2840
+ if (hasTurboTask(projectRoot, "typecheck")) {
2841
+ return { command: "npx turbo typecheck", label: "turbo typecheck" };
2842
+ }
2843
+ const pkgJsonPath = path16.join(projectRoot, "package.json");
2844
+ if (fs16.existsSync(pkgJsonPath)) {
2845
+ try {
2846
+ const pkg = JSON.parse(fs16.readFileSync(pkgJsonPath, "utf-8"));
2847
+ if (pkg.scripts?.typecheck) {
2848
+ const pm = packageManager ?? "npm";
2849
+ return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
2850
+ }
2851
+ } catch {
2852
+ }
2853
+ }
2854
+ if (fs16.existsSync(path16.join(projectRoot, "tsconfig.json"))) {
2855
+ return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
2856
+ }
2857
+ return {
2858
+ reason: "no root tsconfig.json, no typecheck script, and no turbo typecheck task found"
2859
+ };
2860
+ }
2861
+
2862
+ // src/commands/init-hooks.ts
2618
2863
  function setupPreCommitHook(projectRoot) {
2619
- const lefthookPath = path16.join(projectRoot, "lefthook.yml");
2620
- if (fs16.existsSync(lefthookPath)) {
2864
+ const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2865
+ if (fs17.existsSync(lefthookPath)) {
2621
2866
  addLefthookPreCommit(lefthookPath);
2622
- console.log(` ${chalk9.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
2867
+ console.log(` ${chalk10.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
2623
2868
  return "lefthook.yml";
2624
2869
  }
2625
- const huskyDir = path16.join(projectRoot, ".husky");
2626
- if (fs16.existsSync(huskyDir)) {
2870
+ const huskyDir = path17.join(projectRoot, ".husky");
2871
+ if (fs17.existsSync(huskyDir)) {
2627
2872
  writeHuskyPreCommit(huskyDir);
2628
- console.log(` ${chalk9.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
2873
+ console.log(` ${chalk10.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
2629
2874
  return ".husky/pre-commit";
2630
2875
  }
2631
- const gitDir = path16.join(projectRoot, ".git");
2632
- if (fs16.existsSync(gitDir)) {
2633
- const hooksDir = path16.join(gitDir, "hooks");
2634
- if (!fs16.existsSync(hooksDir)) {
2635
- fs16.mkdirSync(hooksDir, { recursive: true });
2876
+ const gitDir = path17.join(projectRoot, ".git");
2877
+ if (fs17.existsSync(gitDir)) {
2878
+ const hooksDir = path17.join(gitDir, "hooks");
2879
+ if (!fs17.existsSync(hooksDir)) {
2880
+ fs17.mkdirSync(hooksDir, { recursive: true });
2636
2881
  }
2637
2882
  writeGitHookPreCommit(hooksDir);
2638
- console.log(` ${chalk9.green("\u2713")} .git/hooks/pre-commit`);
2883
+ console.log(` ${chalk10.green("\u2713")} .git/hooks/pre-commit`);
2639
2884
  return ".git/hooks/pre-commit";
2640
2885
  }
2641
2886
  return void 0;
2642
2887
  }
2643
2888
  function writeGitHookPreCommit(hooksDir) {
2644
- const hookPath = path16.join(hooksDir, "pre-commit");
2645
- if (fs16.existsSync(hookPath)) {
2646
- const existing = fs16.readFileSync(hookPath, "utf-8");
2889
+ const hookPath = path17.join(hooksDir, "pre-commit");
2890
+ if (fs17.existsSync(hookPath)) {
2891
+ const existing = fs17.readFileSync(hookPath, "utf-8");
2647
2892
  if (existing.includes("viberails")) return;
2648
- fs16.writeFileSync(
2893
+ fs17.writeFileSync(
2649
2894
  hookPath,
2650
2895
  `${existing.trimEnd()}
2651
2896
 
2652
2897
  # viberails check
2653
- npx viberails check --staged
2898
+ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi
2654
2899
  `
2655
2900
  );
2656
2901
  return;
@@ -2659,13 +2904,13 @@ npx viberails check --staged
2659
2904
  "#!/bin/sh",
2660
2905
  "# Generated by viberails \u2014 https://viberails.sh",
2661
2906
  "",
2662
- "npx viberails check --staged",
2907
+ "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
2663
2908
  ""
2664
2909
  ].join("\n");
2665
- fs16.writeFileSync(hookPath, script, { mode: 493 });
2910
+ fs17.writeFileSync(hookPath, script, { mode: 493 });
2666
2911
  }
2667
2912
  function addLefthookPreCommit(lefthookPath) {
2668
- const content = fs16.readFileSync(lefthookPath, "utf-8");
2913
+ const content = fs17.readFileSync(lefthookPath, "utf-8");
2669
2914
  if (content.includes("viberails")) return;
2670
2915
  const doc = parseYaml(content) ?? {};
2671
2916
  if (!doc["pre-commit"]) {
@@ -2675,30 +2920,30 @@ function addLefthookPreCommit(lefthookPath) {
2675
2920
  doc["pre-commit"].commands = {};
2676
2921
  }
2677
2922
  doc["pre-commit"].commands.viberails = {
2678
- run: "npx viberails check --staged"
2923
+ run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
2679
2924
  };
2680
- fs16.writeFileSync(lefthookPath, stringifyYaml(doc));
2925
+ fs17.writeFileSync(lefthookPath, stringifyYaml(doc));
2681
2926
  }
2682
2927
  function detectHookManager(projectRoot) {
2683
- if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2684
- if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
2928
+ if (fs17.existsSync(path17.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2929
+ if (fs17.existsSync(path17.join(projectRoot, ".husky"))) return "Husky";
2685
2930
  return void 0;
2686
2931
  }
2687
2932
  function setupClaudeCodeHook(projectRoot) {
2688
- const claudeDir = path16.join(projectRoot, ".claude");
2689
- if (!fs16.existsSync(claudeDir)) {
2690
- fs16.mkdirSync(claudeDir, { recursive: true });
2933
+ const claudeDir = path17.join(projectRoot, ".claude");
2934
+ if (!fs17.existsSync(claudeDir)) {
2935
+ fs17.mkdirSync(claudeDir, { recursive: true });
2691
2936
  }
2692
- const settingsPath = path16.join(claudeDir, "settings.json");
2937
+ const settingsPath = path17.join(claudeDir, "settings.json");
2693
2938
  let settings = {};
2694
- if (fs16.existsSync(settingsPath)) {
2939
+ if (fs17.existsSync(settingsPath)) {
2695
2940
  try {
2696
- settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
2941
+ settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
2697
2942
  } catch {
2698
2943
  console.warn(
2699
- ` ${chalk9.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
2944
+ ` ${chalk10.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
2700
2945
  );
2701
- console.warn(` Fix the JSON manually, then re-run ${chalk9.cyan("viberails init --force")}`);
2946
+ console.warn(` Fix the JSON manually, then re-run ${chalk10.cyan("viberails init --force")}`);
2702
2947
  return;
2703
2948
  }
2704
2949
  }
@@ -2719,30 +2964,30 @@ function setupClaudeCodeHook(projectRoot) {
2719
2964
  }
2720
2965
  ];
2721
2966
  settings.hooks = hooks;
2722
- fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2967
+ fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2723
2968
  `);
2724
- console.log(` ${chalk9.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
2969
+ console.log(` ${chalk10.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
2725
2970
  }
2726
2971
  function setupClaudeMdReference(projectRoot) {
2727
- const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
2972
+ const claudeMdPath = path17.join(projectRoot, "CLAUDE.md");
2728
2973
  let content = "";
2729
- if (fs16.existsSync(claudeMdPath)) {
2730
- content = fs16.readFileSync(claudeMdPath, "utf-8");
2974
+ if (fs17.existsSync(claudeMdPath)) {
2975
+ content = fs17.readFileSync(claudeMdPath, "utf-8");
2731
2976
  }
2732
2977
  if (content.includes("@.viberails/context.md")) return;
2733
2978
  const ref = "\n@.viberails/context.md\n";
2734
2979
  const prefix = content.length === 0 ? "" : content.trimEnd();
2735
- fs16.writeFileSync(claudeMdPath, prefix + ref);
2736
- console.log(` ${chalk9.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2980
+ fs17.writeFileSync(claudeMdPath, prefix + ref);
2981
+ console.log(` ${chalk10.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2737
2982
  }
2738
2983
  function setupGithubAction(projectRoot, packageManager, options) {
2739
- const workflowDir = path16.join(projectRoot, ".github", "workflows");
2740
- const workflowPath = path16.join(workflowDir, "viberails.yml");
2741
- if (fs16.existsSync(workflowPath)) {
2742
- const existing = fs16.readFileSync(workflowPath, "utf-8");
2984
+ const workflowDir = path17.join(projectRoot, ".github", "workflows");
2985
+ const workflowPath = path17.join(workflowDir, "viberails.yml");
2986
+ if (fs17.existsSync(workflowPath)) {
2987
+ const existing = fs17.readFileSync(workflowPath, "utf-8");
2743
2988
  if (existing.includes("viberails")) return void 0;
2744
2989
  }
2745
- fs16.mkdirSync(workflowDir, { recursive: true });
2990
+ fs17.mkdirSync(workflowDir, { recursive: true });
2746
2991
  const pm = packageManager || "npm";
2747
2992
  const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
2748
2993
  const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
@@ -2751,7 +2996,7 @@ function setupGithubAction(projectRoot, packageManager, options) {
2751
2996
  "",
2752
2997
  "on:",
2753
2998
  " pull_request:",
2754
- " branches: [main]",
2999
+ " branches: [main, master]",
2755
3000
  "",
2756
3001
  "jobs:",
2757
3002
  " check:",
@@ -2769,87 +3014,94 @@ function setupGithubAction(projectRoot, packageManager, options) {
2769
3014
  " - uses: actions/setup-node@v4",
2770
3015
  " with:",
2771
3016
  " node-version: 22",
2772
- pm !== "npm" ? ` cache: ${pm}` : "",
3017
+ ` cache: ${pm}`,
2773
3018
  "",
2774
3019
  ` - run: ${installCmd}`
2775
3020
  );
2776
3021
  if (options?.typecheck) {
2777
- lines.push(` - run: ${runPrefix} tsc --noEmit`);
3022
+ const resolved = resolveTypecheckCommand(projectRoot, pm);
3023
+ if (resolved.command) {
3024
+ const ciCmd = resolved.command.startsWith("npx ") ? `${runPrefix} ${resolved.command.slice(4)}` : resolved.command;
3025
+ lines.push(` - run: ${ciCmd}`);
3026
+ }
2778
3027
  }
2779
3028
  if (options?.linter) {
2780
3029
  const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2781
3030
  lines.push(` - run: ${runPrefix} ${lintCmd}`);
2782
3031
  }
2783
3032
  lines.push(
2784
- ` - run: ${runPrefix} viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
3033
+ ` - run: npx viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
2785
3034
  ""
2786
3035
  );
2787
3036
  const content = lines.filter((l) => l !== void 0).join("\n");
2788
- fs16.writeFileSync(workflowPath, content);
3037
+ fs17.writeFileSync(workflowPath, content);
2789
3038
  return ".github/workflows/viberails.yml";
2790
3039
  }
2791
3040
  function writeHuskyPreCommit(huskyDir) {
2792
- const hookPath = path16.join(huskyDir, "pre-commit");
2793
- if (fs16.existsSync(hookPath)) {
2794
- const existing = fs16.readFileSync(hookPath, "utf-8");
3041
+ const hookPath = path17.join(huskyDir, "pre-commit");
3042
+ const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
3043
+ if (fs17.existsSync(hookPath)) {
3044
+ const existing = fs17.readFileSync(hookPath, "utf-8");
2795
3045
  if (!existing.includes("viberails")) {
2796
- fs16.writeFileSync(hookPath, `${existing.trimEnd()}
2797
- npx viberails check --staged
3046
+ fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3047
+ ${cmd}
2798
3048
  `);
2799
3049
  }
2800
3050
  return;
2801
3051
  }
2802
- fs16.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
3052
+ fs17.writeFileSync(hookPath, `#!/bin/sh
3053
+ ${cmd}
3054
+ `, { mode: 493 });
2803
3055
  }
2804
3056
 
2805
3057
  // src/commands/init-hooks-extra.ts
2806
- import * as fs17 from "fs";
2807
- import * as path17 from "path";
2808
- import chalk10 from "chalk";
3058
+ import * as fs18 from "fs";
3059
+ import * as path18 from "path";
3060
+ import chalk11 from "chalk";
2809
3061
  import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
2810
3062
  function addPreCommitStep(projectRoot, name, command, marker) {
2811
- const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2812
- if (fs17.existsSync(lefthookPath)) {
2813
- const content = fs17.readFileSync(lefthookPath, "utf-8");
3063
+ const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3064
+ if (fs18.existsSync(lefthookPath)) {
3065
+ const content = fs18.readFileSync(lefthookPath, "utf-8");
2814
3066
  if (content.includes(marker)) return void 0;
2815
3067
  const doc = parseYaml2(content) ?? {};
2816
3068
  if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
2817
3069
  if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
2818
3070
  doc["pre-commit"].commands[name] = { run: command };
2819
- fs17.writeFileSync(lefthookPath, stringifyYaml2(doc));
3071
+ fs18.writeFileSync(lefthookPath, stringifyYaml2(doc));
2820
3072
  return "lefthook.yml";
2821
3073
  }
2822
- const huskyDir = path17.join(projectRoot, ".husky");
2823
- if (fs17.existsSync(huskyDir)) {
2824
- const hookPath = path17.join(huskyDir, "pre-commit");
2825
- if (fs17.existsSync(hookPath)) {
2826
- const existing = fs17.readFileSync(hookPath, "utf-8");
3074
+ const huskyDir = path18.join(projectRoot, ".husky");
3075
+ if (fs18.existsSync(huskyDir)) {
3076
+ const hookPath = path18.join(huskyDir, "pre-commit");
3077
+ if (fs18.existsSync(hookPath)) {
3078
+ const existing = fs18.readFileSync(hookPath, "utf-8");
2827
3079
  if (existing.includes(marker)) return void 0;
2828
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3080
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2829
3081
  ${command}
2830
3082
  `);
2831
3083
  } else {
2832
- fs17.writeFileSync(hookPath, `#!/bin/sh
3084
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2833
3085
  ${command}
2834
3086
  `, { mode: 493 });
2835
3087
  }
2836
3088
  return ".husky/pre-commit";
2837
3089
  }
2838
- const gitDir = path17.join(projectRoot, ".git");
2839
- if (fs17.existsSync(gitDir)) {
2840
- const hooksDir = path17.join(gitDir, "hooks");
2841
- if (!fs17.existsSync(hooksDir)) fs17.mkdirSync(hooksDir, { recursive: true });
2842
- const hookPath = path17.join(hooksDir, "pre-commit");
2843
- if (fs17.existsSync(hookPath)) {
2844
- const existing = fs17.readFileSync(hookPath, "utf-8");
3090
+ const gitDir = path18.join(projectRoot, ".git");
3091
+ if (fs18.existsSync(gitDir)) {
3092
+ const hooksDir = path18.join(gitDir, "hooks");
3093
+ if (!fs18.existsSync(hooksDir)) fs18.mkdirSync(hooksDir, { recursive: true });
3094
+ const hookPath = path18.join(hooksDir, "pre-commit");
3095
+ if (fs18.existsSync(hookPath)) {
3096
+ const existing = fs18.readFileSync(hookPath, "utf-8");
2845
3097
  if (existing.includes(marker)) return void 0;
2846
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3098
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2847
3099
 
2848
3100
  # ${name}
2849
3101
  ${command}
2850
3102
  `);
2851
3103
  } else {
2852
- fs17.writeFileSync(hookPath, `#!/bin/sh
3104
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2853
3105
  # Generated by viberails
2854
3106
 
2855
3107
  # ${name}
@@ -2862,10 +3114,15 @@ ${command}
2862
3114
  }
2863
3115
  return void 0;
2864
3116
  }
2865
- function setupTypecheckHook(projectRoot) {
2866
- const target = addPreCommitStep(projectRoot, "typecheck", "npx tsc --noEmit", "tsc");
3117
+ function setupTypecheckHook(projectRoot, packageManager) {
3118
+ const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3119
+ if (!resolved.command) {
3120
+ console.log(` ${chalk11.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3121
+ return void 0;
3122
+ }
3123
+ const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
2867
3124
  if (target) {
2868
- console.log(` ${chalk10.green("\u2713")} ${target} \u2014 added typecheck (tsc --noEmit)`);
3125
+ console.log(` ${chalk11.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
2869
3126
  }
2870
3127
  return target;
2871
3128
  }
@@ -2874,7 +3131,7 @@ function setupLintHook(projectRoot, linter) {
2874
3131
  const linterName = linter === "biome" ? "Biome" : "ESLint";
2875
3132
  const target = addPreCommitStep(projectRoot, "lint", command, linter);
2876
3133
  if (target) {
2877
- console.log(` ${chalk10.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3134
+ console.log(` ${chalk11.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
2878
3135
  }
2879
3136
  return target;
2880
3137
  }
@@ -2885,7 +3142,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2885
3142
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
2886
3143
  }
2887
3144
  if (integrations.typecheckHook) {
2888
- const t = setupTypecheckHook(projectRoot);
3145
+ const t = setupTypecheckHook(projectRoot, opts.packageManager);
2889
3146
  if (t) created.push(`${t} \u2014 added typecheck`);
2890
3147
  }
2891
3148
  if (integrations.lintHook && opts.linter) {
@@ -2922,11 +3179,11 @@ async function initCommand(options, cwd) {
2922
3179
  "No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
2923
3180
  );
2924
3181
  }
2925
- const configPath = path18.join(projectRoot, CONFIG_FILE5);
2926
- if (fs18.existsSync(configPath) && !options.force) {
3182
+ const configPath = path19.join(projectRoot, CONFIG_FILE5);
3183
+ if (fs19.existsSync(configPath) && !options.force) {
2927
3184
  console.log(
2928
- `${chalk11.yellow("!")} viberails is already initialized.
2929
- Run ${chalk11.cyan("viberails config")} to edit rules, ${chalk11.cyan("viberails sync")} to update, or ${chalk11.cyan("viberails init --force")} to start fresh.`
3185
+ `${chalk12.yellow("!")} viberails is already initialized.
3186
+ Run ${chalk12.cyan("viberails config")} to edit rules, ${chalk12.cyan("viberails sync")} to update, or ${chalk12.cyan("viberails init --force")} to start fresh.`
2930
3187
  );
2931
3188
  return;
2932
3189
  }
@@ -2934,7 +3191,7 @@ async function initCommand(options, cwd) {
2934
3191
  await initInteractive(projectRoot, configPath, options);
2935
3192
  }
2936
3193
  async function initNonInteractive(projectRoot, configPath) {
2937
- console.log(chalk11.dim("Scanning project..."));
3194
+ console.log(chalk12.dim("Scanning project..."));
2938
3195
  const scanResult = await scan2(projectRoot);
2939
3196
  const config = generateConfig(scanResult);
2940
3197
  for (const pkg of config.packages) {
@@ -2947,11 +3204,11 @@ async function initNonInteractive(projectRoot, configPath) {
2947
3204
  const exempted = getExemptedPackages(config);
2948
3205
  if (exempted.length > 0) {
2949
3206
  console.log(
2950
- ` ${chalk11.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk11.dim("(types-only)")}`
3207
+ ` ${chalk12.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk12.dim("(types-only)")}`
2951
3208
  );
2952
3209
  }
2953
3210
  if (config.packages.length > 1) {
2954
- console.log(chalk11.dim("Building import graph..."));
3211
+ console.log(chalk12.dim("Building import graph..."));
2955
3212
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
2956
3213
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
2957
3214
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -2964,16 +3221,16 @@ async function initNonInteractive(projectRoot, configPath) {
2964
3221
  }
2965
3222
  }
2966
3223
  const compacted = compactConfig3(config);
2967
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3224
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
2968
3225
  `);
2969
3226
  writeGeneratedFiles(projectRoot, config, scanResult);
2970
3227
  updateGitignore(projectRoot);
2971
3228
  setupClaudeCodeHook(projectRoot);
2972
3229
  setupClaudeMdReference(projectRoot);
2973
3230
  const rootPkg = config.packages[0];
2974
- const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
3231
+ const rootPkgPm = rootPkg?.stack?.packageManager?.split("@")[0] ?? "npm";
2975
3232
  const linter = rootPkg?.stack?.linter?.split("@")[0];
2976
- const isTypeScript = rootPkg?.stack?.language === "typescript";
3233
+ const isTypeScript = rootPkg?.stack?.language?.split("@")[0] === "typescript";
2977
3234
  const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
2978
3235
  linter,
2979
3236
  typecheck: isTypeScript
@@ -2981,17 +3238,17 @@ async function initNonInteractive(projectRoot, configPath) {
2981
3238
  const hookManager = detectHookManager(projectRoot);
2982
3239
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
2983
3240
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
2984
- const ok = chalk11.green("\u2713");
3241
+ const ok = chalk12.green("\u2713");
2985
3242
  const created = [
2986
- `${ok} ${path18.basename(configPath)}`,
3243
+ `${ok} ${path19.basename(configPath)}`,
2987
3244
  `${ok} .viberails/context.md`,
2988
3245
  `${ok} .viberails/scan-result.json`,
2989
3246
  `${ok} .claude/settings.json \u2014 added viberails hook`,
2990
3247
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
2991
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk11.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3248
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk12.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
2992
3249
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
2993
3250
  ].filter(Boolean);
2994
- if (hasHookManager && rootPkg?.stack?.language === "typescript") setupTypecheckHook(projectRoot);
3251
+ if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
2995
3252
  if (hasHookManager && linter) setupLintHook(projectRoot, linter);
2996
3253
  console.log(`
2997
3254
  Created:
@@ -2999,9 +3256,9 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
2999
3256
  }
3000
3257
  async function initInteractive(projectRoot, configPath, options) {
3001
3258
  clack8.intro("viberails");
3002
- if (fs18.existsSync(configPath) && options.force) {
3259
+ if (fs19.existsSync(configPath) && options.force) {
3003
3260
  const replace = await confirmDangerous(
3004
- `${path18.basename(configPath)} already exists and will be replaced. Continue?`
3261
+ `${path19.basename(configPath)} already exists and will be replaced. Continue?`
3005
3262
  );
3006
3263
  if (!replace) {
3007
3264
  clack8.outro("Aborted. No files were written.");
@@ -3013,13 +3270,6 @@ async function initInteractive(projectRoot, configPath, options) {
3013
3270
  const scanResult = await scan2(projectRoot);
3014
3271
  const config = generateConfig(scanResult);
3015
3272
  s.stop("Scan complete");
3016
- const prereqResult = await promptMissingPrereqs(
3017
- projectRoot,
3018
- checkCoveragePrereqs(projectRoot, scanResult)
3019
- );
3020
- if (prereqResult.disableCoverage) {
3021
- config.rules.testCoverage = 0;
3022
- }
3023
3273
  if (scanResult.statistics.totalFiles === 0) {
3024
3274
  clack8.log.warn(
3025
3275
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
@@ -3060,20 +3310,28 @@ async function initInteractive(projectRoot, configPath, options) {
3060
3310
  if (denyCount > 0) {
3061
3311
  config.boundaries = inferred;
3062
3312
  config.rules.enforceBoundaries = true;
3063
- bs.stop(`Inferred ${denyCount} boundary rules`);
3064
- const boundaryLines = Object.entries(inferred.deny).map(([pkg, denied]) => `${pkg} must NOT import from: ${denied.join(", ")}`).join("\n");
3065
- clack8.note(boundaryLines, "Boundary rules");
3313
+ const pkgCount = Object.keys(inferred.deny).length;
3314
+ bs.stop(`Inferred ${denyCount} boundary rules across ${pkgCount} packages`);
3066
3315
  } else {
3067
3316
  bs.stop("No boundary rules inferred");
3068
3317
  }
3069
3318
  }
3070
3319
  }
3071
3320
  const hookManager = detectHookManager(projectRoot);
3321
+ const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3322
+ const hasMissingPrereqs = coveragePrereqs.some((p) => !p.installed) || !hookManager;
3323
+ if (hasMissingPrereqs) {
3324
+ clack8.log.info("Some dependencies are needed for full functionality.");
3325
+ }
3326
+ const prereqResult = await promptMissingPrereqs(projectRoot, coveragePrereqs);
3327
+ if (prereqResult.disableCoverage) {
3328
+ config.rules.testCoverage = 0;
3329
+ }
3072
3330
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
3073
3331
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3074
- isTypeScript: rootPkgStack?.language === "typescript",
3332
+ isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3075
3333
  linter: rootPkgStack?.linter?.split("@")[0],
3076
- packageManager: rootPkgStack?.packageManager,
3334
+ packageManager: rootPkgStack?.packageManager?.split("@")[0],
3077
3335
  isWorkspace: config.packages.length > 1
3078
3336
  });
3079
3337
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
@@ -3082,40 +3340,37 @@ async function initInteractive(projectRoot, configPath, options) {
3082
3340
  return;
3083
3341
  }
3084
3342
  const compacted = compactConfig3(config);
3085
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3343
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3086
3344
  `);
3087
3345
  writeGeneratedFiles(projectRoot, config, scanResult);
3088
3346
  updateGitignore(projectRoot);
3089
- const createdFiles = [
3090
- path18.basename(configPath),
3091
- ".viberails/context.md",
3092
- ".viberails/scan-result.json",
3093
- ...setupSelectedIntegrations(projectRoot, integrations, {
3094
- linter: rootPkgStack?.linter?.split("@")[0],
3095
- packageManager: rootPkgStack?.packageManager
3096
- })
3097
- ];
3098
- clack8.log.success(`Created:
3099
- ${createdFiles.map((f) => ` ${f}`).join("\n")}`);
3347
+ const ok = chalk12.green("\u2713");
3348
+ clack8.log.step(`${ok} ${path19.basename(configPath)}`);
3349
+ clack8.log.step(`${ok} .viberails/context.md`);
3350
+ clack8.log.step(`${ok} .viberails/scan-result.json`);
3351
+ setupSelectedIntegrations(projectRoot, integrations, {
3352
+ linter: rootPkgStack?.linter?.split("@")[0],
3353
+ packageManager: rootPkgStack?.packageManager?.split("@")[0]
3354
+ });
3100
3355
  clack8.outro(
3101
3356
  `Done! Next: review viberails.config.json, then run viberails check
3102
- ${chalk11.dim("Tip: use")} ${chalk11.cyan("viberails check --enforce")} ${chalk11.dim("in CI to block PRs on violations.")}`
3357
+ ${chalk12.dim("Tip: use")} ${chalk12.cyan("viberails check --enforce")} ${chalk12.dim("in CI to block PRs on violations.")}`
3103
3358
  );
3104
3359
  }
3105
3360
 
3106
3361
  // src/commands/sync.ts
3107
- import * as fs19 from "fs";
3108
- import * as path19 from "path";
3362
+ import * as fs20 from "fs";
3363
+ import * as path20 from "path";
3109
3364
  import * as clack9 from "@clack/prompts";
3110
3365
  import { compactConfig as compactConfig4, loadConfig as loadConfig5, mergeConfig as mergeConfig2 } from "@viberails/config";
3111
3366
  import { scan as scan3 } from "@viberails/scanner";
3112
- import chalk12 from "chalk";
3367
+ import chalk13 from "chalk";
3113
3368
  var CONFIG_FILE6 = "viberails.config.json";
3114
3369
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
3115
3370
  function loadPreviousStats(projectRoot) {
3116
- const scanResultPath = path19.join(projectRoot, SCAN_RESULT_FILE2);
3371
+ const scanResultPath = path20.join(projectRoot, SCAN_RESULT_FILE2);
3117
3372
  try {
3118
- const raw = fs19.readFileSync(scanResultPath, "utf-8");
3373
+ const raw = fs20.readFileSync(scanResultPath, "utf-8");
3119
3374
  const parsed = JSON.parse(raw);
3120
3375
  if (parsed?.statistics?.totalFiles !== void 0) {
3121
3376
  return parsed.statistics;
@@ -3132,15 +3387,15 @@ async function syncCommand(options, cwd) {
3132
3387
  "No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
3133
3388
  );
3134
3389
  }
3135
- const configPath = path19.join(projectRoot, CONFIG_FILE6);
3390
+ const configPath = path20.join(projectRoot, CONFIG_FILE6);
3136
3391
  const existing = await loadConfig5(configPath);
3137
3392
  const previousStats = loadPreviousStats(projectRoot);
3138
- console.log(chalk12.dim("Scanning project..."));
3393
+ console.log(chalk13.dim("Scanning project..."));
3139
3394
  const scanResult = await scan3(projectRoot);
3140
3395
  const merged = mergeConfig2(existing, scanResult);
3141
3396
  const compacted = compactConfig4(merged);
3142
3397
  const compactedJson = JSON.stringify(compacted, null, 2);
3143
- const rawDisk = fs19.readFileSync(configPath, "utf-8").trim();
3398
+ const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
3144
3399
  const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3145
3400
  const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3146
3401
  const configChanged = diskWithoutSync !== mergedWithoutSync;
@@ -3148,13 +3403,13 @@ async function syncCommand(options, cwd) {
3148
3403
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
3149
3404
  if (changes.length > 0 || statsDelta) {
3150
3405
  console.log(`
3151
- ${chalk12.bold("Changes:")}`);
3406
+ ${chalk13.bold("Changes:")}`);
3152
3407
  for (const change of changes) {
3153
- const icon = change.type === "removed" ? chalk12.red("-") : chalk12.green("+");
3408
+ const icon = change.type === "removed" ? chalk13.red("-") : chalk13.green("+");
3154
3409
  console.log(` ${icon} ${change.description}`);
3155
3410
  }
3156
3411
  if (statsDelta) {
3157
- console.log(` ${chalk12.dim(statsDelta)}`);
3412
+ console.log(` ${chalk13.dim(statsDelta)}`);
3158
3413
  }
3159
3414
  }
3160
3415
  if (options?.interactive) {
@@ -3187,7 +3442,7 @@ ${chalk12.bold("Changes:")}`);
3187
3442
  });
3188
3443
  applyRuleOverrides(merged, overrides);
3189
3444
  const recompacted = compactConfig4(merged);
3190
- fs19.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3445
+ fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3191
3446
  `);
3192
3447
  writeGeneratedFiles(projectRoot, merged, scanResult);
3193
3448
  clack9.log.success("Updated config with your customizations.");
@@ -3195,22 +3450,22 @@ ${chalk12.bold("Changes:")}`);
3195
3450
  return;
3196
3451
  }
3197
3452
  }
3198
- fs19.writeFileSync(configPath, `${compactedJson}
3453
+ fs20.writeFileSync(configPath, `${compactedJson}
3199
3454
  `);
3200
3455
  writeGeneratedFiles(projectRoot, merged, scanResult);
3201
3456
  console.log(`
3202
- ${chalk12.bold("Synced:")}`);
3457
+ ${chalk13.bold("Synced:")}`);
3203
3458
  if (configChanged) {
3204
- console.log(` ${chalk12.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3459
+ console.log(` ${chalk13.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3205
3460
  } else {
3206
- console.log(` ${chalk12.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3461
+ console.log(` ${chalk13.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3207
3462
  }
3208
- console.log(` ${chalk12.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3209
- console.log(` ${chalk12.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3463
+ console.log(` ${chalk13.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3464
+ console.log(` ${chalk13.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3210
3465
  }
3211
3466
 
3212
3467
  // src/index.ts
3213
- var VERSION = "0.5.4";
3468
+ var VERSION = "0.6.0";
3214
3469
  var program = new Command();
3215
3470
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3216
3471
  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) => {
@@ -3218,7 +3473,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
3218
3473
  await initCommand(options);
3219
3474
  } catch (err) {
3220
3475
  const message = err instanceof Error ? err.message : String(err);
3221
- console.error(`${chalk13.red("Error:")} ${message}`);
3476
+ console.error(`${chalk14.red("Error:")} ${message}`);
3222
3477
  process.exit(1);
3223
3478
  }
3224
3479
  });
@@ -3227,7 +3482,7 @@ program.command("sync").description("Re-scan and update generated files").option
3227
3482
  await syncCommand(options);
3228
3483
  } catch (err) {
3229
3484
  const message = err instanceof Error ? err.message : String(err);
3230
- console.error(`${chalk13.red("Error:")} ${message}`);
3485
+ console.error(`${chalk14.red("Error:")} ${message}`);
3231
3486
  process.exit(1);
3232
3487
  }
3233
3488
  });
@@ -3236,7 +3491,7 @@ program.command("config").description("Interactively edit existing config rules"
3236
3491
  await configCommand(options);
3237
3492
  } catch (err) {
3238
3493
  const message = err instanceof Error ? err.message : String(err);
3239
- console.error(`${chalk13.red("Error:")} ${message}`);
3494
+ console.error(`${chalk14.red("Error:")} ${message}`);
3240
3495
  process.exit(1);
3241
3496
  }
3242
3497
  });
@@ -3257,7 +3512,7 @@ program.command("check").description("Check files against enforced rules").optio
3257
3512
  process.exit(exitCode);
3258
3513
  } catch (err) {
3259
3514
  const message = err instanceof Error ? err.message : String(err);
3260
- console.error(`${chalk13.red("Error:")} ${message}`);
3515
+ console.error(`${chalk14.red("Error:")} ${message}`);
3261
3516
  process.exit(1);
3262
3517
  }
3263
3518
  }
@@ -3268,7 +3523,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
3268
3523
  process.exit(exitCode);
3269
3524
  } catch (err) {
3270
3525
  const message = err instanceof Error ? err.message : String(err);
3271
- console.error(`${chalk13.red("Error:")} ${message}`);
3526
+ console.error(`${chalk14.red("Error:")} ${message}`);
3272
3527
  process.exit(1);
3273
3528
  }
3274
3529
  });
@@ -3277,7 +3532,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
3277
3532
  await boundariesCommand(options);
3278
3533
  } catch (err) {
3279
3534
  const message = err instanceof Error ? err.message : String(err);
3280
- console.error(`${chalk13.red("Error:")} ${message}`);
3535
+ console.error(`${chalk14.red("Error:")} ${message}`);
3281
3536
  process.exit(1);
3282
3537
  }
3283
3538
  });