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.js CHANGED
@@ -234,6 +234,10 @@ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
234
234
  ".svelte-kit",
235
235
  ".turbo",
236
236
  "coverage",
237
+ "public",
238
+ "vendor",
239
+ "__generated__",
240
+ "generated",
237
241
  ".viberails"
238
242
  ]);
239
243
  var SOURCE_EXTS = /* @__PURE__ */ new Set([
@@ -255,12 +259,19 @@ var NAMING_PATTERNS = {
255
259
  };
256
260
  function isIgnored(relPath, ignorePatterns) {
257
261
  for (const pattern of ignorePatterns) {
258
- if (pattern.endsWith("/**")) {
262
+ const startsGlob = pattern.startsWith("**/");
263
+ const endsGlob = pattern.endsWith("/**");
264
+ if (startsGlob && endsGlob) {
265
+ const middle = pattern.slice(3, -3);
266
+ if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
267
+ return true;
268
+ }
269
+ } else if (endsGlob) {
259
270
  const prefix = pattern.slice(0, -3);
260
271
  if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
261
- } else if (pattern.startsWith("**/")) {
272
+ } else if (startsGlob) {
262
273
  const suffix = pattern.slice(3);
263
- if (relPath.endsWith(suffix)) return true;
274
+ if (relPath.endsWith(suffix) || relPath === suffix) return true;
264
275
  } else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
265
276
  return true;
266
277
  }
@@ -380,13 +391,13 @@ function checkMissingTests(projectRoot, config, severity) {
380
391
  const testSuffix = testPattern.replace("*", "");
381
392
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
382
393
  for (const relFile of sourceFiles) {
383
- const basename6 = path5.basename(relFile);
384
- if (basename6.includes(".test.") || basename6.includes(".spec.") || basename6.startsWith("index.") || basename6.endsWith(".d.ts")) {
394
+ const basename7 = path5.basename(relFile);
395
+ if (basename7.includes(".test.") || basename7.includes(".spec.") || basename7.startsWith("index.") || basename7.endsWith(".d.ts")) {
385
396
  continue;
386
397
  }
387
- const ext = path5.extname(basename6);
398
+ const ext = path5.extname(basename7);
388
399
  if (!SOURCE_EXTS2.has(ext)) continue;
389
- const stem = basename6.slice(0, basename6.indexOf("."));
400
+ const stem = basename7.slice(0, basename7.indexOf("."));
390
401
  const expectedTestFile = `${stem}${testSuffix}`;
391
402
  const dir = path5.dirname(path5.join(projectRoot, relFile));
392
403
  const colocatedTest = path5.join(dir, expectedTestFile);
@@ -407,6 +418,50 @@ function checkMissingTests(projectRoot, config, severity) {
407
418
 
408
419
  // src/commands/check.ts
409
420
  var CONFIG_FILE2 = "viberails.config.json";
421
+ function isTestFile(relPath) {
422
+ const filename = path6.basename(relPath);
423
+ return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
424
+ }
425
+ function printGroupedViolations(violations, limit) {
426
+ const groups = /* @__PURE__ */ new Map();
427
+ for (const v of violations) {
428
+ const existing = groups.get(v.rule) ?? [];
429
+ existing.push(v);
430
+ groups.set(v.rule, existing);
431
+ }
432
+ const ruleOrder = ["file-size", "file-naming", "missing-test", "boundary-violation"];
433
+ const sortedKeys = [...groups.keys()].sort(
434
+ (a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
435
+ );
436
+ let totalShown = 0;
437
+ const totalLimit = limit ?? Number.POSITIVE_INFINITY;
438
+ for (const rule of sortedKeys) {
439
+ const group = groups.get(rule);
440
+ if (!group) continue;
441
+ const remaining = totalLimit - totalShown;
442
+ if (remaining <= 0) break;
443
+ const toShow = group.slice(0, remaining);
444
+ const hidden = group.length - toShow.length;
445
+ for (const v of toShow) {
446
+ const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
447
+ console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
448
+ }
449
+ totalShown += toShow.length;
450
+ if (hidden > 0) {
451
+ console.log(chalk2.dim(` ... and ${hidden} more ${rule} violations`));
452
+ }
453
+ }
454
+ }
455
+ function printSummary(violations) {
456
+ const counts = /* @__PURE__ */ new Map();
457
+ for (const v of violations) {
458
+ counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
459
+ }
460
+ const word = violations.length === 1 ? "violation" : "violations";
461
+ const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
462
+ console.log(`
463
+ ${violations.length} ${word} found (${parts.join(", ")}).`);
464
+ }
410
465
  async function checkCommand(options, cwd) {
411
466
  const startDir = cwd ?? process.cwd();
412
467
  const projectRoot = findProjectRoot(startDir);
@@ -443,13 +498,15 @@ async function checkCommand(options, cwd) {
443
498
  if (isIgnored(relPath, effectiveIgnore)) continue;
444
499
  if (!fs6.existsSync(absPath)) continue;
445
500
  const resolved = resolveConfigForFile(relPath, config);
446
- if (resolved.rules.maxFileLines > 0) {
501
+ const testFile = isTestFile(relPath);
502
+ const maxLines = testFile ? resolved.rules.maxTestFileLines : resolved.rules.maxFileLines;
503
+ if (maxLines > 0) {
447
504
  const lines = countFileLines(absPath);
448
- if (lines !== null && lines > resolved.rules.maxFileLines) {
505
+ if (lines !== null && lines > maxLines) {
449
506
  violations.push({
450
507
  file: relPath,
451
508
  rule: "file-size",
452
- message: `${lines} lines (max ${resolved.rules.maxFileLines}). Split into focused modules.`,
509
+ message: `${lines} lines (max ${maxLines}). Split into focused modules.`,
453
510
  severity
454
511
  });
455
512
  }
@@ -497,13 +554,10 @@ async function checkCommand(options, cwd) {
497
554
  console.log(`${chalk2.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
498
555
  return 0;
499
556
  }
500
- for (const v of violations) {
501
- const icon = v.severity === "error" ? chalk2.red("\u2717") : chalk2.yellow("!");
502
- console.log(`${icon} ${chalk2.dim(v.rule)} ${v.file}: ${v.message}`);
557
+ if (!options.quiet) {
558
+ printGroupedViolations(violations, options.limit);
503
559
  }
504
- const word = violations.length === 1 ? "violation" : "violations";
505
- console.log(`
506
- ${violations.length} ${word} found.`);
560
+ printSummary(violations);
507
561
  if (config.enforcement === "enforce") {
508
562
  console.log(chalk2.red("Fix violations before committing."));
509
563
  return 1;
@@ -751,8 +805,8 @@ import * as path9 from "path";
751
805
  function generateTestStub(sourceRelPath, config, projectRoot) {
752
806
  const { testPattern } = config.structure;
753
807
  if (!testPattern) return null;
754
- const basename6 = path9.basename(sourceRelPath);
755
- const stem = basename6.slice(0, basename6.indexOf("."));
808
+ const basename7 = path9.basename(sourceRelPath);
809
+ const stem = basename7.slice(0, basename7.indexOf("."));
756
810
  const testSuffix = testPattern.replace("*", "");
757
811
  const testFilename = `${stem}${testSuffix}`;
758
812
  const dir = path9.dirname(path9.join(projectRoot, sourceRelPath));
@@ -1323,7 +1377,7 @@ ${chalk9.bold("Synced:")}`);
1323
1377
  }
1324
1378
 
1325
1379
  // src/index.ts
1326
- var VERSION = "0.2.2";
1380
+ var VERSION = "0.2.3";
1327
1381
  var program = new Command();
1328
1382
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
1329
1383
  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) => {
@@ -1344,19 +1398,21 @@ program.command("sync").description("Re-scan and update generated files").action
1344
1398
  process.exit(1);
1345
1399
  }
1346
1400
  });
1347
- 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) => {
1348
- try {
1349
- const exitCode = await checkCommand({
1350
- ...options,
1351
- noBoundaries: options.boundaries === false
1352
- });
1353
- process.exit(exitCode);
1354
- } catch (err) {
1355
- const message = err instanceof Error ? err.message : String(err);
1356
- console.error(`${chalk10.red("Error:")} ${message}`);
1357
- process.exit(1);
1401
+ 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(
1402
+ async (options) => {
1403
+ try {
1404
+ const exitCode = await checkCommand({
1405
+ ...options,
1406
+ noBoundaries: options.boundaries === false
1407
+ });
1408
+ process.exit(exitCode);
1409
+ } catch (err) {
1410
+ const message = err instanceof Error ? err.message : String(err);
1411
+ console.error(`${chalk10.red("Error:")} ${message}`);
1412
+ process.exit(1);
1413
+ }
1358
1414
  }
1359
- });
1415
+ );
1360
1416
  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) => {
1361
1417
  try {
1362
1418
  const exitCode = await fixCommand(options);