viberails 0.2.2 → 0.2.3

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
@@ -267,6 +267,10 @@ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
267
267
  ".svelte-kit",
268
268
  ".turbo",
269
269
  "coverage",
270
+ "public",
271
+ "vendor",
272
+ "__generated__",
273
+ "generated",
270
274
  ".viberails"
271
275
  ]);
272
276
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
@@ -288,12 +292,19 @@ var NAMING_PATTERNS = {
288
292
  };
289
293
  function isIgnored(relPath, ignorePatterns) {
290
294
  for (const pattern of ignorePatterns) {
291
- if (pattern.endsWith("/**")) {
295
+ const startsGlob = pattern.startsWith("**/");
296
+ const endsGlob = pattern.endsWith("/**");
297
+ if (startsGlob && endsGlob) {
298
+ const middle = pattern.slice(3, -3);
299
+ if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
300
+ return true;
301
+ }
302
+ } else if (endsGlob) {
292
303
  const prefix = pattern.slice(0, -3);
293
304
  if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
294
- } else if (pattern.startsWith("**/")) {
305
+ } else if (startsGlob) {
295
306
  const suffix = pattern.slice(3);
296
- if (relPath.endsWith(suffix)) return true;
307
+ if (relPath.endsWith(suffix) || relPath === suffix) return true;
297
308
  } else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
298
309
  return true;
299
310
  }
@@ -413,13 +424,13 @@ function checkMissingTests(projectRoot, config, severity) {
413
424
  const testSuffix = testPattern.replace("*", "");
414
425
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
415
426
  for (const relFile of sourceFiles) {
416
- const basename6 = path5.basename(relFile);
417
- if (basename6.includes(".test.") || basename6.includes(".spec.") || basename6.startsWith("index.") || basename6.endsWith(".d.ts")) {
427
+ const basename7 = path5.basename(relFile);
428
+ if (basename7.includes(".test.") || basename7.includes(".spec.") || basename7.startsWith("index.") || basename7.endsWith(".d.ts")) {
418
429
  continue;
419
430
  }
420
- const ext = path5.extname(basename6);
431
+ const ext = path5.extname(basename7);
421
432
  if (!SOURCE_EXTS2.has(ext)) continue;
422
- const stem = basename6.slice(0, basename6.indexOf("."));
433
+ const stem = basename7.slice(0, basename7.indexOf("."));
423
434
  const expectedTestFile = `${stem}${testSuffix}`;
424
435
  const dir = path5.dirname(path5.join(projectRoot, relFile));
425
436
  const colocatedTest = path5.join(dir, expectedTestFile);
@@ -440,6 +451,50 @@ function checkMissingTests(projectRoot, config, severity) {
440
451
 
441
452
  // src/commands/check.ts
442
453
  var CONFIG_FILE2 = "viberails.config.json";
454
+ function isTestFile(relPath) {
455
+ const filename = path6.basename(relPath);
456
+ return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
457
+ }
458
+ function printGroupedViolations(violations, limit) {
459
+ const groups = /* @__PURE__ */ new Map();
460
+ for (const v of violations) {
461
+ const existing = groups.get(v.rule) ?? [];
462
+ existing.push(v);
463
+ groups.set(v.rule, existing);
464
+ }
465
+ const ruleOrder = ["file-size", "file-naming", "missing-test", "boundary-violation"];
466
+ const sortedKeys = [...groups.keys()].sort(
467
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
468
+ );
469
+ let totalShown = 0;
470
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
471
+ for (const rule of sortedKeys) {
472
+ const group = groups.get(rule);
473
+ if (!group) continue;
474
+ const remaining = totalLimit - totalShown;
475
+ if (remaining <= 0) break;
476
+ const toShow = group.slice(0, remaining);
477
+ const hidden = group.length - toShow.length;
478
+ for (const v of toShow) {
479
+ const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
480
+ console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
481
+ }
482
+ totalShown += toShow.length;
483
+ if (hidden > 0) {
484
+ console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
485
+ }
486
+ }
487
+ }
488
+ function printSummary(violations) {
489
+ const counts = /* @__PURE__ */ new Map();
490
+ for (const v of violations) {
491
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
492
+ }
493
+ const word = violations.length === 1 ? "violation" : "violations";
494
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
495
+ console.log(`
496
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
497
+ }
443
498
  async function checkCommand(options, cwd) {
444
499
  const startDir = cwd ?? process.cwd();
445
500
  const projectRoot = findProjectRoot(startDir);
@@ -476,13 +531,15 @@ async function checkCommand(options, cwd) {
476
531
  if (isIgnored(relPath, effectiveIgnore)) continue;
477
532
  if (!fs6.existsSync(absPath)) continue;
478
533
  const resolved = resolveConfigForFile(relPath, config);
479
- if (resolved.rules.maxFileLines > 0) {
534
+ const testFile = isTestFile(relPath);
535
+ const maxLines = testFile ? resolved.rules.maxTestFileLines : resolved.rules.maxFileLines;
536
+ if (maxLines > 0) {
480
537
  const lines = countFileLines(absPath);
481
- if (lines !== null && lines > resolved.rules.maxFileLines) {
538
+ if (lines !== null && lines > maxLines) {
482
539
  violations.push({
483
540
  file: relPath,
484
541
  rule: "file-size",
485
- message: `${lines} lines (max ${resolved.rules.maxFileLines}). Split into focused modules.`,
542
+ message: `${lines} lines (max ${maxLines}). Split into focused modules.`,
486
543
  severity
487
544
  });
488
545
  }
@@ -530,13 +587,10 @@ async function checkCommand(options, cwd) {
530
587
  console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
531
588
  return 0;
532
589
  }
533
- for (const v of violations) {
534
- const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
535
- console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
590
+ if (!options.quiet) {
591
+ printGroupedViolations(violations, options.limit);
536
592
  }
537
- const word = violations.length === 1 ? "violation" : "violations";
538
- console.log(`
539
- ${violations.length} ${word} found.`);
593
+ printSummary(violations);
540
594
  if (config.enforcement === "enforce") {
541
595
  console.log(import_chalk2.default.red("Fix violations before committing."));
542
596
  return 1;
@@ -784,8 +838,8 @@ var path9 = __toESM(require("path"), 1);
784
838
  function generateTestStub(sourceRelPath, config, projectRoot) {
785
839
  const { testPattern } = config.structure;
786
840
  if (!testPattern) return null;
787
- const basename6 = path9.basename(sourceRelPath);
788
- const stem = basename6.slice(0, basename6.indexOf("."));
841
+ const basename7 = path9.basename(sourceRelPath);
842
+ const stem = basename7.slice(0, basename7.indexOf("."));
789
843
  const testSuffix = testPattern.replace("*", "");
790
844
  const testFilename = `${stem}${testSuffix}`;
791
845
  const dir = path9.dirname(path9.join(projectRoot, sourceRelPath));
@@ -1356,7 +1410,7 @@ ${import_chalk9.default.bold("Synced:")}`);
1356
1410
  }
1357
1411
 
1358
1412
  // src/index.ts
1359
- var VERSION = "0.2.2";
1413
+ var VERSION = "0.2.3";
1360
1414
  var program = new import_commander.Command();
1361
1415
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1362
1416
  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)").action(async (options) => {
@@ -1377,19 +1431,21 @@ program.command("sync").description("Re-scan and update generated files").action
1377
1431
  process.exit(1);
1378
1432
  }
1379
1433
  });
1380
- program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").action(async (options) => {
1381
- try {
1382
- const exitCode = await checkCommand({
1383
- ...options,
1384
- noBoundaries: options.boundaries === false
1385
- });
1386
- process.exit(exitCode);
1387
- } catch (err) {
1388
- const message = err instanceof Error ? err.message : String(err);
1389
- console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1390
- process.exit(1);
1434
+ program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).action(
1435
+ async (options) => {
1436
+ try {
1437
+ const exitCode = await checkCommand({
1438
+ ...options,
1439
+ noBoundaries: options.boundaries === false
1440
+ });
1441
+ process.exit(exitCode);
1442
+ } catch (err) {
1443
+ const message = err instanceof Error ? err.message : String(err);
1444
+ console.error(`${import_chalk10.default.red("Error:")} ${message}`);
1445
+ process.exit(1);
1446
+ }
1391
1447
  }
1392
- });
1448
+ );
1393
1449
  program.command("fix").description("Auto-fix file naming violations and generate missing test stubs").option("--dry-run", "Show planned fixes without applying them").option("--rule <rules...>", "Fix only specific rules (file-naming, missing-test)").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
1394
1450
  try {
1395
1451
  const exitCode = await fixCommand(options);