# Ruff Admin & Architecture Guide

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](#executive-summary)
- [Repository Architecture](#repository-architecture)
  - [Two products, one workspace](#two-products-one-workspace)
  - [Architectural layers](#architectural-layers)
  - [Crate map](#crate-map)
- [How Ruff lints: the `check_path` pipeline](#how-ruff-lints-the-check_path-pipeline)
- [How Ruff formats](#how-ruff-formats)
- [How ty type-checks: the Salsa engine](#how-ty-type-checks-the-salsa-engine)
- [Configuration system](#configuration-system)
- [Concurrency and performance](#concurrency-and-performance)
- [Caching](#caching)
- [Language servers](#language-servers)
- [Build, requirements, and toolchain](#build-requirements-and-toolchain)
- [Operations](#operations)
- [Security analysis](#security-analysis)
- [Testing strategy](#testing-strategy)
- [Code quality & conventions](#code-quality--conventions)
- [Extending the codebase](#extending-the-codebase)
- [Potential improvements](#potential-improvements)
- [Quick Reference](#quick-reference)

---

## 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](#quick-reference)); 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):**

```bash
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:

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

**Diagnose configuration problems:**

```bash
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:**

```bash
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:**

```bash
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/` |

---

_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. Unofficial; not affiliated with or reviewed by the project._
