From d65d3a7faa8a5d08641bbd91f480b5f233fdd971 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 10:08:28 -0700 Subject: [PATCH 1/9] Allow Mypy as a Build Directory's Only Type Checker (#1941) The validator's type-check step accepts a declared directory whose config names mypy or pyright, while python.pyright.config scored a mypy-only build directory as a finding. Per the maintainer's decision on #1941, relax python.pyright.config and python.mypy.allowed to match the gate: a build directory is type-checked in CI by pyright strict, by mypy, or by both, and pyright runs strict wherever it is a CI checker. Co-Authored-By: Claude Opus 5.5 (1M context) --- spec/project-types.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/project-types.json b/spec/project-types.json index 11c06702..ec11ef74 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.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. Where pyright is a CI checker it runs strict, 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.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 1bb9704122afe553f2895600289dc34ee69a6537 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 10:22:09 -0700 Subject: [PATCH 2/9] Hold a Mypy-Only Build Directory to Mypy Strict The first commit dropped the build profile's strictness bar for a mypy-only directory, so an empty [tool.mypy] passed the audit while pyright in standard mode failed it. Per the maintainer's answer, each CI checker runs strict: pyright strict mode or mypy strict = true. The python-codestyle skill still made mypy conditional on a need and kept a repo with none pyright-only. Reword it to allow mypy strict as a build repo's only CI checker, matching #1941's decision. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/SKILL.md | 7 ++++--- .agents/skills/python-codestyle/references/profiles.md | 4 ++-- .../fleet-skills/.source-digests/python-codestyle | 2 +- .../fleet-skills/skills/python-codestyle/SKILL.md | 7 ++++--- .../skills/python-codestyle/references/profiles.md | 4 ++-- .github/skills/python-codestyle/SKILL.md | 7 ++++--- .github/skills/python-codestyle/references/profiles.md | 4 ++-- spec/project-types.json | 2 +- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index cb911a7a..de65f7f6 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). + pyright strict, mypy strict, or both. - **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 @@ -72,8 +72,9 @@ 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 strict +(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, +which is lighter and inherently consistent. ## Local development loop diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index 315cdf78..cf2abfd2 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 strict in CI with pyright editor-only (Pylance), or + both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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 fb555798..f27b4075 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -258f0a702efc7fb2 +1c4d3ecffaab7165 diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index cb911a7a..de65f7f6 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). + pyright strict, mypy strict, or both. - **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 @@ -72,8 +72,9 @@ 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 strict +(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, +which is lighter and inherently consistent. ## Local development loop 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 315cdf78..cf2abfd2 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 strict in CI with pyright editor-only (Pylance), or + both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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 cb911a7a..de65f7f6 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). + pyright strict, mypy strict, or both. - **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 @@ -72,8 +72,9 @@ 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 strict +(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, +which is lighter and inherently consistent. ## Local development loop diff --git a/.github/skills/python-codestyle/references/profiles.md b/.github/skills/python-codestyle/references/profiles.md index 315cdf78..cf2abfd2 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 strict in CI with pyright editor-only (Pylance), or + both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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/spec/project-types.json b/spec/project-types.json index ec11ef74..9d00870b 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -39,7 +39,7 @@ "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: first-party code (src or the integration package) is type-checked in CI by pyright strict, by mypy, or by both. Where pyright is a CI checker it runs strict, 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.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 strict = true), 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 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" }, From 6806f81b1cc9d5e24d3abcf5eaaa88e1f3e9ea2d Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 10:24:36 -0700 Subject: [PATCH 3/9] State Mypy Strict on Every Surface Naming the CI Checker README.md, the python-codestyle skill's baseline paragraph, and code-style.md still offered mypy in CI with no strictness, so a reader following them configured what python.pyright.config now scores as drift. Name mypy's strict flags there rather than strict = true, since that key is global and a test tree is relaxed flag by flag. The skill also called a mypy-only repo inherently consistent, which holds only for pyright-only, where the editor and CI run one engine. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/SKILL.md | 14 ++++++++------ .../python-codestyle/references/code-style.md | 4 ++-- .../fleet-skills/.source-digests/python-codestyle | 2 +- .../fleet-skills/skills/python-codestyle/SKILL.md | 14 ++++++++------ .../python-codestyle/references/code-style.md | 4 ++-- .github/skills/python-codestyle/SKILL.md | 14 ++++++++------ .../python-codestyle/references/code-style.md | 4 ++-- README.md | 2 +- spec/project-types.json | 2 +- 9 files changed, 33 insertions(+), 27 deletions(-) diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index de65f7f6..7de562d8 100644 --- a/.agents/skills/python-codestyle/SKILL.md +++ b/.agents/skills/python-codestyle/SKILL.md @@ -56,9 +56,10 @@ 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 +default baseline on first-party code (a repo may instead run mypy strict 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 `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only @@ -72,9 +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. mypy may also be a build repo's only CI checker, run strict -(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, -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 9ad17f29..fb28bb42 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/.claude-plugin/fleet-skills/.source-digests/python-codestyle b/.claude-plugin/fleet-skills/.source-digests/python-codestyle index f27b4075..9a11139a 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -1c4d3ecffaab7165 +954cc486f7144a6a diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index de65f7f6..7de562d8 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md @@ -56,9 +56,10 @@ 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 +default baseline on first-party code (a repo may instead run mypy strict 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 `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only @@ -72,9 +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. mypy may also be a build repo's only CI checker, run strict -(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, -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 9ad17f29..fb28bb42 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/.github/skills/python-codestyle/SKILL.md b/.github/skills/python-codestyle/SKILL.md index de65f7f6..7de562d8 100644 --- a/.github/skills/python-codestyle/SKILL.md +++ b/.github/skills/python-codestyle/SKILL.md @@ -56,9 +56,10 @@ 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 +default baseline on first-party code (a repo may instead run mypy strict 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 `unwantedRecommendations` because Pylance covers it. Relax strictness on third-party code only @@ -72,9 +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. mypy may also be a build repo's only CI checker, run strict -(`strict = true`) with pyright kept editor-only, and a repo with no need for both runs one checker, -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 9ad17f29..fb28bb42 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/README.md b/README.md index 1564edb8..56de28ab 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 strict in CI with pyright editor-only, or both. Whichever runs in CI is the gate. ### If Both C# and Python diff --git a/spec/project-types.json b/spec/project-types.json index 9d00870b..f0f10fde 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -39,7 +39,7 @@ "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: 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 strict = true), 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.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 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" }, From 962a50e06b96f85a3ea9cc91420c0b772fc68506 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 11:40:20 -0700 Subject: [PATCH 4/9] Pair the Build Profile's Checker Choice Inside Its Own Clause "pyright strict, mypy strict, or both" sat at the end of a four-item list, so "both" had no clear pair. Name it as the CI type-checker choice. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/SKILL.md | 2 +- .claude-plugin/fleet-skills/.source-digests/python-codestyle | 2 +- .claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md | 2 +- .github/skills/python-codestyle/SKILL.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index 7de562d8..75e04ef9 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, mypy strict, or both. + and pyright strict, mypy strict, 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 diff --git a/.claude-plugin/fleet-skills/.source-digests/python-codestyle b/.claude-plugin/fleet-skills/.source-digests/python-codestyle index 9a11139a..765b971d 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -954cc486f7144a6a +eff739c17a87f389 diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index 7de562d8..75e04ef9 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, mypy strict, or both. + and pyright strict, mypy strict, 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 diff --git a/.github/skills/python-codestyle/SKILL.md b/.github/skills/python-codestyle/SKILL.md index 7de562d8..75e04ef9 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, mypy strict, or both. + and pyright strict, mypy strict, 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 From 56b6d5e1d6edd68b705b9353b2d3561eb6d6c82e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 11:47:00 -0700 Subject: [PATCH 5/9] Name Mypy's Strict Flags Everywhere and Scope the Pylance Claim Review of #2005 found "mypy strict" read as a single config knob on the surfaces that name the CI checker, while the spec and code-style say mypy's strict flags. Use that wording on every such surface. The skill also said the editor and CI never disagree because Pylance embeds pyright, which holds only where CI runs pyright. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/SKILL.md | 12 ++++++------ .../skills/python-codestyle/references/profiles.md | 4 ++-- .../fleet-skills/.source-digests/python-codestyle | 2 +- .../fleet-skills/skills/python-codestyle/SKILL.md | 12 ++++++------ .../skills/python-codestyle/references/profiles.md | 4 ++-- .github/skills/python-codestyle/SKILL.md | 12 ++++++------ .../skills/python-codestyle/references/profiles.md | 4 ++-- README.md | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index 75e04ef9..61479dec 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, - and pyright strict, mypy strict, or both as the CI type checker. + 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,12 +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 strict in CI and keep -pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +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 the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays 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. diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index cf2abfd2..72f531d4 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 strict 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 pyright editor-only + (Pylance), or both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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 765b971d..8c7d7bc8 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -eff739c17a87f389 +b97c3ecb76da8e1f diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index 75e04ef9..61479dec 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, - and pyright strict, mypy strict, or both as the CI type checker. + 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,12 +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 strict in CI and keep -pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +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 the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays 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. 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 cf2abfd2..72f531d4 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 strict 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 pyright editor-only + (Pylance), or both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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 75e04ef9..61479dec 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, - and pyright strict, mypy strict, or both as the CI type checker. + 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,12 +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 strict in CI and keep -pyright editor-only via Pylance, per the next paragraph): `[tool.pyright]` +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 the editor and the CLI/CI (`uv run pyright`) run the same -engine and never disagree. The standalone `ms-pyright.pyright` extension stays 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. diff --git a/.github/skills/python-codestyle/references/profiles.md b/.github/skills/python-codestyle/references/profiles.md index cf2abfd2..72f531d4 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 strict 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 pyright editor-only + (Pylance), or both. Whichever runs in CI is the one the clean-compile and the CI gate invoke. - **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 56de28ab..b4b5c7c0 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 strict in CI with pyright editor-only, or both. 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 pyright editor-only, or both. Whichever runs in CI is the gate. ### If Both C# and Python From be33d4b9155e0edec828f5d88e901f0ffea48411 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 11:55:18 -0700 Subject: [PATCH 6/9] Say Mypy Runs in the Editor Too and Name Its Standalone Configs Review of #2005 read "mypy with its strict flags in CI and pyright editor-only" as mypy being CI-only, while the skill runs mypy in CI and the editor. Say both in README.md and profiles.md. python.config.placement named pyrightconfig.json as drift but none of mypy's standalone forms, which a mypy-only build directory now reaches. Name mypy.ini, .mypy.ini, and a setup.cfg [mypy] section beside it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/references/profiles.md | 5 +++-- .claude-plugin/fleet-skills/.source-digests/python-codestyle | 2 +- .../skills/python-codestyle/references/profiles.md | 5 +++-- .github/skills/python-codestyle/references/profiles.md | 5 +++-- README.md | 2 +- spec/project-types.json | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index 72f531d4..54277ebd 100644 --- a/.agents/skills/python-codestyle/references/profiles.md +++ b/.agents/skills/python-codestyle/references/profiles.md @@ -8,8 +8,9 @@ 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 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. Whichever runs in CI is the one the clean-compile and the + CI gate invoke. - **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 8c7d7bc8..75620f79 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -b97c3ecb76da8e1f +2fa6305750287ddb 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 72f531d4..54277ebd 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,9 @@ 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 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. Whichever runs in CI is the one the clean-compile and the + CI gate invoke. - **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 72f531d4..54277ebd 100644 --- a/.github/skills/python-codestyle/references/profiles.md +++ b/.github/skills/python-codestyle/references/profiles.md @@ -8,8 +8,9 @@ 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 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. Whichever runs in CI is the one the clean-compile and the + CI gate invoke. - **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 b4b5c7c0..69a3e3e9 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 pyright editor-only, or both. 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. Whichever runs in CI is the gate. ### If Both C# and Python diff --git a/spec/project-types.json b/spec/project-types.json index f0f10fde..4f0884d5 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -40,7 +40,7 @@ { "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: 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 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.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" }, From b63b9861f7d2ea48efc5df34d0d119849c5ae01a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 11:56:28 -0700 Subject: [PATCH 7/9] State the Mypy-Only Checker and Its Configs in AUDIT.md AUDIT.md's python line still required pyright and named only pyrightconfig.json as standalone drift, so an auditor following it scored a mypy-only build directory as missing pyright and missed mypy's standalone forms. Match python.pyright.config and python.config.placement. Co-Authored-By: Claude Opus 5.5 (1M context) --- AUDIT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUDIT.md b/AUDIT.md index 774da59c..2645b5e2 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 strict CI type checker, pyright, mypy, or both, present (intent), 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. From a332967152bb60d70efaffd4a61eaeb5bc93c87d Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 11:57:06 -0700 Subject: [PATCH 8/9] Scope AUDIT.md's Strict Checker Demand to the Build Profile The previous wording demanded a strict checker of every Python directory, while python.pyright.config, which carries the strictness rule, is N/A for the lint-only profile. Co-Authored-By: Claude Opus 5.5 (1M context) --- AUDIT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUDIT.md b/AUDIT.md index 2645b5e2..d41f5912 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 a strict CI type checker, pyright, mypy, or both, present (intent), 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. +- **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. From 7990b2118dde213ff1e1fa55153f2423d23e5e88 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 28 Sep 2026 12:03:34 -0700 Subject: [PATCH 9/9] Stop Assuming One CI Checker After Offering Both README.md and profiles.md offer pyright, mypy, or both, then said "whichever runs in CI", which assumes exactly one. State what holds for each checker CI runs instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- .agents/skills/python-codestyle/references/profiles.md | 3 +-- .claude-plugin/fleet-skills/.source-digests/python-codestyle | 2 +- .../skills/python-codestyle/references/profiles.md | 3 +-- .github/skills/python-codestyle/references/profiles.md | 3 +-- README.md | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.agents/skills/python-codestyle/references/profiles.md b/.agents/skills/python-codestyle/references/profiles.md index 54277ebd..42619a2e 100644 --- a/.agents/skills/python-codestyle/references/profiles.md +++ b/.agents/skills/python-codestyle/references/profiles.md @@ -9,8 +9,7 @@ than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate 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. Whichever runs in CI is the one the clean-compile and the - CI gate invoke. + 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 75620f79..4c7f50b4 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -2fa6305750287ddb +f51041024f11173f 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 54277ebd..42619a2e 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/profiles.md @@ -9,8 +9,7 @@ than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate 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. Whichever runs in CI is the one the clean-compile and the - CI gate invoke. + 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/references/profiles.md b/.github/skills/python-codestyle/references/profiles.md index 54277ebd..42619a2e 100644 --- a/.github/skills/python-codestyle/references/profiles.md +++ b/.github/skills/python-codestyle/references/profiles.md @@ -9,8 +9,7 @@ than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate 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. Whichever runs in CI is the one the clean-compile and the - CI gate invoke. + 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/README.md b/README.md index 69a3e3e9..4abb3bc2 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. 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