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.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";
@@ -131,7 +131,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
131
131
  options.push({
132
132
  value: "typecheck",
133
133
  label: "Typecheck (tsc --noEmit)",
134
- hint: "catches type errors before commit"
134
+ hint: "pre-commit hook + CI check"
135
135
  });
136
136
  }
137
137
  if (tools?.linter) {
@@ -139,7 +139,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
139
139
  options.push({
140
140
  value: "lint",
141
141
  label: `Lint check (${linterName})`,
142
- hint: "runs linter on staged files before commit"
142
+ hint: "pre-commit hook + CI check"
143
143
  });
144
144
  }
145
145
  options.push(
@@ -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) {
@@ -1442,30 +1517,31 @@ function formatPackageSummary(pkg) {
1442
1517
  if (pkg.stack.styling) {
1443
1518
  parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
1444
1519
  }
1445
- const files = `${pkg.statistics.totalFiles} files`;
1520
+ const n = pkg.statistics.totalFiles;
1521
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1446
1522
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1447
1523
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1448
1524
  }
1449
1525
  function displayMonorepoResults(scanResult) {
1450
1526
  const { stack, packages } = scanResult;
1451
1527
  console.log(`
1452
- ${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
1453
- 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)}`);
1454
1530
  if (stack.packageManager) {
1455
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1531
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1456
1532
  }
1457
1533
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1458
- 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)`);
1459
1535
  } else {
1460
1536
  if (stack.linter) {
1461
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.linter)}`);
1537
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1462
1538
  }
1463
1539
  if (stack.formatter) {
1464
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1540
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1465
1541
  }
1466
1542
  }
1467
1543
  if (stack.testRunner) {
1468
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1544
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1469
1545
  }
1470
1546
  console.log("");
1471
1547
  for (const pkg of packages) {
@@ -1476,13 +1552,13 @@ ${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`
1476
1552
  );
1477
1553
  if (packagesWithDirs.length > 0) {
1478
1554
  console.log(`
1479
- ${import_chalk3.default.bold("Structure:")}`);
1555
+ ${import_chalk4.default.bold("Structure:")}`);
1480
1556
  for (const pkg of packagesWithDirs) {
1481
1557
  const groups = groupByRole(pkg.structure.directories);
1482
1558
  if (groups.length === 0) continue;
1483
1559
  console.log(` ${pkg.relativePath}:`);
1484
1560
  for (const group of groups) {
1485
- console.log(` ${import_chalk3.default.green("\u2713")} ${formatRoleGroup(group)}`);
1561
+ console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
1486
1562
  }
1487
1563
  }
1488
1564
  }
@@ -1498,7 +1574,8 @@ function formatPackageSummaryPlain(pkg) {
1498
1574
  if (pkg.stack.styling) {
1499
1575
  parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
1500
1576
  }
1501
- const files = `${pkg.statistics.totalFiles} files`;
1577
+ const n = pkg.statistics.totalFiles;
1578
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1502
1579
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1503
1580
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1504
1581
  }
@@ -1562,7 +1639,7 @@ function displayConventions(scanResult) {
1562
1639
  const conventionEntries = Object.entries(scanResult.conventions);
1563
1640
  if (conventionEntries.length === 0) return;
1564
1641
  console.log(`
1565
- ${import_chalk4.default.bold("Conventions:")}`);
1642
+ ${import_chalk5.default.bold("Conventions:")}`);
1566
1643
  for (const [key, convention] of conventionEntries) {
1567
1644
  if (convention.confidence === "low") continue;
1568
1645
  const label = import_types3.CONVENTION_LABELS[key] ?? key;
@@ -1570,19 +1647,19 @@ ${import_chalk4.default.bold("Conventions:")}`);
1570
1647
  const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
1571
1648
  const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
1572
1649
  if (allSame || pkgValues.length <= 1) {
1573
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
1574
- 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)})`);
1575
1652
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1576
1653
  } else {
1577
- console.log(` ${import_chalk4.default.yellow("~")} ${label}: varies by package`);
1654
+ console.log(` ${import_chalk5.default.yellow("~")} ${label}: varies by package`);
1578
1655
  for (const pv of pkgValues) {
1579
1656
  const pct = Math.round(pv.convention.consistency);
1580
1657
  console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
1581
1658
  }
1582
1659
  }
1583
1660
  } else {
1584
- const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
1585
- 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)})`);
1586
1663
  console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
1587
1664
  }
1588
1665
  }
@@ -1590,7 +1667,7 @@ ${import_chalk4.default.bold("Conventions:")}`);
1590
1667
  function displaySummarySection(scanResult) {
1591
1668
  const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
1592
1669
  console.log(`
1593
- ${import_chalk4.default.bold("Summary:")}`);
1670
+ ${import_chalk5.default.bold("Summary:")}`);
1594
1671
  console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
1595
1672
  const ext = formatExtensions(scanResult.statistics.filesByExtension);
1596
1673
  if (ext) {
@@ -1604,47 +1681,47 @@ function displayScanResults(scanResult) {
1604
1681
  }
1605
1682
  const { stack } = scanResult;
1606
1683
  console.log(`
1607
- ${import_chalk4.default.bold("Detected:")}`);
1684
+ ${import_chalk5.default.bold("Detected:")}`);
1608
1685
  if (stack.framework) {
1609
- 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)}`);
1610
1687
  }
1611
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
1688
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
1612
1689
  if (stack.styling) {
1613
- 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)}`);
1614
1691
  }
1615
1692
  if (stack.backend) {
1616
- 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)}`);
1617
1694
  }
1618
1695
  if (stack.orm) {
1619
- 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)}`);
1620
1697
  }
1621
1698
  if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
1622
- 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)`);
1623
1700
  } else {
1624
1701
  if (stack.linter) {
1625
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
1702
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
1626
1703
  }
1627
1704
  if (stack.formatter) {
1628
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1705
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
1629
1706
  }
1630
1707
  }
1631
1708
  if (stack.testRunner) {
1632
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1709
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
1633
1710
  }
1634
1711
  if (stack.packageManager) {
1635
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1712
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
1636
1713
  }
1637
1714
  if (stack.libraries.length > 0) {
1638
1715
  for (const lib of stack.libraries) {
1639
- 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)}`);
1640
1717
  }
1641
1718
  }
1642
1719
  const groups = groupByRole(scanResult.structure.directories);
1643
1720
  if (groups.length > 0) {
1644
1721
  console.log(`
