The CI gates that lint the repo, in canonical order.
Two gates, both via ./ci.sh:
| Gate | Command |
|---|---|
./ci.sh fmt |
cargo fmt --all -- --check |
./ci.sh clippy |
cargo clippy --workspace --all-targets -- -D warnings |
Both run on every push and locally. Failing format is fixable with
cargo fmt --all; failing clippy points at a real issue (a denied
warning, an unused-result, etc.).
One gate covering lint + format:
| Gate | Command |
|---|---|
./ci.sh ruff |
ruff check src tests ci ci.py + ruff format --check src tests ci ci.py |
ruff is provisioned at script-time via uv run --no-project --with ruff>=0.8, so it never triggers a maturin build to lint a few .py
files.
| Gate | Threshold |
|---|---|
./ci.sh loc |
warn > 1000, fail > 1500 (per file) |
Split convention printed on every failure:
foo.rs → foo/mod.rs + per-domain submodules, with pub use
re-exports in mod.rs so the public path is unchanged. The
per-edit half lives in ci/hooks/loc_guard.py.
If you add new tooling:
- write the gate at
ci/gates/<name>.pywithdef run() -> int - register it in
ci.py::GATE_ORDER - add a step to
.github/workflows/ci.yml - update README.md and this file
- explain in the gate's docstring why this gate belongs here instead of the language-native default
mypy/pyright. Type-checking the thin Python surface adds more noise than signal for a template; downstream consumers that grow real Python should add a gate. Pattern:ci/gates/pyright.pywithsubprocess.run(["uv", "run", "--no-project", "--with", "pyright", "pyright", "src", "tests"]).dylint. Rust dylints are heavy (large dependency graph) and the canonical template doesn't depend on any. Downstream consumers addci/gates/dylint.pyif they want it; the Docker-for-Rust gate shape (zccache#835 rule 11) is the right amplifier when they do.