From 3f0e6fea830d07a694f5cc58f9201a34c67146fd Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 25 Jul 2026 08:16:25 -0700 Subject: [PATCH 1/4] Add the cpp type and the profile vocabulary to the spec Define the cpp lint-only type (clang-format), the build and lint-only profiles on the language types that carry them, and a minProfile on the build-requiring checks (both coverage checks, uvlock, pyright) so the audit can hold them N/A for a lint-only language. Declare the profile in the registry as a parallel profiles map rather than an object-or-string types entry, because audit.py builds a set and dict-indexes the types list and an object would break both. validate.py checks a declared profile against the repo's types and the type's allowed profiles. The audit does not consume the new fields yet, so behavior is unchanged. Co-Authored-By: Claude Opus 4.8 --- registry/repos.schema.json | 1 + spec/project-types.json | 18 ++++++++++++++---- spec/project-types.schema.json | 4 +++- spec/validate.py | 7 +++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/registry/repos.schema.json b/registry/repos.schema.json index 032838f4..bd14c2ff 100644 --- a/registry/repos.schema.json +++ b/registry/repos.schema.json @@ -44,6 +44,7 @@ "url": { "type": "string", "format": "uri" }, "status": { "enum": ["cataloged", "backlog"] }, "types": { "type": "array", "items": { "type": "string" } }, + "profiles": { "type": "object", "additionalProperties": { "enum": ["lint-only", "build"] } }, "classificationPending": { "type": "boolean" }, "groundTruthBranch": { "type": "string" }, "workflowModel": { "$ref": "#/$defs/workflowModel" }, diff --git a/spec/project-types.json b/spec/project-types.json index da61749e..9c7d22b7 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -4,13 +4,14 @@ "types": { "csharp": { "detect": ["*.csproj", "*.slnx", "*.sln"], + "profiles": ["build"], "intentRefs": ["CODESTYLE.md", "AGENTS.md#line-endings"], "requiredFiles": [".editorconfig", ".gitattributes"], "checks": [ { "id": "csharp.editorconfig.ruleblock", "verdict": "letter", "assert": ".editorconfig carries the shared [*.cs] plus ReSharper rule block.", "intentRef": "CODESTYLE.md" }, { "id": "csharp.analyzers.zerowarnings", "verdict": "intent", "assert": "Analyzer severities are enforced; warnings are not relaxed or suppressed wholesale.", "intentRef": "CODESTYLE.md" }, { "id": "csharp.centralconfig.props", "verdict": "letter", "assert": "Shared MSBuild configuration is centralized at the repo root: Directory.Build.props carries the common analyzer and warning properties (the Zero Warnings set), and Directory.Packages.props enables ManagePackageVersionsCentrally with every dependency version declared once - a csproj carries only project-specific properties and versionless PackageReference items.", "intentRef": "CODESTYLE.md" }, - { "id": "csharp.coverage.codecov", "verdict": "letter", "assert": "The unit-test job collects coverage (dotnet test --collect:\"XPlat Code Coverage\" --results-directory ./coverage) and uploads it to Codecov via codecov/codecov-action, best-effort (fail_ci_if_error: false so a Codecov outage or an absent token never reds the gate); CODECOV_TOKEN is stored in the repo actions secrets and reaches the reusable validator via secrets: inherit. Required for every C# repo with tests.", "intentRef": "WORKFLOW.md" } + { "id": "csharp.coverage.codecov", "verdict": "letter", "assert": "The unit-test job collects coverage (dotnet test --collect:\"XPlat Code Coverage\" --results-directory ./coverage) and uploads it to Codecov via codecov/codecov-action, best-effort (fail_ci_if_error: false so a Codecov outage or an absent token never reds the gate). CODECOV_TOKEN is stored in the repo actions secrets and reaches the reusable validator via secrets: inherit. Required for every C# repo with tests.", "intentRef": "WORKFLOW.md", "minProfile": "build" } ] }, "nuget": { @@ -31,19 +32,28 @@ }, "python": { "detect": ["pyproject.toml", "setup.py"], + "profiles": ["build", "lint-only"], "canonicalPlacement": "pyproject.toml", "profileNote": "Two structurally-detected profiles share this type (CODESTYLE.md Python 'Two profiles'). PROJECT: the Python has third-party runtime dependencies or is the repo's deliverable - a PEP 621 uv project (pyproject [project]+deps+[build-system], committed uv.lock, uv sync --frozen + uv run in CI). SCRIPTS: stdlib-only utility scripts embedded in a non-Python repo (e.g. a Python tooling subtree of a csharp app) - run with uvx, no uv.lock, no uv project, pyproject carries only [tool.ruff]/[tool.mypy] config. The two differ by whether the Python has third-party runtime dependencies, which the audit detects structurally from pyproject.toml (see python.profile.detect) rather than by inspecting imports: [project]+deps/[build-system] + uv.lock -> PROJECT; tool-config-only, no [project]/[build-system], no uv.lock -> SCRIPTS. The profile changes which checks apply: see python.profile.detect and the per-check N/A notes.", "checks": [ { "id": "python.profile.detect", "verdict": "letter", "assert": "The profile is read from pyproject.toml: a [project] table with runtime dependencies (or a [build-system]) is the PROJECT profile; a pyproject carrying only [tool.*] config with no [project]/[build-system] and no uv.lock is the SCRIPTS profile. A SCRIPTS subtree must not carry a uv.lock or project/build metadata (that would misrepresent it as a shippable package); a PROJECT must.", "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": "PROJECT 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 SCRIPTS profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md" }, + { "id": "python.pyright.config", "verdict": "intent", "assert": "PROJECT 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 SCRIPTS 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); required for a Home Assistant integration (platinum strict-typing) and is the SCRIPTS profile's type checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" }, - { "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false); CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the SCRIPTS profile (lint/type-checked only, no pytest); 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.uvlock.pinned", "verdict": "letter", "assert": "PROJECT profile: the committed uv.lock is pinned to LF in both .editorconfig ([uv.lock]) and .gitattributes (uv.lock text eol=lf); uv regenerates it LF on every platform, so a CRLF-default repo otherwise reds editorconfig-checker on every uv lock/sync. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the SCRIPTS profile (no uv.lock by definition).", "intentRef": "AGENTS.md#line-endings" }, + { "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the SCRIPTS profile (lint/type-checked only, no pytest). 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", "minProfile": "build" }, + { "id": "python.uvlock.pinned", "verdict": "letter", "assert": "PROJECT profile: the committed uv.lock is pinned to LF in both .editorconfig ([uv.lock]) and .gitattributes (uv.lock text eol=lf). uv regenerates it LF on every platform, so a CRLF-default repo otherwise reds editorconfig-checker on every uv lock/sync. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the SCRIPTS profile (no uv.lock by definition).", "intentRef": "AGENTS.md#line-endings", "minProfile": "build" }, { "id": "python.scripts.uvx", "verdict": "letter", "assert": "SCRIPTS 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 PROJECT profile (which pins tool versions via uv.lock + uv sync --frozen instead).", "intentRef": "CODESTYLE.md" } ] }, + "cpp": { + "detect": ["*.cpp", "*.cxx", "*.cc", "*.hpp", "*.hxx", "*.h", "*.ino", "*.c"], + "profiles": ["lint-only"], + "intentRefs": ["CODESTYLE.md"], + "checks": [ + { "id": "cpp.clangformat.shared", "verdict": "intent", "assert": "A clang-format configuration (.clang-format) drives C/C++ formatting and is shared by the editor, the CLI, and CI, feeding the operational lint gate. The scope is style only. Semantic and static analysis are left to the downstream toolchain that compiles the code (an ESPHome build), which has the compile context clang-tidy would need. A repo's .h is treated as C++ by context.", "intentRef": "CODESTYLE.md" } + ] + }, "console": { "detect": ["build-executable-task.yml", "System.CommandLine"], "checks": [ diff --git a/spec/project-types.schema.json b/spec/project-types.schema.json index fb88ec98..6e29ab23 100644 --- a/spec/project-types.schema.json +++ b/spec/project-types.schema.json @@ -20,7 +20,8 @@ "verdict": { "enum": ["letter", "intent"] }, "assert": { "type": "string" }, "intentRef": { "type": "string" }, - "workflowRef": { "type": "string" } + "workflowRef": { "type": "string" }, + "minProfile": { "enum": ["lint-only", "build"] } } }, "typeDef": { @@ -28,6 +29,7 @@ "additionalProperties": true, "properties": { "checks": { "type": "array", "items": { "$ref": "#/$defs/check" } }, + "profiles": { "type": "array", "items": { "enum": ["lint-only", "build"] } }, "appliesTo": { "type": ["string", "array"] }, "priority": { "enum": ["high", "normal"] } } diff --git a/spec/validate.py b/spec/validate.py index 75523c2e..192ff15b 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -139,6 +139,13 @@ def check_secret_set(label, entry, need_kind): for t in repo_types: if t not in known_types: errors.append(f"{name}: type '{t}' not defined in project-types.json") + # A declared profile must name one of the repo's types and a profile that type allows (spec/type-model.md). + for tname, prof in repo.get("profiles", {}).items(): + allowed = types["types"].get(tname, {}).get("profiles", []) + if tname not in repo_types: + errors.append(f"{name}: profile declared for '{tname}', not one of the repo's types") + elif tname in known_types and prof not in allowed: + errors.append(f"{name}: type '{tname}' profile '{prof}' not in its allowed profiles {allowed or '[]'}") model = repo.get("workflowModel") if model is not None and model not in WORKFLOW_MODELS: From a4c1b3b40773f56d8af408bcf0d1355c1202a32c Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 25 Jul 2026 08:19:45 -0700 Subject: [PATCH 2/4] Guard the profiles shape in validate.py Co-Authored-By: Claude Opus 4.8 --- spec/validate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/validate.py b/spec/validate.py index 192ff15b..901b9e5a 100644 --- a/spec/validate.py +++ b/spec/validate.py @@ -140,7 +140,12 @@ def check_secret_set(label, entry, need_kind): if t not in known_types: errors.append(f"{name}: type '{t}' not defined in project-types.json") # A declared profile must name one of the repo's types and a profile that type allows (spec/type-model.md). - for tname, prof in repo.get("profiles", {}).items(): + # CI runs no JSON-schema validation, so guard the shape here rather than crash on .items(). + profiles_decl = repo.get("profiles", {}) + if not isinstance(profiles_decl, dict): + errors.append(f"{name}: profiles must be an object mapping a type to its profile") + profiles_decl = {} + for tname, prof in profiles_decl.items(): allowed = types["types"].get(tname, {}).get("profiles", []) if tname not in repo_types: errors.append(f"{name}: profile declared for '{tname}', not one of the repo's types") From ea4ab26c26c54f759cd0f83806cfc85556479fdd Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 25 Jul 2026 08:28:03 -0700 Subject: [PATCH 3/4] Align the python profile names to build/lint-only Use the declared profile vocabulary (build/lint-only) as the profile names throughout the python type, keeping PROJECT/SCRIPTS only as the structural pyproject shapes they detect. Removes the two-vocabulary ambiguity about what the registry declares, and splits two more pre-existing semicolon splices in the touched asserts. Co-Authored-By: Claude Opus 4.8 --- spec/project-types.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/project-types.json b/spec/project-types.json index 9c7d22b7..3df6f08b 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -34,16 +34,16 @@ "detect": ["pyproject.toml", "setup.py"], "profiles": ["build", "lint-only"], "canonicalPlacement": "pyproject.toml", - "profileNote": "Two structurally-detected profiles share this type (CODESTYLE.md Python 'Two profiles'). PROJECT: the Python has third-party runtime dependencies or is the repo's deliverable - a PEP 621 uv project (pyproject [project]+deps+[build-system], committed uv.lock, uv sync --frozen + uv run in CI). SCRIPTS: stdlib-only utility scripts embedded in a non-Python repo (e.g. a Python tooling subtree of a csharp app) - run with uvx, no uv.lock, no uv project, pyproject carries only [tool.ruff]/[tool.mypy] config. The two differ by whether the Python has third-party runtime dependencies, which the audit detects structurally from pyproject.toml (see python.profile.detect) rather than by inspecting imports: [project]+deps/[build-system] + uv.lock -> PROJECT; tool-config-only, no [project]/[build-system], no uv.lock -> SCRIPTS. The profile changes which checks apply: see python.profile.detect and the per-check N/A notes.", + "profileNote": "The declared profile is build or lint-only, each with a structural pyproject.toml shape (CODESTYLE.md Python 'Two profiles'). The build profile (structurally the PROJECT shape) is Python with third-party runtime dependencies or as the repo's deliverable - a PEP 621 uv project (pyproject [project]+deps+[build-system], committed uv.lock, uv sync --frozen + uv run in CI). The lint-only profile (structurally the SCRIPTS shape) is stdlib-only utility scripts embedded in a non-Python repo (e.g. a Python tooling subtree of a csharp app) - run with uvx, no uv.lock, no uv project, pyproject carries only [tool.ruff]/[tool.mypy] config. The two differ by whether the Python has third-party runtime dependencies, which the audit detects structurally from pyproject.toml (see python.profile.detect) rather than by inspecting imports. The shape [project]+deps/[build-system] + uv.lock is build, and tool-config-only with no [project]/[build-system] and no uv.lock is lint-only. The declared profile chooses which checks apply, validated against that shape (see python.profile.detect and each check's minProfile).", "checks": [ - { "id": "python.profile.detect", "verdict": "letter", "assert": "The profile is read from pyproject.toml: a [project] table with runtime dependencies (or a [build-system]) is the PROJECT profile; a pyproject carrying only [tool.*] config with no [project]/[build-system] and no uv.lock is the SCRIPTS profile. A SCRIPTS subtree must not carry a uv.lock or project/build metadata (that would misrepresent it as a shippable package); a PROJECT must.", "intentRef": "CODESTYLE.md" }, + { "id": "python.profile.detect", "verdict": "letter", "assert": "The declared profile is validated against the pyproject.toml shape. A [project] table with runtime dependencies (or a [build-system]) is the build profile (the PROJECT shape). A pyproject carrying only [tool.*] config with no [project]/[build-system] and no uv.lock 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.", "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": "PROJECT 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 SCRIPTS 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: pyright is configured and runs strict on first-party code (src or the integration package) - the strong typing baseline. Third-party strictness is relaxed only where a dependency has no usable types. N/A for the lint-only profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md", "minProfile": "build" }, { "id": "python.config.placement", "verdict": "letter", "assert": "ruff and the type-checker config live in pyproject.toml (canonical); standalone .ruff.toml / pyrightconfig.json is a drift finding. A Home Assistant integration is the exception - it follows home-assistant/core standalone-config conventions and is scored by ha.python.conventions instead.", "intentRef": "CODESTYLE.md" }, - { "id": "python.mypy.allowed", "verdict": "intent", "assert": "mypy is permitted as an additional type checker (not banned); required for a Home Assistant integration (platinum strict-typing) and is the SCRIPTS profile's type checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" }, - { "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the SCRIPTS profile (lint/type-checked only, no pytest). 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", "minProfile": "build" }, - { "id": "python.uvlock.pinned", "verdict": "letter", "assert": "PROJECT profile: the committed uv.lock is pinned to LF in both .editorconfig ([uv.lock]) and .gitattributes (uv.lock text eol=lf). uv regenerates it LF on every platform, so a CRLF-default repo otherwise reds editorconfig-checker on every uv lock/sync. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the SCRIPTS profile (no uv.lock by definition).", "intentRef": "AGENTS.md#line-endings", "minProfile": "build" }, - { "id": "python.scripts.uvx", "verdict": "letter", "assert": "SCRIPTS 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 PROJECT profile (which pins tool versions via uv.lock + uv sync --frozen 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.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the lint-only profile (lint/type-checked only, no pytest). 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", "minProfile": "build" }, + { "id": "python.uvlock.pinned", "verdict": "letter", "assert": "Build profile: the committed uv.lock is pinned to LF in both .editorconfig ([uv.lock]) and .gitattributes (uv.lock text eol=lf). uv regenerates it LF on every platform, so a CRLF-default repo otherwise reds editorconfig-checker on every uv lock/sync. 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": "AGENTS.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" } ] }, "cpp": { From 51ffb608395ca359e5ab06d87d4306d523de621e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 25 Jul 2026 08:34:18 -0700 Subject: [PATCH 4/4] Word the python profile prose as correspondence, not audit behavior Say the declared profile corresponds to the pyproject shape and that each check names its minimum profile, rather than claiming the audit validates the profile against pyproject or gates checks by it - that consumption lands in a later slice. Co-Authored-By: Claude Opus 4.8 --- 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 3df6f08b..9436e55c 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -34,9 +34,9 @@ "detect": ["pyproject.toml", "setup.py"], "profiles": ["build", "lint-only"], "canonicalPlacement": "pyproject.toml", - "profileNote": "The declared profile is build or lint-only, each with a structural pyproject.toml shape (CODESTYLE.md Python 'Two profiles'). The build profile (structurally the PROJECT shape) is Python with third-party runtime dependencies or as the repo's deliverable - a PEP 621 uv project (pyproject [project]+deps+[build-system], committed uv.lock, uv sync --frozen + uv run in CI). The lint-only profile (structurally the SCRIPTS shape) is stdlib-only utility scripts embedded in a non-Python repo (e.g. a Python tooling subtree of a csharp app) - run with uvx, no uv.lock, no uv project, pyproject carries only [tool.ruff]/[tool.mypy] config. The two differ by whether the Python has third-party runtime dependencies, which the audit detects structurally from pyproject.toml (see python.profile.detect) rather than by inspecting imports. The shape [project]+deps/[build-system] + uv.lock is build, and tool-config-only with no [project]/[build-system] and no uv.lock is lint-only. The declared profile chooses which checks apply, validated against that shape (see python.profile.detect and each check's minProfile).", + "profileNote": "The declared profile is build or lint-only, each with a structural pyproject.toml shape (CODESTYLE.md Python 'Two profiles'). The build profile (structurally the PROJECT shape) is Python with third-party runtime dependencies or as the repo's deliverable - a PEP 621 uv project (pyproject [project]+deps+[build-system], committed uv.lock, uv sync --frozen + uv run in CI). The lint-only profile (structurally the SCRIPTS shape) is stdlib-only utility scripts embedded in a non-Python repo (e.g. a Python tooling subtree of a csharp app) - run with uvx, no uv.lock, no uv project, pyproject carries only [tool.ruff]/[tool.mypy] config. The two differ by whether the Python has third-party runtime dependencies, which the audit detects structurally from pyproject.toml (see python.profile.detect) rather than by inspecting imports. The shape [project]+deps/[build-system] + uv.lock is build, and tool-config-only with no [project]/[build-system] and no uv.lock is lint-only. The declared profile corresponds to this shape, and each check names the minimum profile it needs (see python.profile.detect and each check's minProfile).", "checks": [ - { "id": "python.profile.detect", "verdict": "letter", "assert": "The declared profile is validated against the pyproject.toml shape. A [project] table with runtime dependencies (or a [build-system]) is the build profile (the PROJECT shape). A pyproject carrying only [tool.*] config with no [project]/[build-system] and no uv.lock 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.", "intentRef": "CODESTYLE.md" }, + { "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 carrying only [tool.*] config with no [project]/[build-system] and no uv.lock 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.", "intentRef": "CODESTYLE.md" }, { "id": "python.ruff.config", "verdict": "intent", "assert": "A ruff configuration is present (pyproject.toml [tool.ruff]). Both profiles.", "intentRef": "CODESTYLE.md" }, { "id": "python.pyright.config", "verdict": "intent", "assert": "Build profile: pyright is configured and runs strict on first-party code (src or the integration package) - the strong typing baseline. Third-party strictness is relaxed only where a dependency has no usable types. N/A for the lint-only profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md", "minProfile": "build" }, { "id": "python.config.placement", "verdict": "letter", "assert": "ruff and the type-checker config live in pyproject.toml (canonical); standalone .ruff.toml / pyrightconfig.json is a drift finding. A Home Assistant integration is the exception - it follows home-assistant/core standalone-config conventions and is scored by ha.python.conventions instead.", "intentRef": "CODESTYLE.md" },