1645
- ${import_chalk4.default.bold("Structure:")}`);
1722
+ ${import_chalk5.default.bold("Structure:")}`);
1646
1723
  for (const group of groups) {
1647
- console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
1724
+ console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
1648
1725
  }
1649
1726
  }
1650
1727
  displayConventions(scanResult);
@@ -1654,49 +1731,49 @@ ${import_chalk4.default.bold("Structure:")}`);
1654
1731
  function displayRulesPreview(config) {
1655
1732
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1656
1733
  console.log(
1657
- `${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)")}`
1658
1735
  );
1659
- 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`);
1660
1737
  if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
1661
1738
  console.log(
1662
- ` ${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})`
1663
1740
  );
1664
1741
  } else if (config.rules.testCoverage > 0) {
1665
- 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}%`);
1666
1743
  } else {
1667
- console.log(` ${import_chalk4.default.dim("\u2022")} Test coverage target: disabled`);
1744
+ console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: disabled`);
1668
1745
  }
1669
1746
  if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1670
- 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}`);
1671
1748
  } else {
1672
- console.log(` ${import_chalk4.default.dim("\u2022")} Enforce file naming: no`);
1749
+ console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: no`);
1673
1750
  }
1674
1751
  console.log(
1675
- ` ${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"}`
1676
1753
  );
1677
1754
  console.log("");
1678
1755
  }
1679
1756
  function displayInitSummary(config, exemptedPackages) {
1680
1757
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1681
1758
  const isMonorepo = config.packages.length > 1;
1682
- const ok = import_chalk4.default.green("\u2713");
1683
- const off = import_chalk4.default.dim("\u25CB");
1759
+ const ok = import_chalk5.default.green("\u2713");
1760
+ const off = import_chalk5.default.dim("\u25CB");
1684
1761
  console.log("");
1685
- console.log(` ${import_chalk4.default.bold("Rules to apply:")}`);
1686
- 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`)}`);
1687
1764
  const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1688
1765
  if (config.rules.enforceNaming && fileNaming) {
1689
- console.log(` ${ok} File naming: ${import_chalk4.default.cyan(fileNaming)}`);
1766
+ console.log(` ${ok} File naming: ${import_chalk5.default.cyan(fileNaming)}`);
1690
1767
  } else {
1691
- console.log(` ${off} File naming: ${import_chalk4.default.dim("not enforced")}`);
1768
+ console.log(` ${off} File naming: ${import_chalk5.default.dim("not enforced")}`);
1692
1769
  }
1693
1770
  const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1694
1771
  if (config.rules.enforceMissingTests && testPattern) {
1695
- console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan(`enforced (${testPattern})`)}`);
1772
+ console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan(`enforced (${testPattern})`)}`);
1696
1773
  } else if (config.rules.enforceMissingTests) {
1697
- console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan("enforced")}`);
1774
+ console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan("enforced")}`);
1698
1775
  } else {
1699
- console.log(` ${off} Missing tests: ${import_chalk4.default.dim("not enforced")}`);
1776
+ console.log(` ${off} Missing tests: ${import_chalk5.default.dim("not enforced")}`);
1700
1777
  }
1701
1778
  if (config.rules.testCoverage > 0) {
1702
1779
  if (isMonorepo) {
@@ -1704,27 +1781,27 @@ function displayInitSummary(config, exemptedPackages) {
1704
1781
  (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
1705
1782
  );
1706
1783
  console.log(
1707
- ` ${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)`)}`
1708
1785
  );
1709
1786
  } else {
1710
- console.log(` ${ok} Coverage: ${import_chalk4.default.cyan(`${config.rules.testCoverage}%`)}`);
1787
+ console.log(` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)}`);
1711
1788
  }
1712
1789
  } else {
1713
- console.log(` ${off} Coverage: ${import_chalk4.default.dim("disabled")}`);
1790
+ console.log(` ${off} Coverage: ${import_chalk5.default.dim("disabled")}`);
1714
1791
  }
1715
1792
  if (exemptedPackages.length > 0) {
1716
1793
  console.log(
1717
- ` ${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)")}`
1718
1795
  );
1719
1796
  }
1720
1797
  if (isMonorepo) {
1721
1798
  console.log(
1722
1799
  `
1723
- ${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`)}`
1724
1801
  );
1725
1802
  } else {
1726
1803
  console.log(`
1727
- ${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")}`);
1728
1805
  }
1729
1806
  console.log("");
1730
1807
  }
@@ -2036,7 +2113,7 @@ async function configCommand(options, cwd) {
2036
2113
  }
2037
2114
  const configPath = path9.join(projectRoot, CONFIG_FILE3);
2038
2115
  if (!fs10.existsSync(configPath)) {
2039
- 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.`);
2040
2117
  return;
2041
2118
  }
2042
2119
  clack6.intro("viberails config");
@@ -2121,22 +2198,22 @@ async function rescanAndMerge(projectRoot, config) {
2121
2198
  var fs13 = __toESM(require("fs"), 1);
2122
2199
  var path13 = __toESM(require("path"), 1);
2123
2200
  var import_config7 = require("@viberails/config");
2124
- var import_chalk7 = __toESM(require("chalk"), 1);
2201
+ var import_chalk8 = __toESM(require("chalk"), 1);
2125
2202
 
2126
2203
  // src/commands/fix-helpers.ts
2127
2204
  var import_node_child_process4 = require("child_process");
2128
- var import_chalk6 = __toESM(require("chalk"), 1);
2205
+ var import_chalk7 = __toESM(require("chalk"), 1);
2129
2206
  function printPlan(renames, stubs) {
2130
2207
  if (renames.length > 0) {
2131
- console.log(import_chalk6.default.bold("\nFile renames:"));
2208
+ console.log(import_chalk7.default.bold("\nFile renames:"));
2132
2209
  for (const r of renames) {
2133
- 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)}`);
2134
2211
  }
2135
2212
  }
2136
2213
  if (stubs.length > 0) {
2137
- console.log(import_chalk6.default.bold("\nTest stubs to create:"));
2214
+ console.log(import_chalk7.default.bold("\nTest stubs to create:"));
2138
2215
  for (const s of stubs) {
2139
- console.log(` ${import_chalk6.default.green("+")} ${s.path}`);
2216
+ console.log(` ${import_chalk7.default.green("+")} ${s.path}`);
2140
2217
  }
2141
2218
  }
