Adopting rules
Adopting a rule on a codebase that already breaks it
Section titled “Adopting a rule on a codebase that already breaks it”rulecast run --all-files --summary tells you how much you already owe:
backlog: 57 violations in 31 files
rule violations files python/routes-never-call-crud 57 31
worst files violations app/api/routes/orders.py 12 app/api/routes/users.py 9Watch the count, not the rate. Here is one real codebase on one rule, on main:
| date | violating / total |
|---|---|
| 2026-01-15 | 61 / 80 |
| 2026-06-01 | 58 / 143 |
| 2026-09-25 | 57 / 194 |
The rate fell from 76% to 29% and the count did not move. New code complied; the old violations were never fixed, only diluted. A team watching the percentage would have believed it was winning.
Per-edit enforcement does not touch that stock — it only stops it growing. Fix it a file at a time: edits to files that already followed the rule broke it 2% of the time, against 37% in files that mostly did not, so a file you clean tends to stay clean. (One rule across 19 files: suggestive, not proven.)
When the fix is mechanical, give the rule a fix and burn the stock down in one go: rulecast fix <rule> --all-files --dry-run shows the diff, and without --dry-run it writes it. Hooks never apply a fix; only that command does.
A committed baseline
Section titled “A committed baseline”--from-ref needs a ref. A scheduled full-repository run, such as a nightly convention sweep or an agent loop told to keep main clean, has none, so every legacy violation is new and the run always fails. For those, record what is there today:
rulecast baseline # writes .rulecast/baseline.jsongit add .rulecast/baseline.json && git commit -m "rulecast baseline"rulecast run --all-files # exits 0 unless something got worserun --all-filesandrun --filestreat the findings listed in the file as pre-existing, and fail only on new ones. Sessions,--from-refand staged commits keep their own baseline, and hooks never read the file.- The file lists finding ids (what an id is), so editing a baselined line’s offending text, or moving its file, makes the finding new.
- When a run no longer finds an entry it says so:
baseline: 3 entries no longer found (fixed or changed); rulecast baseline --prune drops them. Run--prunein the same pull request that fixes them. It only ever removes entries, never adds one, and keeps the entries of rules that did not run. - Recording new findings means rewriting the file with
rulecast baseline --force. That is an amnesty, and it shows in review as one. rulecast doctorreports its size, when it was added and when it last changed.llmrules are included unless you pass--no-llm. A model’s matches can drift between runs, so expect those entries to churn.
Where a ref exists, --from-ref is the better baseline: it never needs maintaining and never turns into an amnesty file.
If something is configured but quiet, rulecast doctor says why: it compiles the config, reports whether each rule’s linter, parser, script or API key is actually there, says where the hooks and caches live, and runs every rule against one file it matches.