CodeVetter · Repo Unpack · Contributor handoff
astral-sh/ruff
Read the CLI dispatch and the crate you are changing before editing; Ruff and ty share a parser but not a rule registry. Semantic changes need a focused test, snapshot updates via the environment variables rather than hand edits, and `cargo dev generate-all` when options, rules, or CLI help change. ty code must keep `db` in the Salsa parameter position and must not touch another file's AST from an untracked query. Do not drop Python repros inside this checkout, and do not expect file-watcher tests to pass in a sandbox.
- Start with `AGENTS.md` (test commands, mdtest style, Salsa rules, `db` parameter order, prek) and `crates/ruff/src/lib.rs` plus `args.rs` for Ruff CLI work, or `crates/ty/src/lib.rs` for ty CLI work. Rule work starts at `codes.rs` `code_to_rule` and the plugin module; ty lint work starts at `declare_lint!` and `register_lints`.
- Safe Ruff lint edit: add or change a rule under `crates/ruff_linter/src/rules/<plugin>/`, register it in `code_to_rule`, add a fixture and insta snapshot, and leave unrelated plugins alone. The public `ruff_linter` API is documented as unstable, so do not churn signatures to make a single rule compile.
- Tests to run for a linter change: `cargo nextest run -p ruff_linter` (or the narrower test binary) with `INSTA_FORCE_PASS=1 INSTA_UPDATE=always MDTEST_UPDATE_SNAPSHOTS=1`, then review snapshot diffs. For ty semantic changes, run `cargo nextest run -p ty_python_semantic --test mdtest`. Full CI uses `cargo insta test --all-features --unreferenced reject --test-runner nextest`.
- Never edit `.snap` files or inline snapshot bodies by hand. After option, CLI, rule, or environment-variable definition changes, run `cargo dev generate-all` so `ruff.schema.json`, `ty.schema.json`, and generated docs stay aligned. Finish with `uv run --only-dev --locked prek run --files <each changed path>`.
- ty parameter convention: `db` is the first argument of free functions and comes immediately after `self` on methods. Methods that read `.node()` should be `#[salsa::tracked]`. `check_file_impl` is already a tracked query; do not call raw AST helpers from module A while checking module B.
- Ad-hoc Python repros must be created outside the checkout. The root `pyproject.toml` sets `requires-python = ">=3.7"` and `tool.ruff.target-version = "py38"`, so ty discovered from this tree infers an old Python version. ty preview for 3.15 is still warned unless preview is enabled.
- Do not use `panic!`, `unreachable!`, `unwrap`, or `expect` for conditions the type system can encode. Existing exceptions include the Windows VT assert in `main` and cache timestamp truncation; do not add more. Lint-path failures should become diagnostics the way `catch_unwind` already does.
- ty PR titles should start with `[ty]`. LSP-only changes use the `server` label when you can set labels. Avoid competing with an open PR; `CONTRIBUTING.md` is the duplicate-work check.
- File-watcher tests do not work in the sandbox. Ruff watch mode is `recommended_watcher` in `ruff::check`; ty watch mode is `watch::directory_watcher` plus `ProjectWatcher`. Skip those locally unless the change is specifically about watching.
- Cache keys include the crate version string, so behavior changes that should invalidate user caches need a version bump or an explicit settings-key change. Do not hand-delete logic that compares `package.package_root` or the atomic rename; both exist because of real corruption bugs.
- Formatter and parser changes have their own fixture trees and fuzz targets (`ruff_formatter_idempotency`, `ruff_parse_idempotency`). A parser change that still snap-passes can fail idempotency fuzz; run the crate tests, not only a single rule snapshot.
- Security-sensitive edits (subprocess, LSP trust, playground KV, cache paths, fix application) should be checked against `agents/references/cli-threat-model.md`, `language-server-threat-model.md`, `playground-threat-model.md`, or `repository-threat-model.md` as appropriate. uv must stay default-off.