Skip to content
GitHub
Developer

Type Checking

Spectral’s primary Python type checker is ty (Astral) running in strict-max mode (rules.all = "error"). Ruff’s ANN rule family backfills the missing-annotation coverage; mypy stays as an informational warning step in the pre-push runner during the transition window. Decision lineage in ADR-051.


  • Astral toolchain alignment. Same vendor as uv and ruff; one Python tool family, one upgrade cadence.
  • Speed. Rust-implemented; faster than mypy on the same source tree.
  • Strict-max coverage. rules.all = "error" matches the discipline the dev-tooling stack wanted from mypy.

pyproject.toml:

[tool.ty.environment]
python-version = "3.14"
[tool.ty.src]
include = [
"src",
"apps/api/src",
"apps/workers/src",
]
[tool.ty.rules]
all = "error"

Source roots cover the library + the deployable apps. A passing uv run ty check returns zero diagnostics — anything more than zero is a CI failure.


Pre-push runs uv run ty check as the canonical type-check gate. Pre-commit (tier 1) stays fast (< 3 s) per ADR-012 — full ty runs at pre-push (tier 2) and in CI; tier 1 catches the missing-annotation family via ruff ANN.

CI (.github/workflows/ci.yml) invokes uv run ty check in the architecture job.


Backfills the missing-annotation coverage that mypy --strict provided.

  • All ANN rules enabled
  • ANN401 (no Any in public function signatures) is enforced — catches the worst missing-annotation class

Runs in ruff check at every tier (pre-commit, pre-push, CI).


tools/dev/precheck.sh runs mypy as a non-blocking step that surfaces diagnostic classes ty does not yet cover. Mypy results print as warnings; they do not fail the precheck gate. Pre-commit and CI gates do not invoke mypy.

Removal trigger: when ty’s diagnostic coverage matches mypy’s for our codebase (verified by running both gates on a representative branch and observing parity), the precheck.sh mypy step is removed and the dev dependency is dropped.


tests/_tooling/test_ty_tripwires.py plus fixtures in tests/_tooling/fixtures/ guard against ty misconfiguration:

  • ty_sanity.py — minimal annotated module that ty must accept
  • pydantic_ty_sanity.py — pydantic-specific cases verifying dataclass_transform handling (unknown kwargs + wrong field types are caught)

If a future ty upgrade silently regresses rules.all = "error" or breaks pydantic field-typing detection, the tripwires fail loudly.


CommandPurpose
uv run ty checkFull type check (matches CI)
uv run ruff checkIncludes ANN family backfill
bash tools/dev/precheck.shPre-push gate (ruff + ty + arch + migrations + tests; mypy as informational warning)

  • ADR-051 — decision lineage
  • ADR-012 — tiered hook tier classification (the mypy portion is superseded by ADR-051; biome, git-cliff, and the tier model still apply)
  • Testing — test substrate