From 04c81d22d3007336f9d7782643a3715487fe5331 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 07:13:36 -0700 Subject: [PATCH 1/5] Fix the comment shape in the files every new repo copies first `.editorconfig` and `.gitattributes` are the first files an onboarding repo carries, so until their comments follow the rules a new repo learns the shape the rules forbid. Both are `intent` fidelity, so a downstream copy is adapted rather than byte-matched and no re-vendor follows. 36 findings are gone, 22 in `.editorconfig` and 14 in `.gitattributes`. `.gitignore` was already clean, so the 44 counted in `TODO.md` was stale. Wrapped prose became one sentence per line. Two bare commands in `.gitattributes` gained a label (`Inspect with: git ls-files --eol`) rather than an exemption, since relabeling a command reads better where relabeling a URI would corrupt it. Comments opening on `git` or `uv` were restructured, not capitalized, per the tooling-casing rule. A mid-sentence semicolon and several spaced hyphens went with them, both banned in agent-authored prose and invisible here because those two rules are markdown-only. No setting or pattern changed: every non-comment line in both files is byte-identical, and `repo_gate`'s eol check still cross-validates them. `TODO.md` drops the two swept entries and records the fleet re-vendor that the `configure.sh` sweep in #501 now obliges. Co-Authored-By: Claude Opus 5 (1M context) --- .editorconfig | 58 +++++++++++++++++++++++++------------------------- .gitattributes | 35 +++++++++++++++--------------- TODO.md | 5 ++--- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1cb9cf82..0130b24d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,12 +13,12 @@ # Root config root = true -# Defaults - CRLF is the default; only the LF exceptions below are declared. Most LF pins are shared with -# `.gitattributes` (git-enforced): `*.sh`, the husky pre-commit shebang, `spec/validate.py`, `uv.lock`, and Dockerfiles. -# The workflow-YAML pins (`.github/workflows/*` and the catalog snippet workflows) are `.editorconfig`-only - -# git stays passive on them (`* -text`) and CI (editorconfig-checker) enforces LF. Keep the `[*]` -# `end_of_line = crlf` default - it is required for the Windows-GUI/WSL-engine -# workflow (Windows tooling misbehaves on LF) and every uncovered file type relies on it. +# Defaults: CRLF is the default, and only the LF exceptions below are declared. +# Most LF pins are shared with `.gitattributes`, which git enforces: `*.sh`, the husky pre-commit shebang, `spec/validate.py`, `uv.lock`, and Dockerfiles. +# The workflow-YAML pins (`.github/workflows/*` and the catalog snippet workflows) are `.editorconfig`-only. +# There git stays passive (`* -text`) and CI (editorconfig-checker) enforces LF. +# Keep the `[*]` `end_of_line = crlf` default, which the Windows-GUI and WSL-engine workflow requires because Windows tooling misbehaves on LF. +# Every uncovered file type relies on that default too. [*] charset = utf-8 end_of_line = crlf @@ -39,17 +39,18 @@ indent_size = 2 [*.{yml,yaml}] indent_size = 2 -# Workflow YAML is LF: Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of -# mixed. git still leaves endings alone (`* -text`); this and CI (editorconfig-checker) enforce it. Other YAML is CRLF. +# Workflow YAML is LF, because Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of mixed. +# Endings are still left alone by git (`* -text`), and this file plus CI (editorconfig-checker) enforce it. +# Other YAML stays CRLF. [.github/workflows/*.{yml,yaml}] end_of_line = lf -# Catalog snippet workflows mirror real workflow files (LF above); keep the snippets LF so a copied snippet -# lands compliant instead of needing conversion. +# Catalog snippet workflows mirror the real workflow files pinned LF above. +# Keep the snippets LF, so a copied snippet lands compliant instead of needing conversion. [catalog/snippets/workflows/*.{yml,yaml}] end_of_line = lf -# The husky pre-commit snippet is a shebang script; keep it LF so a copied snippet runs (a CRLF shebang breaks). +# The husky pre-commit snippet is a shebang script, so keep it LF and a copied snippet runs, where a CRLF shebang breaks. [catalog/snippets/husky/pre-commit] end_of_line = lf @@ -57,42 +58,41 @@ end_of_line = lf [*.sh] end_of_line = lf -# Python is CRLF by the `[*]` default (universal newlines; commonly edited on Windows). Pin LF -# only for a `.py` executed directly via its shebang, by path - here the CI validation entry point -# and the fleet-audit runner. +# Python is CRLF by the `[*]` default, since universal newlines accept it and it is commonly edited on Windows. +# Pin LF only for a `.py` executed directly via its shebang, by path. +# Here that is the CI validation entry point and the fleet-audit runner. [spec/{validate,audit,fidelity_honesty}.py] end_of_line = lf -# The agent-safety kit's Python is shebang-executable tooling run by path (the PreToolUse hook and its -# installer), so pin LF for the same reason as the entry points above - a CRLF shebang breaks direct -# execution on a Unix host. +# The agent-safety kit's Python is shebang-executable tooling run by path, being the PreToolUse hook and its installer. +# Pin LF for the same reason as the entry points above, because a CRLF shebang breaks direct execution on a Unix host. [host-setup/agent-safety/*.py] end_of_line = lf -# The repo lint/review scripts are shebang-executable tooling run by path, so a CRLF shebang would -# break direct execution on a Unix host. Pinned in .gitattributes to match. +# The repo lint and review scripts are shebang-executable tooling run by path, so a CRLF shebang would break direct execution on a Unix host. +# They are pinned in .gitattributes to match. [scripts/*.py] end_of_line = lf -# uv regenerates uv.lock with LF on every platform, so pin it or an EOL check (editorconfig-checker/CI) -# reds on every `uv lock`/`uv sync` until the file is manually reconverted - same rationale as the -# shebang/Dockerfile pins (a tool owns the ending). A Python repo on the CRLF default carries this; a repo -# with no lockfile is unaffected. +# The uv tool regenerates uv.lock with LF on every platform, so pin it here. +# Without the pin an EOL check (editorconfig-checker or CI) reds on every `uv lock` or `uv sync` until the file is manually reconverted. +# The rationale matches the shebang and Dockerfile pins, where a tool owns the ending. +# A Python repo on the CRLF default carries this, and a repo with no lockfile is unaffected. [uv.lock] end_of_line = lf -# Dockerfiles - CRLF breaks RUN heredocs and line continuations +# Dockerfiles are LF, because CRLF breaks RUN heredocs and line continuations. [{Dockerfile,*.Dockerfile}] end_of_line = lf -# --- .NET-only below: C# and ReSharper style. Everything above is the line-ending -# governance every derived repo carries; a non-.NET repo may drop from here down. --- +# .NET-only below, covering C# and ReSharper style. +# Everything above is the line-ending governance every derived repo carries, and a non-.NET repo may drop from here down. # C# files [*.cs] -# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions": prefer a -# [SuppressMessage] attribute or the owning project's .editorconfig; relax a rule -# repo-wide here only when it applies to every project (never a brownfield batch). +# Suppressions follow CODESTYLE.md "Analyzer Diagnostics and Suppressions". +# Prefer a [SuppressMessage] attribute, or the owning project's .editorconfig. +# Relax a rule repo-wide here only when it applies to every project, never for a brownfield batch. dotnet_diagnostic.IDE0055.severity = none csharp_indent_block_contents = true csharp_indent_braces = false diff --git a/.gitattributes b/.gitattributes index 0285142c..08224a0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,23 +1,23 @@ -# Default: do not normalize line endings (`* -text`); .editorconfig end_of_line rules guide what the editor writes. -# The exception pins below are git's own enforcement - they force LF for execution-sensitive classes regardless of editor. -# git config --global core.autocrlf false -# git add --renormalize . -# git ls-files --eol +# Default: git does not normalize line endings (`* -text`), and .editorconfig end_of_line rules guide what the editor writes. +# The exception pins below are git's own enforcement, forcing LF for execution-sensitive classes regardless of editor. +# Configure with: git config --global core.autocrlf false +# Renormalize with: git add --renormalize . +# Inspect with: git ls-files --eol * -text -# Exception: scripts must stay LF regardless of the `* -text` default - a CRLF shebang breaks execution. `.editorconfig` -# covers `*.sh`, but extensionless executables match no extension rule, so pin them here so git enforces LF on checkout -# and `--renormalize`. Any repo whose tooling ships extensionless scripts adds the matching path pin, e.g. s6-overlay -# init `Docker/s6-overlay/** text eol=lf` or husky/git hooks `.husky/pre-commit text eol=lf`. +# Exception: scripts must stay LF regardless of the `* -text` default, because a CRLF shebang breaks execution. +# `.editorconfig` covers `*.sh`, but an extensionless executable matches no extension rule. +# Pin those here, so git enforces LF on checkout and on `--renormalize`. +# Any repo whose tooling ships extensionless scripts adds the matching path pin. +# The s6-overlay init is `Docker/s6-overlay/** text eol=lf`, and a husky or git hook is `.husky/pre-commit text eol=lf`. *.sh text eol=lf # The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit). catalog/snippets/husky/pre-commit text eol=lf -# Vanilla `.py` follows the CRLF default - Python's universal newlines accept CRLF, and it is -# commonly edited on Windows. Pin LF only for a `.py` executed directly via its shebang, by path - -# here the CI validation entry point, the fleet-audit runner, the agent-safety hook and its -# installer, and the repo lint/review scripts with their tests. Do not re-add a blanket -# `*.py text eol=lf`. +# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows. +# Pin LF only for a `.py` executed directly via its shebang, by path. +# Those are the CI validation entry point, the fleet-audit runner, the agent-safety hook and its installer, and the repo lint and review scripts with their tests. +# Do not re-add a blanket `*.py text eol=lf`. spec/validate.py text eol=lf spec/audit.py text eol=lf spec/fidelity_honesty.py text eol=lf @@ -30,10 +30,11 @@ scripts/test_prose_lint.py text eol=lf scripts/test_repo_gate.py text eol=lf scripts/test_pr_review.py text eol=lf -# uv regenerates uv.lock with LF on every platform; pin it so git enforces LF on checkout/renormalize and a -# CRLF-default repo does not fight the tool on every `uv lock`/`uv sync`. A repo with no lockfile is unaffected. +# The uv tool regenerates uv.lock with LF on every platform, so pin it here. +# That makes git enforce LF on checkout and renormalize, so a CRLF-default repo does not fight the tool on every `uv lock` or `uv sync`. +# A repo with no lockfile is unaffected. uv.lock text eol=lf -# Dockerfiles must be LF - a CRLF breaks RUN heredocs and line continuations. +# Dockerfiles must be LF, because a CRLF breaks RUN heredocs and line continuations. Dockerfile text eol=lf *.Dockerfile text eol=lf diff --git a/TODO.md b/TODO.md index 43e5b4ad..e07d430b 100644 --- a/TODO.md +++ b/TODO.md @@ -11,9 +11,8 @@ Running backlog for this repo, kept in a committed file so the guidance survives - Refresh the README (it has gone stale) and evaluate a lower-maintenance structure, for example a per-section index that points into each doc with a one-line description, keeping the README as the adoption and audit-instruction entry point with pointers to the other docs. A per-section index trades brevity for a sync obligation: it must track what the docs contain. - Add a linter-only Python project type for codegen/boilerplate Python, code that runs during another tool's build to emit generated source (e.g. ESPHome codegen that produces enriched C++ at compile time), so it ships no unit tests and no coverage and needs only the linter. Keep it distinct from the existing `python` type, which is utility code that can and should carry unit tests and coverage (as in PlexCleaner). Until it exists, ESPHome-Config stays `source-only` and its `+python` reclassification is deferred, so accept its one outstanding validation finding meanwhile. - Add a fleet-standard clang-format config for the `cpp` type: a catalog snippet plus a CODESTYLE C++ section defining the style, the C++ analogue of the shared ruff config, so the `cpp` clang-format check references one canonical style rather than each repo inventing its own. Base it on the ESPHome-Config agent's proposed `.clang-format`. -- Sweep the 13 `dash` and `semicolon` findings in `README.md`. They are deferred rather than dropped, because two changes to that file are in flight and a third overlapping edit would conflict with both for no gain. -- Clean the comment shape in `.editorconfig`, `.gitattributes` and `.gitignore` (44 `comment-wrap` and `comment-case` findings). These are the first files every new repo copies, so until they are fixed a new repo learns the shape the rules forbid. -- Sweep the 54 `comment-wrap` and `comment-case` findings in `repo-config/configure.sh`. It is carried `verbatim`, so a downstream copy is byte-matched and cannot fix them locally, which makes this the hub's whole-class sweep rather than a next-edit correction. +- Sweep the 12 `dash` and `semicolon` findings in `README.md`. The edits that made this conflict-prone have landed, so it is now free to do. +- Re-vendor `repo-config/configure.sh` across the fleet. The hub swept it to one sentence per line, and it is carried `verbatim` with `appliesTo: "*"`, so every repo already holding a copy is byte-mismatched against the hub until it takes the new one. From d2959cdd429f469bdfa422a08274e60aaa7c3ddf Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 07:17:33 -0700 Subject: [PATCH 2/5] Fix four comments that read badly, and gate the verbatim class Copilot found three comments where the rewrite satisfied the linter at the cost of reading well, which is the failure a sweep is supposed to avoid. A fourth had the same fault and was found by re-reading the rest. - "keep it LF and a copied snippet runs, where a CRLF shebang breaks" splits into two sentences. - "being the PreToolUse hook and its installer" becomes "namely". - "reds on every `uv lock`" becomes "fails". That word was inherited rather than introduced, and rewriting the line was the moment to drop it. - "There git stays passive" becomes "For those, git stays passive". The awkward fronting existed only to dodge a lowercase opening. Also gates the class this work belongs to. GOVERNANCE puts verbatim content on the hub, because a downstream copy is byte-matched and cannot be corrected locally. Nothing enforced that, so the sweep could silently regress. `TestCarriedContent` reads the `verbatim` entries live from spec/files.json and asserts every one is comment-clean, with two guards so it cannot pass vacuously: the manifest must still declare verbatim content, and every declared file must exist. Confirmed to fail by reintroducing a wrapped comment in configure.sh. Co-Authored-By: Claude Opus 5 (1M context) --- .editorconfig | 9 ++++---- scripts/test_prose_lint.py | 45 +++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0130b24d..aa6be5c3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,7 +16,7 @@ root = true # Defaults: CRLF is the default, and only the LF exceptions below are declared. # Most LF pins are shared with `.gitattributes`, which git enforces: `*.sh`, the husky pre-commit shebang, `spec/validate.py`, `uv.lock`, and Dockerfiles. # The workflow-YAML pins (`.github/workflows/*` and the catalog snippet workflows) are `.editorconfig`-only. -# There git stays passive (`* -text`) and CI (editorconfig-checker) enforces LF. +# For those, git stays passive (`* -text`) and CI (editorconfig-checker) enforces LF. # Keep the `[*]` `end_of_line = crlf` default, which the Windows-GUI and WSL-engine workflow requires because Windows tooling misbehaves on LF. # Every uncovered file type relies on that default too. [*] @@ -50,7 +50,8 @@ end_of_line = lf [catalog/snippets/workflows/*.{yml,yaml}] end_of_line = lf -# The husky pre-commit snippet is a shebang script, so keep it LF and a copied snippet runs, where a CRLF shebang breaks. +# The husky pre-commit snippet is a shebang script, so keep it LF. +# A copied snippet then runs, where a CRLF shebang would break it. [catalog/snippets/husky/pre-commit] end_of_line = lf @@ -64,7 +65,7 @@ end_of_line = lf [spec/{validate,audit,fidelity_honesty}.py] end_of_line = lf -# The agent-safety kit's Python is shebang-executable tooling run by path, being the PreToolUse hook and its installer. +# The agent-safety kit's Python is shebang-executable tooling run by path, namely the PreToolUse hook and its installer. # Pin LF for the same reason as the entry points above, because a CRLF shebang breaks direct execution on a Unix host. [host-setup/agent-safety/*.py] end_of_line = lf @@ -75,7 +76,7 @@ end_of_line = lf end_of_line = lf # The uv tool regenerates uv.lock with LF on every platform, so pin it here. -# Without the pin an EOL check (editorconfig-checker or CI) reds on every `uv lock` or `uv sync` until the file is manually reconverted. +# Without the pin an EOL check (editorconfig-checker or CI) fails on every `uv lock` or `uv sync` until the file is manually reconverted. # The rationale matches the shebang and Dockerfile pins, where a tool owns the ending. # A Python repo on the CRLF default carries this, and a repo with no lockfile is unaffected. [uv.lock] diff --git a/scripts/test_prose_lint.py b/scripts/test_prose_lint.py index a3e567cd..9fc8c983 100644 --- a/scripts/test_prose_lint.py +++ b/scripts/test_prose_lint.py @@ -8,7 +8,7 @@ Run as `python3 scripts/test_prose_lint.py`, or under `python3 -m unittest discover -s scripts`. """ from __future__ import annotations -import contextlib, io, re, subprocess, sys, tempfile, unittest +import contextlib, io, json, re, subprocess, sys, tempfile, unittest from pathlib import Path from unittest import mock @@ -1136,6 +1136,49 @@ def test_the_repo_is_clean_of_british_spellings(self) -> None: self.assertEqual([], found) +class TestCarriedContent(unittest.TestCase): + """Content the fleet copies byte-matched, which only the hub can ever correct. + + GOVERNANCE "Character Set" states the obligation: correct-as-you-next-edit assumes someone + able to edit the file, and a downstream repo cannot edit a verbatim one, since its copy is + byte-matched against the hub's. So the hub sweeps the class and re-vendors. That makes a + finding in a verbatim file different in kind from the tree-wide backlog: it is not a + correction owed by whoever next edits the file, it is one no downstream repo can make at all. + """ + + def verbatim_paths(self) -> list[Path]: + """Every `verbatim` entry in the carry manifest, read live rather than restated here.""" + def entries(node: object): + if isinstance(node, dict): + if 'path' in node: + yield node + for value in node.values(): + yield from entries(value) + elif isinstance(node, list): + for value in node: + yield from entries(value) + + manifest = json.loads((REPO / 'spec' / 'files.json').read_text(encoding='utf-8')) + seen = {e['path'] for e in entries(manifest) if e.get('fidelity') == 'verbatim'} + return sorted(REPO / p for p in seen) + + def test_the_manifest_still_declares_verbatim_content(self) -> None: + """A manifest that stopped declaring any would make the sweep below vacuously pass.""" + self.assertNotEqual([], self.verbatim_paths()) + + def test_every_verbatim_carried_file_is_comment_clean(self) -> None: + """A downstream repo cannot fix one of these, so the hub may not leave one behind.""" + found = [f'{prose_lint.rel(p.relative_to(REPO))}:{ln}: {kind}' + for p in self.verbatim_paths() if p.exists() + for ln, kind, _ in prose_lint.check_file(p, {'comment-wrap', 'comment-case'})] + self.assertEqual([], found) + + def test_every_declared_verbatim_file_exists(self) -> None: + """A manifest naming a file the hub does not carry would exempt it by absence.""" + self.assertEqual([], [str(p.relative_to(REPO)) for p in self.verbatim_paths() + if not p.exists()]) + + class TestSyntaxDispatch(unittest.TestCase): def test_an_extensionless_file_is_read_as_hash_commented(self) -> None: """A shebang script or a config with no suffix is far more often `#` than nothing.""" From 2667bb9f5f9372f9abee22e942e4a197a7b2c62c Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 07:22:57 -0700 Subject: [PATCH 3/5] Recast the banned constructions in README.md The `README.md` entry from `TODO.md`, 12 findings: 6 spaced hyphens and 6 prose semicolons, both banned in agent-authored prose. It was deferred because two edits to the file were in flight, and both have since landed. The spaced hyphens become a comma, a preposition, or a subordinate clause, and the semicolons become a comma or two sentences, which is what GOVERNANCE prescribes in each case. Two are worth naming. The bullets under Source Code and Versioned Releases used `[link] - explanation`, which is not the `- **Label** - explanation` separator the rule exempts, so the dash was a real clause break and now reads `[link] for explanation`. The Python bullet carried both faults in one line and splits into two sentences. Nothing about the content changed, only its construction. `TODO.md` drops the entry. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 22 +++++++++++----------- TODO.md | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ffd9c874..b5859c3b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Agent enablement for a fleet of repositories: autonomy and repeatable quality in ## Build and Distribution -- **Source Code**: [GitHub][projecttemplate-link] - source, issues, discussions, and CI/CD pipelines. -- **Versioned Releases**: [GitHub Releases][releases-link] - version-tagged source archives. +- **Source Code**: [GitHub][projecttemplate-link] for source, issues, discussions, and CI/CD pipelines. +- **Versioned Releases**: [GitHub Releases][releases-link] for version-tagged source archives. ### Build Status @@ -91,10 +91,10 @@ Keeping a fleet of repositories consistent has always been a tax paid in review ProjectTemplate follows the same model it documents, and audits its own rules against itself (it classifies as the source-only project type in [WORKFLOW.md][workflow]). -- **Branching.** Persistent `main` and `develop`, each with its own ruleset. This repo uses the default `release` workflow model: commit on feature branches only, feature branch to `develop` is squash-merged, `develop` to `main` is a merge commit, and `develop` is forward-only (no `main -> develop` back-merges). Live-service config repos instead use the `operational` model (registry `workflowModel`) - direct signed commits to `develop`, promoted to `main` by an occasional PR. See [GOVERNANCE.md "Branching Model"][governance-branching-model]. -- **CI is lint-only.** There is no build or unit test; the PR gate runs markdownlint, cspell, JSON validation (`jq` parses `registry/`, `spec/`, and `repo-config/`, plus the `spec/validate.py` cross-reference and shape checks), and actionlint, and exposes the ruleset-bound `Check pull request workflow status job` aggregator. The same lint configs (`.markdownlint-cli2.jsonc`, `cspell.json`) drive the editor extensions, the CLI, and CI. +- **Branching.** Persistent `main` and `develop`, each with its own ruleset. This repo uses the default `release` workflow model: commit on feature branches only, feature branch to `develop` is squash-merged, `develop` to `main` is a merge commit, and `develop` is forward-only (no `main -> develop` back-merges). Live-service config repos instead use the `operational` model (registry `workflowModel`), with direct signed commits to `develop`, promoted to `main` by an occasional PR. See [GOVERNANCE.md "Branching Model"][governance-branching-model]. +- **CI is lint-only.** There is no build or unit test. The PR gate runs markdownlint, cspell, JSON validation (`jq` parses `registry/`, `spec/`, and `repo-config/`, plus the `spec/validate.py` cross-reference and shape checks), and actionlint, and exposes the ruleset-bound `Check pull request workflow status job` aggregator. The same lint configs (`.markdownlint-cli2.jsonc`, `cspell.json`) drive the editor extensions, the CLI, and CI. - **Review loop.** Every PR is reviewed by GitHub Copilot, and the agent drives the review loop to green and merges only with explicit maintainer permission. See [GOVERNANCE.md "PR Review Etiquette"][governance-pr-review-etiquette]. -- **Release.** A `develop -> main` merge is promoted through a GitHub release (tag plus a source zip, README, and LICENSE); versioning is NBGV-driven from [version.json][version]. See [WORKFLOW.md][workflow]. +- **Release.** A `develop -> main` merge is promoted through a GitHub release (tag plus a source zip, README, and LICENSE). Versioning is NBGV-driven from [version.json][version]. See [WORKFLOW.md][workflow]. ## Rules @@ -103,23 +103,23 @@ A human-readable index of the rules agents enforce, implement, and audit. The au ### Always - Sign every commit (SSH or GPG). -- Branch feature -> develop (squash) -> main (merge commit); develop is forward-only. +- Branch feature -> develop (squash) -> main (merge commit), and develop is forward-only. - Drive every PR through the Copilot review loop and merge only with maintainer approval. - Write US English and ASCII only (no em-dash, straight quotes). -- Write docs and comments in the present tense, describing only the current state - never as a change from a prior one. +- Write docs and comments in the present tense, describing only the current state, never as a change from a prior one. - Keep comments concise and only for the non-obvious, and never grow them on edit. - Follow `.editorconfig` line endings (CRLF default, LF for shell and Docker) and preserve a file's endings on edit. - One logical paragraph per line, with a trailing `\` for an intentional hard break. - Pin every GitHub Action to a commit SHA with a version comment. - Share one lint config per tool across the editor, the CLI, and CI. - Run the repo's whole lint gate before pushing, not just the parts that look relevant. -- Make gates fail loud - a gate that stops gating must error or annotate, never pass silently. +- Make gates fail loud, since a gate that stops gating must error or annotate, never pass silently. - Favor VS Code tasks and launch configs for building, running, and testing over ad-hoc shell scripts. ### Never - Never force-push or rewrite shared history. -- Never treat a merge as a release; publishing is a separate, explicit step. +- Never treat a merge as a release. Publishing is a separate, explicit step. - Never blanket-delete a workflow run's artifacts. - Never store a static key when OIDC Trusted Publishing is available. @@ -129,11 +129,11 @@ 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, or mypy in CI with pyright editor-only; whichever runs in CI is the gate. +- 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. ### If Both C# and Python -- Both sections above apply; a repo can be both (a C# app plus a Python subtree). The Python is either a full uv project (`uv.lock`, `uv run`) or a stdlib-only `uvx` scripts subtree (no `uv.lock`, `pyproject.toml` carries lint/type config only). See [CODESTYLE.md][codestyle] "Two profiles". +- Both sections above apply, and a repo can be both (a C# app plus a Python subtree). The Python is either a full uv project (`uv.lock`, `uv run`) or a stdlib-only `uvx` scripts subtree (no `uv.lock`, `pyproject.toml` carries lint/type config only). See [CODESTYLE.md][codestyle] "Two profiles". ### If Publishing a Package (NuGet or PyPI) diff --git a/TODO.md b/TODO.md index e07d430b..f5b85d38 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,6 @@ Running backlog for this repo, kept in a committed file so the guidance survives - Refresh the README (it has gone stale) and evaluate a lower-maintenance structure, for example a per-section index that points into each doc with a one-line description, keeping the README as the adoption and audit-instruction entry point with pointers to the other docs. A per-section index trades brevity for a sync obligation: it must track what the docs contain. - Add a linter-only Python project type for codegen/boilerplate Python, code that runs during another tool's build to emit generated source (e.g. ESPHome codegen that produces enriched C++ at compile time), so it ships no unit tests and no coverage and needs only the linter. Keep it distinct from the existing `python` type, which is utility code that can and should carry unit tests and coverage (as in PlexCleaner). Until it exists, ESPHome-Config stays `source-only` and its `+python` reclassification is deferred, so accept its one outstanding validation finding meanwhile. - Add a fleet-standard clang-format config for the `cpp` type: a catalog snippet plus a CODESTYLE C++ section defining the style, the C++ analogue of the shared ruff config, so the `cpp` clang-format check references one canonical style rather than each repo inventing its own. Base it on the ESPHome-Config agent's proposed `.clang-format`. -- Sweep the 12 `dash` and `semicolon` findings in `README.md`. The edits that made this conflict-prone have landed, so it is now free to do. - Re-vendor `repo-config/configure.sh` across the fleet. The hub swept it to one sentence per line, and it is carried `verbatim` with `appliesTo: "*"`, so every repo already holding a copy is byte-mismatched against the hub until it takes the new one. From 10e03ec2b25caf146ceffdcf152e5abb0f89f5d1 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 07:43:02 -0700 Subject: [PATCH 4/5] Record the local-secrets standard as fleet backlog spec/secrets.json declares only the Actions and Dependabot stores, so a repo that deploys somewhere has no declared way to state what it needs at runtime. The required set is then discoverable only by reading the deploy. Two shapes of the answer already run in the fleet. HomeAutomation-Config keeps a gitignored secrets directory of env files and Docker secret files, and ESPHome-Config keeps a gitignored secrets.yaml beside a committed _secrets.yaml. The committed file carries the required names with dummy values, which is the same split the GitHub side already gets from requiredSecrets[]. Blog needs it immediately, since it deploys on the proxmox host through HomeAutomation-Config's Docker Compose stack and carries the copy destinations and the internal URI. Recorded rather than designed: the hub carries neither the directory nor a .gitignore entry for one today, so adopting it here comes before making it a fleet standard. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index f5b85d38..f5aac9c1 100644 --- a/TODO.md +++ b/TODO.md @@ -11,9 +11,11 @@ Running backlog for this repo, kept in a committed file so the guidance survives - Refresh the README (it has gone stale) and evaluate a lower-maintenance structure, for example a per-section index that points into each doc with a one-line description, keeping the README as the adoption and audit-instruction entry point with pointers to the other docs. A per-section index trades brevity for a sync obligation: it must track what the docs contain. - Add a linter-only Python project type for codegen/boilerplate Python, code that runs during another tool's build to emit generated source (e.g. ESPHome codegen that produces enriched C++ at compile time), so it ships no unit tests and no coverage and needs only the linter. Keep it distinct from the existing `python` type, which is utility code that can and should carry unit tests and coverage (as in PlexCleaner). Until it exists, ESPHome-Config stays `source-only` and its `+python` reclassification is deferred, so accept its one outstanding validation finding meanwhile. - Add a fleet-standard clang-format config for the `cpp` type: a catalog snippet plus a CODESTYLE C++ section defining the style, the C++ analogue of the shared ruff config, so the `cpp` clang-format check references one canonical style rather than each repo inventing its own. Base it on the ESPHome-Config agent's proposed `.clang-format`. +- Declare locally-required secrets the way GitHub-stored ones are already declared, and make a gitignored `secrets/` directory the fleet standard that holds them. [`spec/secrets.json`][secrets] covers only the Actions and Dependabot stores, so a repo that deploys somewhere has no declared way to say what it needs at runtime, and the required set is discoverable only by reading the deploy. The pattern already runs in the fleet in two shapes: HomeAutomation-Config keeps a gitignored secrets directory of env files and Docker secret files, and ESPHome-Config keeps a gitignored `secrets.yaml` beside a committed `_secrets.yaml`. The committed file carries the required names with dummy values, so the shape of the requirement is in git while the values never are, which is the same split the GitHub side already gets from `requiredSecrets[]`. Blog needs it immediately, since it deploys on the proxmox host through HomeAutomation-Config's Docker Compose stack and carries the copy destinations and the internal URI. The hub carries neither the directory nor a `.gitignore` entry for one today, so adopting it here comes first. - Re-vendor `repo-config/configure.sh` across the fleet. The hub swept it to one sentence per line, and it is carried `verbatim` with `appliesTo: "*"`, so every repo already holding a copy is byte-mismatched against the hub until it takes the new one. [matrix]: ./reports/conformance-matrix.md [reports]: ./reports/ +[secrets]: ./spec/secrets.json From a51e881d46338af2e70da5e835302238a86322ed Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 07:49:51 -0700 Subject: [PATCH 5/5] Record the onboarding-branch gap as fleet backlog Standing up a new repo pre-creates main and develop, which is right, and then commits onto develop, which is not. The initial commits belong on a feature branch that reaches develop by squash. The two reasons do not carry equally, which is the part worth documenting. A release repo's develop.json carries a pull_request rule, so a direct commit is blocked and the agent learns the rule by hitting it. An operational repo's payload carries only deletion, non_fast_forward and required_signatures, so a direct commit succeeds and nothing corrects it. The reason holding for both is that the squash collapses the exploratory onboarding history, where PII, secrets and noise accumulate, and a public repo gates on that history. There is no recovery afterwards: non_fast_forward is set on both develop payloads, so what reaches develop cannot be rewritten. Blog hit this during its standup and moved to a feature branch. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index f5aac9c1..882da1ec 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,7 @@ Running backlog for this repo, kept in a committed file so the guidance survives - Refresh the README (it has gone stale) and evaluate a lower-maintenance structure, for example a per-section index that points into each doc with a one-line description, keeping the README as the adoption and audit-instruction entry point with pointers to the other docs. A per-section index trades brevity for a sync obligation: it must track what the docs contain. - Add a linter-only Python project type for codegen/boilerplate Python, code that runs during another tool's build to emit generated source (e.g. ESPHome codegen that produces enriched C++ at compile time), so it ships no unit tests and no coverage and needs only the linter. Keep it distinct from the existing `python` type, which is utility code that can and should carry unit tests and coverage (as in PlexCleaner). Until it exists, ESPHome-Config stays `source-only` and its `+python` reclassification is deferred, so accept its one outstanding validation finding meanwhile. - Add a fleet-standard clang-format config for the `cpp` type: a catalog snippet plus a CODESTYLE C++ section defining the style, the C++ analogue of the shared ruff config, so the `cpp` clang-format check references one canonical style rather than each repo inventing its own. Base it on the ESPHome-Config agent's proposed `.clang-format`. +- Document in [`STANDUP.md`][standup] that the initial onboarding commits belong on a feature branch and reach `develop` by squash, never as direct commits to `develop`. Pre-creating `main` and `develop` is right, and committing onto `develop` from there is the wrong next step. Section 0 already teaches the same shape of trap for signing, that the window closes at the first commit, so branch placement belongs beside it. The two reasons do not carry equally across the workflow models, which is the part worth stating rather than assuming. On a `release` repo `repo-config/develop.json` carries a `pull_request` rule, so a direct commit is simply blocked and an agent discovers the rule by hitting it. On an `operational` repo `repo-config/operational/develop.json` carries only `deletion`, `non_fast_forward` and `required_signatures`, so a direct commit succeeds and nothing corrects it, which is exactly why the instruction has to carry the weight there. The reason that holds for both is that the squash collapses the exploratory onboarding history, which is where PII, secrets and noise commits accumulate, and a public repo treats that history as a hard gate. There is no recovery afterwards, since `non_fast_forward` is set on both `develop` payloads, so history that reaches `develop` cannot be rewritten without disabling the ruleset. Blog hit this during its standup and moved to a feature branch. - Declare locally-required secrets the way GitHub-stored ones are already declared, and make a gitignored `secrets/` directory the fleet standard that holds them. [`spec/secrets.json`][secrets] covers only the Actions and Dependabot stores, so a repo that deploys somewhere has no declared way to say what it needs at runtime, and the required set is discoverable only by reading the deploy. The pattern already runs in the fleet in two shapes: HomeAutomation-Config keeps a gitignored secrets directory of env files and Docker secret files, and ESPHome-Config keeps a gitignored `secrets.yaml` beside a committed `_secrets.yaml`. The committed file carries the required names with dummy values, so the shape of the requirement is in git while the values never are, which is the same split the GitHub side already gets from `requiredSecrets[]`. Blog needs it immediately, since it deploys on the proxmox host through HomeAutomation-Config's Docker Compose stack and carries the copy destinations and the internal URI. The hub carries neither the directory nor a `.gitignore` entry for one today, so adopting it here comes first. - Re-vendor `repo-config/configure.sh` across the fleet. The hub swept it to one sentence per line, and it is carried `verbatim` with `appliesTo: "*"`, so every repo already holding a copy is byte-mismatched against the hub until it takes the new one. @@ -19,3 +20,4 @@ Running backlog for this repo, kept in a committed file so the guidance survives [matrix]: ./reports/conformance-matrix.md [reports]: ./reports/ [secrets]: ./spec/secrets.json +[standup]: ./STANDUP.md