Jesse A. Green / notebook

Ruff Admin & Architecture Guide

Generated June 2026 from astral-sh/ruff at fd16b26

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for engineers, architects, and contributors who need to understand how the Ruff repository is built, how its two shipped tools work internally, and how to operate, extend, and reason about the codebase. For day-to-day usage of the ruff and ty commands, see the companion User Guide.


Table of Contents


Executive Summary

The astral-sh/ruff repository is a single Cargo workspace of roughly three dozen crates that compiles into two independent command-line binaries:

  • ruff — an extremely fast Python linter and code formatter.
  • ty — an extremely fast Python type checker and language server.

Both are written entirely in Rust and ship as self-contained native binaries (a thin Python wheel wraps the compiled binary for pip install). The two tools share a large substrate of common crates — a Python parser, an AST, source-text utilities, a diagnostics renderer, and a Salsa-based incremental database — but they are separately published and separately consumed.

Property Value
Primary language Rust (Cargo workspace)
Shipped binaries ruff, ty
Workspace crates ~37 crates/* plus playground/* (web) and python/* (tooling)
Source size ~13.6M characters across ~4,800 code files
CLI framework clap (derive style)
Incremental engine salsa (used by ruff_db / ty)
Parallelism rayon
Source commit fd16b26

Note

Because the linter alone implements 800+ rules (reimplementing Pyflakes, pycodestyle, isort, pyupgrade, pydocstyle, and many flake8 plugins), this guide documents the architecture and operation of the engine rather than enumerating individual rules. The rule set is exposed at runtime through ruff linter and ruff rule.


Repository Architecture

Two products, one workspace

The repository is best understood as a multi-product monorepo rather than a single application:

astral-sh/ruff  (Cargo workspace)
│
├── ruff  (binary)  ──► linting + formatting of Python source
│
├── ty    (binary)  ──► type checking + language server for Python
│
├── shared foundation crates (parser, AST, source text, diagnostics, Salsa db)
│
├── playground/   (TypeScript/React web playgrounds for ruff and ty)
└── python/        (Python-side tooling: ecosystem checks, fuzzer, wheel wrapper)

The top-level directory layout:

Path Contents
crates/ All Rust crates — the engines, both binaries, and every supporting library.
playground/ Browser playgrounds (playground/ruff, playground/ty, plus shared and api) built on the WASM crates.
python/ Python-side packages: ruff (wheel wrapper), ruff-ecosystem (real-world regression checks), py-fuzzer.
scripts/ Maintenance and benchmarking scripts.
docs/ Project documentation site sources (MkDocs Material).
fuzz/ Cargo-fuzz targets.

Architectural layers

Rather than thinking about ~37 crates individually, group them into layers. Data flows upward: low-level text and parsing crates feed the AST, which feeds the semantic/type layers, which feed the linter, formatter, and type checker, which are wrapped by the two binaries.

┌──────────────────────────────────────────────────────────────┐
│  Binaries (user-facing)                                        │
│    ruff (crates/ruff)            ty (crates/ty)                │
├──────────────────────────────────────────────────────────────┤
│  Tool engines                                                  │
│    ruff_linter   ruff_python_formatter   ty_python_semantic    │
│    ruff_formatter (generic IR/printer)   ty_project / ty_ide   │
├──────────────────────────────────────────────────────────────┤
│  Language understanding                                        │
│    ruff_python_parser → ruff_python_ast → ruff_python_semantic │
│    ruff_python_index   ruff_python_trivia   ty_module_resolver │
├──────────────────────────────────────────────────────────────┤
│  Shared foundation                                             │
│    ruff_db (Salsa database, file system, diagnostics render)   │
│    ruff_source_file  ruff_text_size  ruff_diagnostics          │
│    ruff_cache  ruff_index  ruff_macros  ruff_notebook          │
└──────────────────────────────────────────────────────────────┘

Crate map

The most important crates, grouped by role. (Char counts are approximate, from the analyzed snapshot, and indicate relative weight.)

User-facing binaries

Crate Role
ruff The ruff binary: CLI parsing (args.rs), command dispatch (commands/), file caching (cache.rs).
ty The ty binary: CLI (args.rs), main loop & watch mode (lib.rs), logging, printer.
ruff_dev Internal developer CLI: schema/docs/CLI-reference generation, AST/CST/token dumps, format-dev harness.

Linter engine

Crate Role
ruff_linter The linter: rule registry (codes.rs/registry.rs), checkers (checkers/), all rules/ plugins, the check_path/lint_only/lint_fix pipeline, # noqa handling (noqa.rs, directives.rs), fixes (fix/).
ruff_python_semantic Semantic model used by lint rules (bindings, scopes, references, control-flow graph).
ruff_python_stdlib Knowledge of the Python standard library (builtins, module membership).
ruff_diagnostics Diagnostic and fix data types shared across the linter.

Formatter engine

Crate Role
ruff_python_formatter Python-specific formatter: turns the AST into formatter IR (statements, expressions, comments, strings).
ruff_formatter Generic, language-agnostic formatter IR and printer (the "format element" + printer that ruff_python_formatter targets).
ruff_python_codegen Source generation / stylist (detects existing quote and indentation style).

Parser / AST / source primitives

Crate Role
ruff_python_parser Hand-written lexer + parser producing the Python AST and tokens.
ruff_python_ast The Python AST node definitions (largely code-generated, see generated.rs) and visitors.
ruff_python_index Indexes tokens/comments for fast lookups during linting.
ruff_python_trivia Whitespace/comment ("trivia") analysis.
ruff_python_literal Literal evaluation helpers (numbers, strings, f-strings).
ruff_source_file Source file abstraction: line index, positions, encodings.
ruff_text_size Byte-offset text range primitives used everywhere.

ty type system

Crate Role
ty_python_semantic The type inference and type-checking core (the largest semantic crate).
ty_python_core Core type-system building blocks and use-def analysis.
ty_project Project model, project database, configuration metadata, file watching, glob handling.
ty_module_resolver Resolves imports to modules using the Python environment and search paths.
ty_vendored Vendored typeshed stubs (stdlib type stubs bundled into the binary at build time).
ty_ide IDE features (completions, hover, call hierarchy, docstrings) shared by the server.
ty_combine Configuration "combine"/merge trait for layering options.
ty_static Static data including the EnvVars registry.

Shared foundation

Crate Role
ruff_db The Salsa-based incremental database: file inputs, tracked parse/source queries, file-system abstraction (OsSystem), diagnostics rendering, cancellation.
ruff_cache On-disk cache key machinery for the linter.
ruff_index Typed index/arena collections.
ruff_macros Procedural macros (e.g. CacheKey, option metadata derives).
ruff_notebook Jupyter notebook parsing and cell handling.
ruff_annotate_snippets Renders annotated source snippets in diagnostics.
ruff_options_metadata Reflection over configuration options (powers ruff config).
ruff_workspace Configuration resolution for ruff: the Options/Configuration schema and settings layering.
ruff_graph Import dependency-graph construction (powers ruff analyze graph).

Servers, WASM, and tooling

Crate Role
ruff_server The ruff server language server (LSP).
ty_server The ty server language server (LSP).
ruff_wasm / ty_wasm WebAssembly bindings powering the browser playgrounds.
ruff_benchmark / ty_completion_bench Benchmark harnesses.
ruff_mdtest / mdtest / ty_test Markdown-driven test harnesses (notably for ty's inference tests).
ruff_memory_usage Heap-size accounting used by Salsa memory reports.

How Ruff lints: the check_path pipeline

The heart of the linter is ruff_linter::check_path (in crates/ruff_linter/src/linter.rs). For a single file, the source is first tokenized and parsed (producing Parsed<ModModule> and a token stream), then directives (# noqa, # isort: skip, # TODO) are extracted. check_path then runs the enabled rules grouped by their lint source — the kind of input a rule needs — so each pass only runs if at least one enabled rule requires it:

source text
   │
   ▼  ruff_python_parser
tokens + AST (Parsed<ModModule>)  +  Indexer (comment ranges, etc.)
   │
   ▼  directives: extract # noqa / # isort: skip / # TODO
   │
   ├─► token-based rules          (check_tokens)          — always available
   ├─► filesystem-based rules     (check_file_path)       — path/package rules
   ├─► logical-line rules         (check_logical_lines)   — pycodestyle-style
   │
   ▼  if syntax is valid:
   ├─► AST-based rules            (check_ast)             — builds the semantic model
   └─► import rules               (check_imports)         — isort
   │
   ▼
aggregate Diagnostics → apply # noqa suppression → (optionally) apply fixes

Key properties:

  • Rules declare their input. Each rule advertises a "lint source" (tokens, filesystem, logical lines, AST, or imports). A pass is skipped entirely if no enabled rule needs it, which is a major reason Ruff is fast.
  • AST rules require valid syntax. AST- and import-based rules run only when the file parses cleanly (parsed.has_valid_syntax()); token- and filesystem-based rules still run on files with syntax errors.
  • The semantic model is built during the AST pass. check_ast (in crates/ruff_linter/src/checkers/ast/) walks the tree, building bindings, scopes, and references via ruff_python_semantic, and dispatches semantic rules as it goes.
  • Two public entry points wrap check_path: lint_only (report only) and lint_fix (report and apply fixes, re-linting iteratively until the code stabilizes).
  • Suppression is a post-filter. # noqa comments are matched against produced diagnostics after the fact (noqa.rs), which is also how --add-noqa and the RUF100 unused-noqa rule are implemented.

Preview behavior is gated through crates/ruff_linter/src/preview.rs, which exposes named predicate functions instead of raw preview flag checks, so that promoting a preview feature to stable is a matter of deleting its predicate.


How Ruff formats

The formatter is independent of the linter. ruff_python_formatter lowers the parsed AST into a generic, language-agnostic intermediate representation defined by ruff_formatter (the "format element" tree), and a printer then renders that IR back to text under the active [tool.ruff.format] settings (line length, quote style, indentation, trailing commas, line endings, docstring code formatting). Because the formatter consumes the same parser and AST as the linter, formatting and linting always agree on the structure of the code. The result is a Black-compatible style with Ruff-specific options layered on top.

Range formatting (ruff format --range) maps a 1-based line/column range to a byte range (FormatRange::to_text_range) and formats only the enclosing logical lines; it is restricted to a single, non-notebook file.


How ty type-checks: the Salsa engine

ty is built on Salsa, an incremental computation framework. The shared crate ruff_db defines a Salsa database trait (#[salsa::db] pub trait Db: salsa::Database) whose inputs are files (#[salsa::input]) and whose derived values — parsed ASTs, source text, and so on — are #[salsa::tracked] queries with memoization and LRU eviction (for example the parsed-AST query is capped at an LRU of 200 entries). When a file changes, Salsa invalidates only the queries that transitively depend on it and recomputes the minimum necessary, which is what makes watch mode cheap.

ty check (in crates/ty/src/lib.rs) is structured as a message-driven main loop:

ty check
   │
   ▼ discover project metadata (ProjectMetadata::discover or ::from_config_file)
   ▼ apply configuration files + CLI overrides
   ▼ build a ProjectDatabase (Salsa)
   │
   ▼ MainLoop (crossbeam channel of messages)
   │   ├─ CheckWorkspace   → rayon::spawn a background check; report progress
   │   ├─ CheckCompleted   → render diagnostics, compute exit status
   │   ├─ ApplyChanges     → (watch mode) invalidate & re-check on file events
   │   └─ Exit             → cancel pending queries and stop
   ▼
diagnostics → printer → exit code

Notable design points:

  • Module resolution uses your environment. ty_module_resolver resolves third-party imports through the Python environment selected by --python/--venv (or inferred), and stdlib types come from ty_vendored typeshed stubs baked into the binary.
  • Cancellation is first-class. Ctrl+C triggers a cancellation token; in-flight Salsa queries are cancelled cooperatively (salsa::Cancelled::catch), and watch mode cancels and reschedules on every change.
  • Fix and suppression modes share the loop. --fix (fix_all_diagnostics) and --add-ignore (suppress_all_diagnostics) are alternate MainLoopMode::Fix branches that reuse the same check results.
  • Severity drives the exit code. ty maps the maximum diagnostic severity to an exit status (see Exit codes); warnings only fail the build under --error-on-warning, and I/O diagnostics produce exit code 2.
  • Memory introspection is built in. Setting TY_MEMORY_REPORT to short, full, or json dumps Salsa's memory usage after a check.

Configuration system

The two tools have parallel but separate configuration stacks.

Ruff configuration lives in ruff_workspace. The schema is the serde Options struct (options.rs) projected into a resolved Configuration (configuration.rs), split into:

  • top-level settings (line-length, indent-width, target-version, src, include, exclude, respect-gitignore, fix, output-format, preview, required-version, extend, cache-dir, …),
  • LintConfiguration → [tool.ruff.lint] (rule selection, per-file ignores, plus a sub-table per upstream plugin: isort, pycodestyle, pydocstyle, mccabe, pylint, pyupgrade, the flake8-* family, ruff, …),
  • FormatConfiguration → [tool.ruff.format] (quote-style, indent-style, line-ending, skip-magic-trailing-comma, docstring-code-format, docstring-code-line-length, …),
  • AnalyzeConfiguration → [tool.ruff.analyze] (import-graph options).

Resolution walks up the directory tree from each file, so nested projects can override settings. Precedence, lowest to highest: configuration files → --config "KEY = VALUE" inline overrides → dedicated CLI flags (--line-length, --select, …). The --config flag is overloaded: it accepts either a config-file path or an inline TOML override (parsed by ConfigArgumentParser in crates/ruff/src/args.rs). --isolated discards all config files; ruff_options_metadata powers the self-describing ruff config command.

ty configuration lives in ty_project::metadata::options. It reads ty.toml or the [tool.ty] table of pyproject.toml, organized into environment, src, terminal, and rules sub-tables. CLI flags are converted into the same Options type (CheckCommand::into_options) and merged with --config overrides via the ty_combine Combine trait, so the CLI and config file share one schema.


Concurrency and performance

  • Data parallelism via rayon. Both tools build a global rayon thread pool and process files in parallel. ty bounds the pool to max_parallelism() and a fixed stack size; the pool size can be capped with TY_MAX_PARALLELISM.
  • Message passing via crossbeam. ty's main loop communicates with background check tasks over a bounded crossbeam channel, keeping the orchestrating thread responsive (and able to handle Ctrl+C and watch events) while checks run on rayon.
  • Incrementality via Salsa. Memoized, dependency-tracked queries mean unchanged files are not re-parsed or re-analyzed across a watch session.
  • Custom allocator. On supported 64-bit, non-macOS/Windows targets, ty uses jemalloc (tikv-jemallocator) as the global allocator for throughput.
  • Skip-work-by-default. The linter only runs a rule pass if an enabled rule needs that input kind (see the pipeline above), avoiding tokenization/semantic work that no active rule would consume.

Caching

Ruff persists a per-package on-disk cache (crates/ruff/src/cache.rs, built on ruff_cache). The cache directory defaults under the configured cache-dir and is keyed by the Ruff VERSION and the resolved settings, so changing the version or configuration invalidates it automatically. Per-file cache keys combine the file's last-modified time and permission bits (FileCacheKey). Disable reads with --no-cache/RUFF_NO_CACHE; wipe the cache with ruff clean.

ty caches in memory through Salsa for the duration of a process (especially valuable in watch and server modes); it does not rely on the on-disk lint cache.

Important

The Ruff cache is keyed on the binary version. After upgrading Ruff, the old cache is simply ignored rather than producing stale results — there is no manual migration step.


Language servers

Each tool ships an LSP server reusing the same engine as its CLI:

  • ruff server (ruff_server) provides diagnostics, quick-fixes, import organization, and formatting. It shares Ruff's configuration resolution, so editor results match CLI results. --preview enables unstable server features and turns on preview for the linter and formatter.
  • ty server (ty_server, with IDE features from ty_ide) provides type diagnostics, hover, completions, and related language features over the same Salsa project database used by ty check.

Editors normally spawn these servers via an extension rather than the user invoking them directly. A migration note for users coming from ruff-lsp lives in crates/ruff_server/docs.


Build, requirements, and toolchain

  • Build system: Cargo workspace. Build the binaries with cargo build --release -p ruff and cargo build --release -p ty. A Rust toolchain is required (see the repository's rust-toolchain.toml).
  • Runtime requirements: None beyond the native binary. Ruff and ty do not require a Python interpreter to run; ty does need access to your project's Python environment to resolve third-party imports (via --python), but that is data input, not a runtime dependency of the tool itself.
  • Code generation at build time: Parts of the AST (ruff_python_ast/src/generated.rs) and schema/reference docs are generated. ruff_dev exposes generators guarded by environment switches such as RUFF_UPDATE_SCHEMA, TY_UPDATE_SCHEMA (regenerate JSON schemas), and the various generate_ty_* commands (CLI/options/rules/env-var references). ty_vendored/build.rs copies typeshed stubs into the binary using OUT_DIR.
  • Docs site: The documentation site is MkDocs Material. The pinned toolchain in docs/requirements.txt includes mkdocs==1.6.1, mkdocs-material==9.7.6, ruff==0.15.15, mdformat==1.0.0, and related plugins.
  • Python-side tooling: python/ruff-ecosystem runs Ruff against real-world projects to detect behavioral regressions; python/py-fuzzer fuzzes the parser/formatter; scripts/ holds benchmarking and maintenance utilities (with their own pyproject.toml that itself uses [tool.ruff] and [tool.ty]).

Operations

This repository produces developer tools rather than a long-running service, so "operations" means running the binaries in CI and developer workflows.

Run in CI (no writes, fail on issues):

ruff check --output-format github     # lint with GitHub Actions annotations
ruff format --check                   # fail if any file is unformatted
ty check --error-on-warning           # fail on type errors and warnings

Pin the expected version so a project fails fast on drift:

[tool.ruff]
required-version = ">=0.15"

Diagnose configuration problems:

ruff check --show-settings path/to/file.py   # resolved settings for a file
ruff check --show-files                       # which files would be analyzed
ty check -v                                   # verbose; -vv / -vvv for more

Profiling ty:

TY_LOG=ty=debug ty check        # detailed tracing
TY_LOG_PROFILE=1 ty check       # write a flamegraph profile (tracing.folded)
TY_MEMORY_REPORT=full ty check  # dump Salsa memory usage

Tip

The ruff_dev crate is the maintainers' Swiss-army knife — cargo run -p ruff_dev -- … can print the AST/CST/tokens of a file, regenerate schemas and docs, and run the formatter-development harness. It is not shipped to end users.


Security analysis

Ruff and ty are static analysis tools: they read source files and (optionally) write formatted/fixed source back. They are not network services and expose no remote attack surface in normal use.

Area Assessment
Network The core binaries do not make network calls during check/format. (Python-side ecosystem tooling clones repositories for testing; that is developer tooling, not the shipped binary.)
Code execution Ruff and ty parse and analyze Python; they do not execute the target code. This is a fundamental safety property versus runtime tools.
File writes --fix (ruff/ty) and ruff format modify files in place. Treat them like any code-mutating tool: run on a clean VCS tree and review diffs. --diff/--check provide non-destructive previews.
Untrusted input The parser and formatter are continuously fuzzed (fuzz/, python/py-fuzzer) precisely because they ingest arbitrary, possibly malicious source text. Internal panics map to exit code 101 (ty) and are reported as bugs rather than silently ignored.
Configuration Config is TOML parsed into typed structs; unknown keys and malformed values are rejected with actionable errors rather than executed.
Path handling File discovery respects .gitignore by default and supports force-exclude; --isolated removes ambient config influence for reproducible runs.

Caution

The most common operational hazard is --fix/format rewriting files. Always run these against version-controlled code so changes are reviewable and reversible.


Testing strategy

The repository combines several layers of testing:

  • Snapshot tests (insta-style snapshots/ directories across the linter and formatter) pin rule output and formatted output so regressions are caught as diffs.
  • Markdown-driven tests (mdtest, ruff_mdtest, ty_test) — particularly for ty's type inference — encode expected diagnostics inline in Markdown fixtures under resources/mdtest/, which double as living documentation of type-system behavior.
  • CLI integration tests (crates/ruff/tests/cli, crates/ty/tests/cli) exercise the binaries end to end. (Issue #19016 tracks ongoing cleanup of Ruff's CLI tests.)
  • End-to-end LSP tests (crates/ruff_server/tests/e2e, crates/ty_server/tests/e2e).
  • Fuzzing (fuzz/fuzz_targets, python/py-fuzzer) for the parser and formatter.
  • Ecosystem checks (python/ruff-ecosystem) run Ruff against real-world projects to surface behavioral changes before release.
  • Benchmarks (ruff_benchmark, ty_completion_bench, scripts/benchmarks) guard performance, using vendored real-world files (numpy, pydantic, CPython's tomllib, etc.).

Code quality & conventions

  • Crate granularity. Functionality is split into many small, single-responsibility crates, which keeps compile units focused and makes the dependency graph (and therefore the data-flow layering) explicit.
  • Code generation. The AST and several reference documents are generated rather than hand-maintained (generated.rs, ruff_dev generators), reducing drift between the implementation and its schemas/docs.
  • Preview-feature discipline. New rules and behaviors land behind named preview predicates (preview.rs) so they can be tested in the field and promoted by deleting a single function.
  • Self-describing surfaces. Both CLIs use clap derive structs as the single source of truth for arguments and help; ruff_options_metadata makes the configuration schema introspectable (ruff config).
  • Centralized environment variables. ty centralizes its environment variables in ty_static's EnvVars registry, keeping the documented set and the consumed set in sync.

Extending the codebase

Adding a lint rule (high level):

  1. Implement the rule under crates/ruff_linter/src/rules/<plugin>/rules/, declaring its lint source (tokens / AST / logical lines / filesystem / imports) so it joins the right pass in check_path.
  2. Register its code in the registry (codes.rs); registry.rs re-exports from there.
  3. Add fixtures and snapshots under the plugin's resources//snapshots/ directories.
  4. Gate unstable behavior behind a preview.rs predicate until it stabilizes.

Adding/adjusting a configuration option:

  1. Add the field to the relevant Options struct in ruff_workspace (or the ty options in ty_project::metadata::options).
  2. Thread it into the resolved Configuration/settings.
  3. Regenerate schemas/docs via the ruff_dev generators (RUFF_UPDATE_SCHEMA / TY_UPDATE_SCHEMA).

Working on the type checker: add Markdown inference tests under crates/ty_python_semantic/resources/mdtest/ — they are the canonical way to specify and verify ty's behavior, and they render as documentation.


Potential improvements

These are observations from the codebase and its tracked issues, not defects:

  • CLI test cleanup is explicitly tracked (issue #19016: "Cleanup Ruff's CLI tests"), indicating the CLI test suite is an area of active consolidation.
  • A known panic exists in ty's IDE layer (issue #25639: a panic when hovering over an inlay hint in VS Code). Panics in the language server degrade the editor experience and surface as internal errors — hardening hover/inlay paths against unexpected states would improve robustness.
  • Preview surface management. The named-predicate approach in preview.rs is sound; periodic audits to delete predicates for already-promoted features keep the surface clean.
  • Rule-message consistency. Tracked issues such as #14743 (a violation message showing the wrong type) show that diagnostic wording occasionally lags behavior; snapshot tests help, but message accuracy benefits from continued review.

Quick Reference

Build:

cargo build --release -p ruff
cargo build --release -p ty

ruff subcommands: check, format, rule, linter, config, clean, analyze graph, server, version, help.

ty subcommands: check, server, explain rule, version.

Ruff environment variables: RUFF_OUTPUT_FORMAT, RUFF_OUTPUT_FILE, RUFF_NO_CACHE, RUFF_CACHE_DIR.

ty environment variables: TY_CONFIG_FILE, TY_OUTPUT_FORMAT, TY_LOG, TY_LOG_PROFILE, TY_MEMORY_REPORT, TY_MAX_PARALLELISM.

Build/dev environment switches: RUFF_UPDATE_SCHEMA, TY_UPDATE_SCHEMA, MDTEST_UPGRADE_LOCKFILES, CARGO_MANIFEST_DIR/OUT_DIR/TARGET (standard Cargo).

ty exit codes: 0 success · 1 errors (or warnings with --error-on-warning) · 2 invocation/I-O error · 101 internal error (panic).

Ruff exit codes: 0 no violations · 1 violations found (modifiable with --exit-zero / --exit-non-zero-on-fix).

Config files: ruff.toml / .ruff.toml / [tool.ruff] in pyproject.toml (linter+formatter); ty.toml / [tool.ty] in pyproject.toml (type checker).

Key source locations:

Concern Location
Ruff CLI definition crates/ruff/src/args.rs
Ruff command dispatch crates/ruff/src/commands/
Lint pipeline crates/ruff_linter/src/linter.rs (check_path)
Rule registry crates/ruff_linter/src/codes.rs, registry.rs
Ruff config schema crates/ruff_workspace/src/{options,configuration}.rs
Formatter crates/ruff_python_formatter/, crates/ruff_formatter/
ty CLI + main loop crates/ty/src/{args.rs,lib.rs}
Type system crates/ty_python_semantic/, crates/ty_python_core/
Project/Salsa db crates/ty_project/, crates/ruff_db/
Parser / AST crates/ruff_python_parser/, crates/ruff_python_ast/