From af0b7ac59cc7788cdeb75a5a70cf5e3dee34b8cb Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 12:07:30 -0700 Subject: [PATCH 1/2] Allow Mypy Strict as a Build Directory's Only Type Checker (#2005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This applies the maintainer's decision on #1941: a build-profile Python directory may use mypy as its only CI type checker. The validator's type-check step already accepts that configuration. A follow-up answer holds whichever checker runs in CI to strict. - `spec/project-types.json`: `python.pyright.config` now says first-party code is type-checked in CI by pyright strict, by mypy, or by both, and that each CI checker runs strict (pyright strict mode, mypy's strict flags). `python.mypy.allowed` now allows mypy as a build directory's only CI checker. - `python-codestyle` skill (`SKILL.md`, `references/profiles.md`, `references/code-style.md`) and `README.md` say the same, including that mypy runs strict. The skill no longer says a pyright-only repo is the default when there is no need for mypy. It also no longer calls a mypy-only repo "inherently consistent", which is true only when the editor and CI run the same engine. - The generated skill distributions are regenerated. Mypy is named by "its strict flags" rather than `strict = true`, because that key is global and a test tree can only be relaxed flag by flag. Local strict review: three passes plus a confirming pass, with every introduced finding fixed. A pre-existing gap it found, suppression guidance that names only pyright mechanisms, is filed as #2004. Refs #1941 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/SKILL.md | 19 +++++++++++-------- .../python-codestyle/references/code-style.md | 4 ++-- .../python-codestyle/references/profiles.md | 4 ++-- .../.source-digests/python-codestyle | 2 +- .../skills/python-codestyle/SKILL.md | 19 +++++++++++-------- .../python-codestyle/references/code-style.md | 4 ++-- .../python-codestyle/references/profiles.md | 4 ++-- .github/skills/python-codestyle/SKILL.md | 19 +++++++++++-------- .../python-codestyle/references/code-style.md | 4 ++-- .../python-codestyle/references/profiles.md | 4 ++-- AUDIT.md | 2 +- README.md | 2 +- spec/project-types.json | 6 +++--- 13 files changed, 51 insertions(+), 42 deletions(-) diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index cb911a7ac..61479dec7 100644 --- a/.agents/skills/python-codestyle/SKILL.md +++ b/.agents/skills/python-codestyle/SKILL.md @@ -31,7 +31,7 @@ Read the repo's `OPERATIONS.md` local-verification commands before substituting Then read the `pyproject.toml` shape and pick the profile before running Python tooling or tests: - **build** (Project): `[project]` + `[build-system]` + committed `uv.lock`. Uses `uv run`, pytest, - pyright strict (or mypy where the repo requires it). + and pyright strict, mypy with its strict flags, or both as the CI type checker. - **lint-only** (Scripts): no `[project]`, no lockfile, no `requirements*.txt` (the hub validator runs pytest wherever one sits). Uses `uvx` for third-party tools, unittest for tests, and mypy as the CI gate. Do not run pytest or diagnose its absence as an environment @@ -56,11 +56,12 @@ declaration, versioning, VS Code config), see `references/profiles.md`. | [pytest][docs-link] | test runner (build profile only, lint-only uses `unittest`) | `pyproject.toml` `[tool.pytest.ini_options]` | **Type checking targets strongly typed, deterministic code.** pyright in strict mode is the -default baseline on first-party code (a repo may instead run mypy in CI and keep pyright -editor-only via Pylance, per the next paragraph): `[tool.pyright]` `strict = ["src"]`, or the -integration package for a Home Assistant repo, with tests run in standard mode. pyright is the -anchor because Pylance embeds it, so the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays in +default baseline on first-party code (a repo may instead run mypy with its strict flags in CI +and keep pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +`strict = ["src"]`, or the integration package for a Home Assistant repo, with tests run in +standard mode. pyright is the anchor because Pylance embeds it, so where CI runs pyright, the +editor and the CLI/CI (`uv run pyright`) run the same engine and never disagree. +The standalone `ms-pyright.pyright` extension stays in `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only when a dependency has no usable types and no alternative (e.g. `pandas`): a targeted, commented `# pyright: ignore[...]` or a scoped `[tool.pyright]` override, never a blanket relaxation. @@ -72,8 +73,10 @@ than one checker is normal when each serves a purpose (the .NET side pairs CShar `mypy --strict` because the platinum `strict-typing` quality-scale tier requires it, and a pydantic-heavy library may opt in for the plugin. When a repo uses mypy it runs in CI and the editor (the `ms-python.mypy-type-checker` extension) so the two stay consistent, and its mypy -command joins the clean-compile. A repo with no such need stays pyright-only, which is lighter and -inherently consistent. +command joins the clean-compile. mypy may also be a build repo's only CI checker, run with its +strict flags, and Pylance's pyright diagnostics are then advisory, since CI never runs them. A +pyright-only repo is the lightest and is inherently consistent, since the editor and CI run one +engine. ## Local development loop diff --git a/.agents/skills/python-codestyle/references/code-style.md b/.agents/skills/python-codestyle/references/code-style.md index 9ad17f29d..fb28bb428 100644 --- a/.agents/skills/python-codestyle/references/code-style.md +++ b/.agents/skills/python-codestyle/references/code-style.md @@ -39,8 +39,8 @@ ## Type hints - **All public APIs are typed.** The repo's configured type checker runs on `src/` (pyright strict - via `[tool.pyright]` `strict = ["src"]`, or mypy where that is the CI checker), and tests run in - the checker's looser/standard mode. + via `[tool.pyright]` `strict = ["src"]`, or mypy's strict flags where mypy is the CI checker), + and tests run in the checker's looser/standard mode. - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index 315cdf785..42619a2e4 100644 --- a/.agents/skills/python-codestyle/references/profiles.md +++ b/.agents/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy in CI with pyright editor-only (Pylance), or both. - Whichever runs in CI is the one the clean-compile and the CI gate invoke. +- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with + pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/.claude-plugin/fleet-skills/.source-digests/python-codestyle b/.claude-plugin/fleet-skills/.source-digests/python-codestyle index fb555798e..4c7f50b42 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -258f0a702efc7fb2 +f51041024f11173f diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index cb911a7ac..61479dec7 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md @@ -31,7 +31,7 @@ Read the repo's `OPERATIONS.md` local-verification commands before substituting Then read the `pyproject.toml` shape and pick the profile before running Python tooling or tests: - **build** (Project): `[project]` + `[build-system]` + committed `uv.lock`. Uses `uv run`, pytest, - pyright strict (or mypy where the repo requires it). + and pyright strict, mypy with its strict flags, or both as the CI type checker. - **lint-only** (Scripts): no `[project]`, no lockfile, no `requirements*.txt` (the hub validator runs pytest wherever one sits). Uses `uvx` for third-party tools, unittest for tests, and mypy as the CI gate. Do not run pytest or diagnose its absence as an environment @@ -56,11 +56,12 @@ declaration, versioning, VS Code config), see `references/profiles.md`. | [pytest][docs-link] | test runner (build profile only, lint-only uses `unittest`) | `pyproject.toml` `[tool.pytest.ini_options]` | **Type checking targets strongly typed, deterministic code.** pyright in strict mode is the -default baseline on first-party code (a repo may instead run mypy in CI and keep pyright -editor-only via Pylance, per the next paragraph): `[tool.pyright]` `strict = ["src"]`, or the -integration package for a Home Assistant repo, with tests run in standard mode. pyright is the -anchor because Pylance embeds it, so the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays in +default baseline on first-party code (a repo may instead run mypy with its strict flags in CI +and keep pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +`strict = ["src"]`, or the integration package for a Home Assistant repo, with tests run in +standard mode. pyright is the anchor because Pylance embeds it, so where CI runs pyright, the +editor and the CLI/CI (`uv run pyright`) run the same engine and never disagree. +The standalone `ms-pyright.pyright` extension stays in `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only when a dependency has no usable types and no alternative (e.g. `pandas`): a targeted, commented `# pyright: ignore[...]` or a scoped `[tool.pyright]` override, never a blanket relaxation. @@ -72,8 +73,10 @@ than one checker is normal when each serves a purpose (the .NET side pairs CShar `mypy --strict` because the platinum `strict-typing` quality-scale tier requires it, and a pydantic-heavy library may opt in for the plugin. When a repo uses mypy it runs in CI and the editor (the `ms-python.mypy-type-checker` extension) so the two stay consistent, and its mypy -command joins the clean-compile. A repo with no such need stays pyright-only, which is lighter and -inherently consistent. +command joins the clean-compile. mypy may also be a build repo's only CI checker, run with its +strict flags, and Pylance's pyright diagnostics are then advisory, since CI never runs them. A +pyright-only repo is the lightest and is inherently consistent, since the editor and CI run one +engine. ## Local development loop diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md index 9ad17f29d..fb28bb428 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md @@ -39,8 +39,8 @@ ## Type hints - **All public APIs are typed.** The repo's configured type checker runs on `src/` (pyright strict - via `[tool.pyright]` `strict = ["src"]`, or mypy where that is the CI checker), and tests run in - the checker's looser/standard mode. + via `[tool.pyright]` `strict = ["src"]`, or mypy's strict flags where mypy is the CI checker), + and tests run in the checker's looser/standard mode. - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md index 315cdf785..42619a2e4 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy in CI with pyright editor-only (Pylance), or both. - Whichever runs in CI is the one the clean-compile and the CI gate invoke. +- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with + pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/.github/skills/python-codestyle/SKILL.md b/.github/skills/python-codestyle/SKILL.md index cb911a7ac..61479dec7 100644 --- a/.github/skills/python-codestyle/SKILL.md +++ b/.github/skills/python-codestyle/SKILL.md @@ -31,7 +31,7 @@ Read the repo's `OPERATIONS.md` local-verification commands before substituting Then read the `pyproject.toml` shape and pick the profile before running Python tooling or tests: - **build** (Project): `[project]` + `[build-system]` + committed `uv.lock`. Uses `uv run`, pytest, - pyright strict (or mypy where the repo requires it). + and pyright strict, mypy with its strict flags, or both as the CI type checker. - **lint-only** (Scripts): no `[project]`, no lockfile, no `requirements*.txt` (the hub validator runs pytest wherever one sits). Uses `uvx` for third-party tools, unittest for tests, and mypy as the CI gate. Do not run pytest or diagnose its absence as an environment @@ -56,11 +56,12 @@ declaration, versioning, VS Code config), see `references/profiles.md`. | [pytest][docs-link] | test runner (build profile only, lint-only uses `unittest`) | `pyproject.toml` `[tool.pytest.ini_options]` | **Type checking targets strongly typed, deterministic code.** pyright in strict mode is the -default baseline on first-party code (a repo may instead run mypy in CI and keep pyright -editor-only via Pylance, per the next paragraph): `[tool.pyright]` `strict = ["src"]`, or the -integration package for a Home Assistant repo, with tests run in standard mode. pyright is the -anchor because Pylance embeds it, so the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays in +default baseline on first-party code (a repo may instead run mypy with its strict flags in CI +and keep pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +`strict = ["src"]`, or the integration package for a Home Assistant repo, with tests run in +standard mode. pyright is the anchor because Pylance embeds it, so where CI runs pyright, the +editor and the CLI/CI (`uv run pyright`) run the same engine and never disagree. +The standalone `ms-pyright.pyright` extension stays in `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only when a dependency has no usable types and no alternative (e.g. `pandas`): a targeted, commented `# pyright: ignore[...]` or a scoped `[tool.pyright]` override, never a blanket relaxation. @@ -72,8 +73,10 @@ than one checker is normal when each serves a purpose (the .NET side pairs CShar `mypy --strict` because the platinum `strict-typing` quality-scale tier requires it, and a pydantic-heavy library may opt in for the plugin. When a repo uses mypy it runs in CI and the editor (the `ms-python.mypy-type-checker` extension) so the two stay consistent, and its mypy -command joins the clean-compile. A repo with no such need stays pyright-only, which is lighter and -inherently consistent. +command joins the clean-compile. mypy may also be a build repo's only CI checker, run with its +strict flags, and Pylance's pyright diagnostics are then advisory, since CI never runs them. A +pyright-only repo is the lightest and is inherently consistent, since the editor and CI run one +engine. ## Local development loop diff --git a/.github/skills/python-codestyle/references/code-style.md b/.github/skills/python-codestyle/references/code-style.md index 9ad17f29d..fb28bb428 100644 --- a/.github/skills/python-codestyle/references/code-style.md +++ b/.github/skills/python-codestyle/references/code-style.md @@ -39,8 +39,8 @@ ## Type hints - **All public APIs are typed.** The repo's configured type checker runs on `src/` (pyright strict - via `[tool.pyright]` `strict = ["src"]`, or mypy where that is the CI checker), and tests run in - the checker's looser/standard mode. + via `[tool.pyright]` `strict = ["src"]`, or mypy's strict flags where mypy is the CI checker), + and tests run in the checker's looser/standard mode. - **Use modern syntax**: `list[int]` not `List[int]`, `dict[str, X]` not `Dict[str, X]`, `X | None` not `Optional[X]`, `from __future__ import annotations` only when needed for forward references. diff --git a/.github/skills/python-codestyle/references/profiles.md b/.github/skills/python-codestyle/references/profiles.md index 315cdf785..42619a2e4 100644 --- a/.github/skills/python-codestyle/references/profiles.md +++ b/.github/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy in CI with pyright editor-only (Pylance), or both. - Whichever runs in CI is the one the clean-compile and the CI gate invoke. +- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with + pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/AUDIT.md b/AUDIT.md index 774da59c6..d41f5912a 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -79,7 +79,7 @@ A check with `intentRef`/`workflowRef` points at the prose section that owns the - **csharp** - `.editorconfig` carries the shared `[*.cs]` rule block (letter), and analyzer severities are enforced, not relaxed (intent). - **nuget** - `nuget.publish.oidc` (intent): publish uses OIDC Trusted Publishing with no stored `NUGET_API_KEY` secret, from a job in the publishing repository's own publisher, never inside a build leaf and never in a reusable workflow a different repository hosts, for the reason [`WORKFLOW.md`][workflow] section 3's `Output Seam by Destination` gives for both package registries. `nuget.publish.skipduplicate` (letter): the push carries `--skip-duplicate` and is gated on the publish decision, not on an existence check. `nuget.publish.job` (letter): that publish job declares `id-token: write` and `actions: write`, and consume-then-deletes `nuget-build-`. - **pypi** - `pypi.publish.oidc` (intent): OIDC publish with no stored token, from a job in the publishing repository's own publisher under the same seam the **nuget** check names. `pypi.publish.environment` (letter): that job declares `environment: pypi` and `id-token: write`, with `skip-existing: true`. -- **python** - ruff and pyright present (intent), canonical in `pyproject.toml` (letter), and a standalone `.ruff.toml` / `pyrightconfig.json` is a drift finding. +- **python** - ruff and a CI type checker present (intent), strict in a build-profile directory (pyright, mypy, or both), canonical in `pyproject.toml` (letter), and a standalone `.ruff.toml` / `pyrightconfig.json` / `mypy.ini` / `.mypy.ini`, or a `setup.cfg` `[mypy]` section, is a drift finding. - **dotnet-publish** - `dotnet-publish.smoke.subset` (letter): the smoke runtime matrix is a strict subset of the full set. `dotnet-publish.release.asset` (letter): the per-runtime outputs aggregate to one `release-asset-*`, gated `!smoke`. - **docker** - registry layer cache (`buildcache-`, never `type=gha`), the size-limited Docker Hub README is published via the docker-readme task, and the image always re-pushes on publish. - **hugo** - the build fails on a generator warning, the URL-parity gate asserts a length floor before comparing, the rendered output is untracked, the generator is pinned by version and checksum and declared once, a vendored tree records its upstream ref, and the deploy asserts what the host serves (the release id and the environment). Retention is bounded by a declared count with one side recorded as owning the prune, which is the deploy where its credential can observe the destination and the host where that credential is confined write-only, so grade which shape the repo uses rather than looking for a prune step. Deploy credentials are per-environment, which `spec/secrets.json` cannot express, so a clean **repo-setup** verdict says nothing about whether the environments are configured. diff --git a/README.md b/README.md index 1564edb8f..4abb3bc2e 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ A human-readable index of the rules agents enforce, implement, and audit. The au ### If a Python Project -- Configure ruff and a type checker in `pyproject.toml`, either pyright strict or mypy in CI with pyright editor-only. Whichever runs in CI is the gate. +- Configure ruff and a type checker in `pyproject.toml`: pyright strict, mypy with its strict flags in CI and the editor with pyright editor-only, or both. Every checker CI runs is a gate. ### If Both C# and Python diff --git a/spec/project-types.json b/spec/project-types.json index 11c06702c..4f0884d57 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -39,9 +39,9 @@ "checks": [ { "id": "python.profile.detect", "verdict": "letter", "assert": "The declared profile corresponds to the pyproject.toml shape. A [project] table with runtime dependencies (or a [build-system]) is the build profile (the PROJECT shape). A pyproject.toml beside a requirements*.txt is the build profile too, installed with pip, whether or not it carries a [project] table. A pyproject carrying only [tool.*] config with no [project]/[build-system], no uv.lock, and no requirements*.txt is the lint-only profile (the SCRIPTS shape). A lint-only subtree must not carry a uv.lock or project/build metadata, which would misrepresent it as a shippable package, and a build one must carry one of those or a requirements*.txt. A lint-only subtree carries no requirements*.txt either. A virtual uv workspace root, whose root pyproject.toml carries [tool.uv.workspace] with no [project] or [build-system] and commits a uv.lock, is the build shape, its [project] tables living in the workspace members under subtrees.", "intentRef": "CODESTYLE.md" }, { "id": "python.ruff.config", "verdict": "intent", "assert": "A ruff configuration is present (pyproject.toml [tool.ruff]). Both profiles.", "intentRef": "CODESTYLE.md" }, - { "id": "python.pyright.config", "verdict": "intent", "assert": "Build profile: pyright is configured and runs strict on first-party code (src or the integration package) - the strong typing baseline. Third-party strictness is relaxed only where a dependency has no usable types. N/A for the lint-only profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md", "minProfile": "build" }, - { "id": "python.config.placement", "verdict": "letter", "assert": "ruff and the type-checker config live in pyproject.toml (canonical); standalone .ruff.toml / pyrightconfig.json is a drift finding. A Home Assistant integration is the exception - it follows home-assistant/core standalone-config conventions and is scored by ha.python.conventions instead.", "intentRef": "CODESTYLE.md" }, - { "id": "python.mypy.allowed", "verdict": "intent", "assert": "mypy is permitted as an additional type checker, not banned. It is required for a Home Assistant integration (platinum strict-typing) and is the lint-only profile's type checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" }, + { "id": "python.pyright.config", "verdict": "intent", "assert": "Build profile: first-party code (src or the integration package) is type-checked in CI by pyright strict, by mypy, or by both. Each CI checker runs strict (pyright strict mode, mypy's strict flags), the strong typing baseline. Third-party strictness is relaxed only where a dependency has no usable types. N/A for the lint-only profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md", "minProfile": "build" }, + { "id": "python.config.placement", "verdict": "letter", "assert": "ruff and the type-checker config live in pyproject.toml (canonical); standalone .ruff.toml / pyrightconfig.json / mypy.ini / .mypy.ini, or a setup.cfg [mypy] section, is a drift finding. A Home Assistant integration is the exception - it follows home-assistant/core standalone-config conventions and is scored by ha.python.conventions instead.", "intentRef": "CODESTYLE.md" }, + { "id": "python.mypy.allowed", "verdict": "intent", "assert": "mypy is permitted as an additional type checker or as the only one, not banned. It is required for a Home Assistant integration (platinum strict-typing), is the lint-only profile's type checker, and may be a build directory's only CI checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" }, { "id": "python.coverage.codecov", "verdict": "letter", "assert": "Every Python directory with tests runs them under coverage and uploads the report to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). A directory with a uv.lock or a requirements*.txt runs pytest --cov-report=xml, and because --cov-report=xml alone measures nothing and writes no file, it declares pytest-cov among its test dependencies, a dev dependency group in a uv project and a requirements*.txt entry on pip, and selects the coverage source in its own pyproject.toml, an addopts --cov= entry in practice. A lint-only directory runs coverage run -m unittest discover -s tests, then coverage xml. The validator's Python leg runs in each directory the caller declares in python-directories, or at the root where it declares none and a root pyproject.toml is tracked. It deletes the directory's coverage.xml before the run and fails the test step when the run did not write it, and it fails a declared directory that has no tests/. CODECOV_TOKEN is stored in both the repo actions and dependabot secrets stores, the second so the upload does not skip on a Dependabot PR, and the caller maps it to the reusable validator by name. N/A for a repo carrying no tests for this type, which the validator permits only at an undeclared root. In a mixed repo the codecov.yml file-presence is still required by any co-present type that has tests, e.g. csharp.", "intentRef": "WORKFLOW.md" }, { "id": "python.directories.declared", "verdict": "letter", "assert": "A repository carrying a tracked .py file other than the catalog's carried hub-fetch-run.py declares the python type, or suppresses that discovery advisory with a driftNote naming (python.directories.declared) and its reason, per spec/type-model.md. Every such file sits in a directory the registry's pythonDirectories names, or anywhere under the root where it names none and a root pyproject.toml is tracked. Every caller of the hub's validate-task.yml passes exactly those directories in its python-directories input, as a literal (|) block or a single value, so the audit can see a missing declaration the validator itself only warns about. A root tests/ suite with no root uv.lock or requirements*.txt is never run by the undeclared root default, so a lint-only root declares '.' and a root declaring [project] adds a manifest. spec/audit.py checks each of these mechanically.", "intentRef": "WORKFLOW.md" }, { "id": "python.uvlock.pinned", "verdict": "letter", "assert": "Build profile: the committed uv.lock resolves to LF through the repository-wide .editorconfig and .gitattributes defaults. A CRLF-native operational repo adds a narrow uv.lock LF override only if it adopts the uv build profile. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the lint-only profile (no uv.lock by definition).", "intentRef": "GOVERNANCE.md#line-endings", "minProfile": "build" }, From a5f39494ff0f85c5fe3526c8b77e075a359fc02a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 12:18:16 -0700 Subject: [PATCH 2/2] Group the Mypy Option's Clauses So It Reads One Way (#2007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Review of promotion #2006 found that "pyright strict, mypy with its strict flags in CI and the editor with pyright editor-only, or both" parses as two separate alternatives. This change puts where mypy runs, and what pyright does beside it, in a parenthetical attached to the mypy option. That leaves exactly three alternatives, in `README.md` and in `python-codestyle/references/profiles.md`. A local strict review pass found nothing. Refs #1941 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/references/profiles.md | 4 ++-- .claude-plugin/fleet-skills/.source-digests/python-codestyle | 2 +- .../skills/python-codestyle/references/profiles.md | 4 ++-- .github/skills/python-codestyle/references/profiles.md | 4 ++-- README.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index 42619a2e4..4fd1c705a 100644 --- a/.agents/skills/python-codestyle/references/profiles.md +++ b/.agents/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with - pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. +- **Type checker in CI**: pyright strict, mypy with its strict flags (run in CI and the editor, with + pyright kept editor-only through Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/.claude-plugin/fleet-skills/.source-digests/python-codestyle b/.claude-plugin/fleet-skills/.source-digests/python-codestyle index 4c7f50b42..ee3756903 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -f51041024f11173f +8e5d146c2db9a49f diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md index 42619a2e4..4fd1c705a 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with - pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. +- **Type checker in CI**: pyright strict, mypy with its strict flags (run in CI and the editor, with + pyright kept editor-only through Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/.github/skills/python-codestyle/references/profiles.md b/.github/skills/python-codestyle/references/profiles.md index 42619a2e4..4fd1c705a 100644 --- a/.github/skills/python-codestyle/references/profiles.md +++ b/.github/skills/python-codestyle/references/profiles.md @@ -8,8 +8,8 @@ often differs, and when it does, adapt these fields to match the repo's actual t than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected in review). The axes that commonly vary per repo: -- **Type checker in CI**: pyright strict, mypy with its strict flags in CI and the editor with - pyright editor-only (Pylance), or both. The clean-compile runs every checker CI runs. +- **Type checker in CI**: pyright strict, mypy with its strict flags (run in CI and the editor, with + pyright kept editor-only through Pylance), or both. The clean-compile runs every checker CI runs. - **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` (dev tools installed with `uv sync --extra `). - **Versioning / publishing**: a published package (`_version.py` plus a version source, diff --git a/README.md b/README.md index 4abb3bc2e..aeba6deb0 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ A human-readable index of the rules agents enforce, implement, and audit. The au ### If a Python Project -- Configure ruff and a type checker in `pyproject.toml`: pyright strict, mypy with its strict flags in CI and the editor with pyright editor-only, or both. Every checker CI runs is a gate. +- Configure ruff and a type checker in `pyproject.toml`: pyright strict, mypy with its strict flags (run in CI and the editor, with pyright kept editor-only), or both. Every checker CI runs is a gate. ### If Both C# and Python