2142
2219
  }
@@ -2170,8 +2247,62 @@ function computeNewSpecifier(oldSpecifier, newBare) {
2170
2247
  const newSpec = prefix + newBare;
2171
2248
  return hasJsExt ? `${newSpec}.js` : newSpec;
2172
2249
  }
2173
- async function updateImportsAfterRenames(renames, projectRoot) {
2250
+ async function scanForAliasImports(renames, projectRoot) {
2174
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: [] };
2175
2306
  const { Project, SyntaxKind } = await import("ts-morph");
2176
2307
  const renameMap = /* @__PURE__ */ new Map();
2177
2308
  for (const r of renames) {
@@ -2186,15 +2317,44 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2186
2317
  });
2187
2318
  project.addSourceFilesAtPaths(path10.join(projectRoot, "**/*.{ts,tsx,js,jsx,mjs,cjs}"));
2188
2319
  const updates = [];
2320
+ const skippedAliases = [];
2189
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
+ }
2190
2327
  for (const sourceFile of project.getSourceFiles()) {
2191
2328
  const filePath = sourceFile.getFilePath();
2192
2329
  const segments = filePath.split(path10.sep);
2193
- 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;
2194
2341
  const fileDir = path10.dirname(filePath);
2195
2342
  for (const decl of sourceFile.getImportDeclarations()) {
2196
2343
  const specifier = decl.getModuleSpecifierValue();
2197
- 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
+ }
2198
2358
  const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
2199
2359
  if (!match) continue;
2200
2360
  const newSpec = computeNewSpecifier(specifier, match.newBare);
@@ -2244,7 +2404,7 @@ async function updateImportsAfterRenames(renames, projectRoot) {
2244
2404
  if (updates.length > 0) {
2245
2405
  await project.save();
2246
2406
  }
2247
- return updates;
2407
+ return { updates, skippedAliases };
2248
2408
  }
2249
2409
  function resolveToRenamedFile(specifier, fromDir, renameMap, extensions) {
2250
2410
  const cleanSpec = specifier.endsWith(".js") ? specifier.slice(0, -3) : specifier;
@@ -2347,10 +2507,10 @@ function generateTestStub(sourceRelPath, config, projectRoot) {
2347
2507
  const pkg = resolvePackageForFile(sourceRelPath, config);
2348
2508
  const testPattern = pkg?.structure?.testPattern;
2349
2509
  if (!testPattern) return null;
2350
- const basename8 = path12.basename(sourceRelPath);
2351
- const ext = path12.extname(basename8);
2510
+ const basename9 = path12.basename(sourceRelPath);
2511
+ const ext = path12.extname(basename9);
2352
2512
  if (!ext) return null;
2353
- const stem = basename8.slice(0, -ext.length);
2513
+ const stem = basename9.slice(0, -ext.length);
2354
2514
  const testSuffix = testPattern.replace("*", "");
2355
2515
  const testFilename = `${stem}${testSuffix}`;
2356
2516
  const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
@@ -2381,13 +2541,13 @@ async function fixCommand(options, cwd) {
2381
2541
  const startDir = cwd ?? process.cwd();
2382
2542
  const projectRoot = findProjectRoot(startDir);
2383
2543
  if (!projectRoot) {
2384
- 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?`);
2385
2545
  return 1;
2386
2546
  }
2387
2547
  const configPath = path13.join(projectRoot, CONFIG_FILE4);
2388
2548
  if (!fs13.existsSync(configPath)) {
2389
2549
  console.error(
2390
- `${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.`
2391
2551
  );
2392
2552
  return 1;
2393
2553
  }
@@ -2396,7 +2556,7 @@ async function fixCommand(options, cwd) {
2396
2556
  const isDirty = checkGitDirty(projectRoot);
2397
2557
  if (isDirty) {
2398
2558
  console.log(
2399
- 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.")
2400
2560
  );
2401
2561
  }
2402
2562
  }
@@ -2425,13 +2585,59 @@ async function fixCommand(options, cwd) {
2425
2585
  if (stub) testStubs.push(stub);
2426
2586
  }
2427
2587
  }
2428
- if (dedupedRenames.length === 0 && testStubs.length === 0) {
2429
- 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.`);
2430
2637
  return 0;
2431
2638
  }
2432
- printPlan(dedupedRenames, testStubs);
2433
2639
  if (options.dryRun) {
2434
- 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."));
2435
2641
  return 0;
2436
2642
  }
2437
2643
  if (!options.yes) {
@@ -2442,15 +2648,15 @@ async function fixCommand(options, cwd) {
2442
2648
  }
2443
2649
  }
2444
2650
  let renameCount = 0;
2445
- for (const rename of dedupedRenames) {
2651
+ for (const rename of safeRenames) {
2446
2652
  if (executeRename(rename)) {
2447
2653
  renameCount++;
2448
2654
  }
2449
2655
  }
2450
2656
  let importUpdateCount = 0;
2451
2657
  if (renameCount > 0) {
2452
- const appliedRenames = dedupedRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2453
- const updates = await updateImportsAfterRenames(appliedRenames, projectRoot);
2658
+ const appliedRenames = safeRenames.filter((r) => fs13.existsSync(r.newAbsPath));
2659
+ const { updates } = await updateImportsAfterRenames(appliedRenames, projectRoot);
2454
2660
  importUpdateCount = updates.length;
2455
2661
  }
2456
2662
  let stubCount = 0;
@@ -2462,33 +2668,33 @@ async function fixCommand(options, cwd) {
2462
2668
  }
2463
2669
  console.log("");
2464
2670
  if (renameCount > 0) {
2465
- 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" : ""}`);
2466
2672
  }
2467
2673
  if (importUpdateCount > 0) {
2468
2674
  console.log(
2469
- `${import_chalk7.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2675
+ `${import_chalk8.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
2470
2676
  );
2471
2677
  }
2472
2678
  if (stubCount > 0) {
2473
- 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" : ""}`);
2474
2680
  }
2475
2681
  return 0;
2476
2682
  }
2477
2683
 
2478
2684
  // src/commands/init.ts
2479
- var fs18 = __toESM(require("fs"), 1);
2480
- var path18 = __toESM(require("path"), 1);
2685
+ var fs19 = __toESM(require("fs"), 1);
2686
+ var path19 = __toESM(require("path"), 1);
2481
2687
  var clack8 = __toESM(require("@clack/prompts"), 1);
2482
2688
  var import_config8 = require("@viberails/config");
2483
2689
  var import_scanner2 = require("@viberails/scanner");
2484
- var import_chalk11 = __toESM(require("chalk"), 1);
2690
+ var import_chalk12 = __toESM(require("chalk"), 1);
2485
2691
 
2486
2692
  // src/utils/check-prerequisites.ts
2487
2693
  var import_node_child_process5 = require("child_process");
2488
2694
  var fs14 = __toESM(require("fs"), 1);
2489
2695
  var path14 = __toESM(require("path"), 1);
2490
2696
  var clack7 = __toESM(require("@clack/prompts"), 1);
2491
- var import_chalk8 = __toESM(require("chalk"), 1);
2697
+ var import_chalk9 = __toESM(require("chalk"), 1);
2492
2698
  function checkCoveragePrereqs(projectRoot, scanResult) {
2493
2699
  const pm = scanResult.stack.packageManager.name;
2494
2700
  const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
@@ -2519,9 +2725,9 @@ function displayMissingPrereqs(prereqs) {
2519
2725
  const missing = prereqs.filter((p) => !p.installed);
2520
2726
  for (const m of missing) {
2521
2727
  const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2522
- console.log(` ${import_chalk8.default.yellow("!")} ${m.label} not installed${suffix}`);
2728
+ console.log(` ${import_chalk9.default.yellow("!")} ${m.label} not installed${suffix}`);
2523
2729
  if (m.installCommand) {
2524
- console.log(` Install: ${import_chalk8.default.cyan(m.installCommand)}`);
2730
+ console.log(` Install: ${import_chalk9.default.cyan(m.installCommand)}`);
2525
2731
  }
2526
2732
  }
2527
2733
  }
@@ -2632,46 +2838,85 @@ function updateGitignore(projectRoot) {
2632
2838
  }
2633
2839
 
2634
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
2635
2847
  var fs16 = __toESM(require("fs"), 1);
2636
2848
  var path16 = __toESM(require("path"), 1);
2637
- var import_chalk9 = __toESM(require("chalk"), 1);
2638
- 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
2639
2884
  function setupPreCommitHook(projectRoot) {
2640
- const lefthookPath = path16.join(projectRoot, "lefthook.yml");
2641
- if (fs16.existsSync(lefthookPath)) {
2885
+ const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2886
+ if (fs17.existsSync(lefthookPath)) {
2642
2887
  addLefthookPreCommit(lefthookPath);
2643
- 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`);
2644
2889
  return "lefthook.yml";
2645
2890
  }
2646
- const huskyDir = path16.join(projectRoot, ".husky");
2647
- if (fs16.existsSync(huskyDir)) {
2891
+ const huskyDir = path17.join(projectRoot, ".husky");
2892
+ if (fs17.existsSync(huskyDir)) {
2648
2893
  writeHuskyPreCommit(huskyDir);
2649
- 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`);
2650
2895
  return ".husky/pre-commit";
2651
2896
  }
2652
- const gitDir = path16.join(projectRoot, ".git");
2653
- if (fs16.existsSync(gitDir)) {
2654
- const hooksDir = path16.join(gitDir, "hooks");
2655
- if (!fs16.existsSync(hooksDir)) {
2656
- 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 });
2657
2902
  }
2658
2903
  writeGitHookPreCommit(hooksDir);
2659
- console.log(` ${import_chalk9.default.green("\u2713")} .git/hooks/pre-commit`);
2904
+ console.log(` ${import_chalk10.default.green("\u2713")} .git/hooks/pre-commit`);
2660
2905
  return ".git/hooks/pre-commit";
2661
2906
  }
2662
2907
  return void 0;
2663
2908
  }
2664
2909
  function writeGitHookPreCommit(hooksDir) {
2665
- const hookPath = path16.join(hooksDir, "pre-commit");
2666
- if (fs16.existsSync(hookPath)) {
2667
- 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");
2668
2913
  if (existing.includes("viberails")) return;
2669
- fs16.writeFileSync(
2914
+ fs17.writeFileSync(
2670
2915
  hookPath,
2671
2916
  `${existing.trimEnd()}
2672
2917
 
2673
2918
  # viberails check
2674
- npx viberails check --staged
2919
+ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi
2675
2920
  `
2676
2921
  );
2677
2922
  return;
@@ -2680,13 +2925,13 @@ npx viberails check --staged
2680
2925
  "#!/bin/sh",
2681
2926
  "# Generated by viberails \u2014 https://viberails.sh",
2682
2927
  "",
2683
- "npx viberails check --staged",
2928
+ "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
2684
2929
  ""
2685
2930
  ].join("\n");
2686
- fs16.writeFileSync(hookPath, script, { mode: 493 });
2931
+ fs17.writeFileSync(hookPath, script, { mode: 493 });
2687
2932
  }
2688
2933
  function addLefthookPreCommit(lefthookPath) {
2689
- const content = fs16.readFileSync(lefthookPath, "utf-8");
2934
+ const content = fs17.readFileSync(lefthookPath, "utf-8");
2690
2935
  if (content.includes("viberails")) return;
2691
2936
  const doc = (0, import_yaml.parse)(content) ?? {};
2692
2937
  if (!doc["pre-commit"]) {
@@ -2696,30 +2941,30 @@ function addLefthookPreCommit(lefthookPath) {
2696
2941
  doc["pre-commit"].commands = {};
2697
2942
  }
2698
2943
  doc["pre-commit"].commands.viberails = {
2699
- 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"
2700
2945
  };
2701
- fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
2946
+ fs17.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
2702
2947
  }
2703
2948
  function detectHookManager(projectRoot) {
2704
- if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2705
- 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";
2706
2951
  return void 0;
2707
2952
  }
2708
2953
  function setupClaudeCodeHook(projectRoot) {
2709
- const claudeDir = path16.join(projectRoot, ".claude");
2710
- if (!fs16.existsSync(claudeDir)) {
2711
- fs16.mkdirSync(claudeDir, { recursive: true });
2954
+ const claudeDir = path17.join(projectRoot, ".claude");
2955
+ if (!fs17.existsSync(claudeDir)) {
2956
+ fs17.mkdirSync(claudeDir, { recursive: true });
2712
2957
  }
2713
- const settingsPath = path16.join(claudeDir, "settings.json");
2958
+ const settingsPath = path17.join(claudeDir, "settings.json");
2714
2959
  let settings = {};
2715
- if (fs16.existsSync(settingsPath)) {
2960
+ if (fs17.existsSync(settingsPath)) {
2716
2961
  try {
2717
- settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
2962
+ settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
2718
2963
  } catch {
2719
2964
  console.warn(
2720
- ` ${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`
2721
2966
  );
2722
- 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")}`);
2723
2968
  return;
2724
2969
  }
2725
2970
  }
@@ -2740,30 +2985,30 @@ function setupClaudeCodeHook(projectRoot) {
2740
2985
  }
2741
2986
  ];
2742
2987
  settings.hooks = hooks;
2743
- fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2988
+ fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2744
2989
  `);
2745
- 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`);
2746
2991
  }
2747
2992
  function setupClaudeMdReference(projectRoot) {
2748
- const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
2993
+ const claudeMdPath = path17.join(projectRoot, "CLAUDE.md");
2749
2994
  let content = "";
2750
- if (fs16.existsSync(claudeMdPath)) {
2751
- content = fs16.readFileSync(claudeMdPath, "utf-8");
2995
+ if (fs17.existsSync(claudeMdPath)) {
2996
+ content = fs17.readFileSync(claudeMdPath, "utf-8");
2752
2997
  }
2753
2998
  if (content.includes("@.viberails/context.md")) return;
2754
2999
  const ref = "\n@.viberails/context.md\n";
2755
3000
  const prefix = content.length === 0 ? "" : content.trimEnd();
2756
- fs16.writeFileSync(claudeMdPath, prefix + ref);
2757
- 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`);
2758
3003
  }
2759
3004
  function setupGithubAction(projectRoot, packageManager, options) {
2760
- const workflowDir = path16.join(projectRoot, ".github", "workflows");
2761
- const workflowPath = path16.join(workflowDir, "viberails.yml");
2762
- if (fs16.existsSync(workflowPath)) {
2763
- 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");
2764
3009
  if (existing.includes("viberails")) return void 0;
2765
3010
  }
2766
- fs16.mkdirSync(workflowDir, { recursive: true });
3011
+ fs17.mkdirSync(workflowDir, { recursive: true });
2767
3012
  const pm = packageManager || "npm";
2768
3013
  const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
2769
3014
  const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
@@ -2772,7 +3017,7 @@ function setupGithubAction(projectRoot, packageManager, options) {
2772
3017
  "",
2773
3018
  "on:",
2774
3019
  " pull_request:",
2775
- " branches: [main]",
3020
+ " branches: [main, master]",
2776
3021
  "",
2777
3022
  "jobs:",
2778
3023
  " check:",
@@ -2790,87 +3035,94 @@ function setupGithubAction(projectRoot, packageManager, options) {
2790
3035
  " - uses: actions/setup-node@v4",
2791
3036
  " with:",
2792
3037
  " node-version: 22",
2793
- pm !== "npm" ? ` cache: ${pm}` : "",
3038
+ ` cache: ${pm}`,
2794
3039
  "",
2795
3040
  ` - run: ${installCmd}`
2796
3041
  );
2797
3042
  if (options?.typecheck) {
2798
- 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
+ }
2799
3048
  }
2800
3049
  if (options?.linter) {
2801
3050
  const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2802
3051
  lines.push(` - run: ${runPrefix} ${lintCmd}`);
2803
3052
  }
2804
3053
  lines.push(
2805
- ` - 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 }}`,
2806
3055
  ""
2807
3056
  );
2808
3057
  const content = lines.filter((l) => l !== void 0).join("\n");
2809
- fs16.writeFileSync(workflowPath, content);
3058
+ fs17.writeFileSync(workflowPath, content);
2810
3059
  return ".github/workflows/viberails.yml";
2811
3060
  }
2812
3061
  function writeHuskyPreCommit(huskyDir) {
2813
- const hookPath = path16.join(huskyDir, "pre-commit");
2814
- if (fs16.existsSync(hookPath)) {
2815
- 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");
2816
3066
  if (!existing.includes("viberails")) {
2817
- fs16.writeFileSync(hookPath, `${existing.trimEnd()}
2818
- npx viberails check --staged
3067
+ fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3068
+ ${cmd}
2819
3069
  `);
2820
3070
  }
2821
3071
  return;
2822
3072
  }
2823
- fs16.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
3073
+ fs17.writeFileSync(hookPath, `#!/bin/sh
3074
+ ${cmd}
3075
+ `, { mode: 493 });
2824
3076
  }
2825
3077
 
2826
3078
  // src/commands/init-hooks-extra.ts
2827
- var fs17 = __toESM(require("fs"), 1);
2828
- var path17 = __toESM(require("path"), 1);
2829
- 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);
2830
3082
  var import_yaml2 = require("yaml");
2831
3083
  function addPreCommitStep(projectRoot, name, command, marker) {
2832
- const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2833
- if (fs17.existsSync(lefthookPath)) {
2834
- const content = fs17.readFileSync(lefthookPath, "utf-8");
3084
+ const lefthookPath = path18.join(projectRoot, "lefthook.yml");
3085
+ if (fs18.existsSync(lefthookPath)) {
3086
+ const content = fs18.readFileSync(lefthookPath, "utf-8");
2835
3087
  if (content.includes(marker)) return void 0;
2836
3088
  const doc = (0, import_yaml2.parse)(content) ?? {};
2837
3089
  if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
2838
3090
  if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
2839
3091
  doc["pre-commit"].commands[name] = { run: command };
2840
- fs17.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
3092
+ fs18.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
2841
3093
  return "lefthook.yml";
2842
3094
  }
2843
- const huskyDir = path17.join(projectRoot, ".husky");
2844
- if (fs17.existsSync(huskyDir)) {
2845
- const hookPath = path17.join(huskyDir, "pre-commit");
2846
- if (fs17.existsSync(hookPath)) {
2847
- 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");
2848
3100
  if (existing.includes(marker)) return void 0;
2849
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3101
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2850
3102
  ${command}
2851
3103
  `);
2852
3104
  } else {
2853
- fs17.writeFileSync(hookPath, `#!/bin/sh
3105
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2854
3106
  ${command}
2855
3107
  `, { mode: 493 });
2856
3108
  }
2857
3109
  return ".husky/pre-commit";
2858
3110
  }
2859
- const gitDir = path17.join(projectRoot, ".git");
2860
- if (fs17.existsSync(gitDir)) {
2861
- const hooksDir = path17.join(gitDir, "hooks");
2862
- if (!fs17.existsSync(hooksDir)) fs17.mkdirSync(hooksDir, { recursive: true });
2863
- const hookPath = path17.join(hooksDir, "pre-commit");
2864
- if (fs17.existsSync(hookPath)) {
2865
- 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");
2866
3118
  if (existing.includes(marker)) return void 0;
2867
- fs17.writeFileSync(hookPath, `${existing.trimEnd()}
3119
+ fs18.writeFileSync(hookPath, `${existing.trimEnd()}
2868
3120
 
2869
3121
  # ${name}
2870
3122
  ${command}
2871
3123
  `);
2872
3124
  } else {
2873
- fs17.writeFileSync(hookPath, `#!/bin/sh
3125
+ fs18.writeFileSync(hookPath, `#!/bin/sh
2874
3126
  # Generated by viberails
2875
3127
 
2876
3128
  # ${name}
@@ -2883,10 +3135,15 @@ ${command}
2883
3135
  }
2884
3136
  return void 0;
2885
3137
  }
2886
- function setupTypecheckHook(projectRoot) {
2887
- 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");
2888
3145
  if (target) {
2889
- 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})`);
2890
3147
  }
2891
3148
  return target;
2892
3149
  }
@@ -2895,7 +3152,7 @@ function setupLintHook(projectRoot, linter) {
2895
3152
  const linterName = linter === "biome" ? "Biome" : "ESLint";
2896
3153
  const target = addPreCommitStep(projectRoot, "lint", command, linter);
2897
3154
  if (target) {
2898
- console.log(` ${import_chalk10.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3155
+ console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
2899
3156
  }
2900
3157
  return target;
2901
3158
  }
@@ -2906,7 +3163,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2906
3163
  created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
2907
3164
  }
2908
3165
  if (integrations.typecheckHook) {
2909
- const t = setupTypecheckHook(projectRoot);
3166
+ const t = setupTypecheckHook(projectRoot, opts.packageManager);
2910
3167
  if (t) created.push(`${t} \u2014 added typecheck`);
2911
3168
  }
2912
3169
  if (integrations.lintHook && opts.linter) {
@@ -2943,11 +3200,11 @@ async function initCommand(options, cwd) {
2943
3200
  "No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
2944
3201
  );
2945
3202
  }
2946
- const configPath = path18.join(projectRoot, CONFIG_FILE5);
2947
- if (fs18.existsSync(configPath) && !options.force) {
3203
+ const configPath = path19.join(projectRoot, CONFIG_FILE5);
3204
+ if (fs19.existsSync(configPath) && !options.force) {
2948
3205
  console.log(
2949
- `${import_chalk11.default.yellow("!")} viberails is already initialized.
2950
- 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.`
3206
+ `${import_chalk12.default.yellow("!")} viberails is already initialized.
3207
+ 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.`
2951
3208
  );
2952
3209
  return;
2953
3210
  }
@@ -2955,7 +3212,7 @@ async function initCommand(options, cwd) {
2955
3212
  await initInteractive(projectRoot, configPath, options);
2956
3213
  }
2957
3214
  async function initNonInteractive(projectRoot, configPath) {
2958
- console.log(import_chalk11.default.dim("Scanning project..."));
3215
+ console.log(import_chalk12.default.dim("Scanning project..."));
2959
3216
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
2960
3217
  const config = (0, import_config8.generateConfig)(scanResult);
2961
3218
  for (const pkg of config.packages) {
@@ -2968,11 +3225,11 @@ async function initNonInteractive(projectRoot, configPath) {
2968
3225
  const exempted = getExemptedPackages(config);
2969
3226
  if (exempted.length > 0) {
2970
3227
  console.log(
2971
- ` ${import_chalk11.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk11.default.dim("(types-only)")}`
3228
+ ` ${import_chalk12.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk12.default.dim("(types-only)")}`
2972
3229
  );
2973
3230
  }
2974
3231
  if (config.packages.length > 1) {
2975
- console.log(import_chalk11.default.dim("Building import graph..."));
3232
+ console.log(import_chalk12.default.dim("Building import graph..."));
2976
3233
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
2977
3234
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
2978
3235
  const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
@@ -2985,16 +3242,16 @@ async function initNonInteractive(projectRoot, configPath) {
2985
3242
  }
2986
3243
  }
2987
3244
  const compacted = (0, import_config8.compactConfig)(config);
2988
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3245
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
2989
3246
  `);
2990
3247
  writeGeneratedFiles(projectRoot, config, scanResult);
2991
3248
  updateGitignore(projectRoot);
2992
3249
  setupClaudeCodeHook(projectRoot);
2993
3250
  setupClaudeMdReference(projectRoot);
2994
3251
  const rootPkg = config.packages[0];
2995
- const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
3252
+ const rootPkgPm = rootPkg?.stack?.packageManager?.split("@")[0] ?? "npm";
2996
3253
  const linter = rootPkg?.stack?.linter?.split("@")[0];
2997
- const isTypeScript = rootPkg?.stack?.language === "typescript";
3254
+ const isTypeScript = rootPkg?.stack?.language?.split("@")[0] === "typescript";
2998
3255
  const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
2999
3256
  linter,
3000
3257
  typecheck: isTypeScript
@@ -3002,17 +3259,17 @@ async function initNonInteractive(projectRoot, configPath) {
3002
3259
  const hookManager = detectHookManager(projectRoot);
3003
3260
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3004
3261
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3005
- const ok = import_chalk11.default.green("\u2713");
3262
+ const ok = import_chalk12.default.green("\u2713");
3006
3263
  const created = [
3007
- `${ok} ${path18.basename(configPath)}`,
3264
+ `${ok} ${path19.basename(configPath)}`,
3008
3265
  `${ok} .viberails/context.md`,
3009
3266
  `${ok} .viberails/scan-result.json`,
3010
3267
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3011
3268
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3012
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk11.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3269
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk12.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3013
3270
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3014
3271
  ].filter(Boolean);
3015
- if (hasHookManager && rootPkg?.stack?.language === "typescript") setupTypecheckHook(projectRoot);
3272
+ if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
3016
3273
  if (hasHookManager && linter) setupLintHook(projectRoot, linter);
3017
3274
  console.log(`
3018
3275
  Created:
@@ -3020,9 +3277,9 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
3020
3277
  }
3021
3278
  async function initInteractive(projectRoot, configPath, options) {
3022
3279
  clack8.intro("viberails");
3023
- if (fs18.existsSync(configPath) && options.force) {
3280
+ if (fs19.existsSync(configPath) && options.force) {
3024
3281
  const replace = await confirmDangerous(
3025
- `${path18.basename(configPath)} already exists and will be replaced. Continue?`
3282
+ `${path19.basename(configPath)} already exists and will be replaced. Continue?`
3026
3283
  );
3027
3284
  if (!replace) {
3028
3285
  clack8.outro("Aborted. No files were written.");
@@ -3034,13 +3291,6 @@ async function initInteractive(projectRoot, configPath, options) {
3034
3291
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
3035
3292
  const config = (0, import_config8.generateConfig)(scanResult);
3036
3293
  s.stop("Scan complete");
3037
- const prereqResult = await promptMissingPrereqs(
3038
- projectRoot,
3039
- checkCoveragePrereqs(projectRoot, scanResult)
3040
- );
3041
- if (prereqResult.disableCoverage) {
3042
- config.rules.testCoverage = 0;
3043
- }
3044
3294
  if (scanResult.statistics.totalFiles === 0) {
3045
3295
  clack8.log.warn(
3046
3296
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
@@ -3081,20 +3331,28 @@ async function initInteractive(projectRoot, configPath, options) {
3081
3331
  if (denyCount > 0) {
3082
3332
  config.boundaries = inferred;
3083
3333
  config.rules.enforceBoundaries = true;
3084
- bs.stop(`Inferred ${denyCount} boundary rules`);
3085
- const boundaryLines = Object.entries(inferred.deny).map(([pkg, denied]) => `${pkg} must NOT import from: ${denied.join(", ")}`).join("\n");
3086
- clack8.note(boundaryLines, "Boundary rules");
3334
+ const pkgCount = Object.keys(inferred.deny).length;
3335
+ bs.stop(`Inferred ${denyCount} boundary rules across ${pkgCount} packages`);
3087
3336
  } else {
3088
3337
  bs.stop("No boundary rules inferred");
3089
3338
  }
3090
3339
  }
3091
3340
  }
3092
3341
  const hookManager = detectHookManager(projectRoot);
3342
+ const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3343
+ const hasMissingPrereqs = coveragePrereqs.some((p) => !p.installed) || !hookManager;
3344
+ if (hasMissingPrereqs) {
3345
+ clack8.log.info("Some dependencies are needed for full functionality.");
3346
+ }
3347
+ const prereqResult = await promptMissingPrereqs(projectRoot, coveragePrereqs);
3348
+ if (prereqResult.disableCoverage) {
3349
+ config.rules.testCoverage = 0;
3350
+ }
3093
3351
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
3094
3352
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3095
- isTypeScript: rootPkgStack?.language === "typescript",
3353
+ isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
3096
3354
  linter: rootPkgStack?.linter?.split("@")[0],
3097
- packageManager: rootPkgStack?.packageManager,
3355
+ packageManager: rootPkgStack?.packageManager?.split("@")[0],
3098
3356
  isWorkspace: config.packages.length > 1
3099
3357
  });
3100
3358
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
@@ -3103,40 +3361,37 @@ async function initInteractive(projectRoot, configPath, options) {
3103
3361
  return;
3104
3362
  }
3105
3363
  const compacted = (0, import_config8.compactConfig)(config);
3106
- fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3364
+ fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3107
3365
  `);
3108
3366
  writeGeneratedFiles(projectRoot, config, scanResult);
3109
3367
  updateGitignore(projectRoot);
3110
- const createdFiles = [
3111
- path18.basename(configPath),
3112
- ".viberails/context.md",
3113
- ".viberails/scan-result.json",
3114
- ...setupSelectedIntegrations(projectRoot, integrations, {
3115
- linter: rootPkgStack?.linter?.split("@")[0],
3116
- packageManager: rootPkgStack?.packageManager
3117
- })
3118
- ];
3119
- clack8.log.success(`Created:
3120
- ${createdFiles.map((f) => ` ${f}`).join("\n")}`);
3368
+ const ok = import_chalk12.default.green("\u2713");
3369
+ clack8.log.step(`${ok} ${path19.basename(configPath)}`);
3370
+ clack8.log.step(`${ok} .viberails/context.md`);
3371
+ clack8.log.step(`${ok} .viberails/scan-result.json`);
3372
+ setupSelectedIntegrations(projectRoot, integrations, {
3373
+ linter: rootPkgStack?.linter?.split("@")[0],
3374
+ packageManager: rootPkgStack?.packageManager?.split("@")[0]
3375
+ });
3121
3376
  clack8.outro(
3122
3377
  `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.")}`
3378
+ ${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
3379
  );
3125
3380
  }
3126
3381
 
3127
3382
  // src/commands/sync.ts
3128
- var fs19 = __toESM(require("fs"), 1);
3129
- var path19 = __toESM(require("path"), 1);
3383
+ var fs20 = __toESM(require("fs"), 1);
3384
+ var path20 = __toESM(require("path"), 1);
3130
3385
  var clack9 = __toESM(require("@clack/prompts"), 1);
3131
3386
  var import_config9 = require("@viberails/config");
3132
3387
  var import_scanner3 = require("@viberails/scanner");
3133
- var import_chalk12 = __toESM(require("chalk"), 1);
3388
+ var import_chalk13 = __toESM(require("chalk"), 1);
3134
3389
  var CONFIG_FILE6 = "viberails.config.json";
3135
3390
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
3136
3391
  function loadPreviousStats(projectRoot) {
3137
- const scanResultPath = path19.join(projectRoot, SCAN_RESULT_FILE2);
3392
+ const scanResultPath = path20.join(projectRoot, SCAN_RESULT_FILE2);
3138
3393
  try {
3139
- const raw = fs19.readFileSync(scanResultPath, "utf-8");
3394
+ const raw = fs20.readFileSync(scanResultPath, "utf-8");
3140
3395
  const parsed = JSON.parse(raw);
3141
3396
  if (parsed?.statistics?.totalFiles !== void 0) {
3142
3397
  return parsed.statistics;
@@ -3153,15 +3408,15 @@ async function syncCommand(options, cwd) {
3153
3408
  "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
3409
  );
3155
3410
  }
3156
- const configPath = path19.join(projectRoot, CONFIG_FILE6);
3411
+ const configPath = path20.join(projectRoot, CONFIG_FILE6);
3157
3412
  const existing = await (0, import_config9.loadConfig)(configPath);
3158
3413
  const previousStats = loadPreviousStats(projectRoot);
3159
- console.log(import_chalk12.default.dim("Scanning project..."));
3414
+ console.log(import_chalk13.default.dim("Scanning project..."));
3160
3415
  const scanResult = await (0, import_scanner3.scan)(projectRoot);
3161
3416
  const merged = (0, import_config9.mergeConfig)(existing, scanResult);
3162
3417
  const compacted = (0, import_config9.compactConfig)(merged);
3163
3418
  const compactedJson = JSON.stringify(compacted, null, 2);
3164
- const rawDisk = fs19.readFileSync(configPath, "utf-8").trim();
3419
+ const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
3165
3420
  const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3166
3421
  const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3167
3422
  const configChanged = diskWithoutSync !== mergedWithoutSync;
@@ -3169,13 +3424,13 @@ async function syncCommand(options, cwd) {
3169
3424
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
3170
3425
  if (changes.length > 0 || statsDelta) {
3171
3426
  console.log(`
3172
- ${import_chalk12.default.bold("Changes:")}`);
3427
+ ${import_chalk13.default.bold("Changes:")}`);
3173
3428
  for (const change of changes) {
3174
- const icon = change.type === "removed" ? import_chalk12.default.red("-") : import_chalk12.default.green("+");
3429
+ const icon = change.type === "removed" ? import_chalk13.default.red("-") : import_chalk13.default.green("+");
3175
3430
  console.log(` ${icon} ${change.description}`);
3176
3431
  }
3177
3432
  if (statsDelta) {
3178
- console.log(` ${import_chalk12.default.dim(statsDelta)}`);
3433
+ console.log(` ${import_chalk13.default.dim(statsDelta)}`);
3179
3434
  }
3180
3435
  }
3181
3436
  if (options?.interactive) {
@@ -3208,7 +3463,7 @@ ${import_chalk12.default.bold("Changes:")}`);
3208
3463
  });
3209
3464
  applyRuleOverrides(merged, overrides);
3210
3465
  const recompacted = (0, import_config9.compactConfig)(merged);
3211
- fs19.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3466
+ fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3212
3467
  `);
3213
3468
  writeGeneratedFiles(projectRoot, merged, scanResult);
3214
3469
  clack9.log.success("Updated config with your customizations.");
@@ -3216,22 +3471,22 @@ ${import_chalk12.default.bold("Changes:")}`);
3216
3471
  return;
3217
3472
  }
3218
3473
  }
3219
- fs19.writeFileSync(configPath, `${compactedJson}
3474
+ fs20.writeFileSync(configPath, `${compactedJson}
3220
3475
  `);
3221
3476
  writeGeneratedFiles(projectRoot, merged, scanResult);
3222
3477
  console.log(`
3223
- ${import_chalk12.default.bold("Synced:")}`);
3478
+ ${import_chalk13.default.bold("Synced:")}`);
3224
3479
  if (configChanged) {
3225
- console.log(` ${import_chalk12.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3480
+ console.log(` ${import_chalk13.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3226
3481
  } else {
3227
- console.log(` ${import_chalk12.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3482
+ console.log(` ${import_chalk13.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3228
3483
  }
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`);
3484
+ console.log(` ${import_chalk13.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3485
+ console.log(` ${import_chalk13.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3231
3486
  }
3232
3487
 
3233
3488
  // src/index.ts
3234
- var VERSION = "0.5.4";
3489
+ var VERSION = "0.6.0";
3235
3490
  var program = new import_commander.Command();
3236
3491
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3237
3492
  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 +3494,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
3239
3494
  await initCommand(options);
3240
3495
  } catch (err) {
3241
3496
  const message = err instanceof Error ? err.message : String(err);
3242
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3497
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3243
3498
  process.exit(1);
3244
3499
  }
3245
3500
  });
@@ -3248,7 +3503,7 @@ program.command("sync").description("Re-scan and update generated files").option
3248
3503
  await syncCommand(options);
3249
3504
  } catch (err) {
3250
3505
  const message = err instanceof Error ? err.message : String(err);
3251
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3506
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3252
3507
  process.exit(1);
3253
3508
  }
3254
3509
  });
@@ -3257,7 +3512,7 @@ program.command("config").description("Interactively edit existing config rules"
3257
3512
  await configCommand(options);
3258
3513
  } catch (err) {
3259
3514
  const message = err instanceof Error ? err.message : String(err);
3260
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3515
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3261
3516
  process.exit(1);
3262
3517
  }
3263
3518
  });
@@ -3278,7 +3533,7 @@ program.command("check").description("Check files against enforced rules").optio
3278
3533
  process.exit(exitCode);
3279
3534
  } catch (err) {
3280
3535
  const message = err instanceof Error ? err.message : String(err);
3281
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3536
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3282
3537
  process.exit(1);
3283
3538
  }
3284
3539
  }
@@ -3289,7 +3544,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
3289
3544
  process.exit(exitCode);
3290
3545
  } catch (err) {
3291
3546
  const message = err instanceof Error ? err.message : String(err);
3292
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3547
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3293
3548
  process.exit(1);
3294
3549
  }
3295
3550
  });
@@ -3298,7 +3553,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
3298
3553
  await boundariesCommand(options);
3299
3554
  } catch (err) {
3300
3555
  const message = err instanceof Error ? err.message : String(err);
3301
- console.error(`${import_chalk13.default.red("Error:")} ${message}`);
3556
+ console.error(`${import_chalk14.default.red("Error:")} ${message}`);
3302
3557
  process.exit(1);
3303
3558
  }
3304
3559
  });