Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions registry/repos.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@
"name": "ESPHome-Config",
"url": "https://github.com/ptr727/ESPHome-Config",
"status": "cataloged",
"types": ["source-only"],
"types": ["source-only", "python", "cpp"],
"profiles": { "python": "lint-only", "cpp": "lint-only" },
"groundTruthBranch": "main",
"workflowModel": "operational",
"lineEndings": "lf",
Expand All @@ -210,7 +211,7 @@
"requiredSecrets": [],
"consumerModel": "pull",
"releaseTrigger": "dispatch-only",
"driftNotes": ["ESPHome device config YAML consumed by the cataloged ESPHome-NonRoot image at runtime; distinct from that Docker repo."]
"driftNotes": ["ESPHome device config YAML consumed by the cataloged ESPHome-NonRoot image at runtime. Distinct from that Docker repo.", "First repo with a lint-only python codegen subtree (easystart/python: ruff/mypy config only, no tests, no uv.lock) and lint-only cpp (committed custom-component and template headers). A shared clang-format config for the cpp style is not yet added."]
},
{
"name": "HomeAssistant-Config",
Expand Down
1 change: 1 addition & 0 deletions registry/repos.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
10 changes: 9 additions & 1 deletion spec/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,14 +565,20 @@ def audit_repo(entry, spec):

# --- Secrets (names only) ---
secrets = spec["secrets"]
# The codecov coverage requirement (the CODECOV_TOKEN secret and the codecov.yml file) is claimed by a
# type only at build profile: a lint-only language has no tests, so no coverage (spec/type-model.md).
repo_profiles = entry.get("profiles", {})
if not isinstance(repo_profiles, dict):
repo_profiles = {}
coverage_active = any(secrets.get("typeMechanisms", {}).get(t) == "codecov" and repo_profiles.get(t) != "lint-only" for t in types)
stores = {}
# No ok404: an empty store returns {"secrets": []}, so a 404/403 (permissions, rename) must
# surface as ERROR rather than cascade into false missing-secret DEFECTs.
for store, path in [("actions", f"repos/{slug}/actions/secrets?per_page=100"), ("dependabot", f"repos/{slug}/dependabot/secrets?per_page=100")]:
data = gh(path)
stores[store] = {s["name"] for s in (data or {}).get("secrets", [])}
mechanisms = [secrets["targetMechanisms"].get(p.get("target")) for p in entry.get("publish", [])]
mechanisms += [secrets.get("typeMechanisms", {}).get(t) for t in types]
mechanisms += [secrets.get("typeMechanisms", {}).get(t) for t in types if repo_profiles.get(t) != "lint-only"]
claimed = [secrets["mechanisms"][m] for m in mechanisms if m and m in secrets["mechanisms"]]
required_by_store = {"actions": set(), "dependabot": set()}
for store in secrets["baseline"].get("stores", []):
Expand Down Expand Up @@ -643,6 +649,8 @@ def audit_repo(entry, spec):
if not applies(item.get("appliesTo", "*"), sel):
continue
path = item["path"]
if path == "codecov.yml" and not coverage_active:
continue # coverage feature file: N/A when no type claims codecov at build profile (spec/type-model.md)
if path not in wanted_sections:
wanted_sections[path] = set()
verbatim_secs[path] = set()
Expand Down
28 changes: 19 additions & 9 deletions spec/project-types.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
{
"$schema": "./project-types.schema.json",
"note": "Type -> requirements and checks. Each check names a verdict tier (letter = exact form, intent = equivalent outcome) and points at the prose section that owns the rationale. The audit (AUDIT.md) runs the checks for a repo's applicable types plus the cross-cutting dimensions; an absent construct is N/A, not a defect.",
"note": "Type -> requirements and checks. Each check names a verdict tier (letter = exact form, intent = equivalent outcome) and points at the prose section that owns the rationale. The audit (AUDIT.md) runs the checks for a repo's declared types plus the cross-cutting dimensions. An absent construct is N/A, not a defect. How types and profiles are declared, validated against detection, and gated by profile is defined in spec/type-model.md.",
"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": {
Expand All @@ -31,17 +32,26 @@
},
"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.",
"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 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 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": "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": "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" },
{ "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.scripts.uvx", "verdict": "letter", "assert": "SCRIPTS profile only: the tools run via uvx (no project install, no lockfile). A uvx <tool>@<ver> 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 <tool>@<ver> 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": {
"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": {
Expand Down
4 changes: 3 additions & 1 deletion spec/project-types.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
"verdict": { "enum": ["letter", "intent"] },
"assert": { "type": "string" },
"intentRef": { "type": "string" },
"workflowRef": { "type": "string" }
"workflowRef": { "type": "string" },
"minProfile": { "enum": ["lint-only", "build"] }
}
},
"typeDef": {
"type": "object",
"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"] }
}
Expand Down
2 changes: 2 additions & 0 deletions spec/section-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ A section is one of the following. Fidelity is declared in [files.json][files],
| Devcontainer | intent | describes this repo's toolchain and devcontainer, genuinely per-repo |
| Repository Layout | intent | describes this repo's directory tree, genuinely per-repo |

**Devcontainer content.** A devcontainer is optional infrastructure, not required by any repo type. An operational (live config) repo is edited and deployed live and typically has none, so its Devcontainer section states that plainly. A repo that keeps one - a code repo's toolchain, or an offline-debugging aid for a config repo - describes it. The section is present in every carried `AGENTS.md` so the development model is always answered, even when the answer is none.

**Not carried (hub-only).** `Repository Onboarding and Conformance` lives in the hub's `AGENTS.md` as hub-audit context (reconciling the registry, the STANDUP cold-start, the conformance matrix) but is not a carried section - a downstream agent never runs those. Its one universal rule, that a repo is done when it passes `AUDIT.md` for its type, is carried in `AUDIT.md` itself. Like the model docs and `STANDUP.md`, it is hub machinery, not fleet law.

## Changing the structure carries review weight
Expand Down
Loading