viberails 0.5.5 → 0.6.1

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_chalk13 = __toESM(require("chalk"), 1);
37
+ var import_chalk14 = __toESM(require("chalk"), 1);
38
38
  var import_commander = require("commander");
39
39
 
40
40
  // src/commands/boundaries.ts
@@ -95,11 +95,11 @@ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace
95
95
  stdio: "pipe"
96
96
  });
97
97
  if (result.status === 0) {
98
- const fs20 = await import("fs");
99
- const path20 = await import("path");
100
- const lefthookPath = path20.join(projectRoot, "lefthook.yml");
101
- if (!fs20.existsSync(lefthookPath)) {
102
- fs20.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
98
+ const fs21 = await import("fs");
99
+ const path21 = await import("path");
100
+ const lefthookPath = path21.join(projectRoot, "lefthook.yml");
101
+ if (!fs21.existsSync(lefthookPath)) {
102
+ fs21.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
103
103
  }
104
104
  s.stop("Installed Lefthook");
105
105
  return "Lefthook";
@@ -731,7 +731,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
731
731
  var fs7 = __toESM(require("fs"), 1);
732
732
  var path7 = __toESM(require("path"), 1);
733
733
  var import_config5 = require("@viberails/config");
734
- var import_chalk2 = __toESM(require("chalk"), 1);
734
+ var import_chalk3 = __toESM(require("chalk"), 1);
735
735
 
736
736
  // src/commands/check-config.ts
737
737
  var import_config2 = require("@viberails/config");
@@ -822,7 +822,8 @@ function runCoverageCommand(pkgRoot, command) {
822
822
  cwd: pkgRoot,
823
823
  shell: true,
824
824
  encoding: "utf-8",
825
- stdio: "pipe"
825
+ stdio: "pipe",
826
+ timeout: 3e5
826
827
  });
827
828
  if (result.status === 0) return { ok: true };
828
829
  const stderr = result.stderr?.trim() ?? "";
@@ -985,7 +986,7 @@ function checkNaming(relPath, conventions) {
985
986
  }
986
987
  function getStagedFiles(projectRoot) {
987
988
  try {
988
- const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=ACM", {
989
+ const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=ACMR", {
989
990
  cwd: projectRoot,
990
991
  encoding: "utf-8",
991
992
  stdio: ["ignore", "pipe", "ignore"]
@@ -1012,7 +1013,62 @@ function getDiffFiles(projectRoot, base) {
1012
1013
  added: addedOutput.trim().split("\n").filter(Boolean)
1013
1014
  };
1014
1015
  } catch {
1015
- return { all: [], added: [] };
1016
+ const msg = `git diff failed for base '${base}' \u2014 no files will be checked`;
1017
+ process.stderr.write(`Warning: ${msg}
1018
+ `);
1019
+ return { all: [], added: [], error: msg };
1020
+ }
1021
+ }
1022
+ function testFileToSourceFile(testFile) {
1023
+ const match = testFile.match(/^(.+)\.(test|spec)(\.[^.]+)$/);
1024
+ if (!match) return null;
1025
+ return `${match[1]}${match[3]}`;
1026
+ }
1027
+ function deletedTestFileToSourceFile(deletedTestFile, config) {
1028
+ const normalized = deletedTestFile.replaceAll("\\", "/");
1029
+ const sortedPackages = [...config.packages].sort((a, b) => b.path.length - a.path.length);
1030
+ for (const pkg of sortedPackages) {
1031
+ const relInPkg = pkg.path === "." ? normalized : normalized.startsWith(`${pkg.path}/`) ? normalized.slice(pkg.path.length + 1) : null;
1032
+ if (relInPkg === null) continue;
1033
+ const srcDir = pkg.structure?.srcDir;
1034
+ if (!srcDir) continue;
1035
+ const testsDir = pkg.structure?.tests;
1036
+ if (testsDir && relInPkg.startsWith(`${testsDir}/`)) {
1037
+ const relWithinTests = relInPkg.slice(testsDir.length + 1);
1038
+ const relWithinSrc = testFileToSourceFile(relWithinTests);
1039
+ if (relWithinSrc) {
1040
+ return pkg.path === "." ? path5.posix.join(srcDir, relWithinSrc) : path5.posix.join(pkg.path, srcDir, relWithinSrc);
1041
+ }
1042
+ }
1043
+ const colocated = testFileToSourceFile(relInPkg);
1044
+ if (colocated) {
1045
+ return pkg.path === "." ? colocated : path5.posix.join(pkg.path, colocated);
1046
+ }
1047
+ }
1048
+ return null;
1049
+ }
1050
+ function getStagedDeletedTestSourceFiles(projectRoot, config) {
1051
+ try {
1052
+ const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=D", {
1053
+ cwd: projectRoot,
1054
+ encoding: "utf-8",
1055
+ stdio: ["ignore", "pipe", "ignore"]
1056
+ });
1057
+ return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
1058
+ } catch {
1059
+ return [];
1060
+ }
1061
+ }
1062
+ function getDiffDeletedTestSourceFiles(projectRoot, base, config) {
1063
+ try {
1064
+ const output = (0, import_node_child_process3.execSync)(`git diff --name-only --diff-filter=D ${base}...HEAD`, {
1065
+ cwd: projectRoot,
1066
+ encoding: "utf-8",
1067
+ stdio: ["ignore", "pipe", "ignore"]
1068
+ });
1069
+ return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
1070
+ } catch {
1071
+ return [];
1016
1072
  }
1017
1073
  }
