Respond to a red check
When a check goes red, there are two responses: fix the code, or record a deliberate omission here. A single testing-conventions.toml at your repo root (or per package, in a monorepo) tunes what the checks require — coverage floors and reason-required exemptions. Anything you don't set keeps its strict default; the configuration reference carries every key.
Relax a coverage floor
Every coverage check ships a strict 100% floor. Lower one under the language's table:
# Drop the Python floor from the strict default 100 to 90:
[python]
coverage = { branch = true, fail_under = 90 }
[typescript]
coverage = { branches = 90 } # lines / functions / statements stay at 100
[rust]
coverage = { regions = 90 } # lines stays at 100A [<language>].coverage table is a partial override — set only the fields you want to move and the rest keep their default. (A typo'd key is still rejected; only missing keys fall back.) See Reference — coverage keys for every key and default, and Why a 100% floor before you lower one.
Raise the one-function-per-file threshold
one-function-per-file runs for Python and TypeScript with no configuration; Rust opts in by naming a threshold. It allows one module-scope function per file whose body runs longer than max_lines, and the default is 1. A codebase whose natural grain is longer functions moves the line under the language's table:
[python]
one_function_per_file = { max_lines = 5 }
[typescript]
one_function_per_file = { max_lines = 20 }A function at or under the threshold is trivial and shares a file freely. Raising the number moves where "trivial" ends; the one-per-file ceiling above it holds at every setting. See One function per file for what counts as a function and as a line.
Exempt a file
Some files genuinely shouldn't be tested — a launcher shim, a process entry point, generated code. A blocking gate needs that escape hatch, but here it's explicit and reason-required, never a silent ignore — see Scoping and exemptions for the design.
Declaration-only modules need no exemption
A file with no function and no control flow anywhere in it has nothing a test can drive and is never flagged: an empty or comment-only file, a re-export barrel __init__.py / index.ts, a constants module, an Enum, a type-only module. A TypeScript declaration file (*.d.ts) is ignored too. The parser decides from the file's contents, never from its name — the moment a file gains a def, an arrow, a method, an if, or a ternary, it becomes a subject and needs either a colocated test or an exemption. What it enforces carries the node list per language.
Exempt a real file
Add a [[<language>.exempt]] entry naming the rules it lifts and why. Whole-file exemptions are for the presence and lint checks — a launcher shim or a process entry point with no colocated test:
# A launcher shim with no unit test:
[[python.exempt]]
path = "mypkg/cli.py"
rules = ["colocated-test"]
reason = "thin launcher; logic lives in run(), tested in run_test.py"
# A process entry point, exempt from the colocated-test check:
[[typescript.exempt]]
path = "src/bin.ts"
rules = ["colocated-test"]
reason = "process entry; maps run() to an exit code, tested in run.test.ts"pathis relative to the scannedsource, and must point to a file that exists — a stale entry is a hard error, so the list can't silently rot.rulesnames the checks the entry lifts (colocated-test,one-function-per-file) or an individual lint rule (no-monkeypatch) — see the glossary for how this key spans both. Forcoverage/mutation, see the line-scoped form below — those are never whole-file.reasonis required; a reason-less entry is rejected when the config loads.
Because every exemption lives in this one file, names its rules, and carries a reason, the whole exemption surface is auditable in a single diff — unlike scattered ignore comments. See Reference — Exemptions for the exact schema.
Exempt specific lines (coverage / mutation)
coverage and mutation exemptions are never whole-file — they must carry a lines list naming the exact lines they lift:
[[python.exempt]]
path = "mypkg/config/tomlcompat.py"
rules = ["coverage", "mutation"]
lines = [9, 10, "12-13"] # single lines and inclusive "start-end" ranges
reason = "version-conditional tomllib/tomli import; one branch is dead on any single interpreter"A determinism guard checks the list:
- A listed line that isn't failing (it's covered, has a killed mutant, or carries no measured code) is a hard error.
- A failing line that isn't listed fails the gate as normal.
So the list is exactly the failing lines. lines is required with coverage / mutation and rejected with any whole-file check, so the two never share an entry — a file exempt from both colocated-test and coverage is two entries.
For a Rust mutation survivor asserted by an integration or e2e test, name that test in a [[rust.exempt]] entry with rules = ["mutation"] and the survivor's exact line. The reason states which behavior the other tier verifies.
See also
- Reference — Configuration: every key and the full schema.
- Scoping and exemptions: why a blocking gate needs a reasoned door.