diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f70ca7a..c538e05 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -22,9 +22,15 @@ COPY --from=ghcr.io/astral-sh/uv:0.11.21 /uv /uvx /usr/local/bin/ # Renovate's regex manager tracks RUFF_VERSION via the comment above the ARG. # renovate: datasource=pypi depName=ruff ARG RUFF_VERSION=0.15.17 +# ty: static type checker. Same `uv tool install` pattern as ruff so the +# binary lands on PATH for CI, pre-commit, and the `astral-sh.ty` editor +# extension. UV_TOOL_{BIN_DIR,DIR} are shared across both installs. +# renovate: datasource=pypi depName=ty +ARG TY_VERSION=0.0.1a25 ENV UV_TOOL_BIN_DIR=/usr/local/bin \ UV_TOOL_DIR=/usr/local/share/uv-tools -RUN uv tool install --no-cache "ruff==${RUFF_VERSION}" +RUN uv tool install --no-cache "ruff==${RUFF_VERSION}" \ + && uv tool install --no-cache "ty==${TY_VERSION}" RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 777264f..e0f8610 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -54,6 +54,7 @@ "customizations": { "vscode": { "extensions": [ + "astral-sh.ty", "bazelbuild.vscode-bazel", "charliermarsh.ruff", "cnshenj.vscode-task-manager", diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 40acce0..780ff71 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -48,6 +48,13 @@ fi # Full path: postCreate may not see remoteEnv's PATH yet. "$HOME/.local/bin/pre-commit" install +# Materialize the uv workspace's .venv so the ty editor extension (and any +# CLI `ty check` run) can resolve third-party imports. Without it, ty has no +# search path beyond first-party + stdlib and every non-stdlib import is +# flagged. Idempotent; subsequent rebuilds are no-ops if the lockfile is +# unchanged. +uv sync + # Warm Bazel: fetches the registered Go SDK, rules_go, gazelle, etc. bazel version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3868937..48a02c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,6 +118,28 @@ jobs: args: format --check - run: ruff check + # ty: Astral's static type checker. Config lives in `[tool.ty]` in + # //:pyproject.toml — same single-source-of-truth posture as ruff. No + # dedicated GitHub Action exists yet (ty is still alpha), so we install uv + # and run `uvx ty@ check`. The pin is Renovate-tracked via the comment + # above TY_VERSION. + ty: + name: ty + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v6 + # `uv sync` materializes `.venv` from uv.lock so ty can resolve + # third-party imports (e.g. the smoke target's `requests`). Without it, + # ty's only search paths are first-party + stdlib and any non-stdlib + # import fails with `unresolved-import`. + - run: uv sync + - name: ty check + env: + # renovate: datasource=pypi depName=ty + TY_VERSION: "0.0.1a25" + run: uvx "ty@${TY_VERSION}" check + # Build and test runs once per supported target platform, on a runner whose host matches # the target. (Running tests natively per platform is the only way (without an emulation # layer we do not have) to actually exercise platform-specific code paths and catch regressions @@ -137,6 +159,7 @@ jobs: no-cgo-check, golangci-lint, ruff, + ty, ] strategy: fail-fast: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e6640d7..3a03be8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "astral-sh.ty", "bazelbuild.vscode-bazel", "charliermarsh.ruff", "esbenp.prettier-vscode", diff --git a/README.md b/README.md index 03be6a1..8dfab9a 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Two GitHub Actions workflows run on every push and pull request to `main`. | No-cgo policy check | Always - rejects `import "C"` and transitive deps that compile C/C++/cgo/SWIG | | golangci-lint | After module check passes - runs per Go module | | ruff | Always - `ruff format --check` and `ruff check` over all Python | +| ty | Always - `uvx ty check` (Astral's static type checker) over all Python | | Build and test | After all checks above pass | | Coverage | After build and test - `bazel coverage //...`, uploads merged lcov to Codecov | @@ -205,6 +206,9 @@ VS Code-derived editors (e.g. Google Antigravity). Recommended extensions - [`charliermarsh.ruff`](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) - surfaces `ruff check` diagnostics inline and applies `ruff format` on save, matching what the CI `ruff` job and the pre-commit hooks enforce. +- [`astral-sh.ty`](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty) - surfaces + `ty check` diagnostics inline, matching what the CI `ty` job enforces. Config lives in + `[tool.ty]` in `//:pyproject.toml`. - [`emeraldwalk.runonsave`](https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave) - triggers the repo-health scripts on save. - [`ryanluker.vscode-coverage-gutters`](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) - @@ -214,6 +218,7 @@ VS Code-derived editors (e.g. Google Antigravity). Recommended extensions | -------------------- | ------------------------------------------ | | `golangci-lint` | `*.go` files | | `ruff` (diagnostics + format) | `*.py` files | +| `ty` (type diagnostics) | `*.py` files | | `check-go-modules` | `go.mod`, workflow `.yml`, `.golangci.yml` | | `check-go-work` | `go.mod`, `go.work` | diff --git a/pyproject.toml b/pyproject.toml index fa8d9d5..4728248 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,3 +65,24 @@ select = ["E", "F", "I", "B", "UP", "SIM", "RUF", "S"] # like "/tmp/repo" used as mock arguments (no actual temp file is created). "**/test_*.py" = ["S101", "S105", "S106", "S108", "S311"] "**/*_test.py" = ["S101", "S105", "S106", "S108", "S311"] + +# ty: static type checker. Single repo-wide config. CI runs `uvx ty check`; +# the editor extension (`astral-sh.ty` in devcontainer.json) reads the same +# section. Strict mode — promote inference warnings to errors so missing +# annotations don't silently pass. +[tool.ty] + +[tool.ty.environment] +python-version = "3.14" + +[tool.ty.src] +# Mirror ruff's `src` so ty scans the same first-party trees. Excludes cover +# Bazel's symlink farm, virtualenvs, and the host-state directory populated by +# .devcontainer/initialize.sh (none of which are first-party source). +include = ["meta", "tools"] +exclude = ["bazel-*", ".venv", "venv", ".git-plumbing"] + +# Rule severities are left at ty's defaults for now. ty ships with a +# strict-leaning default profile and refining the rule map preemptively (before +# real Python code lands) would be speculative; tighten as needed when real +# code surfaces a category of finding we want to escalate. diff --git a/renovate.json b/renovate.json index 888f10e..f982c15 100644 --- a/renovate.json +++ b/renovate.json @@ -47,7 +47,7 @@ "customType": "regex", "managerFilePatterns": [ "/^\\.github/workflows/.*\\.ya?ml$/" ], "matchStrings": [ - "#\\s*renovate:\\s*datasource=(?[a-z-]+?)\\s+depName=(?\\S+?)\\s*\\n\\s*[a-z-]*version:\\s*\"(?[^\"]+)\"" + "#\\s*renovate:\\s*datasource=(?[a-z-]+?)\\s+depName=(?\\S+?)\\s*\\n\\s*[A-Za-z_-]*[Vv]ersion:\\s*\"(?[^\"]+)\"" ] } ],