Why this case study matters
ruff is a linter that can’t lint its own structure. It maintains
its structural validation in two places: .pre-commit-config.yaml
(16 hooks, run via prek — Astral’s own pre-commit runner) and
.github/workflows/ci.yaml (~22 jobs).
Unlike rust-lang/rust’s src/tools/tidy/ (which IS structural
validation), ruff’s crates/ruff_dev/ is exclusively a codegen +
introspection binary. The conventions exist (every internal crate
is version = "0.0.0", publish = false; only ruff and ruff_linter
and ruff_wasm get versioned), but their enforcement is entirely
social.
This case study has two distinct narrative angles worth featuring, both of which position alint orthogonally to Astral’s ecosystem rather than competitively.
Headline catch
ruff has 900+ rules for Python, but zero rules for its own per-crate manifest discipline.
The rule ruff-internal-crates-unpublished in this config is
something ruff literally does not check today and would benefit
from on day one. Same shape applies to most “linter for language X”
projects across the inventory.
Where alint earns its keep here
Two distinct angles worth featuring:
1. The “prek replacement” angle
“alint replaces 15 of ruff’s 16 pre-commit hooks with one declarative
config.” The prek framework is itself an Astral product (the modern
pre-commit replacement); pitching alint as the next layer up — the
rule-config side, not the hook-runner side — positions alint
orthogonally rather than competitively. There’s no overlap: prek
runs hooks, alint declares rules.
2. The “ruff is a linter that can’t lint its own structure” angle
Ruff has 900+ rules for Python, but zero rules for its own per-crate
manifest discipline. Its structural gates live entirely in prek
hooks calling third-party tools (typos, zizmor, actionlint, mdformat,
shellcheck, prettier, ruff itself). This is a legitimately different
shape from rust-lang/rust’s tidy: ruff has chosen the “compose
existing tools via prek” path rather than building its own structural
linter. Alint is exactly the missing piece — one declarative file
replaces the prek wrapper, plus the per-prek-hook YAML, plus the
priority-order metadata, plus the per-hook exclusion patterns.
Future story angles
- Astral-ecosystem narrative — pair alint with prek + ruff + uv in the Astral stack. Both ruff (Python AST) and alint (structural) are linters; both prek (runner) and alint (rule declaration) sit at different layers. A “next layer up” pitch lands cleanly with the Astral audience.
pair_inverserule-kind candidate — ruff has thousands ofcrates/ruff_linter/src/rules/<linter>/snapshots/*.snapfiles paired withcrates/ruff_linter/src/rules/<linter>/rules/*.rssources. The inverse direction (“does every partner trace back to a primary?”) is whatcargo insta test --unreferenced=rejectdoes. ruff is one of 2 demand sources forpair_inverse(alongside angular’s goldens parity). v0.10 design candidate.command_idempotentmode — generalises the “fixer in —check mode” pattern. Many of ruff’s prek hooks (mdformat, markdownlint-fix, ruff-format, prettier) are fixers that the validation pass invokes in--checkmode. ruff is one of 2 demand sources forcommand_idempotent(alongside prettier — covers mdformat, markdownlint, prettier, ruff-format, dprint-check, all of which share this shape). v0.10 design candidate.- Rust-ecosystem narrative — pair ruff’s “linter that can’t lint
its own structure” framing with rust-lang/rust’s
tidy(which IS structural validation) for a contrast story about how language ecosystems handle their own meta-validation differently.