Skip to content

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 9

Watch 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.

--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.json
git add .rulecast/baseline.json && git commit -m "rulecast baseline"
rulecast run --all-files # exits 0 unless something got worse
  • run --all-files and run --files treat the findings listed in the file as pre-existing, and fail only on new ones. Sessions, --from-ref and 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 --prune in 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 doctor reports its size, when it was added and when it last changed.
  • llm rules 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.