This repository contains both Ruff (a Python linter and formatter) and ty (a Python type checker). The crates follow a naming convention: ruff_* for Ruff-specific code and ty_* for ty-specific code. ty reuses several Ruff crates, including the Python parser (ruff_python_parser) and AST definitions (ruff_python_ast).
When reviewing a branch or pull request, be deliberately nitpicky. Report not only bugs and regressions, but also architectural and maintenance risks, weak test coverage, unclear code, unnecessary complexity, and meaningful style or consistency issues. Order findings by severity, cite files and lines, and distinguish blockers from non-blocking improvements. Number each review point for easy reference in subsequent review discussion.
During code review, check the proposed changes against all applicable code, test, documentation, and architectural conventions in this AGENTS.md. Report meaningful violations introduced by the changes; do not apply agent-only workflow instructions to PR authors or flag unrelated pre-existing issues.
Run all tests (using nextest for faster execution and setting INSTA_FORCE_PASS=1 INSTA_UPDATE=always MDTEST_UPDATE_SNAPSHOTS=1 to ensure all snapshots are updated):
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo nextest run
Run tests for a specific crate:
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo nextest run -p ty_python_semantic
Run a single mdtest file. The path to the mdtest file should be relative to the crates/ty_python_semantic/resources/mdtest folder. Include --test mdtest to avoid building unrelated test binaries:
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo nextest run -p ty_python_semantic --test mdtest -- mdtest::<path/to/mdtest_file.md>
To run a specific mdtest within a file, use a substring of the Markdown header text as MDTEST_TEST_FILTER. Only use this if it's necessary to isolate a single test case:
MDTEST_TEST_FILTER="<filter>" CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo nextest run -p ty_python_semantic --test mdtest -- mdtest::<path/to/mdtest_file.md>
If cargo nextest is not available, use cargo test with the same environment variables:
# Run all tests. CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo test # Run tests for a specific crate. CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo test -p ty_python_semantic # Run a single mdtest file. CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo test -p ty_python_semantic --test mdtest -- <path/to/mdtest_file.md> # Run a specific mdtest within a file. MDTEST_TEST_FILTER="<filter>" CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off INSTA_FORCE_PASS=1 INSTA_UPDATE=always CARGO_PROFILE_DEV_DEBUG="line-tables-only" MDTEST_UPDATE_SNAPSHOTS=1 cargo test -p ty_python_semantic --test mdtest -- <path/to/mdtest_file.md>
After running the tests, always review the contents of any snapshots that have been added or updated.
When running tests with INSTA_FORCE_PASS=1, check for .pending-snap files if any affected tests use inline snapshots.
Never edit snapshot files or inline snapshot bodies manually. Regenerate them by running the relevant tests with the snapshot-update environment variables documented above, then review the generated diff.
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off CARGO_PROFILE_DEV_DEBUG="line-tables-only" cargo clippy --workspace --all-targets --all-features -- -D warnings
Use debug builds (not --release) when developing, as release builds lack debug assertions and have slower compile times.
Run Ruff:
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off CARGO_PROFILE_DEV_DEBUG="line-tables-only" cargo run --bin ruff -- check path/to/file.py
Run ty:
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off CARGO_PROFILE_DEV_DEBUG="line-tables-only" cargo run --bin ty -- check path/to/file.py
The guidance in this section applies to edits to ty* crates, reviews of ty PRs, or other work when the ty type checker has been specifically mentioned by the user.
When the task matches a more specific ty workflow, also read and follow that skill from the repository root:
.agents/skills/adding-ty-diagnostics/SKILL.md..agents/skills/summarise-ecosystem-results/SKILL.md..agents/skills/minimizing-ty-ecosystem-changes/SKILL.md.When changing ty autocomplete ranking, add or update evaluation fixtures under crates/ty_completion_eval/truth/. Extend an existing project when it is a good fit for the behavior being tested; otherwise, add a new one. Use <CURSOR:expected_name> directives to assert ranking, and include the expected module for auto-import completions. Add completion.rs unit tests only when the evaluation fixtures cannot adequately cover the behavior.
Regenerate and review the committed evaluation results after changing ranking behavior or fixtures:
CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off CARGO_PROFILE_DEV_DEBUG="line-tables-only" cargo run --package ty_completion_eval -- all --threshold 0.4 --tasks crates/ty_completion_eval/completion-evaluation-tasks.csv
To inspect one evaluation task, run cargo run --package ty_completion_eval -- show-one <fixture-name> --file-name <file-name> --index <cursor-index>.
When running ty against a temporary Python reproduction file, create it outside the Ruff checkout (for example, under /tmp). A file inside the checkout discovers Ruff's root pyproject.toml, whose requires-python = ">=3.7" causes ty to infer Python 3.7 as the default Python version.
When working on ty, PR titles should start with [ty]. Add the ty GitHub label if you have permission to do so; if you don‘t, however, automation should add it anyway, so there’s no need to worry about it. Similarly, add the server label if your change only affects the LSP server and you have permission to add that label.
db parameterFor free functions and associated functions without a self parameter, db should be the first parameter. For methods with a self parameter, db should come immediately after self.
Adding #[salsa::tracked] to a function or method means that the Salsa framework will cache the function/method. This can sometimes be done for performance reasons, and can also be done to ensure incremental computation in an IDE context.
Methods that access .node() should usually be #[salsa::tracked], or ty‘s incrementality will suffer: we don’t want to accidentally introduce a dependency on module a's AST in a Salsa query that would be called when type-checking module b. Prefer higher-level semantic APIs over raw AST access where possible, but ask for guidance from the user if this would require significant refactoring.
For Salsa-cached values, avoid retaining excess collection capacity. Prefer boxed slices; otherwise shrink collections that may have spare capacity before returning them. In particular, inspect HashMap and HashSet values constructed via extend, collect, explicit reservation, or removal, since those operations can leave capacity that insert-only construction does not.
Salsa caching can occur due to a function/method having #[salsa::tracked] on it, or due to a struct with #[salsa::interned] being constructed.
Parts of .github/workflows/release.yml are generated by cargo-dist from dist-workspace.toml. Before editing the release workflow, check whether the relevant section is generated. Prefer changing dist-workspace.toml or the referenced reusable workflow instead of editing generated YAML. After modifying cargo-dist configuration, regenerate the workflow with the cargo-dist version pinned in dist-workspace.toml and inspect the resulting diff to ensure the change will survive future regenerations.
pub: make an item public when another workspace crate needs it and that produces the cleaner implementation.uv run --only-group dev --locked prek at the end of a task if you changed files in the repo. This includes changes such as rebases or addressing review comments. Use uv run --only-group dev --locked prek run --files <path1> <path2> and pass every file you changed. This keeps the hook run independent of staged state and avoids sweeping unrelated changes. Use uv run --only-group dev --locked prek run --all-files when a full-repository hook sweep is specifically needed.panic!, unreachable!, .unwrap() or .expect(). Instead, try to encode those constraints in the type system. Don‘t be afraid to write code that’s more verbose or requires largeish refactors if it enables you to avoid these unsafe calls.if let combined with &&) and let guards (PAT if let ... =>) over nested if let statements to reduce indentation and improve readability. At the end of a task, always check your work to see if you missed opportunities to use let chains or let guards.#[expect()] over [allow()], where possible. But if a lint is complaining about unused/dead code, it's usually best to just delete the unused code.CARGO_PROFILE_DEV_OPT_LEVEL=1 CARGO_PROFILE_DEV_LTO=off CARGO_PROFILE_DEV_DEBUG="line-tables-only" cargo dev generate-all after changing configuration options, CLI arguments, lint rules, or environment variable definitions, as these changes require regeneration of schemas, docs, and CLI references.test_.impl blocks unless the impl is deliberately placed in a separate file, as for large structs.uv run for any scripts from the repository root unless you use --no-project, --script or similar. Using uv run from the Ruff repo root without these flags will build Ruff from source, which is very slow and usually unnecessary.