@alxwlw/repo-rules
v1.7.1
Published
Whole-repo lint rules from the alxwlw defect corpus — long files, unjustified suppressions, skipped/focused tests, Number(x) || <default>, removed assertions — with a shrink-only baseline.
Readme
@alxwlw/repo-rules
Whole-repository checks that came out of a defect corpus: each one is a class of defect that already cost a review round. They run in CI and locally, on Node ≥ 20, with no dependencies.
pnpm add -D @alxwlw/repo-rules
pnpm exec repo-rules baseline --init # once: record what is already there
pnpm exec repo-rules check # every run: nothing new, nothing stale
pnpm exec repo-rules diff --base origin/mainrepo-rules [-C <dir>] check [--json]
repo-rules [-C <dir>] diff [--base <ref>] [--json]
repo-rules [-C <dir>] baseline [--init] [--allow-grow]-C <dir> runs in that directory (any place inside the repository). --json prints { violations, stale, notes } instead of text lines. --help prints the usage; a missing or unknown command prints it on stderr and exits 2.
Rules
| Rule | Flags |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| max-lines | a tracked text file over 800 lines (plans, lockfiles, JSON, snapshots, generated and vendored files excluded) |
| suppression-reason | eslint-disable* / oxlint-disable* / solhint-disable* without -- reason; @ts-expect-error / @ts-ignore without trailing text; biome-ignore without text after :; Python # noqa / # type: ignore without -- reason |
| focused-skipped-tests | in JS/TS test files: .only( on any line (the scan is line-based, see Limits); .skip( / .todo( / xit( / xdescribe( / xtest( without a -- reason comment on the line or the comment line above. .skipIf / .runIf and Playwright's test.skip(condition, reason) are fine |
| number-or-default | in non-test JS/TS files: Number(…), Number.parseInt(…), Number.parseFloat(…), parseInt(…), parseFloat(…) followed by \|\| <non-zero literal>: a real 0 silently becomes the default |
| removed-assertions (diff) | a test file whose diff removes more expect( / assert… than it adds; an assertion inside a comment counts as neither removed nor added (comments recognised: //, /* … */ on the line, a line starting with *; # in Python; an unclosed /* and a line starting with * hide the rest of the line, including code after a */). exclude (* or removed-assertions) applies. A Tests-Removed: <reason> commit trailer in the range accepts it |
A reason is at least three non-space characters.
Baseline
.repo-rules-baseline.json records the violations that already exist: line count per long file, hit count per file for the other rules. check fails when a file goes over its entry, and also when an entry is higher than reality (stale), so a fixed place cannot be reused by a new one. baseline only lowers entries; raising one takes --allow-grow, which shows in the PR diff.
Configuration
Optional .repo-rules.json:
{
"maxLines": 800,
"exclude": { "*": ["public/vendor/**"], "max-lines": ["docs/*.html"] },
"testGlobs": [
"**/*.{test,spec}.*",
"**/__tests__/**",
"**/test/**",
"**/tests/**",
"**/*.t.sol",
"**/test_*.py",
"**/*_test.py"
]
}testGlobs replaces the default list, shown in full above; it does not add to it. exclude takes * or a rule name — the four rules above and removed-assertions.
Limits
- Comments are found by a line heuristic, not a parser: a quote inside a regex literal or JSX text can hide a directive later on that line.
<!-- -->comments in.vueand.sveltefiles are not read.number-or-defaultmisses|| .5, and a||=default applied to a variable that holds aNumber(…)result.removed-assertionsdoes not see a test disabled by a rename with no content change (for examplea.test.ts→a.test.ts.off).removed-assertionsreads a line inside a multi-line/* … */block that does not start with*(for example a re-indented one) as code.- Assertions moved into a helper count as removed; the
Tests-Removedtrailer is the way out. - The scan is line-based and does not know whether a line sits inside a string or a template literal, so
.only(or.skip(inside a fixture string is flagged.
CI
- uses: actions/checkout@v5
with:
fetch-depth: 0 # diff needs the merge-base
- run: pnpm exec repo-rules check
- run: pnpm exec repo-rules diff # base = origin/$GITHUB_BASE_REF in a pull_request job, skipped otherwiseExit codes: 0 clean, 1 violations or stale baseline, 2 error (never read as clean).
