diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7f6149b..ea4b97b 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -42,6 +42,15 @@ jobs: - name: Ruff format run: ruff format --check src tests + - name: Mypy + run: | + set -o pipefail + mypy 2>&1 | tee mypy.log + if grep -q 'unused section' mypy.log; then + echo "::error::A mypy exemption in pyproject.toml matches nothing. Remove it." + exit 1 + fi + - name: Pytest run: >- python -m pytest -q diff --git a/.gitignore b/.gitignore index f95354e..dafcdd1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build/ *.egg-info/ .env .env.local +.mypy_cache/ +mypy.log diff --git a/AGENTS.md b/AGENTS.md index d095ca7..93b2468 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,6 +40,8 @@ python3 -m venv --clear .venv # --clear: a uv-made .venv has no pip python -m pip install -r requirements-dev.txt python -m pip install -e . ruff check src tests +ruff format --check src tests +mypy pytest -q -m 'not live' --cov=eip_search_v3 --cov-fail-under=90 ! git grep --untracked -n -I -P '[\x{2013}\x{2014}]' -- . # CI rejects en/em dashes ``` diff --git a/pyproject.toml b/pyproject.toml index 16ced71..52eb720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,38 @@ eip-search = "eip_search_v3.cli:entrypoint" [tool.setuptools.packages.find] where = ["src"] +[tool.mypy] +python_version = "3.12" +files = ["src"] +ignore_missing_imports = true +# Report an override that matches nothing, so an exemption cannot outlive the +# errors it was added for. mypy prints that as a note and still exits 0, so the +# CI step greps for it and fails. +warn_unused_configs = true + +# Type checking is a ratchet, not an allowlist. Everything is checked by default +# and the exemptions are listed, never the inverse. Listing what IS checked would +# leave every new module unchecked by default, so debt would accumulate silently +# and the list would be the same size in a year. This way new code is checked +# without anyone deciding to check it, and the list below can only shrink. +# +# 7 of 14 modules are exempt, holding 22 errors: cli, client, config, render.common, render.exploit, render.labs, render.vulnerability. +# Most are one shape: a corpus lookup that may return None, then read with .get +# in the renderers. Fixing that properly means giving the lookup a precise return +# type, which is worth doing deliberately rather than inside a tooling change. +# Removing a module from this list plus fixing its errors is the unit of progress. +[[tool.mypy.overrides]] +module = [ + "eip_search_v3.cli", + "eip_search_v3.client", + "eip_search_v3.config", + "eip_search_v3.render.common", + "eip_search_v3.render.exploit", + "eip_search_v3.render.labs", + "eip_search_v3.render.vulnerability", +] +ignore_errors = true + [tool.ruff] # Pin the formatter to the version CI installs. `ruff check` findings are stable # across releases; `ruff format` output is not, and AGENTS.md tells a maintainer diff --git a/requirements-dev.txt b/requirements-dev.txt index 45887f1..8ac1883 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ twine>=6,<7 pytest==8.4.2 pytest-cov>=5 ruff==0.16.3 +mypy==2.3.1