Contributing a rule
On this page
- Development setup
- The guided path
- The manual steps
- Constraints on a rule
- Writing the explanation
- Documentation
Development setup
git clone https://github.com/huggingface/transformers-mlinter
cd transformers-mlinter
pip install -e ".[dev]"
The repo’s own checks:
make test # pytest under tests/
make lint # ruff check + format --check
make format # auto-fix style
make typecheck # ty on mlinter/
make docs # regenerate the rule pages and build this site
The guided path
The repo ships an add-mlinter-rule skill that walks an agent through duplicate detection, numbering,
module creation, running the candidate against every model in the library, and deciding whether the
findings mean “fix the models” or “allowlist them”. Enable it for your agent:
make claude # symlinks .claude/skills -> .ai/skills (for Claude Code)
make codex # symlinks .agents/skills -> .ai/skills (for Codex)
Then invoke /add-mlinter-rule in a new session.
The step that matters most is the one that is easy to skip: run the candidate rule against the whole library before proposing it. A rule that looks obviously correct routinely turns up dozens of pre-existing violations, and how many there are decides whether the rule needs a cutoff date, an allowlist, or a rethink.
The manual steps
- Add a
[rules.TRFXXX]entry tomlinter/rules.toml. - Fill in
description,default_enabled,explanation.what_it_does,explanation.why_bad, andexplanation.diff. Optional:allowlist_modelsfor per-model exemptions andcutoff_dateto scope the rule to newer models. - Create
mlinter/trfXXX.pywith acheck(tree, file_path, source_lines) -> list[Violation]function. - Use the
RULE_IDmodule constant instead of hardcoding"TRFXXX"inside the check. - Add or update focused tests in
tests/.
Registration is automatic from there: rule modules are discovered by filename, and import-time validation fails if a module has no TOML entry or a TOML entry has no module. There is no registry list to update.
Constraints on a rule
- Static analysis only. Use Python’s
astmodule. A rule must never import the model, download weights, or execute the file under inspection. - Gate on the filename. Rules are handed every file kind mlinter discovers, so a rule that only
makes sense for
modeling_*.pyhas to check the prefix itself. Widening file discovery must not expose an existing rule to a file type it was never written for. - One
checksignature.check(tree, file_path, source_lines) -> list[Violation]. - Honour suppressions. Call the shared suppression helper rather than reimplementing the comment
scan, unless the rule deliberately supports no suppression — in which case say so in a comment, as
TRF038does. - Cross-file reads are allowed but must be cheap. Some rules read the companion
configuration_*.pyfrom disk. The cache accounts for this: a modeling file is re-checked when its companion config changes.
Writing the explanation
The three explanation fields are what a contributor sees when a rule fires on their pull request, both
in mlinter --rule TRFXXX and on this site’s rule pages, which are generated from
them.
what_it_does— the mechanical description. What construct is detected.why_bad— the consequence. What actually breaks, or what a reader gets wrong. “It is inconsistent” is not a consequence; “it can break weight loading key mapping” is.diff— a single diff block with-for the flagged form and+for the fix. Keep it to the few lines that carry the difference. UseAcmeas the model name, matching the existing rules.
Prose may contain code spans; the site generator escapes everything around them, so <Model>Config
and modeling_*.py render as written rather than being eaten as markup.
Documentation
This site’s rule reference is generated, so adding a rule to rules.toml documents it — there is no
separate page to write. Run make docs to see how it renders. Only the hand-written pages (home, CLI
usage, suppressing, this page) live as files under docs/.
