viberails 0.2.1 → 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 +103 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +103 -34
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -256,6 +256,23 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
256
256
|
var import_node_child_process = require("child_process");
|
|
257
257
|
var fs4 = __toESM(require("fs"), 1);
|
|
258
258
|
var path4 = __toESM(require("path"), 1);
|
|
259
|
+
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
260
|
+
"node_modules",
|
|
261
|
+
".git",
|
|
262
|
+
"dist",
|
|
263
|
+
"build",
|
|
264
|
+
".next",
|
|
265
|
+
".expo",
|
|
266
|
+
".output",
|
|
267
|
+
".svelte-kit",
|
|
268
|
+
".turbo",
|
|
269
|
+
"coverage",
|
|
270
|
+
"public",
|
|
271
|
+
"vendor",
|
|
272
|
+
"__generated__",
|
|
273
|
+
"generated",
|
|
274
|
+
".viberails"
|
|
275
|
+
]);
|
|
259
276
|
var SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
260
277
|
".ts",
|
|
261
278
|
".tsx",
|
|
@@ -275,12 +292,19 @@ var NAMING_PATTERNS = {
|
|
|
275
292
|
};
|
|
276
293
|
function isIgnored(relPath, ignorePatterns) {
|
|
277
294
|
for (const pattern of ignorePatterns) {
|
|
278
|
-
|
|
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) {
|
|
279
303
|
const prefix = pattern.slice(0, -3);
|
|
280
304
|
if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
|
|
281
|
-
} else if (
|
|
305
|
+
} else if (startsGlob) {
|
|
282
306
|
const suffix = pattern.slice(3);
|
|
283
|
-
if (relPath.endsWith(suffix)) return true;
|
|
307
|
+
if (relPath.endsWith(suffix) || relPath === suffix) return true;
|
|
284
308
|
} else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
|
|
285
309
|
return true;
|
|
286
310
|
}
|
|
@@ -304,7 +328,7 @@ function checkNaming(relPath, conventions) {
|
|
|
304
328
|
const filename = path4.basename(relPath);
|
|
305
329
|
const ext = path4.extname(filename);
|
|
306
330
|
if (!SOURCE_EXTS.has(ext)) return void 0;
|
|
307
|
-
if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".")) {
|
|
331
|
+
if (filename.startsWith("index.") || filename.includes(".config.") || filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith(".") || filename.startsWith("_") || filename.startsWith("+") || filename.startsWith("$") || filename.startsWith("[")) {
|
|
308
332
|
return void 0;
|
|
309
333
|
}
|
|
310
334
|
const bare = filename.slice(0, filename.indexOf("."));
|
|
@@ -337,7 +361,7 @@ function getAllSourceFiles(projectRoot, config) {
|
|
|
337
361
|
for (const entry of entries) {
|
|
338
362
|
const rel = path4.relative(projectRoot, path4.join(dir, entry.name));
|
|
339
363
|
if (entry.isDirectory()) {
|
|
340
|
-
if (
|
|
364
|
+
if (ALWAYS_SKIP_DIRS.has(entry.name)) {
|
|
341
365
|
continue;
|
|
342
366
|
}
|
|
343
367
|
if (isIgnored(rel, config.ignore)) continue;
|
|
@@ -400,13 +424,13 @@ function checkMissingTests(projectRoot, config, severity) {
|
|
|
400
424
|
const testSuffix = testPattern.replace("*", "");
|
|
401
425
|
const sourceFiles = collectSourceFiles(srcPath, projectRoot);
|
|
402
426
|
for (const relFile of sourceFiles) {
|
|
403
|
-
const
|
|
404
|
-
if (
|
|
427
|
+
const basename7 = path5.basename(relFile);
|
|
428
|
+
if (basename7.includes(".test.") || basename7.includes(".spec.") || basename7.startsWith("index.") || basename7.endsWith(".d.ts")) {
|
|
405
429
|
continue;
|
|
406
430
|
}
|
|
407
|
-
const ext = path5.extname(
|
|
431
|
+
const ext = path5.extname(basename7);
|
|
408
432
|
if (!SOURCE_EXTS2.has(ext)) continue;
|
|
409
|
-
const stem =
|
|
433
|
+
const stem = basename7.slice(0, basename7.indexOf("."));
|
|
410
434
|
const expectedTestFile = `${stem}${testSuffix}`;
|
|
411
435
|
const dir = path5.dirname(path5.join(projectRoot, relFile));
|
|
412
436
|
const colocatedTest = path5.join(dir, expectedTestFile);
|
|
@@ -427,6 +451,50 @@ function checkMissingTests(projectRoot, config, severity) {
|
|
|
427
451
|
|
|
428
452
|
// src/commands/check.ts
|
|
429
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
|
+
}
|
|
430
498
|
async function checkCommand(options, cwd) {
|
|
431
499
|
const startDir = cwd ?? process.cwd();
|
|
432
500
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -463,13 +531,15 @@ async function checkCommand(options, cwd) {
|
|
|
463
531
|
if (isIgnored(relPath, effectiveIgnore)) continue;
|
|
464
532
|
if (!fs6.existsSync(absPath)) continue;
|
|
465
533
|
const resolved = resolveConfigForFile(relPath, config);
|
|
466
|
-
|
|
534
|
+
const testFile = isTestFile(relPath);
|
|
535
|
+
const maxLines = testFile ? resolved.rules.maxTestFileLines : resolved.rules.maxFileLines;
|
|
536
|
+
if (maxLines > 0) {
|
|
467
537
|
const lines = countFileLines(absPath);
|
|
468
|
-
if (lines !== null && lines >
|
|
538
|
+
if (lines !== null && lines > maxLines) {
|
|
469
539
|
violations.push({
|
|
470
540
|
file: relPath,
|
|
471
541
|
rule: "file-size",
|
|
472
|
-
message: `${lines} lines (max ${
|
|
542
|
+
message: `${lines} lines (max ${maxLines}). Split into focused modules.`,
|
|
473
543
|
severity
|
|
474
544
|
});
|
|
475
545
|
}
|
|
@@ -517,13 +587,10 @@ async function checkCommand(options, cwd) {
|
|
|
517
587
|
console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
518
588
|
return 0;
|
|
519
589
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
590
|
+
if (!options.quiet) {
|
|
591
|
+
printGroupedViolations(violations, options.limit);
|
|
523
592
|
}
|
|
524
|
-
|
|
525
|
-
console.log(`
|
|
526
|
-
${violations.length} ${word} found.`);
|
|
593
|
+
printSummary(violations);
|
|
527
594
|
if (config.enforcement === "enforce") {
|
|
528
595
|
console.log(import_chalk2.default.red("Fix violations before committing."));
|
|
529
596
|
return 1;
|
|
@@ -771,8 +838,8 @@ var path9 = __toESM(require("path"), 1);
|
|
|
771
838
|
function generateTestStub(sourceRelPath, config, projectRoot) {
|
|
772
839
|
const { testPattern } = config.structure;
|
|
773
840
|
if (!testPattern) return null;
|
|
774
|
-
const
|
|
775
|
-
const stem =
|
|
841
|
+
const basename7 = path9.basename(sourceRelPath);
|
|
842
|
+
const stem = basename7.slice(0, basename7.indexOf("."));
|
|
776
843
|
const testSuffix = testPattern.replace("*", "");
|
|
777
844
|
const testFilename = `${stem}${testSuffix}`;
|
|
778
845
|
const dir = path9.dirname(path9.join(projectRoot, sourceRelPath));
|
|
@@ -812,7 +879,7 @@ async function fixCommand(options, cwd) {
|
|
|
812
879
|
return 1;
|
|
813
880
|
}
|
|
814
881
|
const config = await (0, import_config3.loadConfig)(configPath);
|
|
815
|
-
if (!options.
|
|
882
|
+
if (!options.dryRun) {
|
|
816
883
|
const isDirty = checkGitDirty(projectRoot);
|
|
817
884
|
if (isDirty) {
|
|
818
885
|
console.log(
|
|
@@ -1343,7 +1410,7 @@ ${import_chalk9.default.bold("Synced:")}`);
|
|
|
1343
1410
|
}
|
|
1344
1411
|
|
|
1345
1412
|
// src/index.ts
|
|
1346
|
-
var VERSION = "0.2.
|
|
1413
|
+
var VERSION = "0.2.3";
|
|
1347
1414
|
var program = new import_commander.Command();
|
|
1348
1415
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1349
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) => {
|
|
@@ -1364,19 +1431,21 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1364
1431
|
process.exit(1);
|
|
1365
1432
|
}
|
|
1366
1433
|
});
|
|
1367
|
-
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").
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
+
}
|
|
1378
1447
|
}
|
|
1379
|
-
|
|
1448
|
+
);
|
|
1380
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) => {
|
|
1381
1450
|
try {
|
|
1382
1451
|
const exitCode = await fixCommand(options);
|