1018
1074
  function getAllSourceFiles(projectRoot, config) {
@@ -1055,7 +1111,7 @@ function collectSourceFiles(dir, projectRoot) {
1055
1111
  }
1056
1112
  for (const entry of entries) {
1057
1113
  if (entry.isDirectory()) {
1058
- if (entry.name === "node_modules") continue;
1114
+ if (ALWAYS_SKIP_DIRS.has(entry.name)) continue;
1059
1115
  walk(path5.join(d, entry.name));
1060
1116
  } else if (entry.isFile()) {
1061
1117
  files.push(path5.relative(projectRoot, path5.join(d, entry.name)));
@@ -1066,6 +1122,55 @@ function collectSourceFiles(dir, projectRoot) {
1066
1122
  return files;
1067
1123
  }
1068
1124
 
1125
+ // src/commands/check-print.ts
1126
+ var import_chalk2 = __toESM(require("chalk"), 1);
1127
+ function printGroupedViolations(violations, limit) {
1128
+ const groups = /* @__PURE__ */ new Map();
1129
+ for (const v of violations) {
1130
+ const existing = groups.get(v.rule) ?? [];
1131
+ existing.push(v);
1132
+ groups.set(v.rule, existing);
1133
+ }
1134
+ const ruleOrder = [
1135
+ "file-size",
1136
+ "file-naming",
1137
+ "missing-test",
1138
+ "test-coverage",
1139
+ "boundary-violation"
1140
+ ];
1141
+ const sortedKeys = [...groups.keys()].sort(
1142
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
1143
+ );
1144
+ let totalShown = 0;
1145
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
1146
+ for (const rule of sortedKeys) {
1147
+ const group = groups.get(rule);
1148
+ if (!group) continue;
1149
+ const remaining = totalLimit - totalShown;
1150
+ if (remaining <= 0) break;
1151
+ const toShow = group.slice(0, remaining);
1152
+ const hidden = group.length - toShow.length;
1153
+ for (const v of toShow) {
1154
+ const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
1155
+ console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
1156
+ }
1157
+ totalShown += toShow.length;
1158
+ if (hidden > 0) {
1159
+ console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
1160
+ }
1161
+ }
1162
+ }
1163
+ function printSummary(violations) {
1164
+ const counts = /* @__PURE__ */ new Map();
1165
+ for (const v of violations) {
1166
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
1167
+ }
1168
+ const word = violations.length === 1 ? "violation" : "violations";
1169
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
1170
+ console.log(`
1171
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
1172
+ }
1173
+
1069
1174
  // src/commands/check-tests.ts
1070
1175
  var fs6 = __toESM(require("fs"), 1);
1071
1176
  var path6 = __toESM(require("path"), 1);
@@ -1095,18 +1200,19 @@ function checkMissingTests(projectRoot, config, severity) {
1095
1200
  const testSuffix = testPattern.replace("*", "");
1096
1201
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
1097
1202
  for (const relFile of sourceFiles) {
1098
- const basename8 = path6.basename(relFile);
1099
- if (basename8.includes(".test.") || basename8.includes(".spec.") || basename8.startsWith("index.") || basename8.endsWith(".d.ts")) {
1203
+ const basename9 = path6.basename(relFile);
1204
+ if (basename9.includes(".test.") || basename9.includes(".spec.") || basename9.startsWith("index.") || basename9.endsWith(".d.ts")) {
1100
1205
  continue;
1101
1206
  }
1102
- const ext = path6.extname(basename8);
1207
+ const ext = path6.extname(basename9);
1103
1208
  if (!SOURCE_EXTS2.has(ext)) continue;
1104
- const stem = basename8.slice(0, -ext.length);
1209
+ const stem = basename9.slice(0, -ext.length);
1105
1210
  const expectedTestFile = `${stem}${testSuffix}`;
1106
1211
  const dir = path6.dirname(path6.join(projectRoot, relFile));
1107
1212
  const colocatedTest = path6.join(dir, expectedTestFile);
1108
1213
  const testsDir = pkg.structure?.tests;
1109
- const dedicatedTest = testsDir ? path6.join(packageRoot2, testsDir, expectedTestFile) : null;
1214
+ const relToSrc = path6.relative(srcPath, path6.join(projectRoot, path6.dirname(relFile)));
1215
+ const dedicatedTest = testsDir ? path6.join(packageRoot2, testsDir, relToSrc, expectedTestFile) : null;
1110
1216
  const hasTest = fs6.existsSync(colocatedTest) || dedicatedTest !== null && fs6.existsSync(dedicatedTest);
1111
1217
  if (!hasTest) {
1112
1218
  violations.push({
@@ -1136,91 +1242,52 @@ function isTestFile(relPath) {
1136
1242
  const filename = path7.basename(relPath);
1137
1243
  return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
1138
1244
  }
1139
- function printGroupedViolations(violations, limit) {
1140
- const groups = /* @__PURE__ */ new Map();
1141
- for (const v of violations) {
1142
- const existing = groups.get(v.rule) ?? [];
1143
- existing.push(v);
1144
- groups.set(v.rule, existing);
1145
- }
1146
- const ruleOrder = [
1147
- "file-size",
1148
- "file-naming",
1149
- "missing-test",
1150
- "test-coverage",
1151
- "boundary-violation"
1152
- ];
1153
- const sortedKeys = [...groups.keys()].sort(
1154
- (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
1155
- );
1156
- let totalShown = 0;
1157
- const totalLimit = limit ?? Number.POSITIVE_INFINITY;
1158
- for (const rule of sortedKeys) {
1159
- const group = groups.get(rule);
1160
- if (!group) continue;
1161
- const remaining = totalLimit - totalShown;
1162
- if (remaining <= 0) break;
1163
- const toShow = group.slice(0, remaining);
1164
- const hidden = group.length - toShow.length;
1165
- for (const v of toShow) {
1166
- const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
1167
- console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
1168
- }
1169
- totalShown += toShow.length;
1170
- if (hidden > 0) {
1171
- console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
1172
- }
1173
- }
1174
- }
1175
- function printSummary(violations) {
1176
- const counts = /* @__PURE__ */ new Map();
1177
- for (const v of violations) {
1178
- counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
1179
- }
1180
- const word = violations.length === 1 ? "violation" : "violations";
1181
- const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
1182
- console.log(`
1183
- ${violations.length} ${word} found (${parts.join(", ")}).`);
1184
- }
1185
1245
  async function checkCommand(options, cwd) {
1186
1246
  const startDir = cwd ?? process.cwd();
1187
1247
  const projectRoot = findProjectRoot(startDir);
1188
1248
  if (!projectRoot) {
1189
- console.error(`${import_chalk2.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1249
+ console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
1190
1250
  return 1;
1191
1251
  }
1192
1252
  const configPath = path7.join(projectRoot, CONFIG_FILE2);
1193
1253
  if (!fs7.existsSync(configPath)) {
1194
1254
  console.error(
1195
- `${import_chalk2.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1255
+ `${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
1196
1256
  );
1197
1257
  return 1;
1198
1258
  }
1199
1259
  const config = await (0, import_config5.loadConfig)(configPath);
1200
1260
  let filesToCheck;
1201
1261
  let diffAddedFiles = null;
1262
+ let deletedTestSourceFiles = [];
1202
1263
  if (options.staged) {
1203
- filesToCheck = getStagedFiles(projectRoot);
1264
+ filesToCheck = getStagedFiles(projectRoot).filter((f) => SOURCE_EXTS.has(path7.extname(f)));
1265
+ deletedTestSourceFiles = getStagedDeletedTestSourceFiles(projectRoot, config);
1204
1266
  } else if (options.diffBase) {
1205
1267
  const diff = getDiffFiles(projectRoot, options.diffBase);
1268
+ if (diff.error && options.enforce) {
1269
+ console.error(`${import_chalk3.default.red("Error:")} ${diff.error}`);
1270
+ return 1;
1271
+ }
1206
1272
  filesToCheck = diff.all.filter((f) => SOURCE_EXTS.has(path7.extname(f)));
1207
1273
  diffAddedFiles = new Set(diff.added);
1274
+ deletedTestSourceFiles = getDiffDeletedTestSourceFiles(projectRoot, options.diffBase, config);
1208
1275
  } else if (options.files && options.files.length > 0) {
1209
1276
  filesToCheck = options.files;
1210
1277
  } else {
1211
1278
  filesToCheck = getAllSourceFiles(projectRoot, config);
1212
1279
  }
1213
- if (filesToCheck.length === 0) {
1280
+ if (filesToCheck.length === 0 && deletedTestSourceFiles.length === 0) {
1214
1281
  if (options.format === "json") {
1215
1282
  console.log(JSON.stringify({ violations: [], checkedFiles: 0 }));
1216
1283
  } else {
1217
- console.log(`${import_chalk2.default.green("\u2713")} No files to check.`);
1284
+ console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
1218
1285
  }
1219
1286
  return 0;
1220
1287
  }
1221
1288
  const violations = [];
1222
1289
  const severity = options.enforce ? "error" : "warn";
1223
- const log7 = options.format !== "json" && !options.hook ? (msg) => process.stderr.write(import_chalk2.default.dim(msg)) : () => {
1290
+ const log7 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk3.default.dim(msg)) : () => {
1224
1291
  };
1225
1292
  log7(" Checking files...");
1226
1293
  for (const file of filesToCheck) {
@@ -1256,12 +1323,20 @@ async function checkCommand(options, cwd) {
1256
1323
  }
1257
1324
  }
1258
1325
  log7(" done\n");
1259
- if (!options.staged && !options.files) {
1326
+ if (!options.files) {
1260
1327
  log7(" Checking missing tests...");
1261
1328
  const testViolations = checkMissingTests(projectRoot, config, severity);
1262
- violations.push(
1263
- ...diffAddedFiles ? testViolations.filter((v) => diffAddedFiles.has(v.file)) : testViolations
1264
- );
1329
+ if (options.staged) {
1330
+ const stagedSet = new Set(filesToCheck);
1331
+ for (const f of deletedTestSourceFiles) stagedSet.add(f);
1332
+ violations.push(...testViolations.filter((v) => stagedSet.has(v.file)));
1333
+ } else if (diffAddedFiles) {
1334
+ const checkSet = new Set(diffAddedFiles);
1335
+ for (const f of deletedTestSourceFiles) checkSet.add(f);
1336
+ violations.push(...testViolations.filter((v) => checkSet.has(v.file)));
1337
+ } else {
1338
+ violations.push(...testViolations);
1339
+ }
1265
1340
  log7(" done\n");
1266
1341
  }
1267
1342
  if (!options.files && !options.staged && !options.diffBase) {
@@ -1307,7 +1382,7 @@ async function checkCommand(options, cwd) {
1307
1382
  return options.enforce && violations.length > 0 ? 1 : 0;
1308
1383
  }
1309
1384
  if (violations.length === 0) {
1310
- console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1385
+ console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
1311
1386
  return 0;
1312
1387
  }
1313
1388
  if (!options.quiet) {
@@ -1315,7 +1390,7 @@ async function checkCommand(options, cwd) {
1315
1390
  }
1316
1391
  printSummary(violations);
1317
1392
  if (options.enforce) {
1318
- console.log(import_chalk2.default.red("Fix violations before committing."));
1393
+ console.log(import_chalk3.default.red("Fix violations before committing."));
1319
1394
  return 1;
1320
1395
  }
1321
1396
  return 0;
@@ -1373,14 +1448,14 @@ var path9 = __toESM(require("path"), 1);
1373
1448
  var clack6 = __toESM(require("@clack/prompts"), 1);
1374
1449
  var import_config6 = require("@viberails/config");
1375
1450
  var import_scanner = require("@viberails/scanner");
1376
- var import_chalk5 = __toESM(require("chalk"), 1);
1451
+ var import_chalk6 = __toESM(require("chalk"), 1);
1377
1452
 
1378
1453
  // src/display-text.ts
1379
1454
  var import_types4 = require("@viberails/types");
1380
1455
 
1381
1456
  // src/display.ts
1382
1457
  var import_types3 = require("@viberails/types");
1383
- var import_chalk4 = __toESM(require("chalk"), 1);
1458
+ var import_chalk5 = __toESM(require("chalk"), 1);
1384
1459
 
1385
1460
  // src/display-helpers.ts
1386
1461
  var import_types = require("@viberails/types");
@@ -1433,7 +1508,7 @@ function formatRoleGroup(group) {
1433
1508
 
1434
1509
  // src/display-monorepo.ts
1435
1510
  var import_types2 = require("@viberails/types");
1436
- var import_chalk3 = __toESM(require("chalk"), 1);
1511
+ var import_chalk4 = __toESM(require("chalk"), 1);
1437
1512
  function formatPackageSummary(pkg) {
1438
1513
  const parts = [];
1439
1514
  if (pkg.stack.framework) {
@@ -1450,23 +1525,23 @@ function formatPackageSummary(pkg) {
1450
1525
  function displayMonorepoResults(scanResult) {
1451
1526
  const { stack, packages } = scanResult;
1452
1527
  console.log(`
1453
- ${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1454
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.language)}`);
1528
+ ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1529
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1455
1530
  if (stack.packageManager) {
1456
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1531
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1457
1532
  }
1458
1533
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1459
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1534
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1460
1535
  } else {
1461
1536
  if (stack.linter) {
1462
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.linter)}`);
1537
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1463
1538
  }
1464
1539
  if (stack.formatter) {
1465
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1540
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1466
1541
  }
1467
1542
  }
1468
1543
  if (stack.testRunner) {
1469
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1544
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1470
1545
  }
1471
1546
  console.log("");
1472
1547
  for (const pkg of packages) {
@@ -1477,13 +1552,13 @@ ${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`
1477
1552
  );
1478
1553
  if (packagesWithDirs.length > 0) {
1479
1554
  console.log(`
1480
- ${import_chalk3.default.bold("Structure:")}`);
1555
+ ${import_chalk4.default.bold("Structure:")}`);
1481
1556
  for (const pkg of packagesWithDirs) {
1482
1557
  const groups = groupByRole(pkg.structure.directories);
1483
1558
  if (groups.length === 0) continue;
1484
1559
  console.log(` ${pkg.relativePath}:`);
1485
1560
  for (const group of groups) {
1486
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatRoleGroup(group)}`);
1561
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
1487
1562
  }
1488
1563
  }
1489
1564
  }
@@ -1564,7 +1639,7 @@ function displayConventions(scanResult) {
1564
1639
  const conventionEntries = Object.entries(scanResult.conventions);
1565
1640
  if (conventionEntries.length === 0) return;
1566
1641
  console.log(`
1567
- ${import_chalk4.default.bold("Conventions:")}`);
1642
+ ${import_chalk5.default.bold("Conventions:")}`);
1568
1643
  for (const [key, convention] of conventionEntries) {
1569
1644
  if (convention.confidence === "low") continue;
1570
1645
  const label = import_types3.CONVENTION_LABELS[key] ?? key;
@@ -1572,19 +1647,19 @@ ${import_chalk4.default.bold("Conventions:")}`);
1572
1647
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1573
1648
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1574
1649
  if (allSame || pkgValues.length <= 1) {
1575
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
1576
- const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
1650
+ const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
1651
+ const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
1577
1652
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1578
1653
  } else {
1579
- console.log(` ${import_chalk4.default.yellow("~")} ${label}: varies by package`);
1654
+ console.log(` ${import_chalk5.default.yellow("~")} ${label}: varies by package`);
1580
1655
  for (const pv of pkgValues) {
1581
1656
  const pct = Math.round(pv.convention.consistency);
1582
1657
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1583
1658
  }
1584
1659
  }
1585
1660
  } else {
1586
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
1587
- const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
1661
+ const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
1662
+ const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
1588
1663
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1589
1664
  }
1590
1665
  }
@@ -1592,7 +1667,7 @@ ${import_chalk4.default.bold("Conventions:")}`);
1592
1667
  function displaySummarySection(scanResult) {
1593
1668
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1594
1669
  console.log(`
1595
- ${import_chalk4.default.bold("Summary:")}`);
1670
+ ${import_chalk5.default.bold("Summary:")}`);
1596
1671
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1597
1672
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1598
1673
  if (ext) {
@@ -1606,47 +1681,47 @@ function displayScanResults(scanResult) {
1606
1681
  }
1607
1682
  const { stack } = scanResult;
1608
1683
  console.log(`
1609
- ${import_chalk4.default.bold("Detected:")}`);
1684
+ ${import_chalk5.default.bold("Detected:")}`);
1610
1685
  if (stack.framework) {
1611
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1686
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
1612
1687
  }
1613
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1688
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1614
1689
  if (stack.styling) {
1615
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1690
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
1616
1691
  }
1617
1692
  if (stack.backend) {
1618
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1693
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
1619
1694
  }
1620
1695
  if (stack.orm) {
1621
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1696
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
1622
1697
  }
1623
1698
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1624
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1699
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
1625
1700
  } else {
1626
1701
  if (stack.linter) {
1627
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1702
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1628
1703
  }
1629
1704
  if (stack.formatter) {
1630
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1705
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1631
1706
  }
1632
1707
  }
1633
1708
  if (stack.testRunner) {
1634
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1709
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1635
1710
  }
1636
1711
  if (stack.packageManager) {
1637
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1712
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1638
1713
  }
1639
1714
  if (stack.libraries.length > 0) {
1640
1715
  for (const lib of stack.libraries) {
1641
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1716
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
1642
1717
  }
1643
1718
  }
1644
1719
  const groups = groupByRole(scanResult.structure.directories);
1645
1720
  if (groups.length > 0) {
1646
1721
  console.log(`
1647
- ${import_chalk4.default.bold("Structure:")}`);
1722
+ ${import_chalk5.default.bold("Structure:")}`);
1648
1723
  for (const group of groups) {
1649
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
1724
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1650
1725
  }
1651
1726
  }
1652
1727
  displayConventions(scanResult);
@@ -1656,49 +1731,49 @@ ${import_chalk4.default.bold("Structure:")}`);
1656
1731
  function displayRulesPreview(config) {
1657
1732
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1658
1733
  console.log(
1659
- `${import_chalk4.default.bold("Rules:")} ${import_chalk4.default.dim("(warns on violation; use --enforce in CI to block)")}`
1734
+ `${import_chalk5.default.bold("Rules:")} ${import_chalk5.default.dim("(warns on violation; use --enforce in CI to block)")}`
1660
1735
  );
1661
- console.log(` ${import_chalk4.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1736
+ console.log(` ${import_chalk5.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
1662
1737
  if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
1663
1738
  console.log(
1664
- ` ${import_chalk4.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1739
+ ` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
1665
1740
  );
1666
1741
  } else if (config.rules.testCoverage > 0) {
1667
- console.log(` ${import_chalk4.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1742
+ console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
1668
1743
  } else {
1669
- console.log(` ${import_chalk4.default.dim("\u2022")} Test coverage target: disabled`);
1744
+ console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: disabled`);
1670
1745
  }
1671
1746
  if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1672
- console.log(` ${import_chalk4.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1747
+ console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
1673
1748
  } else {
1674
- console.log(` ${import_chalk4.default.dim("\u2022")} Enforce file naming: no`);
1749
+ console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: no`);
1675
1750
  }
1676
1751
  console.log(
1677
- ` ${import_chalk4.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1752
+ ` ${import_chalk5.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
1678
1753
  );
1679
1754
  console.log("");
1680
1755
  }
1681
1756
  function displayInitSummary(config, exemptedPackages) {
1682
1757
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1683
1758
  const isMonorepo = config.packages.length > 1;
1684
- const ok = import_chalk4.default.green("\u2713");
1685
- const off = import_chalk4.default.dim("\u25CB");
1759
+ const ok = import_chalk5.default.green("\u2713");
1760
+ const off = import_chalk5.default.dim("\u25CB");
1686
1761
  console.log("");
1687
- console.log(` ${import_chalk4.default.bold("Rules to apply:")}`);
1688
- console.log(` ${ok} Max file size: ${import_chalk4.default.cyan(`${config.rules.maxFileLines} lines`)}`);
1762
+ console.log(` ${import_chalk5.default.bold("Rules to apply:")}`);
1763
+ console.log(` ${ok} Max file size: ${import_chalk5.default.cyan(`${config.rules.maxFileLines} lines`)}`);
1689
1764
  const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1690
1765
  if (config.rules.enforceNaming && fileNaming) {
1691
- console.log(` ${ok} File naming: ${import_chalk4.default.cyan(fileNaming)}`);
1766
+ console.log(` ${ok} File naming: ${import_chalk5.default.cyan(fileNaming)}`);
1692
1767
  } else {
1693
- console.log(` ${off} File naming: ${import_chalk4.default.dim("not enforced")}`);
1768
+ console.log(` ${off} File naming: ${import_chalk5.default.dim("not enforced")}`);
1694
1769
  }
1695
1770
  const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1696
1771
  if (config.rules.enforceMissingTests && testPattern) {
1697
- console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan(`enforced (${testPattern})`)}`);
1772
+ console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan(`enforced (${testPattern})`)}`);
1698
1773
  } else if (config.rules.enforceMissingTests) {
1699
- console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan("enforced")}`);
1774
+ console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan("enforced")}`);
1700
1775
  } else {
1701
- console.log(` ${off} Missing tests: ${import_chalk4.default.dim("not enforced")}`);
1776
+ console.log(` ${off} Missing tests: ${import_chalk5.default.dim("not enforced")}`);
1702
1777
  }
1703
1778
  if (config.rules.testCoverage > 0) {
1704
1779
  if (isMonorepo) {
@@ -1706,27 +1781,27 @@ function displayInitSummary(config, exemptedPackages) {
1706
1781
  (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
1707
1782
  );
1708
1783
  console.log(
1709
- ` ${ok} Coverage: ${import_chalk4.default.cyan(`${config.rules.testCoverage}%`)} default ${import_chalk4.default.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1784
+ ` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)} default ${import_chalk5.default.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1710
1785
  );
1711
1786
  } else {
1712
- console.log(` ${ok} Coverage: ${import_chalk4.default.cyan(`${config.rules.testCoverage}%`)}`);
1787
+ console.log(` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)}`);
1713
1788
  }
1714
1789
  } else {
1715
- console.log(` ${off} Coverage: ${import_chalk4.default.dim("disabled")}`);
1790
+ console.log(` ${off} Coverage: ${import_chalk5.default.dim("disabled")}`);
1716
1791
  }
1717
1792
  if (exemptedPackages.length > 0) {
1718
1793
  console.log(
1719
- ` ${import_chalk4.default.dim(" exempted:")} ${import_chalk4.default.dim(exemptedPackages.join(", "))} ${import_chalk4.default.dim("(types-only)")}`
1794
+ ` ${import_chalk5.default.dim(" exempted:")} ${import_chalk5.default.dim(exemptedPackages.join(", "))} ${import_chalk5.default.dim("(types-only)")}`
1720
1795
  );
1721
1796
  }
1722
1797
  if (isMonorepo) {
1723
1798
  console.log(
1724
1799
  `
1725
- ${import_chalk4.default.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1800
+ ${import_chalk5.default.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1726
1801
  );
1727
1802
  } else {
1728
1803
  console.log(`
1729
- ${import_chalk4.default.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1804
+ ${import_chalk5.default.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1730
1805
  }
1731
1806
  console.log("");
1732
1807
  }
@@ -2038,7 +2113,7 @@ async function configCommand(options, cwd) {
2038
2113
  }
2039
2114
  const configPath = path9.join(projectRoot, CONFIG_FILE3);
2040
2115
  if (!fs10.existsSync(configPath)) {
2041
- console.log(`${import_chalk5.default.yellow("!")} No config found. Run ${import_chalk5.default.cyan("viberails init")} first.`);
2116
+ console.log(`${import_chalk6.default.yellow("!")} No config found. Run ${import_chalk6.default.cyan("viberails init")} first.`);
2042
2117
  return;
2043
2118
  }
2044
2119
  clack6.intro("viberails config");
@@ -2123,22 +2198,22 @@ async function rescanAndMerge(projectRoot, config) {
2123
2198
  var fs13 = __toESM(require("fs"), 1);
2124
2199
  var path13 = __toESM(require("path"), 1);
2125
2200
  var import_config7 = require("@viberails/config");
2126
- var import_chalk7 = __toESM(require("chalk"), 1);
2201
+ var import_chalk8 = __toESM(require("chalk"), 1);
2127
2202
 
2128
2203
  // src/commands/fix-helpers.ts
2129
2204
  var import_node_child_process4 = require("child_process");
2130
- var import_chalk6 = __toESM(require("chalk"), 1);
2205
+ var import_chalk7 = __toESM(require("chalk"), 1);
2131
2206
  function printPlan(renames, stubs) {
2132
2207
  if (renames.length > 0) {
2133
- console.log(import_chalk6.default.bold("\nFile renames:"));
2208
+ console.log(import_chalk7.default.bold("\nFile renames:"));
2134
2209
  for (const r of renames) {
2135
- console.log(` ${import_chalk6.default.red(r.oldPath)} \u2192 ${import_chalk6.default.green(r.newPath)}`);
2210
+ console.log(` ${import_chalk7.default.red(r.oldPath)} \u2192 ${import_chalk7.default.green(r.newPath)}`);
2136
2211
  }
2137
2212
  }
2138
2213
  if (stubs.length > 0) {
2139
- console.log(import_chalk6.default.bold("\nTest stubs to create:"));
2214
+ console.log(import_chalk7.default.bold("\nTest stubs to create:"));
2140
2215
  for (const s of stubs) {
2141
- console.log(` ${import_chalk6.default.green("+")} ${s.path}`);
2216
+ console.log(` ${import_chalk7.default.green("+")} ${s.path}`);
2142
2217
  }
2143
2218
  }
2144
2219
  }
@@ -2172,8 +2247,62 @@ function computeNewSpecifier(oldSpecifier, newBare) {
2172
2247
  const newSpec = prefix + newBare;
2173
2248
  return hasJsExt ? `${newSpec}.js` : newSpec;
2174
2249
  }
2175
- async function updateImportsAfterRenames(renames, projectRoot) {
2250
+ async function scanForAliasImports(renames, projectRoot) {
2176
2251
  if (renames.length === 0) return [];
2252
+ const { readFile, readdir } = await import("fs/promises");
2253
+ const oldBareNames = /* @__PURE__ */ new Set();
2254
+ for (const r of renames) {
2255
+ const oldFilename = path10.basename(r.oldPath);
2256
+ oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
2257
+ }
2258
+ const importPattern = /(?:import|export)\s+.*?from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2259
+ const skipDirs = /* @__PURE__ */ new Set([
2260
+ "node_modules",
2261
+ "dist",
2262
+ "build",
2263
+ ".next",
2264
+ ".expo",
2265
+ ".turbo",
2266
+ "coverage"
2267
+ ]);
2268
+ const sourceExts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
2269
+ const allEntries = await readdir(projectRoot, { recursive: true, withFileTypes: true });
2270
+ const files = allEntries.filter((e) => {
2271
+ if (!e.isFile()) return false;
2272
+ const ext = path10.extname(e.name);
2273
+ if (!sourceExts.has(ext)) return false;
2274
+ const rel = path10.join(e.parentPath, e.name);
2275
+ const segments = path10.relative(projectRoot, rel).split(path10.sep);
2276
+ return !segments.some((s) => skipDirs.has(s));
2277
+ }).map((e) => path10.join(e.parentPath, e.name));
2278
+ const aliases = [];
2279
+ for (const file of files) {
2280
+ let content;
2281
+ try {
2282
+ content = await readFile(file, "utf-8");
2283
+ } catch {
2284
+ continue;
2285
+ }
2286
+ const lines = content.split("\n");
2287
+ for (let i = 0; i < lines.length; i++) {
2288
+ const line = lines[i];
2289
+ importPattern.lastIndex = 0;
2290
+ for (let match = importPattern.exec(line); match !== null; match = importPattern.exec(line)) {
2291
+ const specifier = match[1] ?? match[2];
2292
+ if (!specifier || specifier.startsWith(".")) continue;
2293
+ if (!specifier.includes("/")) continue;
2294
+ const lastSegment = specifier.split("/").pop() ?? "";
2295
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2296
+ if (oldBareNames.has(bare)) {
2297
+ aliases.push({ file, specifier, line: i + 1 });
2298
+ }
2299
+ }
2300
+ }
2301
+ }
2302
+ return aliases;
2303
+ }
2304
+ async function updateImportsAfterRenames(renames, projectRoot) {
2305
+ if (renames.length === 0) return { updates: [], skippedAliases: [] };
2177
2306
  const { Project, SyntaxKind } = await import("ts-morph");
2178
2307
  const renameMap = /* @__PURE__ */ new Map();
2179
2308
  for (const r of renames) {
@@ -2188,15 +2317,44 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2188
2317
  });
2189
2318
  project.addSourceFilesAtPaths(path10.join(projectRoot, "**/*.{ts,tsx,js,jsx,mjs,cjs}"));
2190
2319
  const updates = [];
2320
+ const skippedAliases = [];
2191
2321
  const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
2322
+ const oldBareNames = /* @__PURE__ */ new Set();
2323
+ for (const r of renames) {
2324
+ const oldFilename = path10.basename(r.oldPath);
2325
+ oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
2326
+ }
2192
2327
  for (const sourceFile of project.getSourceFiles()) {
2193
2328
  const filePath = sourceFile.getFilePath();
2194
2329
  const segments = filePath.split(path10.sep);
2195
- if (segments.includes("node_modules") || segments.includes("dist")) continue;
2330
+ const skipDirs = /* @__PURE__ */ new Set([
2331
+ "node_modules",
2332
+ "dist",
2333
+ "build",
2334
+ ".next",
2335
+ ".expo",
2336
+ ".svelte-kit",
2337
+ ".turbo",
2338
+ "coverage"
2339
+ ]);
2340
+ if (segments.some((s) => skipDirs.has(s))) continue;
2196
2341
  const fileDir = path10.dirname(filePath);
2197
2342
  for (const decl of sourceFile.getImportDeclarations()) {
2198
2343
  const specifier = decl.getModuleSpecifierValue();
2199
- if (!specifier.startsWith(".")) continue;
2344
+ if (!specifier.startsWith(".")) {
2345
+ if (specifier.includes("/")) {
2346
+ const lastSegment = specifier.split("/").pop() ?? "";
2347
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2348
+ if (oldBareNames.has(bare)) {
2349
+ skippedAliases.push({
2350
+ file: filePath,
2351
+ specifier,
2352
+ line: decl.getStartLineNumber()
2353
+ });
2354
+ }
2355
+ }
2356
+ continue;
2357
+ }
2200
2358
  const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
2201
2359
  if (!match) continue;
2202
2360
  const newSpec = computeNewSpecifier(specifier, match.newBare);
@@ -2246,7 +2404,7 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2246
2404
  if (updates.length > 0) {
2247
2405
  await project.save();
2248
2406
  }
2249
- return updates;
2407
+ return { updates, skippedAliases };
2250
2408
  }
2251
2409
  function resolveToRenamedFile(specifier, fromDir, renameMap, extensions) {
2252
2410
  const cleanSpec = specifier.endsWith(".js") ? specifier.slice(0, -3) : specifier;
@@ -2349,10 +2507,10 @@ function generateTestStub(sourceRelPath, config, projectRoot) {
2349
2507
  const pkg = resolvePackageForFile(sourceRelPath, config);
2350
2508
  const testPattern = pkg?.structure?.testPattern;
2351
2509
  if (!testPattern) return null;
2352
- const basename8 = path12.basename(sourceRelPath);
2353
- const ext = path12.extname(basename8);
2510
+ const basename9 = path12.basename(sourceRelPath);
2511
+ const ext = path12.extname(basename9);
2354
2512
  if (!ext) return null;
2355
- const stem = basename8.slice(0, -ext.length);
2513
+ const stem = basename9.slice(0, -ext.length);
2356
2514
  const testSuffix = testPattern.replace("*", "");
2357
2515
  const testFilename = `${stem}${testSuffix}`;
2358
2516
  const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
@@ -2383,13 +2541,13 @@ async function fixCommand(options, cwd) {
2383
2541
  const startDir = cwd ?? process.cwd();
2384
2542
  const projectRoot = findProjectRoot(startDir);
2385
2543
  if (!projectRoot) {
2386
- console.error(`${import_chalk7.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2544
+ console.error(`${import_chalk8.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
2387
2545
  return 1;
2388
2546
  }
2389
2547
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
2390
2548
  if (!fs13.existsSync(configPath)) {
2391
2549
  console.error(
2392
- `${import_chalk7.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2550
+ `${import_chalk8.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
2393
2551
  );
2394
2552
  return 1;
2395
2553
  }
@@ -2398,7 +2556,7 @@ async function fixCommand(options, cwd) {
2398
2556
  const isDirty = checkGitDirty(projectRoot);
2399
2557
  if (isDirty) {
2400
2558
  console.log(
2401
- import_chalk7.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
2559
+ import_chalk8.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
2402
2560
  );
2403
2561
  }
2404
2562
  }
@@ -2427,13 +2585,59 @@ async function fixCommand(options, cwd) {
2427
2585
  if (stub) testStubs.push(stub);
2428
2586
  }
2429
2587
  }
2430
- if (dedupedRenames.length === 0 && testStubs.length === 0) {
2431
- console.log(`${import_chalk7.default.green("\u2713")} No fixable violations found.`);
2588
+ const aliasImports = await scanForAliasImports(dedupedRenames, projectRoot);
2589
+ const blockedOldBareNames = /* @__PURE__ */ new Set();
2590
+ for (const alias of aliasImports) {
2591
+ const lastSegment = alias.specifier.split("/").pop() ?? "";
2592
+ const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2593
+ blockedOldBareNames.add(bare);
2594
+ }
2595
+ const safeRenames = dedupedRenames.filter((r) => {
2596
+ const oldFilename = path13.basename(r.oldPath);
2597
+ const bare = oldFilename.slice(0, oldFilename.indexOf("."));
2598
+ return !blockedOldBareNames.has(bare);
2599
+ });
2600
+ const skippedRenames = dedupedRenames.filter((r) => {
2601
+ const oldFilename = path13.basename(r.oldPath);
2602
+ const bare = oldFilename.slice(0, oldFilename.indexOf("."));
2603
+ return blockedOldBareNames.has(bare);
2604
+ });
2605
+ if (safeRenames.length === 0 && testStubs.length === 0 && skippedRenames.length === 0) {
2606
+ console.log(`${import_chalk8.default.green("\u2713")} No fixable violations found.`);
2607
+ return 0;
2608
+ }
2609
+ printPlan(safeRenames, testStubs);
2610
+ if (skippedRenames.length > 0) {
2611
+ console.log("");
2612
+ console.log(
2613
+ import_chalk8.default.yellow(
2614
+ `Skipping ${skippedRenames.length} rename${skippedRenames.length > 1 ? "s" : ""} \u2014 aliased imports would break:`
2615
+ )
2616
+ );
2617
+ for (const r of skippedRenames.slice(0, 5)) {
2618
+ console.log(import_chalk8.default.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
2619
+ }
2620
+ if (skippedRenames.length > 5) {
2621
+ console.log(import_chalk8.default.dim(` ... and ${skippedRenames.length - 5} more`));
2622
+ }
2623
+ console.log("");
2624
+ console.log(import_chalk8.default.yellow("Affected aliased imports:"));
2625
+ for (const alias of aliasImports.slice(0, 5)) {
2626
+ const relFile = path13.relative(projectRoot, alias.file);
2627
+ console.log(import_chalk8.default.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
2628
+ }
2629
+ if (aliasImports.length > 5) {
2630
+ console.log(import_chalk8.default.dim(` ... and ${aliasImports.length - 5} more`));
2631
+ }
2632
+ console.log(import_chalk8.default.dim(" Update these imports to relative paths first, then re-run fix."));
2633
+ }
2634
+ if (safeRenames.length === 0 && testStubs.length === 0) {
2635
+ console.log(`
2636
+ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2432
2637
  return 0;
2433
2638
  }
2434
- printPlan(dedupedRenames, testStubs);
2435
2639
  if (options.dryRun) {
2436
- console.log(import_chalk7.default.dim("\nDry run \u2014 no changes applied."));
2640
+ console.log(import_chalk8.default.dim("\nDry run \u2014 no changes applied."));
2437
2641
  return 0;
2438
2642
  }
2439
2643
  if (!options.yes) {
@@ -2444,15 +2648,15 @@ async function fixCommand(options, cwd) {
2444
2648
  }
2445
2649
  }
2446
2650
  let renameCount = 0;
2447
- for (const rename of dedupedRenames) {
2651
+ for (const rename of safeRenames) {
2448
2652
  if (executeRename(rename)) {
2449
2653
  renameCount++;
2450
2654
  }
2451
2655
  }
2452
2656
  let importUpdateCount = 0;
2453
2657
  if (renameCount > 0) {
2454
- const appliedRenames = dedupedRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2455
- const updates = await updateImportsAfterRenames(appliedRenames, projectRoot);
2658
+ const appliedRenames = safeRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2659
+ const { updates } = await updateImportsAfterRenames(appliedRenames, projectRoot);
2456
2660
  importUpdateCount = updates.length;
2457
2661
  }
2458
2662
  let stubCount = 0;
@@ -2464,33 +2668,33 @@ async function fixCommand(options, cwd) {
2464
2668
  }
2465
2669
  console.log("");
2466
2670
  if (renameCount > 0) {
2467
- console.log(`${import_chalk7.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2671
+ console.log(`${import_chalk8.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
2468
2672
  }
2469
2673
  if (importUpdateCount > 0) {
2470
2674
  console.log(
2471
- `${import_chalk7.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2675
+ `${import_chalk8.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2472
2676
  );
2473
2677
  }
2474
2678
  if (stubCount > 0) {
2475
- console.log(`${import_chalk7.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2679
+ console.log(`${import_chalk8.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
2476
2680
  }
2477
2681
  return 0;
2478
2682
  }
2479
2683
 
2480
2684
  // src/commands/init.ts
2481
- var fs18 = __toESM(require("fs"), 1);
2482
- var path18 = __toESM(require("path"), 1);
2685
+ var fs19 = __toESM(require("fs"), 1);
2686
+ var path19 = __toESM(require("path"), 1);
2483
2687
  var clack8 = __toESM(require("@clack/prompts"), 1);
2484
2688
  var import_config8 = require("@viberails/config");
2485
2689
  var import_scanner2 = require("@viberails/scanner");
2486
- var import_chalk11 = __toESM(require("chalk"), 1);
2690
+ var import_chalk12 = __toESM(require("chalk"), 1);
2487
2691
 
2488
2692
  // src/utils/check-prerequisites.ts
2489
2693
  var import_node_child_process5 = require("child_process");
2490
2694
  var fs14 = __toESM(require("fs"), 1);
2491
2695
  var path14 = __toESM(require("path"), 1);
2492
2696
  var clack7 = __toESM(require("@clack/prompts"), 1);
2493
- var import_chalk8 = __toESM(require("chalk"), 1);
2697
+ var import_chalk9 = __toESM(require("chalk"), 1);
2494
2698
  function checkCoveragePrereqs(projectRoot, scanResult) {
2495
2699
  const pm = scanResult.stack.packageManager.name;
2496
2700
  const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
@@ -2521,9 +2725,9 @@ function displayMissingPrereqs(prereqs) {
2521
2725
  const missing = prereqs.filter((p) => !p.installed);
2522
2726
  for (const m of missing) {
2523
2727
  const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2524
- console.log(` ${import_chalk8.default.yellow("!")} ${m.label} not installed${suffix}`);
2728
+ console.log(` ${import_chalk9.default.yellow("!")} ${m.label} not installed${suffix}`);
2525
2729
  if (m.installCommand) {
2526
- console.log(` Install: ${import_chalk8.default.cyan(m.installCommand)}`);
2730
+ console.log(` Install: ${import_chalk9.default.cyan(m.installCommand)}`);
2527
2731
  }
2528
2732
  }
2529
2733
  }
@@ -2634,46 +2838,85 @@ function updateGitignore(projectRoot) {
2634
2838
  }
2635
2839
 
2636
2840
  // src/commands/init-hooks.ts
2841
+ var fs17 = __toESM(require("fs"), 1);
2842
+ var path17 = __toESM(require("path"), 1);
2843
+ var import_chalk10 = __toESM(require("chalk"), 1);
2844
+ var import_yaml = require("yaml");
2845
+
2846
+ // src/commands/resolve-typecheck.ts
2637
2847
  var fs16 = __toESM(require("fs"), 1);
2638
2848
  var path16 = __toESM(require("path"), 1);
2639
- var import_chalk9 = __toESM(require("chalk"), 1);
2640
- var import_yaml = require("yaml");
2849
+ function hasTurboTask(projectRoot, taskName) {
2850
+ const turboPath = path16.join(projectRoot, "turbo.json");
2851
+ if (!fs16.existsSync(turboPath)) return false;
2852
+ try {
2853
+ const turbo = JSON.parse(fs16.readFileSync(turboPath, "utf-8"));
2854
+ const tasks = turbo.tasks ?? turbo.pipeline ?? {};
2855
+ return taskName in tasks;
2856
+ } catch {
2857
+ return false;
2858
+ }
2859
+ }
2860
+ function resolveTypecheckCommand(projectRoot, packageManager) {
2861
+ if (hasTurboTask(projectRoot, "typecheck")) {
2862
+ return { command: "npx turbo typecheck", label: "turbo typecheck" };
2863
+ }
2864
+ const pkgJsonPath = path16.join(projectRoot, "package.json");
2865
+ if (fs16.existsSync(pkgJsonPath)) {
2866
+ try {
2867
+ const pkg = JSON.parse(fs16.readFileSync(pkgJsonPath, "utf-8"));
2868
+ if (pkg.scripts?.typecheck) {
2869
+ const pm = packageManager ?? "npm";
2870
+ return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
2871
+ }
2872
+ } catch {
2873
+ }
2874
+ }
2875
+ if (fs16.existsSync(path16.join(projectRoot, "tsconfig.json"))) {
2876
+ return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
2877
+ }
2878
+ return {
2879
+ reason: "no root tsconfig.json, no typecheck script, and no turbo typecheck task found"
2880
+ };
2881
+ }
2882
+
2883
+ // src/commands/init-hooks.ts
2641
2884
  function setupPreCommitHook(projectRoot) {
2642
- const lefthookPath = path16.join(projectRoot, "lefthook.yml");
2643
- if (fs16.existsSync(lefthookPath)) {
2885
+ const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2886
+ if (fs17.existsSync(lefthookPath)) {
2644
2887
  addLefthookPreCommit(lefthookPath);
2645
- console.log(` ${import_chalk9.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
2888
+ console.log(` ${import_chalk10.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
2646
2889
  return "lefthook.yml";
2647
2890
  }
2648
- const huskyDir = path16.join(projectRoot, ".husky");
2649
- if (fs16.existsSync(huskyDir)) {
2891
+ const huskyDir = path17.join(projectRoot, ".husky");
2892
+ if (fs17.existsSync(huskyDir)) {
2650
2893
  writeHuskyPreCommit(huskyDir);
2651
- console.log(` ${import_chalk9.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
2894
+ console.log(` ${import_chalk10.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
2652
2895
  return ".husky/pre-commit";
2653
2896
  }
2654
- const gitDir = path16.join(projectRoot, ".git");
2655
- if (fs16.existsSync(gitDir)) {
2656
- const hooksDir = path16.join(gitDir, "hooks");
2657
- if (!fs16.existsSync(hooksDir)) {
2658
- fs16.mkdirSync(hooksDir, { recursive: true });
2897
+ const gitDir = path17.join(projectRoot, ".git");
2898
+ if (fs17.existsSync(gitDir)) {
2899
+ const hooksDir = path17.join(gitDir, "hooks");
2900
+ if (!fs17.existsSync(hooksDir)) {
2901
+ fs17.mkdirSync(hooksDir, { recursive: true });
2659
2902
  }
2660
2903
  writeGitHookPreCommit(hooksDir);
2661
- console.log(` ${import_chalk9.default.green("\u2713")} .git/hooks/pre-commit`);
2904
+ console.log(` ${import_chalk10.default.green("\u2713")} .git/hooks/pre-commit`);
2662
2905
  return ".git/hooks/pre-commit";
2663
2906
  }
2664
2907
  return void 0;
2665
2908
  }
2666
2909
  function writeGitHookPreCommit(hooksDir) {
2667
- const hookPath = path16.join(hooksDir, "pre-commit");
2668
- if (fs16.existsSync(hookPath)) {
2669
- const existing = fs16.readFileSync(hookPath, "utf-8");
2910
+ const hookPath = path17.join(hooksDir, "pre-commit");
2911
+ if (fs17.existsSync(hookPath)) {
2912
+ const existing = fs17.readFileSync(hookPath, "utf-8");
2670
2913
  if (existing.includes("viberails")) return;
2671
- fs16.writeFileSync(
2914
+ fs17.writeFileSync(
2672
2915
  hookPath,
2673
2916
  `${existing.trimEnd()}
2674
2917
 
2675
2918
  # viberails check
2676
- npx viberails check --staged
2919
+ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi
2677
2920
  `
2678
2921
  );
2679
2922
  return;
@@ -2682,13 +2925,13 @@ npx viberails check --staged
2682
2925
  "#!/bin/sh",
2683
2926
  "# Generated by viberails \u2014 https://viberails.sh",
2684
2927
  "",
2685
- "npx viberails check --staged",
2928
+ "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
2686
2929
  ""
2687
2930
  ].join("\n");
2688
- fs16.writeFileSync(hookPath, script, { mode: 493 });
2931
+ fs17.writeFileSync(hookPath, script, { mode: 493 });
2689
2932
  }
2690
2933
  function addLefthookPreCommit(lefthookPath) {
2691
- const content = fs16.readFileSync(lefthookPath, "utf-8");
2934
+ const content = fs17.readFileSync(lefthookPath, "utf-8");
2692
2935
  if (content.includes("viberails")) return;
2693
2936
  const doc = (0, import_yaml.parse)(content) ?? {};
2694
2937
  if (!doc["pre-commit"]) {
@@ -2698,30 +2941,30 @@ function addLefthookPreCommit(lefthookPath) {
2698
2941
  doc["pre-commit"].commands = {};
2699
2942
  }
2700
2943
  doc["pre-commit"].commands.viberails = {
2701
- run: "npx viberails check --staged"
2944
+ run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
2702
2945
  };
2703
- fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
2946
+ fs17.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
2704
2947
  }
2705
2948
  function detectHookManager(projectRoot) {
2706
- if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2707
- if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
2949
+ if (fs17.existsSync(path17.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2950
+ if (fs17.existsSync(path17.join(projectRoot, ".husky"))) return "Husky";
2708
2951
  return void 0;
2709
2952
  }
2710
2953
  function setupClaudeCodeHook(projectRoot) {
2711
- const claudeDir = path16.join(projectRoot, ".claude");
2712
- if (!fs16.existsSync(claudeDir)) {
2713
- fs16.mkdirSync(claudeDir, { recursive: true });
2954
+ const claudeDir = path17.join(projectRoot, ".claude");
2955
+ if (!fs17.existsSync(claudeDir)) {
2956
+ fs17.mkdirSync(claudeDir, { recursive: true });
2714
2957
  }
2715
- const settingsPath = path16.join(claudeDir, "settings.json");
2958
+ const settingsPath = path17.join(claudeDir, "settings.json");
2716
2959
  let settings = {};
2717
- if (fs16.existsSync(settingsPath)) {
2960
+ if (fs17.existsSync(settingsPath)) {
2718
2961
  try {
2719
- settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
2962
+ settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
2720
2963
  } catch {
2721
2964
  console.warn(
2722
- ` ${import_chalk9.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
2965
+ ` ${import_chalk10.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
2723
2966
  );
2724
- console.warn(` Fix the JSON manually, then re-run ${import_chalk9.default.cyan("viberails init --force")}`);
2967
+ console.warn(` Fix the JSON manually, then re-run ${import_chalk10.default.cyan("viberails init --force")}`);
2725
2968
  return;
2726
2969
  }
2727
2970
  }
@@ -2742,30 +2985,30 @@ function setupClaudeCodeHook(projectRoot) {
2742
2985
  }
2743
2986
  ];
2744
2987
  settings.hooks = hooks;
2745
- fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2988
+ fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2746
2989
  `);
2747
- console.log(` ${import_chalk9.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
2990
+ console.log(` ${import_chalk10.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
2748
2991
  }
2749
2992
  function setupClaudeMdReference(projectRoot) {
2750
- const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
2993
+ const claudeMdPath = path17.join(projectRoot, "CLAUDE.md");
2751
2994
  let content = "";
2752
- if (fs16.existsSync(claudeMdPath)) {
2753
- content = fs16.readFileSync(claudeMdPath, "utf-8");
2995
+ if (fs17.existsSync(claudeMdPath)) {
2996
+ content = fs17.readFileSync(claudeMdPath, "utf-8");
2754
2997
  }
2755
2998
  if (content.includes("@.viberails/context.md")) return;
2756
2999
  const ref = "\n@.viberails/context.md\n";
2757
3000
  const prefix = content.length === 0 ? "" : content.trimEnd();
2758
- fs16.writeFileSync(claudeMdPath, prefix + ref);
2759
- console.log(` ${import_chalk9.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3001
+ fs17.writeFileSync(claudeMdPath, prefix + ref);
3002
+ console.log(` ${import_chalk10.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2760
3003
  }
2761
3004
  function setupGithubAction(projectRoot, packageManager, options) {
2762
- const workflowDir = path16.join(projectRoot, ".github", "workflows");
2763
- const workflowPath = path16.join(workflowDir, "viberails.yml");
2764
- if (fs16.existsSync(workflowPath)) {
2765
- const existing = fs16.readFileSync(workflowPath, "utf-8");
3005
+ const workflowDir = path17.join(projectRoot, ".github", "workflows");
3006
+ const workflowPath = path17.join(workflowDir, "viberails.yml");
3007
+ if (fs17.existsSync(workflowPath)) {
3008
+ const existing = fs17.readFileSync(workflowPath, "utf-8");
2766
3009
  if (existing.includes("viberails")) return void 0;
2767
3010
  }
2768
- fs16.mkdirSync(workflowDir, { recursive: true });
3011
+ fs17.mkdirSync(workflowDir, { recursive: true });
2769
3012
  const pm = packageManager || "npm";
2770
3013
  const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
2771
3014
  const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
@@ -2774,7 +3017,7 @@ function setupGithubAction(projectRoot, packageManager, options) {
2774
3017
  "",
2775
3018
  "on:",
2776
3019
  " pull_request:",
2777
- " branches: [main]",
3020
+ " branches: [main, master]",
2778
3021
  "",
2779
3022
  "jobs:",
2780
3023
  " check:",
@@ -2792,87 +3035,94 @@ function setupGithubAction(projectRoot, packageManager, options) {
2792
3035
  " - uses: actions/setup-node@v4",
2793
3036
  " with:",
2794
3037
  " node-version: 22",
2795
- pm !== "npm" ? ` cache: ${pm}` : "",
3038
+ ` cache: ${pm}`,
2796
3039
  "",
2797
3040
  ` - run: ${installCmd}`
2798
3041
  );
2799
3042
  if (options?.typecheck) {
2800
- lines.push(` - run: ${runPrefix} tsc --noEmit`);
3043
+ const resolved = resolveTypecheckCommand(projectRoot, pm);
3044
+ if (resolved.command) {
3045
+ const ciCmd = resolved.command.startsWith("npx ") ? `${runPrefix} ${resolved.command.slice(4)}` : resolved.command;
3046
+ lines.push(` - run: ${ciCmd}`);
3047
+ }
2801
3048
  }
2802
3049
  if (options?.linter) {
2803
3050
  const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2804
3051
  lines.push(` - run: ${runPrefix} ${lintCmd}`);
2805
3052
  }
2806
3053
  lines.push(
2807
- ` - run: ${runPrefix} viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
3054
+ ` - run: npx viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
2808
3055
  ""
2809
3056
  );
2810
3057
  const content = lines.filter((l) => l !== void 0).join("\n");
2811
- fs16.writeFileSync(workflowPath, content);
3058
+ fs17.writeFileSync(workflowPath, content);
2812
3059
  return ".github/workflows/viberails.yml";
2813
3060
  }
2814
3061
  function writeHuskyPreCommit(huskyDir) {
2815
- const hookPath = path16.join(huskyDir, "pre-commit");
2816
- if (fs16.existsSync(hookPath)) {
2817
- const existing = fs16.readFileSync(hookPath, "utf-8");
3062
+ const hookPath = path17.join(huskyDir, "pre-commit");
3063
+ const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
3064
+ if (fs17.existsSync(hookPath)) {
3065
+ const existing = fs17.readFileSync(hookPath, "utf-8");
2818
3066
  if (!existing.includes("viberails")) {
2819
- fs16.writeFileSync(hookPath, `${existing.trimEnd()}
2820
- npx viberails check --staged
3067
+ fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3068
+ ${cmd}
2821
3069
  `);
2822
3070
  }
2823
3071
  return;
2824
3072
  }
2825
- fs16.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
3073
+ fs17.writeFileSync(hookPath, `#!/bin/sh
3074
+ ${cmd}
3075
+ `, { mode: 493 });
2826
3076
  }
2827
3077
 
2828
3078
  // src/commands/init-hooks-extra.ts
2829
- var fs17 = __toESM(require("fs"), 1);
2830
- var path17 = __toESM(require("path"), 1);
2831
- var import_chalk10 = __toESM(require("chalk"), 1);
3079
+ var fs18 = __toESM(require("fs"), 1);
3080
+ var path18 = __toESM(require("path"), 1);
3081
+ var import_chalk11 = __toESM(require("chalk"), 1);
2832
3082
  var import_yaml2 = require("yaml");
2833
- function addPreCommitStep(projectRoot, name, command, marker) {
2834
- const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2835
- if (fs17.existsSync(lefthookPath)) {
2836
- const content = fs17.readFileSync(lefthookPath, "utf-8");
3083
+ function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
3084
+ const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3085
+ if (fs18.existsSync(lefthookPath)) {
3086
+ const content = fs18.readFileSync(lefthookPath, "utf-8");
2837
3087
  if (content.includes(marker)) return void 0;
2838
3088
  const doc = (0, import_yaml2.parse)(content) ?? {};
2839
3089
  if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
2840
3090
  if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
2841
- doc["pre-commit"].commands[name] = { run: command };
2842
- fs17.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
3091
+ doc["pre-commit"].commands[name] = { run: command, ...lefthookExtra };
3092
+ fs18.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
2843
3093
  return "lefthook.yml";
2844
3094
  }
2845
- const huskyDir = path17.join(projectRoot, ".husky");
2846
- if (fs17.existsSync(huskyDir)) {
2847
- const hookPath = path17.join(huskyDir, "pre-commit");
2848
- if (fs17.existsSync(hookPath)) {
2849
- const existing = fs17.readFileSync(hookPath, "utf-8");
3095
+ const huskyDir = path18.join(projectRoot, ".husky");
3096
+ if (fs18.existsSync(huskyDir)) {
3097
+ const hookPath = path18.join(huskyDir, "pre-commit");
3098
+ if (fs18.existsSync(hookPath)) {
3099
+ const existing = fs18.readFileSync(hookPath, "utf-8");
2850
3100
  if (existing.includes(marker)) return void 0;
2851
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3101
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2852
3102
  ${command}
2853
3103
  `);
2854
3104
  } else {
2855
- fs17.writeFileSync(hookPath, `#!/bin/sh
3105
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2856
3106
  ${command}
2857
3107
  `, { mode: 493 });
2858
3108
  }
2859
3109
  return ".husky/pre-commit";
2860
3110
  }
2861
- const gitDir = path17.join(projectRoot, ".git");
2862
- if (fs17.existsSync(gitDir)) {
2863
- const hooksDir = path17.join(gitDir, "hooks");
2864
- if (!fs17.existsSync(hooksDir)) fs17.mkdirSync(hooksDir, { recursive: true });
2865
- const hookPath = path17.join(hooksDir, "pre-commit");
2866
- if (fs17.existsSync(hookPath)) {
2867
- const existing = fs17.readFileSync(hookPath, "utf-8");
3111
+ const gitDir = path18.join(projectRoot, ".git");
3112
+ if (fs18.existsSync(gitDir)) {
3113
+ const hooksDir = path18.join(gitDir, "hooks");
3114
+ if (!fs18.existsSync(hooksDir)) fs18.mkdirSync(hooksDir, { recursive: true });
3115
+ const hookPath = path18.join(hooksDir, "pre-commit");
3116
+ if (fs18.existsSync(hookPath)) {
3117
+ const existing = fs18.readFileSync(hookPath, "utf-8");
2868
3118
  if (existing.includes(marker)) return void 0;
2869
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3119
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2870
3120
 
2871
3121
  # ${name}
2872
3122
  ${command}
2873
3123
  `);
2874
3124
  } else {
2875
- fs17.writeFileSync(hookPath, `#!/bin/sh
3125
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2876
3126
  # Generated by viberails
2877
3127
 
2878
3128
  # ${name}
@@ -2885,19 +3135,36 @@ ${command}
2885
3135
  }
2886
3136
  return void 0;
2887
3137
  }
2888
- function setupTypecheckHook(projectRoot) {
2889
- const target = addPreCommitStep(projectRoot, "typecheck", "npx tsc --noEmit", "tsc");
3138
+ function setupTypecheckHook(projectRoot, packageManager) {
3139
+ const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3140
+ if (!resolved.command) {
3141
+ console.log(` ${import_chalk11.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3142
+ return void 0;
3143
+ }
3144
+ const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
2890
3145
  if (target) {
2891
- console.log(` ${import_chalk10.default.green("\u2713")} ${target} \u2014 added typecheck (tsc --noEmit)`);
3146
+ console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
2892
3147
  }
2893
3148
  return target;
2894
3149
  }
2895
3150
  function setupLintHook(projectRoot, linter) {
2896
- const command = linter === "biome" ? "npx biome check ." : "npx eslint .";
3151
+ const isLefthook = fs18.existsSync(path18.join(projectRoot, "lefthook.yml"));
2897
3152
  const linterName = linter === "biome" ? "Biome" : "ESLint";
2898
- const target = addPreCommitStep(projectRoot, "lint", command, linter);
3153
+ let command;
3154
+ let lefthookExtra;
3155
+ if (isLefthook) {
3156
+ command = linter === "biome" ? "npx biome check {staged_files}" : "npx eslint {staged_files}";
3157
+ lefthookExtra = {
3158
+ glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}"
3159
+ };
3160
+ } else {
3161
+ const exts = linter === "biome" ? "'*.js' '*.ts' '*.jsx' '*.tsx' '*.json' '*.css'" : "'*.js' '*.ts' '*.jsx' '*.tsx'";
3162
+ const lintCmd = linter === "biome" ? "biome check" : "eslint";
3163
+ command = `git diff --cached --name-only --diff-filter=ACMR -- ${exts} | xargs npx ${lintCmd}`;
3164
+ }
3165
+ const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
2899
3166
  if (target) {
2900
- console.log(` ${import_chalk10.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3167
+ console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
2901
3168
  }
2902
3169
  return target;
2903
3170
  }
@@ -2908,7 +3175,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2908
3175
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
2909
3176
  }
2910
3177
  if (integrations.typecheckHook) {
2911
- const t = setupTypecheckHook(projectRoot);
3178
+ const t = setupTypecheckHook(projectRoot, opts.packageManager);
2912
3179
  if (t) created.push(`${t} \u2014 added typecheck`);
2913
3180
  }
2914
3181
  if (integrations.lintHook && opts.linter) {
@@ -2945,11 +3212,11 @@ async function initCommand(options, cwd) {
2945
3212
  "No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
2946
3213
  );
2947
3214
  }
2948
- const configPath = path18.join(projectRoot, CONFIG_FILE5);
2949
- if (fs18.existsSync(configPath) && !options.force) {
3215
+ const configPath = path19.join(projectRoot, CONFIG_FILE5);
3216
+ if (fs19.existsSync(configPath) && !options.force) {
2950
3217
  console.log(
2951
- `${import_chalk11.default.yellow("!")} viberails is already initialized.
2952
- Run ${import_chalk11.default.cyan("viberails config")} to edit rules, ${import_chalk11.default.cyan("viberails sync")} to update, or ${import_chalk11.default.cyan("viberails init --force")} to start fresh.`
3218
+ `${import_chalk12.default.yellow("!")} viberails is already initialized.
3219
+ Run ${import_chalk12.default.cyan("viberails config")} to edit rules, ${import_chalk12.default.cyan("viberails sync")} to update, or ${import_chalk12.default.cyan("viberails init --force")} to start fresh.`
2953
3220
  );
2954
3221
  return;
2955
3222
  }
@@ -2957,7 +3224,7 @@ async function initCommand(options, cwd) {
2957
3224
  await initInteractive(projectRoot, configPath, options);
2958
3225
  }
2959
3226
  async function initNonInteractive(projectRoot, configPath) {
2960
- console.log(import_chalk11.default.dim("Scanning project..."));
3227
+ console.log(import_chalk12.default.dim("Scanning project..."));
2961
3228
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
2962
3229
  const config = (0, import_config8.generateConfig)(scanResult);
2963
3230
  for (const pkg of config.packages) {
@@ -2970,11 +3237,11 @@ async function initNonInteractive(projectRoot, configPath) {
2970
3237
  const exempted = getExemptedPackages(config);
2971
3238
  if (exempted.length > 0) {
2972
3239
  console.log(
2973
- ` ${import_chalk11.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk11.default.dim("(types-only)")}`
3240
+ ` ${import_chalk12.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk12.default.dim("(types-only)")}`
2974
3241
  );
2975
3242
  }
2976
3243
  if (config.packages.length > 1) {
2977
- console.log(import_chalk11.default.dim("Building import graph..."));
3244
+ console.log(import_chalk12.default.dim("Building import graph..."));
2978
3245
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
2979
3246
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
2980
3247
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -2987,16 +3254,16 @@ async function initNonInteractive(projectRoot, configPath) {
2987
3254
  }
2988
3255
  }
2989
3256
  const compacted = (0, import_config8.compactConfig)(config);
2990
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3257
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
2991
3258
  `);
2992
3259
  writeGeneratedFiles(projectRoot, config, scanResult);
2993
3260
  updateGitignore(projectRoot);
2994
3261
  setupClaudeCodeHook(projectRoot);
2995
3262
  setupClaudeMdReference(projectRoot);
2996
3263
  const rootPkg = config.packages[0];
2997
- const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
3264
+ const rootPkgPm = rootPkg?.stack?.packageManager?.split("@")[0] ?? "npm";
2998
3265
  const linter = rootPkg?.stack?.linter?.split("@")[0];
2999
- const isTypeScript = rootPkg?.stack?.language === "typescript";
3266
+ const isTypeScript = rootPkg?.stack?.language?.split("@")[0] === "typescript";
3000
3267
  const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
3001
3268
  linter,
3002
3269
  typecheck: isTypeScript
@@ -3004,17 +3271,17 @@ async function initNonInteractive(projectRoot, configPath) {
3004
3271
  const hookManager = detectHookManager(projectRoot);
3005
3272
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3006
3273
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3007
- const ok = import_chalk11.default.green("\u2713");
3274
+ const ok = import_chalk12.default.green("\u2713");
3008
3275
  const created = [
3009
- `${ok} ${path18.basename(configPath)}`,
3276
+ `${ok} ${path19.basename(configPath)}`,
3010
3277
  `${ok} .viberails/context.md`,
3011
3278
  `${ok} .viberails/scan-result.json`,
3012
3279
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3013
3280
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3014
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk11.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3281
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk12.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3015
3282
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3016
3283
  ].filter(Boolean);
3017
- if (hasHookManager && rootPkg?.stack?.language === "typescript") setupTypecheckHook(projectRoot);
3284
+ if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
3018
3285
  if (hasHookManager && linter) setupLintHook(projectRoot, linter);
3019
3286
  console.log(`
3020
3287
  Created:
@@ -3022,9 +3289,9 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
3022
3289
  }
3023
3290
  async function initInteractive(projectRoot, configPath, options) {
3024
3291
  clack8.intro("viberails");
3025
- if (fs18.existsSync(configPath) && options.force) {
3292
+ if (fs19.existsSync(configPath) && options.force) {
3026
3293
  const replace = await confirmDangerous(
3027
- `${path18.basename(configPath)} already exists and will be replaced. Continue?`
3294
+ `${path19.basename(configPath)} already exists and will be replaced. Continue?`
3028
3295
  );
3029
3296
  if (!replace) {
3030
3297
  clack8.outro("Aborted. No files were written.");
@@ -3095,9 +3362,9 @@ async function initInteractive(projectRoot, configPath, options) {
3095
3362
  }
3096
3363
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
3097
3364
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3098
- isTypeScript: rootPkgStack?.language === "typescript",
3365
+ isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3099
3366
  linter: rootPkgStack?.linter?.split("@")[0],
3100
- packageManager: rootPkgStack?.packageManager,
3367
+ packageManager: rootPkgStack?.packageManager?.split("@")[0],
3101
3368
  isWorkspace: config.packages.length > 1
3102
3369
  });
3103
3370
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
@@ -3106,37 +3373,37 @@ async function initInteractive(projectRoot, configPath, options) {
3106
3373
  return;
3107
3374
  }
3108
3375
  const compacted = (0, import_config8.compactConfig)(config);
3109
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3376
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3110
3377
  `);
3111
3378
  writeGeneratedFiles(projectRoot, config, scanResult);
3112
3379
  updateGitignore(projectRoot);
3113
- const ok = import_chalk11.default.green("\u2713");
3114
- clack8.log.step(`${ok} ${path18.basename(configPath)}`);
3380
+ const ok = import_chalk12.default.green("\u2713");
3381
+ clack8.log.step(`${ok} ${path19.basename(configPath)}`);
3115
3382
  clack8.log.step(`${ok} .viberails/context.md`);
3116
3383
  clack8.log.step(`${ok} .viberails/scan-result.json`);
3117
3384
  setupSelectedIntegrations(projectRoot, integrations, {
3118
3385
  linter: rootPkgStack?.linter?.split("@")[0],
3119
- packageManager: rootPkgStack?.packageManager
3386
+ packageManager: rootPkgStack?.packageManager?.split("@")[0]
3120
3387
  });
3121
3388
  clack8.outro(
3122
3389
  `Done! Next: review viberails.config.json, then run viberails check
3123
- ${import_chalk11.default.dim("Tip: use")} ${import_chalk11.default.cyan("viberails check --enforce")} ${import_chalk11.default.dim("in CI to block PRs on violations.")}`
3390
+ ${import_chalk12.default.dim("Tip: use")} ${import_chalk12.default.cyan("viberails check --enforce")} ${import_chalk12.default.dim("in CI to block PRs on violations.")}`
3124
3391
  );
3125
3392
  }
3126
3393
 
3127
3394
  // src/commands/sync.ts
3128
- var fs19 = __toESM(require("fs"), 1);
3129
- var path19 = __toESM(require("path"), 1);
3395
+ var fs20 = __toESM(require("fs"), 1);
3396
+ var path20 = __toESM(require("path"), 1);
3130
3397
  var clack9 = __toESM(require("@clack/prompts"), 1);
3131
3398
  var import_config9 = require("@viberails/config");
3132
3399
  var import_scanner3 = require("@viberails/scanner");
3133
- var import_chalk12 = __toESM(require("chalk"), 1);
3400
+ var import_chalk13 = __toESM(require("chalk"), 1);
3134
3401
  var CONFIG_FILE6 = "viberails.config.json";
3135
3402
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
3136
3403
  function loadPreviousStats(projectRoot) {
3137
- const scanResultPath = path19.join(projectRoot, SCAN_RESULT_FILE2);
3404
+ const scanResultPath = path20.join(projectRoot, SCAN_RESULT_FILE2);
3138
3405
  try {
3139
- const raw = fs19.readFileSync(scanResultPath, "utf-8");
3406
+ const raw = fs20.readFileSync(scanResultPath, "utf-8");
3140
3407
  const parsed = JSON.parse(raw);
3141
3408
  if (parsed?.statistics?.totalFiles !== void 0) {
3142
3409
  return parsed.statistics;
@@ -3153,15 +3420,15 @@ async function syncCommand(options, cwd) {
3153
3420
  "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"
3154
3421
  );
3155
3422
  }
3156
- const configPath = path19.join(projectRoot, CONFIG_FILE6);
3423
+ const configPath = path20.join(projectRoot, CONFIG_FILE6);
3157
3424
  const existing = await (0, import_config9.loadConfig)(configPath);
3158
3425
  const previousStats = loadPreviousStats(projectRoot);
3159
- console.log(import_chalk12.default.dim("Scanning project..."));
3426
+ console.log(import_chalk13.default.dim("Scanning project..."));
3160
3427
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3161
3428
  const merged = (0, import_config9.mergeConfig)(existing, scanResult);
3162
3429
  const compacted = (0, import_config9.compactConfig)(merged);
3163
3430
  const compactedJson = JSON.stringify(compacted, null, 2);
3164
- const rawDisk = fs19.readFileSync(configPath, "utf-8").trim();
3431
+ const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
3165
3432
  const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3166
3433
  const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3167
3434
  const configChanged = diskWithoutSync !== mergedWithoutSync;
@@ -3169,13 +3436,13 @@ async function syncCommand(options, cwd) {
3169
3436
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
3170
3437
  if (changes.length > 0 || statsDelta) {
3171
3438
  console.log(`
3172
- ${import_chalk12.default.bold("Changes:")}`);
3439
+ ${import_chalk13.default.bold("Changes:")}`);
3173
3440
  for (const change of changes) {
3174
- const icon = change.type === "removed" ? import_chalk12.default.red("-") : import_chalk12.default.green("+");
3441
+ const icon = change.type === "removed" ? import_chalk13.default.red("-") : import_chalk13.default.green("+");
3175
3442
  console.log(` ${icon} ${change.description}`);
3176
3443
  }
3177
3444
  if (statsDelta) {
3178
- console.log(` ${import_chalk12.default.dim(statsDelta)}`);
3445
+ console.log(` ${import_chalk13.default.dim(statsDelta)}`);
3179
3446
  }
3180
3447
  }
3181
3448
  if (options?.interactive) {
@@ -3208,7 +3475,7 @@ ${import_chalk12.default.bold("Changes:")}`);
3208
3475
  });
3209
3476
  applyRuleOverrides(merged, overrides);
3210
3477
  const recompacted = (0, import_config9.compactConfig)(merged);
3211
- fs19.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3478
+ fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3212
3479
  `);
3213
3480
  writeGeneratedFiles(projectRoot, merged, scanResult);
3214
3481
  clack9.log.success("Updated config with your customizations.");
@@ -3216,22 +3483,22 @@ ${import_chalk12.default.bold("Changes:")}`);
3216
3483
  return;
3217
3484
  }
3218
3485
  }
3219
- fs19.writeFileSync(configPath, `${compactedJson}
3486
+ fs20.writeFileSync(configPath, `${compactedJson}
3220
3487
  `);
3221
3488
  writeGeneratedFiles(projectRoot, merged, scanResult);
3222
3489
  console.log(`
3223
- ${import_chalk12.default.bold("Synced:")}`);
3490
+ ${import_chalk13.default.bold("Synced:")}`);
3224
3491
  if (configChanged) {
3225
- console.log(` ${import_chalk12.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3492
+ console.log(` ${import_chalk13.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3226
3493
  } else {
3227
- console.log(` ${import_chalk12.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3494
+ console.log(` ${import_chalk13.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3228
3495
  }
3229
- console.log(` ${import_chalk12.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3230
- console.log(` ${import_chalk12.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3496
+ console.log(` ${import_chalk13.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3497
+ console.log(` ${import_chalk13.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3231
3498
  }
3232
3499
 
3233
3500
  // src/index.ts
3234
- var VERSION = "0.5.5";
3501
+ var VERSION = "0.6.1";
3235
3502
  var program = new import_commander.Command();
3236
3503
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3237
3504
  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) => {
@@ -3239,7 +3506,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
3239
3506
  await initCommand(options);
3240
3507
  } catch (err) {
3241
3508
  const message = err instanceof Error ? err.message : String(err);
3242
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3509
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3243
3510
  process.exit(1);
3244
3511
  }
3245
3512
  });
@@ -3248,7 +3515,7 @@ program.command("sync").description("Re-scan and update generated files").option
3248
3515
  await syncCommand(options);
3249
3516
  } catch (err) {
3250
3517
  const message = err instanceof Error ? err.message : String(err);
3251
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3518
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3252
3519
  process.exit(1);
3253
3520
  }
3254
3521
  });
@@ -3257,7 +3524,7 @@ program.command("config").description("Interactively edit existing config rules"
3257
3524
  await configCommand(options);
3258
3525
  } catch (err) {
3259
3526
  const message = err instanceof Error ? err.message : String(err);
3260
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3527
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3261
3528
  process.exit(1);
3262
3529
  }
3263
3530
  });
@@ -3278,7 +3545,7 @@ program.command("check").description("Check files against enforced rules").optio
3278
3545
  process.exit(exitCode);
3279
3546
  } catch (err) {
3280
3547
  const message = err instanceof Error ? err.message : String(err);
3281
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3548
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3282
3549
  process.exit(1);
3283
3550
  }
3284
3551
  }
@@ -3289,7 +3556,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
3289
3556
  process.exit(exitCode);
3290
3557
  } catch (err) {
3291
3558
  const message = err instanceof Error ? err.message : String(err);
3292
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3559
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3293
3560
  process.exit(1);
3294
3561
  }
3295
3562
  });
@@ -3298,7 +3565,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
3298
3565
  await boundariesCommand(options);
3299
3566
  } catch (err) {
3300
3567
  const message = err instanceof Error ? err.message : String(err);
3301
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3568
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3302
3569
  process.exit(1);
3303
3570
  }
3304
3571
  });