From b64d48eb0774a64d054461c314dcba72434cb953 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 15 Oct 2026 16:55:55 -0700 Subject: [PATCH 1/4] Add a Pip Ecosystem to the Carried Dependabot Snippet Add a dual-targeted pip pair to catalog/snippets/configs/dependabot.yml, kept only by a repo with a pip-form Python directory, carrying the same toolchain exclude-patterns as the uv pair. Widen the python.scripts.uvx assert in spec/project-types.json to name the pip form's pins as well as the uv form's lockfile. Co-Authored-By: Claude Opus 5.5 --- catalog/snippets/configs/dependabot.yml | 30 +++++++++++++++++++++++++ spec/project-types.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/catalog/snippets/configs/dependabot.yml b/catalog/snippets/configs/dependabot.yml index e60ae9da0..f806672e2 100644 --- a/catalog/snippets/configs/dependabot.yml +++ b/catalog/snippets/configs/dependabot.yml @@ -98,3 +98,33 @@ updates: - "ruff" - "pyright" - "mypy" + + # The pip pair is kept only by a repo with a pip-form Python directory, a requirements*.txt with no uv.lock. + # Its directory points at that Python directory, so adjust it per repo layout. + - package-ecosystem: "pip" + target-branch: "main" + directory: "/PythonScripts" + schedule: + interval: "daily" + groups: + pypi-deps: + patterns: + - "*" + exclude-patterns: + - "ruff" + - "pyright" + - "mypy" + + - package-ecosystem: "pip" + target-branch: "develop" + directory: "/PythonScripts" + schedule: + interval: "daily" + groups: + pypi-deps: + patterns: + - "*" + exclude-patterns: + - "ruff" + - "pyright" + - "mypy" diff --git a/spec/project-types.json b/spec/project-types.json index 2dd82d3d5..f12b6eae8 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -45,7 +45,7 @@ { "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" }, - { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the build profile (which pins tool versions via uv.lock + uv sync --frozen instead).", "intentRef": "CODESTYLE.md" } + { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the build profile, which pins tool versions instead, via uv.lock + uv sync --frozen in its uv form and via requirements*.txt pins that Dependabot's pip ecosystem bumps in its pip form.", "intentRef": "CODESTYLE.md" } ] }, "cpp": { From 8de601de1d45c6565b3b2cc2795e675cd11bfa8e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 15 Oct 2026 16:58:07 -0700 Subject: [PATCH 2/4] Scope the Pip Form's Pin to a Declared Directory's Environment The pip form runs a tool at its requirements version only from the venv of a directory declared in python-directories, so the assert says that and keeps uvx at @latest for every other pip-form directory. Rename the pip placeholder directory away from the lint-only Scripts vocabulary. Co-Authored-By: Claude Opus 5.5 --- catalog/snippets/configs/dependabot.yml | 4 ++-- spec/project-types.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/catalog/snippets/configs/dependabot.yml b/catalog/snippets/configs/dependabot.yml index f806672e2..85b55f861 100644 --- a/catalog/snippets/configs/dependabot.yml +++ b/catalog/snippets/configs/dependabot.yml @@ -103,7 +103,7 @@ updates: # Its directory points at that Python directory, so adjust it per repo layout. - package-ecosystem: "pip" target-branch: "main" - directory: "/PythonScripts" + directory: "/PythonProject" schedule: interval: "daily" groups: @@ -117,7 +117,7 @@ updates: - package-ecosystem: "pip" target-branch: "develop" - directory: "/PythonScripts" + directory: "/PythonProject" schedule: interval: "daily" groups: diff --git a/spec/project-types.json b/spec/project-types.json index f12b6eae8..ce4fd3fc8 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -45,7 +45,7 @@ { "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" }, - { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the build profile, which pins tool versions instead, via uv.lock + uv sync --frozen in its uv form and via requirements*.txt pins that Dependabot's pip ecosystem bumps in its pip form.", "intentRef": "CODESTYLE.md" } + { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A where the build profile pins a tool instead: its uv form via uv.lock + uv sync --frozen, and its pip form via a requirements*.txt that Dependabot's pip ecosystem bumps, run from the .venv of a directory declared in python-directories. A pip-form directory whose environment installs no such tool runs it via uvx at @latest, as above.", "intentRef": "CODESTYLE.md" } ] }, "cpp": { From 47c6c48812947ac73cf7accf56a62a7a878c80a9 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 15 Oct 2026 16:59:56 -0700 Subject: [PATCH 3/4] Keep the Lint-Only Assert's Build-Profile Clause Inside Its Scope Drop the uvx fallback sentence, which stated a build-profile rule in an assert scoped to the lint-only profile, and keep the pip form's clause to the declared-directory case it actually describes. Co-Authored-By: Claude Opus 5.5 --- spec/project-types.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/project-types.json b/spec/project-types.json index ce4fd3fc8..0744b18fb 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -45,7 +45,7 @@ { "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" }, - { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A where the build profile pins a tool instead: its uv form via uv.lock + uv sync --frozen, and its pip form via a requirements*.txt that Dependabot's pip ecosystem bumps, run from the .venv of a directory declared in python-directories. A pip-form directory whose environment installs no such tool runs it via uvx at @latest, as above.", "intentRef": "CODESTYLE.md" } + { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the build profile, whose uv form pins tool versions via uv.lock + uv sync --frozen instead, and whose pip form takes a tool's version from the requirements*.txt that Dependabot's pip ecosystem bumps where a directory declared in python-directories installs that tool into its .venv.", "intentRef": "CODESTYLE.md" } ] }, "cpp": { From 59f8b998f90b816e59a16d67301891d8ef138060 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Thu, 15 Oct 2026 17:03:12 -0700 Subject: [PATCH 4/4] Name Pip Wherever the Snippet's Ecosystems Are Listed The hub's own dependabot.yml pointer and the setup.dependabot.ecosystems assert each listed the snippet's language ecosystems, and both now name pip beside nuget and uv. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 2 +- spec/project-types.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6f2fbe56f..99f9f05f0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ # The github-actions ecosystem is the only one this repo ships. # Both main and develop are targeted so each stays current independently of the develop to main release cadence. # The merge-bot auto-merges each base with its per-base method. -# See catalog/snippets/configs/dependabot.yml for the multi-ecosystem reference, covering nuget and uv, that a code-shipping repo uses. +# See catalog/snippets/configs/dependabot.yml for the multi-ecosystem reference, covering nuget, uv, and pip, that a code-shipping repo uses. version: 2 updates: diff --git a/spec/project-types.json b/spec/project-types.json index 0744b18fb..2f0d2b2ab 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -161,7 +161,7 @@ { "id": "setup.secrets.present", "verdict": "letter", "assert": "Every requiredSecret for the repo's publish mechanisms is configured (per spec/secrets.json).", "intentRef": "repo-config/README.md" }, { "id": "setup.secrets.noforbidden", "verdict": "letter", "assert": "No forbidden secret is present (e.g. a static NUGET_API_KEY on an OIDC repo).", "intentRef": "spec/secrets.json" }, { "id": "setup.driftnotes.current", "verdict": "intent", "assert": "A registry driftNote records a current deviation from the baseline; once resolved the note is deleted, not left describing finished work. spec/audit.py flags a note asserting outstanding work (pending / not yet / missing / behind / ...) on a repo that otherwise audits clean. Findings derived from an audit run carry its stamp and are re-verified at pickup, never trusted as current state.", "intentRef": "AUDIT.md" }, - { "id": "setup.dependabot.ecosystems", "verdict": "intent", "assert": "For each ecosystem the repo's tree implies, .github/dependabot.yml declares it (dual-target main+develop per the fleet norm): github-actions when .github/workflows/ is present (its workflows reference actions, else their versions go stale and a stood-up merge-bot has no PRs to auto-merge), devcontainers when a .devcontainer is present. A missing implied ecosystem is a drift finding. Language ecosystems (nuget/uv/npm) are directory-scoped, audited by inspection.", "intentRef": "GOVERNANCE.md#branching-model" } + { "id": "setup.dependabot.ecosystems", "verdict": "intent", "assert": "For each ecosystem the repo's tree implies, .github/dependabot.yml declares it (dual-target main+develop per the fleet norm): github-actions when .github/workflows/ is present (its workflows reference actions, else their versions go stale and a stood-up merge-bot has no PRs to auto-merge), devcontainers when a .devcontainer is present. A missing implied ecosystem is a drift finding. Language ecosystems (nuget/uv/pip/npm) are directory-scoped, audited by inspection.", "intentRef": "GOVERNANCE.md#branching-model" } ] }, "runtime-secrets": {