Conversation
… blessing (#333) Incorporates the hub-side findings filed from the Vantage-Config operational onboarding (Vantage-Config #9). Fixes #328, fixes #329, fixes #331. (#330, the downstream carry-drift sweep, is handled by per-repo PRs, not here.) ## AGENTS.md: every-tier bots (#328) "Foundational Principles" claimed operational repos "run no bots and commit directly to `develop`" - contradicting the owner-confirmed every-tier merge-bot policy (and reality: Vantage-Config runs Dependabot dual-target plus the App-signed merge-bot). Reworded: operational repos run no **codegen or auto-publish** bots; Dependabot's dual-target sync and the App-signed merge-bot run on **every** tier. A next operational adoptee reading the old wording would have skipped the merge-bot standup. ## Registry: Vantage-Config driftNotes (#329) The entry recorded onboarding as "pending once content lands"; it completed 2026-07-16 per Vantage-Config #9. The stale note is replaced with the completed state (baseline docs, advisory lint CI, dispatch-only publisher, repo-config operational carry verified in sync, Dependabot + App merge-bot with the secret pair in both stores, adapted self-audit). `spec/validate.py` passes. ## Downstream Carry: reference self-audit shape (#331) "Adapted self-audit carry" required every downstream repo to carry adapted `AUDIT.md` + `spec/secrets.json` but shipped no reference shape - Vantage-Config had to invent the first one. Now: - The Vantage-Config carry is blessed as the reference adaptation (settings diff, normalized ruleset diff against the carried payloads, names-only secrets check, all targeting the current repo); a `release` repo adapts the same shape with its `develop.json` payload and its publish mechanisms' secret names. - `spec/files.json` letter-checks both carried files, so the fleet audit surfaces repos that don't carry them yet (today: all but Vantage-Config; the #330 sweep starts closing that). ## Verification - `python3 spec/validate.py`: OK (21 cataloged, 0 backlog). - markdownlint, cspell (CI scope), editorconfig-checker clean on the changed files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #332 (core gap + the six bundled nits from the #330 sweep reviews; two of the six live in the blessed downstream reference and propagate via the Vantage-Config carry, not this repo - see below). ## configure.sh: model inference on downstream carries In a carry the registry is absent, so the no-arg form defaulted to `release` and then hit the intended fail-safe abort on operational carries (missing `develop.json`) - arg 2 was effectively mandatory there. Now the model is inferred from which `develop` payload is carried (a carry holds exactly its own model's); ambiguous layouts (both or neither - e.g. a partial copy) abort rather than guess. Also: the model may be passed as the sole argument (`configure.sh operational`) - previously arg 1 was always parsed as a repo name (bundled nit, Utilities #417). Matrix-tested with the apply path stubbed: infer release, infer operational, both-present abort (exit 1), neither-present abort (exit 1), explicit arg-2 override, model-as-sole-arg, unknown-model abort. Hub behavior unchanged (registry present -> registry lookup, as before). shellcheck + bash -n clean. ## Ruleset snippet hardening (duplicate-name id corruption) The `id=$(gh api ... select(.name==...).id)` pattern in the README regen snippet and AUDIT.md section 6 produced a **multiline id** when duplicate same-name rulesets exist, silently corrupting the follow-up API path (bundled nit, Utilities #417 + LanguageTags #278). Both snippets now fetch the list once and enforce **exactly one** ruleset per name: the regen snippet fails loudly (never regen from a guess); the audit snippet reports `found N (defect/drift)` and continues - matching the declared drift semantics. Live-tested read-only against this repo (develop + main in sync; duplicate simulation counts correctly). ## README wording Documents the inference fallback and the model-only argument form (bundled nit, Utilities #417). ## Bundled nits NOT in this PR (with rationale) - `gh api --input -` portability (MediaTools #19): declined - fleet standard is current gh; not worth temp-file churn. - jq-derived secret name lists + `--paginate` in the secrets check (MediaTools #19, LanguageTags #278, PlexCleaner #854): these live in the **blessed downstream self-audit reference** (the Vantage-Config carry), not in hub files - propagated there directly after this merges (the hub's fleet audit reads `spec/secrets.json` programmatically already). ## Propagation caveat This re-drifts the five existing carries (Vantage-Config + the four #330 sweep repos) until their next conformance touch; Vantage-Config is updated immediately after this merges since it is the blessed reference the queued operational standups (HomeAssistant-Config #16, ESPHome-Config #46, HomeAutomation-Config #21) will adapt from. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconciles the HomeAutomation-Config registry entry after the #21 conformance carry landed on its develop (54637c4, 2026-07-16) - same close-out pattern as #329 did for Vantage-Config. - "Operational rollout pending" was stale: lint CI (test-pull-request.yml feeding the required check), the dispatch-only publisher (publish-release.yml + version.json), and develop-as-ground-truth were already live; the carry added the repo-config operational baseline (rulesets/settings verified in sync via the carried self-audit), the adapted AUDIT.md + spec/secrets.json, and the Dependabot merge-bot. - The "strip the legacy Vantage/ subtree" instruction is updated to record it as done (verified: no such tree on develop). - The outstanding owner task (App secret pair in both stores; merge-bot inert until then) is recorded so the entry reflects reality. `spec/validate.py` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…337) Fixes #336: the branch-drift check derived "main carries content develop lacks" from the three-dot compare alone, which lists files changed on main since the merge-base and is blind to cherry-picked (patch-equivalent) promotions. Any repo promoting via cherry-pick branches re-triggered the finding on every audit - HomeAutomation-Config #21's forward-sync task was authored and worked against exactly this false positive. ## Fix Post-filter the compare's `files[]` by **blob equality at the two heads** (one `git/trees?recursive=1` call per head, as the issue suggested): a path whose blob SHA matches at `develop` and `main` is content develop already has, not content it lacks. Only the remainder raises the DRIFT finding. - The finding now **names the lacking files** (up to 8) - addressing the issue's impact note that "N+ changed file(s)" gave a downstream reader nothing to spot-check. - If either tree comes back `truncated` (very large repos), the filter is skipped and the unfiltered finding is kept, marked "tree too large to blob-filter cherry-pick noise" - conservative, never silently suppressing. - Deletions on main still flag (path absent from main's blobs but present on develop's is a blob mismatch), as do renames. ## Verification (live, read-only) - **False positive resolved**: `python3 spec/audit.py HomeAutomation-Config` -> clean (previously: `DRIFT branch: main carries 2+ changed file(s)...` from the cherry-picked cspell-scope promotions; both files are blob-identical at the heads). - **Genuine positives still fire, now with names**: homeassistant-purpleair -> `main carries 2 file(s) develop lacks: requirements-test.txt, requirements.txt`; DevKitCIoT -> `main carries 12 file(s) develop lacks: .editorconfig, ...` (first 8 shown). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the stale `HomeAssistant-Config` registry `driftNotes` after the [`#16`](ptr727/HomeAssistant-Config#16) operational onboarding. Mirrors the `#329` reconciliation for Vantage-Config. ## What changed The prior driftNotes described work that is now complete and planned a `groundTruthBranch` flip that is declined: - "develop is behind main by 2 commits / fast-forward then flip groundTruthBranch to develop" - develop is content-ahead; the flip is **declined** (see below). - "pending: lint CI" - `test-pull-request.yml` with the `Check pull request workflow status job` required check is live. - "pending: dispatch-only source-release scaffolding (version.json + publish-release.yml)" - both present. Replaced with a completed-onboarding summary: baseline carried (repo-config operational, `AUDIT.md`, `spec/secrets.json`, App-signed Dependabot merge-bot) and promoted `develop -> main` via HomeAssistant-Config PR `#17`. Self-audit verified in sync (settings, both rulesets, `CODEGEN_APP_*` present in both stores). ## groundTruthBranch stays `main` (deliberate) Kept `groundTruthBranch: main`, **not** flipped to `develop`. develop is the working branch (direct signed commits); main is the promoted stable snapshot the audit should target. This is a deliberate divergence from the other operational repos (Vantage-Config / ESPHome-Config / HomeAutomation-Config use `develop`); the broader convention question is raised in #340. ## Findings note (context, see #341) The `#16` audit block was stale: the `CODEGEN_APP_*` secrets were already present in both stores and develop was already ahead of main. Only the `repo-config/... absent` LETTER finding was real, and it is now cleared on `main` by PR `#17`. Validation: `jq` parses clean, `spec/validate.py` -> "21 cataloged, 0 backlog repos classify cleanly." 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #339. Registers the fleet's first mixed-language shape (PlexCleaner: a .NET console app plus a stdlib-only Python tooling subtree) and codifies how the `csharp` and `python` baselines coexist. Reference implementation: ptr727/PlexCleaner#855. Per the issue, this is **not** a new `polyglot` type - a mixed repo is expressed as `types: ["csharp","console","docker","python"]` and the audit runs the union of the per-type checks. The work is: register the shape, codify the coexistence rules, add a matrix row. ## Three decisions (settled with the owner before writing) 1. **Two python profiles, one type, split by a third-party runtime import.** PROJECT = a PEP 621 uv project with runtime deps + committed LF-pinned `uv.lock` (the existing Financial-Modeling shape). SCRIPTS = stdlib-only utility scripts embedded in a non-Python repo, run via `uvx`, no lockfile, `pyproject.toml` carrying only `[tool.ruff]`/`[tool.mypy]`. Detected structurally from `pyproject.toml`. 2. **Tool-version pinning for SCRIPTS is CI-only.** CI pins exact versions in the `uvx` command (`uvx ruff@X`, bumpable there); tasks/README run latest - a deliberate CI-vs-local gap so local never silently falls behind. PROJECT keeps pinning through `uv.lock` + `uv sync --frozen` (unchanged). 3. **Coverage N/A for SCRIPTS.** `python.coverage.codecov` is N/A for a lint/type-only subtree (no pytest); `codecov.yml` presence stays required by any co-present type with tests (the C# side here). ## Changes | File | Change | |---|---| | `registry/repos.json` | PlexCleaner `types` += `python`; driftNote records the SCRIPTS subtree + which checks go N/A | | `spec/project-types.json` | `python` type: `profileNote` + new `python.profile.detect`; SCRIPTS-profile N/A wording on `pyright`/`coverage`/`uvlock`; new `python.scripts.uvx` check | | `CODESTYLE.md` | "Two profiles" paragraph (project vs scripts: uvx, tool-config-only pyproject, mypy, CI-pinned/local-latest, `.py` on repo EOL default, coverage N/A) | | `README.md` `## Rules` | Python bullet names the type-checker choice; new "If Both C# and Python" both-apply subsection | | `reports/conformance-matrix.md` | `csharp` + `python` shape row (PlexCleaner) | | `cspell.json` | `bumpable`, `stdlib`, `uvx` | ## Issue items mapped - **(1) line endings** - CODESTYLE "Scripts" bullet states `.py`/`.toml` follow the repo CRLF default (only a shebang-executed script is LF-pinned), so nobody "fixes" `.py` to LF; `recurring.eol` already covers the general rule. - **(2) uvx-vs-uv-project** - `python.profile.detect` + CODESTYLE "Two profiles". - **(3) CI-only pinning** - `python.scripts.uvx` + CODESTYLE. - **(4) coverage** - `python.coverage.codecov` N/A wording. - **(5) tasks.json union** - confirmed, **no change needed**: `spec/files.json` already carries separate `csharp` and `python` `.vscode/tasks.json` references, so a mixed repo satisfies both by union (that is the intended audit behavior). - **(6) docs both-apply** - README "If Both C# and Python" + CODESTYLE "Two profiles". ## Validation - `python3 spec/validate.py` -> OK (21 cataloged, 0 backlog). - markdownlint + editorconfig-checker clean on changed files; CI-scoped cspell (README + HISTORY) clean. Related: #306 (closed - source-only Python adaptation gaps), #310 (open - the conformance-matrix row here is a `not-tested` entry pending that issue's cold-start self-test). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce rule (#344) A file the fleet carries verbatim must name no sibling fleet repo and link none. Raised by the ESPHome-Config adoption: the `repo-config/README.md` "blessed reference" pointed a `[vantage-config]` link at the **private** Vantage-Config, which **404s in every public carrier** (PlexCleaner, LanguageTags, Utilities already carry it). Two reasons, both general: any fleet repo may be private -> a cross-repo link 404s in a public carrier; and a cross-repo reference couples the repos and rots as they diverge. ## The rule (now codified in AGENTS.md) A carried file (`AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, `README.md`, `.github/copilot-instructions.md`, `repo-config/*`, `spec/*`, the carried `AUDIT.md`) is **repo-agnostic**: it describes the shape/pattern self-containedly and names a sibling repo **only where that repo is the subject**. A current good example is named in the **onboarding/conformance issue** (one-off, not carried) or the hub-only **`reports/conformance-matrix.md`** (a fleet tracking table) - never a carried doc. This pairs with the present-tense rule: state the current shape, not its provenance. ## Changes | File | Change | |---|---| | `repo-config/README.md` | Remove the `[vantage-config]` link + External group; the self-audit-carry bullet describes the adapted `AUDIT.md` shape self-containedly, pointing to the issue for a current example | | `AGENTS.md` | New rule under Documentation Style Conventions; the mixed-consumer-config example keeps its Windows-editor/CRLF **context** but drops the repo names | | `CODESTYLE.md` | Logging-seam and Python "Two profiles" examples describe the pattern, no repo names | | `spec/project-types.json` | `python` profileNote genericized; new `recurring.norepoxref` audit check | | `spec/secrets.json` | Coverage-threshold note no longer names repos | Hub-only files (`registry/repos.json`, `reports/conformance-matrix.md`) keep repo names - naming repos **is** their subject and they are not carried. ## Rollout Hub only. The 6 repos that already carry the bad link (public: PlexCleaner, LanguageTags, Utilities - live 404; private: Vantage-Config, HomeAutomation-Config, MediaTools) correct on each repo's **next conformance touch**. ESPHome-Config's in-flight adoption carries the fixed hub `repo-config/README.md`, so it never inherits the link. ## Validation - `python3 spec/validate.py` -> OK; markdownlint + editorconfig-checker clean. - Grep confirms no residual illustrative cross-repo reference in any carried file, and the Vantage URL is gone from all carried paths. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…346) (#347) Fixes the audit-coverage half of #346. (The concrete ESPHome-Config drift is filed as a separate downstream issue for that repo to fix.) ## The gap `spec/audit.py` checked Dependabot **secret stores** but never that a repo's `.github/dependabot.yml` declares the **ecosystems its tree implies**. So ESPHome-Config - which ships `test-pull-request.yml` / `publish-release.yml` / `merge-bot-pull-request.yml` with SHA-pinned actions but tracks only `devcontainers` - passed the standup audit clean, even though its action pins get no bump PRs and the merge-bot it just stood up has no action-update PRs to act on. ## The check New crossCutting check `setup.dependabot.ecosystems` (repo-setup dimension): for each ecosystem the tree implies, `dependabot.yml` must declare it (dual-target `main`+`develop` per the fleet norm): - **`github-actions`** when `.github/workflows/` ships actions, - **`devcontainers`** when a `.devcontainer` is present. A missing implied ecosystem is a **DRIFT** finding. `dependabot.yml` is YAML (audit.py is stdlib-only, no parser), so the declared `package-ecosystem` values are scanned by regex - enough to assert presence. The check only runs when the file exists (its absence is already a file-presence LETTER). Language ecosystems (`nuget`/`uv`/`npm`) are directory-scoped and left to inspection for now (noted in AUDIT.md). Codified in `AUDIT.md` section 6 (with a shell snippet) and `spec/project-types.json`. ## Verification (live, read-only) ``` ESPHome-Config -> DRIFT dependabot: github-actions ecosystem not declared though .github/workflows/ ships SHA-pinned actions Vantage-Config -> clean HomeAutomation-Config -> clean PlexCleaner -> clean Utilities -> clean aiopurpleair -> clean ``` The `devcontainers` ecosystem ESPHome-Config *does* declare is correctly not flagged. `spec/validate.py` passes; markdownlint + editorconfig-checker clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…esolved (#348) Reconciles the ESPHome-Config registry entry after its conformance standup (ESPHome-Config#46) and the Dependabot ecosystem fix (ESPHome-Config#49 / promotion #50) completed - same close-out pattern as #329/#335. The second `driftNote` recorded the operational rollout as pending and Dependabot as `devcontainers`-only. Both are resolved and **verified from the live repo** before dropping the note: - esphome-config validation job feeds the required check; `version.json` + `publish-release.yml` present; **2.0.12** released 2026-07-18; `develop` is ground truth. - `.github/dependabot.yml` declares `github-actions` **dual-target** on both `develop` and `main`. - `python3 spec/audit.py ESPHome-Config` (with the #346 check now on develop) -> **clean**; no `dependabot` DRIFT. The first note (ESPHome device config consumed by the ESPHome-NonRoot image) stays - still true. `spec/validate.py` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…only doc (#345) (#349) Fixes #345 (both findings). Surfaced when ESPHome-Config became the first **public** repo to carry `repo-config/README.md`. ## Finding 1: carry-process narration shipped downstream `repo-config/README.md` is carried verbatim by every fleet repo, but it shipped carry-**process** guidance - `## Downstream Carry`, the ruleset import/regen procedure, and the maintainer `## Brownfield Migration`. That is meta-guidance for whoever performs the carry, not a current fact about the repo the file lands in (it's also what hosted the private-repo link #344 removed). **Split** (the settled approach): - **New hub-only `docs/repo-config-carry.md`** holds the process: Downstream Carry, Applying the Config, Regenerating the Payloads, Brownfield Migration. It's not in `spec/files.json` baseline, so it's never carried; being hub-only it may name repos and link the hub freely. - **`repo-config/README.md` trims to current facts** about a repo's own config: file descriptions, what the rulesets enforce, the secrets each mechanism needs, the general settings. - **Cross-refs fixed** (carried docs can't link a hub-only doc): `AGENTS.md` plain-text-mentions the hub doc for the full procedure/brownfield and drops two stale anchors (`Rules / Rulesets`, `Template - GitHub Setup`); `STANDUP.md` and `spec/files.json` (both hub-only) link the new doc directly. ## Finding 2: repo-scoped secrets schema clarity The rule for the adapted `spec/secrets.json` now lives in the hub doc: a **source-only** repo whose publish targets all map to a null mechanism carries just `baseline` (+ `note`) - it omits `targetMechanisms`, since a lone routing map with no `mechanisms` reads as "the audit ignores the mechanism list" (Copilot tripped on it 3x across the standup PRs). A repo with a real mechanism carries `mechanisms` **and** its `targetMechanisms` routing, which the audit picks up. ## Rollout Hub only. Downstream repos' carried `repo-config/README.md` (and any source-only `spec/secrets.json` still carrying an all-null `targetMechanisms`) correct on each repo's next conformance touch - consistent with the prior sweeps. ESPHome-Config already stripped both in its standup. ## Validation `spec/validate.py` OK; markdownlint + editorconfig-checker clean on all changed files; CI-scoped cspell (README+HISTORY) clean; grep confirms no dangling references to the moved sections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #338 - but not as filed. Worth reading the first section before the diff. ## The issue's premise was inverted #338 states the fleet mandates `set -Eeuo pipefail` and that `repo-config/configure.sh` violates it. Verified against the hub: - `AGENTS.md` said **`set -euo pipefail`**, and `configure.sh` used exactly that - **they already agreed**. - The `-Eeuo` mandate exists only in **HomeAutomation-Config's carried `AGENTS.md`**, which has drifted from the hub. - There are **no `trap ... ERR` anywhere in the fleet**, so `-E` is functionally inert today. Taking the issue at face value would have changed the canonical hub script to match a *drifted downstream copy*. Instead, per the owner's call: adopt `-Eeuo` at the hub as the better default and let the carries converge on it. (The divergence itself is more evidence for #305.) ## Changes - **Swept all 26 occurrences** to `set -Eeuo pipefail`: the 4 hub workflows, `repo-config/configure.sh`, the 7 catalog workflow snippets, and both devcontainer `post-create.sh` scripts. - **`AGENTS.md` rule prose** now states the rationale (`-E` lets an `ERR` trap inherit into functions/subshells/command substitutions; defense in depth since the fleet ships no trap yet) **and resolves the scope ambiguity** #338's last paragraph raises - it covers "every bash surface: a multi-line `run:` block and every committed `.sh` script alike", so inline snippets are explicitly in scope. - **`WORKFLOW.md`** style bullet and D9.3 match. ## Verification - shellcheck + `bash -n` clean on all three scripts. - actionlint clean on the hub workflows (snippet fragments show only the pre-existing "reusable workflow not found" note). - `configure.sh` re-run in place: still resolves `release` and `operational` correctly. - markdownlint, editorconfig-checker, `spec/validate.py`, CI-scoped cspell: clean. CRLF/LF endings preserved (swept with `sed`, which leaves line endings untouched). Downstream carries pick this up on their next conformance touch, as usual. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) (#351) Fixes #340 - and it turns out to be a **reconciliation, not a policy change**. ## What I found `AUDIT.md` section 1 already says, with **no model exception**: > Read the target's **`main` branch** as ground truth: `main` is the released, authoritative state. But three operational entries in `registry/repos.json` named `develop` - Vantage-Config, ESPHome-Config, HomeAutomation-Config. So the registry was **contradicting the hub's own documented rule**; #340's proposal is what the docs already required. (HomeAssistant-Config was correctly on `main`.) ## Changes - **`registry/repos.json`**: the three operational entries flip to `groundTruthBranch: main`. - **`AUDIT.md` section 1**: states the both-models rule explicitly, since that is precisely where the ambiguity arose - an operational repo commits directly to `develop`, but its ground truth is still `main`, the promoted snapshot; `develop` is mid-flight by design, so conformance work landed there but not yet promoted is *un-promoted work*, not a defect. A `groundTruthBranch` naming `develop` contradicts the section, for either model. ## Verification (the risk was manufacturing false findings) Before flipping, I confirmed **all four conformance files** (`repo-config/main.json`, `repo-config/operational/develop.json`, `AUDIT.md`, `spec/secrets.json`) are **already present on `main`** in all three repos - so measuring `main` surfaces nothing new. ```text BEFORE (develop ground truth): Vantage/ESPHome/HomeAutomation -> all clean AFTER (main ground truth): all four operational repos -> all clean, 0 findings ``` `spec/validate.py`, markdownlint, editorconfig-checker: clean. ## Out of scope here: four `release` repos also name `develop` Checking the fleet surfaced that **KiCadLibrary, EspDinIoT, PhotoCleaner, and HolidayLights** (all `release` model, all with a `main` branch) also carry `groundTruthBranch: develop` - the same contradiction. They are **deliberately not flipped in this PR**, because unlike the operational three their `main` is stale, so the flip is not mechanical: | repo | baseline files present on `main` | |---|---| | KiCadLibrary | 4/4 | | PhotoCleaner | 2/4 | | HolidayLights | 1/4 | | EspDinIoT | 0/4 | Flipping them would surface real-but-voluminous findings (their released state genuinely is non-conformant - which is arguably the correct signal, but it is a scope and noise decision for the owner, not a mechanical reconciliation). Raised for a separate call. ## Note on the counter-view #340 raises deploy-from-develop as the one case for a `develop` ground truth, and suggests a separate `deployBranch` field rather than overloading `groundTruthBranch`. No fleet repo deploys from `develop` today, so I have not added the field - cheap to add later if one genuinely needs it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #341. Both halves are the same failure mode: **a point-in-time snapshot presented as current state**, so an agent picking it up "fixes" what is already fixed. ## 1. Freshness signal on every run `spec/audit.py` now prints: ```text audit run 2026-07-18T15:11:28Z | hub bd48caf == HomeAutomation-Config (source-only; operational) @ main@6fadcbe == clean (deterministic checks; the full operational verdict is AUDIT.md's) ... Findings are a point-in-time snapshot: re-run this audit before acting on them, and quote the run stamp above in any issue derived from it (AUDIT.md section 8). ``` - a **run stamp** (UTC + the hub commit the ground truth came from), and - **per repo, the exact commit read** (`@ <branch>@<sha>`) - so a finding is attributable to a specific state. `AUDIT.md` section 8 now requires anything derived from a run - a report, and **especially an onboarding/conformance issue** - to quote the stamp, and requires the agent picking that issue up to **re-run the audit and act on the live result, not the pasted findings**. Findings are evidence for *why* the issue was filed, never a claim about current state. That is exactly the #16 failure: two of three findings were already resolved at pickup. ## 2. driftNotes flagged when they outlive the deviation A `driftNote` records a **current** deviation; once resolved it is deleted, not left describing finished work. `audit.py` now raises a drift finding when a repo **audits clean** but a note still asserts outstanding work (`pending`, `not yet`, `missing`, `behind`, `owed`, `todo`, `still`, `absent`). The marker list is deliberately narrow and the check only fires on an otherwise-clean repo, so a note recording a *permanent* deviation ("no get-version-task; relies on validate-task") never trips it. Codified as `setup.driftnotes.current`. ## The check earned its keep immediately It caught **a stale note I wrote myself** for HomeAutomation-Config yesterday - `"OWNER TASK OPEN: CODEGEN_APP_CLIENT_ID + CODEGEN_APP_PRIVATE_KEY not yet set in either secret store"` - which the owner resolved (I verified the secrets and closed the downstream issue) but I never reconciled in the registry. Verified both secrets present in both stores, then removed the note entirely: with conformance complete it recorded no deviation at all. **Noise check:** run across the fleet, the heuristic flagged exactly that one note. ## Validation `spec/validate.py`, markdownlint, editorconfig-checker clean; `audit.py` parses and runs; CRLF/LF preserved. This lands before the fleet-wide audit sweep deliberately - it determines whether the issues that sweep produces are accurate on arrival. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tes (#354) (#355) Fixes #354, taking **option 1** (the minimal, recommended one) plus the generalization. ## The fix A `main` pin push by an actor outside the allowlist now emits a `::warning::` rather than falling through silently: ```sh elif [[ "$REF" == "main" ]]; then echo "::warning::Pin push on main by unrecognized actor '$ACTOR'; not publishing. If this is the codegen App under a new identity, update the allowlist in publish-plan-task.yml." fi ``` ## Verification: the publish decision is unchanged Simulated the patched logic across the full matrix - the gate behaves **identically** for every legitimate case, and the warning fires only on the silent-failure path: ```text push ptr727-codegen[bot] main -> publish=true push dependabot[bot] main -> publish=true push ptr727-codegen-v2[bot] main -> publish=false + ::warning:: <- the renamed-App case push ptr727 main -> publish=false + ::warning:: <- hand edit, worth seeing push ptr727-codegen[bot] develop -> publish=false schedule github main -> publish=true workflow_dispatch ptr727 develop -> publish=true ``` ## Generalization codified: WORKFLOW.md D8.4 > **An identity allowlist used as a gate fails loud.** with the failure mode it prevents, and two judgment calls worth recording: - **Where an annotation is optional:** the merge-bot hard-codes the same identities, but its failure is **self-announcing** - it stops merging and bot PRs visibly pile up. The publish gate is the acute case precisely because the weekly schedule *masks* it, leaving only a timeliness symptom. - **The escalation:** option 2 (mint an App token, resolve the identity via `GET /app`) removes the hard-coded string entirely, and is the right move only if an allowlist proves fragile in practice - noted rather than adopted, per the issue. ## Validation actionlint clean on the snippet; `spec/validate.py`, markdownlint, editorconfig-checker clean; LF preserved on the workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nge (#356) (#358) Fixes #356, including the amendment. ## What lands **New `## Verification Discipline` section** in AGENTS.md, placed between `Documentation Style Conventions` and `PR Review Etiquette` - write it right, verify it, then review. It opens with the unifying property: **every failure below is green**, so no linter, status check, or review layer catches any of them. The six from the proposal, plus the amendment's review companion: 1. A test must assert the mechanism it names. 2. Gates, filters, and gate-like watchers fail loud, never narrow quietly. 3. Run the repo's whole lint gate before every push. 4. Editing CRLF files programmatically: `.` matches `\r`. 5. A green check is not evidence the work happened. 6. A workflow change is only fully exercised by CI. 7. A review flags an instance; fix the class. **Rule 7 from the amendment** - search for prose asserting the old behavior after changing it - goes under **`Documentation Style Conventions`** per your placement note, directly beside the present-tense rule as its maintenance counterpart: that rule governs *how to phrase* a doc, this one *how to keep it true* when the behavior underneath moves. **Placement judgment on the companion:** you grouped "fix the class" with rule 7 but did not place it. I put it in `Verification Discipline` rather than the docs section, because it generalizes past stale prose to any flagged defect class (a silent-narrowing pattern, a mis-worded contract). Happy to move it if you'd rather keep the pair together. ## Complementing rather than duplicating You named three adjacent rules; each generalization now **cross-references its instance** instead of restating it: - fail-loud -> `WORKFLOW.md` **D8.4** (the identity-allowlist gate, landed yesterday via #354) - whole-lint-gate -> "Running the Linters Locally" (which documents *how* to invoke each, not that **all** run) - green-check -> the `changes`-job note under Branching Model (its instance for that one job) - CRLF regex -> the Line Endings warning, whose mechanism it names I also folded the mirror failure into rule 4: a **text-mode rewrite** silently flattens CRLF to LF, the inverse of the `CRCRLF` case. I hit exactly that two days ago - a scripted edit flattened `spec/project-types.json` and CI caught it, not my local run, which is also rule 3 in action. ## README `Rules -> Always` gains the two that read as fleet-wide invariants rather than technique: run the whole lint gate before pushing, and make gates fail loud. Both are **bullets under an existing heading**, so the auto-generated Table of Contents needs no change (and is never hand-edited per its own rule). ## Validation `spec/validate.py`, markdownlint, CI-scoped cspell, editorconfig-checker: clean. CRLF preserved on both files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ck (#359) The template is private, so any reference to it from a carried file 404s for that repo's readers and exposes template machinery a consumer should never have to see. Raised from downstream work where the references were being stripped by hand, repo by repo. ## Scope of the leak Swept every cataloged repo. **13 of 20 reference the template; 8 are public:** | repo | public | files | |---|---|---| | Utilities | yes | copilot-instructions, HISTORY | | LanguageTags | yes | copilot-instructions | | aiopurpleair | yes | copilot-instructions | | homeassistant-purpleair | yes | AGENTS, copilot-instructions | | PlexCleaner | yes | copilot-instructions | | VSCode-Server-DotNetCore | yes | copilot-instructions | | NxWitness | yes | AGENTS, copilot-instructions | | ESPHome-Config | yes | AGENTS | | Financial-Modeling / HomeAutomation-Config / KiCadLibrary / HomeAssistant-Config / MediaTools | no | assorted | The dominant instance is a stale `copilot-instructions.md` paragraph telling agents to report drift upstream, which **hard-links the private template twice** - and one link targets an `AGENTS.md#staying-in-sync-and-reporting-drift-upstream` section **the hub no longer has**, so it 404s even for someone with access. The hub itself carries neither the paragraph nor that section: this is stale carried content that outlived its source (#305 territory, and exactly the "prose asserting the old behavior" rule from #356). ## The rule #344 barred *sibling*-repo references but arguably let the template through as "the subject". `AGENTS.md` now says explicitly: > **This includes the template repo itself.** A carried file names it in neither prose nor a link ... Where a carried file needs to express a template-level behavior - "report a rule discrepancy upstream" - state the behavior, not the destination; the maintainer supplies the destination out of band. That preserves the agent-to-agent reporting flow (which is real and valuable) while keeping it out of a public file. ## Mechanized, not left to review This spread silently across 13 repos, so `spec/audit.py` now checks the two agent-instruction files for the template name and raises a drift finding. The template name is derived from **the git remote**, not the checkout directory name - a clone into a differently-named folder would otherwise stop detecting with no signal, which is the fail-open case the check exists to prevent (AGENTS.md "Verification Discipline": gates fail loud, never narrow quietly). ```text PlexCleaner -> DRIFT carried: .github/copilot-instructions.md references the template repo ... ESPHome-Config -> DRIFT carried: AGENTS.md references the template repo ... Vantage-Config -> clean ``` ## Downstream Not fixed here - the 13 carried copies need the reference removed in their own repos. Raising the delivery shape (per-repo issues vs. folding into each repo's existing conformance issue) rather than filing 13 issues unilaterally. `spec/validate.py`, markdownlint, editorconfig-checker: clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopts **item 2** of #357, in the sections the owner chose. Item 1 is #360. ## Comments: no header summary blocks > **No class-, type-, or file-header summary comment blocks.** A type or file gets a comment only for a specific non-obvious point, kept terse. Placed in `AGENTS.md` "Comments", which already bans design narrative, rule citations, and verbosity creep - a header summary is the same failure at file scope: it restates the declaration below it and goes stale as the file grows. **One carve-out added:** a licence or provenance header that a tool or policy requires is not a summary, so it is unaffected. That was the only real objection to a blanket ban. ## Character Set: no clause-joining semicolons > **No semicolon joining two independent clauses in agent-authored prose** - documentation, comments, commit messages, and PR descriptions. Placed beside the ASCII/em-dash rules, as the same class of house-style constraint on agent prose habits. **Written to the proposal's actual scope.** It says "as sentence separators", which is narrower than all semicolons, so the rule bans the clause-joining use and explicitly preserves two legitimate ones: a semicolon separating items in a list that already contains commas, and a statement terminator in code. ## On sweeping existing prose I measured before writing the rule, because a rule the hub immediately violates is the trap this repo hit with `set -Eeuo` (#338/#350): ```text AGENTS.md 79 WORKFLOW.md 80 CODESTYLE.md 33 AUDIT.md 22 copilot-instructions 16 README.md 9 STANDUP.md 5 repo-config/README 4 TOTAL 248 prose lines containing a semicolon ``` Not all are clause-joining - many are list separators the rule preserves - but a full sweep would still be a sprawling rewrite of dense governance prose with real risk of changing meaning in text that is load-bearing. So the rule states that **existing prose is corrected as each file is next edited, not swept**. That matches how the fleet already handles propagation debt, and it is honest about the hub's current state rather than pretending compliance. Flagging plainly: this changes my own output style going forward, and the 248 lines are a known, deliberate residue rather than an oversight. `spec/validate.py`, markdownlint, editorconfig-checker: clean. CRLF preserved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses **item 1** of #357. Item 2 (the two prose/comment style proposals) is deliberately not in this PR - it is a house-style decision I am putting to the owner separately. ## The gap this closes #359 fixed the **hard** leak: a carried file naming or linking the private template, which 404s for that repo's readers. It also mechanized a check for it. A downstream agent caught the **soft** half, which that check cannot see because it greps for the repo name: carried prose that attributes a rule to *"the template"*. > "A repo owner may widen their own CI file list, but **the template ships** README + HISTORY as the default" > "Guarantees say 'default branch' portably; **the template implements it** as the literal `main`" No repo is named, so nothing 404s - but it still exposes a coordinator the reader cannot reach, and it frames the repo's own rule as someone else's. Genuinely good catch; the durable fix belongs here, as the filer said, or it returns on every reconvergence. ## What changed Eleven attributions across `AGENTS.md`, `CODESTYLE.md`, and `WORKFLOW.md` now state the rule directly: | before | after | |---|---| | "the template ships README + HISTORY as the default" | "README + HISTORY are the default" | | "the template's default Python profile" | "the default Python profile" | | "the template uses `client-id: ...`" | "use `client-id: ...`" | | "the template implements it as the literal `main`" | "it is implemented as the literal `main`" | | "the template constrains only that the output be input-deterministic" | "the constraint is only that the output be input-deterministic" | | "not when it matches the template byte for byte" | "not when it matches a reference implementation byte for byte" | | "the template ships the tracker but not this consumer wiring" | "the tracker ships without this consumer wiring" | | "by exporting the template's two rulesets" | "by importing the committed `repo-config/*.json` ruleset payloads" | | "Because the template has no such leaf" | "Because no such leaf ships" | Behavior is unchanged throughout; only the framing moves from *reported* to *owned*. **One occurrence remains by necessity:** the rule that bans the practice (`AGENTS.md` "This includes the template repo itself"). It cannot be stated without referring to the concept, and it names no repo and gives no location. Two later passes caught what the first regex missed - "this template" (AGENTS.md, CODESTYLE.md) and "the `release` template" (WORKFLOW.md). Remaining matches for the word are different meanings, not attributions: Serilog structured message templates, and the `reports/_template.md` filename. ## Note on the mechanical check `spec/audit.py` greps for the repo **name**, so it catches the hard leak but not this class - phrasing is a judgment call and a keyword sweep for "the template" would false-positive on legitimate uses (this PR's own rule statement, for one). Flagging that limitation rather than pretending the check covers it. `spec/validate.py`, markdownlint, editorconfig-checker: clean. CRLF preserved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported from a downstream session: Copilot insisted on removing a `README.md` link pointing to a related device-builder repo, citing the no-cross-repo-link rule. That link was legitimate content. The rule I wrote in #344/#359 made that misreading reasonable, so this fixes the rule rather than the reviewer. ## Two defects in the rule as written **1. It led with the mechanism and buried the carve-out.** The sentence opened with "adds no link (inline URL or reference definition) to another fleet repo" and only qualified it fifteen lines later with "unless that repo is the subject". Read top-down, the ban looks absolute. **2. It claimed `README.md` is carried verbatim. It is not.** `spec/files.json` gives `README.md` neither `whole: true` nor `sections`, so it is a per-repo file governed only by `readme-structure.md`. A repo's own README is its own content. The rule never should have reached it, which is exactly where the over-application landed. ## Restated by intent Two things banned in carried files: - **Any reference to the template repo**, prose or link. Private, so it 404s, and the coordination flow is machinery a consumer should not see. - **A sibling fleet repo named as an illustrative example** ("repo X does it this way", "see repo Y's adoption"). That couples repos and rots as they diverge. And explicitly permitted: > **A contextually relevant link to a related project is not a coordination reference, and is expected.** Where another repo is part of this repo's subject matter - the image that consumes this config, the builder that generates this hardware, a library this depends on - link it normally. The test is whether the link serves a reader of *this* repo's content, not whether the target happens to be in the fleet. Plus the scope correction: the rule governs carried template content, and a repo's own `README.md` and topical docs are outside it. `spec/project-types.json`'s `recurring.norepoxref` is reworded to match. ## The mechanical check needed no change `spec/audit.py` greps only for the **template name** in the two agent-instruction files. It never looked for sibling links, so it was not the source of the over-application and its behavior is unchanged - re-verified against PlexCleaner (flags) and Vantage-Config (clean). `spec/validate.py`, markdownlint, editorconfig-checker: clean. *Downstream note: ESPHome-NonRoot is amending its local AGENTS.md for the same reason. Once this lands, its next carry converges on this wording.* 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…write rules, runbook fix, and a portable enforcement kit (#364/#365/#366) (#367) Addresses the cross-repo comment incident (#364, #365, #366): an autonomous PR-review loop hand-fabricated a GraphQL thread node id and fired a reply mutation with output suppressed (`>/dev/null 2>&1 || true`). Because node ids resolve globally, the guessed id landed on a real thread in a **stranger's repo** (`mankatcheung/job-finder` #15) and the write **succeeded under the maintainer's identity**, while the suppressed output read as a harmless failure. Three root causes -> three rules, applied in three guard layers. ## 1. Carried template rule (travels to the fleet) New `AGENTS.md` section **`## Repository Boundaries and Write Safety`** (after Foundational Principles, beside the existing never-force-push safety rules), added to the carried-sections allowlist in `spec/files.json`: - **Write only to the current project's own repository** - a broad identity is capability, not permission. - **Never fabricate, guess, or reuse an identifier passed to a write** - capture every id from a live query in the same session. - **A write is never a probe, and its output is never suppressed** - a write that appears to fail is verified, not assumed harmless. ## 2. Runbook fix (`.github/copilot-instructions.md`, carried whole) The reply/resolve example **modeled the footgun**: a disembodied `-F threadId="PRRT_..."` literal disconnected from the `reviewThreads(first:100)` query above it. Rewritten to capture `$TID` from that live query with an empty-result guard, use `-F threadId="$TID"`, show the mutation output, and confirm `isResolved` before closing. Added an intra-doc cross-reference to the new section. (The `requestReviews` example already captured its ids from live queries.) ## 3. Portable per-machine safety kit (`host-setup/agent-safety/`, not carried) The prose layers are necessary but not sufficient - the incident happened *under* a "Verification Discipline" section. This adds the mechanical backstop and makes it deployable everywhere as the first thing on a new system: - **`gh-write-guard.py`** - a PreToolUse hook that DENIES the three write footguns (suppressed-output mutation, literal-node-id mutation, cross-origin explicit target). Reads pass through. It fires even in autonomous / bypass-permissions sessions - the exact condition of the incident. `--selftest` runs an 11-case decision matrix. - **`claude-md-safety.md`** - the same rules as host-scope guidance, appended into `~/.claude/CLAUDE.md` so every session on a machine inherits them, including ad-hoc work outside any project. - **`install.py`** + thin `install.sh` / `install.ps1` wrappers - one tested cross-platform installer: self-tests the hook before registering it, merges the settings.json entry without clobbering other keys, updates the CLAUDE.md block in place. Fully idempotent. This is the rollout #365 tracks; its per-machine checklist becomes "run `install.sh` / `install.ps1`". ## Deliberately out of scope - **Credentials** stay out-of-band (per owner decision): a fine-grained PAT limited to owned repos is stronger structural defense but is per-machine credential setup, not a carried rule. - **No `spec/audit.py` change**: section carriage is intent-checked, not mechanized; a literal-id grep on a whole-carried runbook is fragile. Raised separately: "Verification Discipline" is also absent from the carried-sections allowlist - a follow-up, not bundled here. ## Verification (driven, not assumed) - Hook `--selftest`: 11/11 incl. the exact incident (deny), the empty-id guard idiom (allow), reads (allow). - Real stdin path emits `hookSpecificOutput`/`permissionDecision: "deny"` for the incident command; reads and non-Bash tools pass through; malformed input never interferes. - Installer: fresh install, same-version re-run (idempotent), and version-upgrade replace-in-place all verified against throwaway homes; `settings.json` merge preserves other keys. - `spec/validate.py`, markdownlint (whole-repo `**/*.md`), editorconfig-checker: clean. **Note on the section-name/anchor lockstep:** the copilot-instructions cross-reference `#repository-boundaries-and-write-safety` and the `spec/files.json` `sections` entry both depend on the section title; rename them together if ever changed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1) (#369) ## What and why First of four staged PRs for #368: give every governance rule a single explicit **scope**, so carried docs become granular single-scope pieces composed per repo rather than large pieces with internal carve-outs a reader must piece out. This PR is the **inert foundation** — it adds the mechanism and documents the model, with **zero fleet-finding delta**. Policy changes (turning on section-presence checking, the doc surgery, carrying Verification Discipline) follow in later PRs, each held before `main`. ## Changes - **`spec/scope-model.md`** (new, hub-only, not in the carried baseline): the two axes — **home** (host vs repo) and **reach** (hub-only / all-downstream / type-specific) — and the `appliesTo` **selector vocabulary**: four disjoint namespaces (project types, `workflowModel`, `releaseTrigger`, `consumerModel`). States the load-bearing semantics: `appliesTo` is **any-of** (cross-axis AND is deliberately not expressible — that's the signal to split a piece), and entry-level AND section-level `appliesTo` compose. Records `CODESTYLE.md`'s section→scope mapping (whole-carried, so documented not mechanized). - **`spec/files.schema.json`**: a `sections` element may be a bare string **or** a `{name, appliesTo}` object (`oneOf`). Backward-compatible — every existing entry stays valid. - **`spec/audit.py`**: match `appliesTo` against a repo's full selector set (`repo_selectors`), and **replace the hardcoded operational `develop.json` path-swap** with two data entries in `files.json` (`appliesTo: ["release"]` vs `["operational"]`). This is the proof that the selector model subsumes an existing ad-hoc carve-out. - **`spec/validate.py`**: first pass over `files.json` — every `appliesTo` token must resolve to a known selector, no project type may collide with a reserved token (keeps the namespaces disjoint), and every cataloged repo must declare `consumerModel` so a `push`/`pull`-scoped section can't fail open. - **`AUDIT.md` §3 + `files.json` note**: one-line pointers to the scope model. ## Inertness — verified The four selector namespaces are disjoint and no current `appliesTo` token is non-`*`, so broadening the matcher changes no match. Proven by computing the required-file set for **all 21 cataloged repos** under old-code+old-data vs new-code+new-data: **0 differences**. `spec/validate.py` stays green (and now also validates `files.json`). `markdownlint`, `editorconfig-checker` clean. Held on `develop` — no `develop → main` promotion — per the standing instruction to observe what returns to develop first. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## What and why Second of the staged PRs for #368. PR-1 gave sections a scope; this turns on the **section-presence check** in the audit. Previously `spec/audit.py` verified only file-**path** presence — `sections` were never read, so a repo carrying an `AGENTS.md` missing half its required sections passed clean. ## Change - `required_sections(item, sel)` — the sections a repo must carry from a baseline entry, filtered by each section's own `appliesTo` (a bare-string section is `*`; an object section carries its selector). - `heading_texts(md)` — case-insensitive heading extraction. - The file-presence loop unions required sections per path, and for a **markdown** file that exists, reports a **DRIFT** for any required section heading that is missing. ## Why DRIFT, not LETTER A heading rename reads as "missing," and equivalence is judged by hand (AUDIT.md §7). DRIFT surfaces in every report and blocks a "clean" verdict but does **not** flip the exit code (only DEFECT/LETTER/ERROR do), so this is safe to turn on fleet-wide. Non-markdown "sections" (e.g. the tasks.json task group) are intent markers, not headings, so the heading check is gated to `.md` files. ## Verified - **File-presence behavior unchanged:** the checked-path list is identical for all 21 cataloged repos, so this only *adds* section DRIFT findings — it never changes a LETTER finding or the exit code. - Hub's own `AGENTS.md` passes clean (all 8 required sections present); a doc missing a heading is flagged; section-level `appliesTo` filtering resolves correctly (ready for PR-3's scoped sections). Held on `develop` — no `develop → main` promotion. The fleet triage (running the live audit to see which repos' `AGENTS.md` drift) happens after merge. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…371) ## What and why Third of the staged PRs for #368 — the first doc surgery. The `operational`-vs-`release` carve-out was scattered inline across **Foundational Principles**, **Branching Model**, and **Release Model**, so those all-downstream sections mixed two scopes a reader (and the audit) had to piece apart. This makes the operational rules one granular single-scope piece. ## Change - **New `## Operational Repositories` section** consolidates the operational workflow rules — direct-to-`develop`, advisory CI, the `develop`/`main` ruleset differences, dispatch-only release, dependabot/merge-bot on every tier. It is tagged **`appliesTo: ["operational"]`** in `spec/files.json`, so PR-2's section-presence check requires it only on the 4 operational repos. - The three inline carve-outs slim to one-line pointers, leaving Foundational, Branching, and Release single-scope (the `release` model). **No operational detail is lost** — it all moves into the new section. - The operational **line-ending** rule stays in `## Line Endings` (findable where EOL rules live) with a cross-pointer, rather than moving into the new section. ## Scope Deliberately just the operational extraction. The other two contaminants from the plan — hub-only pointers inside carried sections, and the csharp/python/docker examples baked into Release Model / Workflow YAML — are separate follow-up PRs, to keep each a small composable piece. ## Verified - Operational repos require the new section; release repos do not. - The hub's own `AGENTS.md` is complete (no missing sections in either view). - `spec/validate.py`, `markdownlint`, `editorconfig-checker` clean. Once merged, the 4 operational repos will DRIFT on this section until they adopt it — the intended convergence signal, not a regression. Held on `develop` — no `main` promotion. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to PR-3 (#368). The carried **Branching Model** and **Documentation Style Conventions** sections pointed at hub-only docs a downstream reader cannot follow. - **Branching Model:** drop the two references to the hub-only `docs/repo-config-carry.md` (the export/import procedure, and the brownfield migration procedure). The behavioral content stays — import the committed `repo-config/*.json` payloads, `configure.sh` selects the payload, brownfield repos temporarily disable the ruleset to re-sign. The full setup procedure already lives in the hub-only `STANDUP.md`. - **Documentation Style:** drop "or the hub-only `reports/conformance-matrix.md`" from the no-coordination-references rule; the onboarding or conformance issue remains the destination. The one `reports/conformance-matrix.md` reference that stays is inside the **hub-only** Repository Onboarding section, so it is correctly not a carried contaminant. Deliberately minimal — `configure.sh` (also hub-only) and the onboarding-flavored setup mechanics in that Branching bullet are left for a possible follow-up. Held on `develop`. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…373) Resolves the original #368 question. **Verification Discipline** is clean all-downstream guidance (test-asserts-mechanism, gates-fail-loud, run-all-linters, green-is-not-proof, fix-the-class), so it joins the carried `AGENTS.md` sections allowlist in `spec/files.json`. Its two cross-references (WORKFLOW.md D8.4, the `changes`-job under Branching Model) point at other **carried** content, so no genericizing is needed. The section-presence check (PR-2) now requires it fleet-wide — a repo whose `AGENTS.md` predates it shows a **DRIFT** until it adopts the section, the intended convergence signal. Verified: required for both release and operational selectors, the hub's own `AGENTS.md` already has it, `validate.py` clean. One-line change. Held on `develop`. Closes the scope-model doc-surgery series (PR-1 mechanism, PR-2 section-presence, PR-3 operational extraction, PR-3b hub-pointer removal, PR-3c assessed and skipped, PR-4 this). The fixed-vs-customizable / per-unit-fidelity verification is the next design. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
First of the fidelity series (#374). Adds a second per-unit axis to the carry model: **fidelity** — how faithfully carried content must survive — orthogonal to the `appliesTo` **scope** (which repos get it). This PR is the **inert foundation**; the engines that consume it follow. ## Changes - **`spec/files.schema.json`** — the `fidelity` enum (`presence | intent | verbatim | interface`) + the optional `contract` object (the interface engine's spec, used in PR-B). - **`spec/files.json`** — note cross-links the fidelity model; each entry gets an explicit `fidelity`: `verbatim` on `.markdownlint-cli2.jsonc`, `intent` on the whole-carried governed configs/docs, `presence` (default) on the rest. - **`spec/validate.py`** — enforces the vocabulary: valid enum, `contract` only on (and required for) an `interface` unit, and a `verbatim` unit's canonical source must resolve. - **`spec/fidelity-model.md`** (new, hub-only, sibling to `scope-model.md`) — the fixed-vs-overridable contract, the four levels, why each unit sits where, the workflow override-seam contract, placeholder normalization, and the **hash-not-version** stale-vs-violated semantics. ## Inert `audit.py` is untouched and no baseline path is added, so the required-file set and every finding are identical — this PR only adds capability and documents the model. `validate.py` stays green and now also enforces fidelity; markdownlint/editorconfig-checker clean. Held on `develop`. Fidelity levels: **presence** (exists), **intent** (carried faithfully, judged by meaning), **verbatim** (byte-identical to canonical after placeholder normalization → content-hash), **interface** (honors a named contract by name/wiring, body owned). PR-B adds the interface engine + workflow entries; PR-C adds the verbatim engine. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… PR-B) (#375) Second of the fidelity series (#374). Consumes `fidelity: interface`: the audit now verifies a carried workflow honors its **fixed contract by name and wiring**, never the owned body — the virtual-function signature check. ## Change - **`spec/audit.py`** - `split_jobs(text)` — slices a workflow into `{job_key: block}` by the jobs-level indent, no YAML parser (stdlib), mirroring `heading_texts`. - `check_interface(path, contract, text)` — required job keys, the ruleset-bound check `name:`, the `release-asset-<branch>-<target>` handoff, and per-job required (`pattern:`/`merge-multiple:`) and forbidden (`artifact-ids:`) tokens. **All findings DRIFT.** - An `interface` unit's **absence** is DRIFT too (not a hard LETTER) — a workflow's naming varies more than a carried config, so everything here is advisory. - `--selftest` (no network) — 7 fixture cases. - **`spec/files.json`** — two workflow `interface` entries: `test-pull-request.yml` (`appliesTo: "*"`, the universal `check-workflow-status` job + its ruleset-bound name) and `build-release-task.yml` (build types, the `get-version`/`github-release` jobs + artifact handoff + no-`artifact-ids:` rule). - **`AUDIT.md`** — notes the new check, and fixes pre-existing clause-joining semicolons in this file's comments/docstrings so they don't fester. ## Verified - `python3 spec/audit.py --selftest` → **7/7** (conformant, missing job+name, renamed check, artifact-ids fork, missing merge-multiple, owned extra-leaf-job no-finding, split_jobs). - **Positive control:** the hub's own canonical `test-pull-request.yml` and `build-release-task.yml` are **conformant** (0 findings). - `validate.py` green (contracts pass PR-A's value-type guards), lints clean. Scoping of `build-release-task.yml` is best-effort (excludes source-only/docs to avoid false "absent" DRIFTs); any mis-scope only yields advisory DRIFTs to triage. It refines as downstreams are audited. Held on `develop`. PR-C adds the verbatim engine. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Markdown-doc hygiene on two carried docs. - **AGENTS.md** "Documentation Style Conventions" — the reference-link rule required definitions at the bottom, grouped by type, and alphabetized, but never required the reference **names** to be contextual, so an agent could satisfy the letter with numeric/opaque names (which is why it's been inconsistently upheld). State the convention README.md already uses: a name encodes the target and its group — `foo-shield` for a shield, `foo-link` for an external URL, a bare `foo` for a local path/anchor — never numeric (`[1]`) or opaque. - **AUDIT.md** — fix clause-joining semicolons in prose (house rule bans them), leaving the legitimate list-separators (a series whose items contain commas), the code-fence example, and the quoted phrase untouched. Verified: markdownlint + editorconfig-checker clean, `validate.py` green (AGENTS.md sections intact). Held on `develop`. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…374, PR-C) (#377) Adds the verbatim fidelity engine: content-hash a carried unit (whole file, or a workflow job region) against the hub's canonical after EOL normalization, classifying a mismatch as stale (matches a past hub revision) or modified (matches none) via the canonical's git history. Integrity by hash, never a version stamp. Runs at DRIFT. Enabled on .markdownlint-cli2.jsonc and the github-release job region.
…lements) (#378) Remove the dead MD033:false (HTML comments pass MD033 regardless; disabling it only permitted unwanted inline HTML elements, of which the hub uses none). Backtick the placeholder tokens in reports/_template.md so MD033 does not misread them as elements. 0 markdownlint errors fleet-wide with MD033 enforcing. Surfaced during the Financial-Modeling fidelity adoption.
…379 items 2, 3, 6) (#380) catalog workflows README file list corrected to the actual .github/workflows/ tree; copilot-instructions CODESTYLE reference generalized from (.NET, Python); repo-config/README.md residual coordination reference dropped and the file added to the spec/files.json manifest as an intent unit (it was carried but unlisted, so downstream copies drifted unnoticed). Surfaced by #379.
…tems 4, 5) (#381) Source-only repos publish via the standalone publish-release.yml (inlined nbgv + action-gh-release, gated by validate-task), not the multi-target build-release-task.yml edited down. Reconciled all occurrences: AGENTS.md Release Model, WORKFLOW.md section 6 + the no-file-target/D4.3/operational siblings, and the spec/project-types.json sourceonly assertions. No build-release-task.yml, no expect_release_assets for source-only.
…follow-up) (#382) configure.sh (verbatim) and settings.json (intent) are carried by every repo but were unlisted, so they drifted unaudited - re-vendoring repo-config/README.md alone shipped a doc describing a stale script. The audit now catches a stale configure.sh directly. Surfaced from Financial-Modeling#28.
Contributor
There was a problem hiding this comment.
Pull request overview
Promotes develop to main by bringing in the new fidelity + scope verification system for carried units, hardening agent GitHub write-safety (prose + enforceable host kit), and reconciling workflow/docs/spec guidance surfaced during early adoptee onboarding.
Changes:
- Add scoped (
appliesTo) and fidelity-graded (presence/intent/verbatim/interface) verification across the spec and audit tooling. - Introduce a host-level agent write-safety kit (PreToolUse hook + installer) and align governance docs/runbooks with the write-safety model.
- Reconcile workflow and onboarding documentation (including source-only/operational guidance) and update snippets/configs to match the strengthened conventions.
Reviewed changes
Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| WORKFLOW.md | Updates workflow contract text to align with interface/verbatim fidelity and stronger shell/gating guidance. |
| STANDUP.md | Points standup flow at the hub-only repo-config carry/apply procedure doc. |
| spec/validate.py | Adds selector + fidelity validation and shape-checking for spec/files.json (CI-side enforcement). |
| spec/secrets.json | Clarifies Codecov note wording to be repo-agnostic. |
| spec/scope-model.md | Documents the selector model and appliesTo semantics (hub-only). |
| spec/project-types.json | Adds Python profile detection + mixed-language (C# + Python scripts subtree) expectations and related checks. |
| spec/files.schema.json | Extends schema for scoped sections and fidelity/contract fields. |
| spec/files.json | Adds fidelity and scoped sections; introduces interface/verbatim-tracked units and repo-config/self-audit baselines. |
| spec/fidelity-model.md | Defines fidelity levels and the interface/verbatim enforcement rationale (hub-only). |
| reports/conformance-matrix.md | Records the mixed C# + Python shape and how checks apply. |
| reports/_template.md | Tweaks template to avoid MD033 false positives by formatting placeholders as code. |
| repo-config/README.md | Refactors content to be repo-local facts; removes hub-only/carry-process details now housed elsewhere. |
| repo-config/configure.sh | Adds model inference for downstream carries and strengthens safety/usage semantics. |
| registry/repos.json | Reconciles types/ground-truth/driftNotes based on recent operational rollouts and mixed-language classification. |
| README.md | Updates quick rules index for verification discipline and mixed-language (C# + Python) guidance. |
| host-setup/agent-safety/README.md | Documents the per-machine write-safety kit installation and verification steps. |
| host-setup/agent-safety/install.sh | POSIX wrapper to run the cross-platform installer via Python 3. |
| host-setup/agent-safety/install.py | Cross-platform installer that deploys + registers the hook and updates CLAUDE.md. |
| host-setup/agent-safety/install.ps1 | Windows wrapper to run the installer via an unambiguous Python 3 launcher. |
| host-setup/agent-safety/gh-write-guard.py | Implements the PreToolUse guard to deny specific high-risk GitHub-write command shapes. |
| host-setup/agent-safety/claude-md-safety.md | Marker-delimited CLAUDE.md snippet containing the write-safety rules. |
| host-setup/agent-safety/.markdownlint-cli2.jsonc | Local markdownlint override to allow H2-first snippet semantics (MD041 disabled). |
| docs/repo-config-carry.md | New hub-only procedure doc for carrying/applying/regenerating repo-config payloads. |
| cspell.json | Adds new domain terms introduced by the fidelity/scope/write-safety work. |
| CODESTYLE.md | Reconciles wording to be non-template-specific and documents Python “two profiles” convention. |
| catalog/snippets/workflows/run-codegen-pull-request-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/workflows/README.md | Corrects orchestrator set for this repo’s .github/workflows/ reality. |
| catalog/snippets/workflows/publish-plan-task.yml | Adds “fail loud” warning for unrecognized actors pushing to main; updates bash strict mode. |
| catalog/snippets/workflows/publish-docker-readme-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/workflows/check-upstream-version-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/workflows/build-release-task.yml | Updates bash strict mode to set -Eeuo pipefail in key jobs. |
| catalog/snippets/workflows/build-pypilibrary-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/workflows/build-nugetlibrary-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/workflows/build-executable-task.yml | Adds missing set -Eeuo pipefail to the publish step. |
| catalog/snippets/devcontainer/python/post-create.sh | Updates bash strict mode to set -Eeuo pipefail. |
| catalog/snippets/devcontainer/dotnet/post-create.sh | Updates bash strict mode to set -Eeuo pipefail. |
| AUDIT.md | Expands deterministic audit coverage narrative (scope selectors, interface/verbatim checks, run stamping). |
| .markdownlint-cli2.jsonc | Enables MD033 and documents why (HTML comments are allowed; elements are flagged). |
| .github/workflows/validate-task.yml | Updates bash strict mode to set -Eeuo pipefail. |
| .github/workflows/test-pull-request.yml | Updates bash strict mode to set -Eeuo pipefail. |
| .github/workflows/publish-release.yml | Updates bash strict mode to set -Eeuo pipefail. |
| .github/workflows/merge-bot-pull-request.yml | Updates bash strict mode to set -Eeuo pipefail. |
| .github/copilot-instructions.md | Hardens the runbook examples to prevent ID fabrication and suppressed-output writes. |
| .gitattributes | Pins LF for additional shebang-executed Python tooling in agent-safety kit. |
| .editorconfig | Pins LF for agent-safety Python tooling to keep shebang execution reliable. |
…385) Removed the stale groundTruthBranch: develop override from KiCadLibrary/EspDinIoT/PhotoCleaner/HolidayLights (all have main; AUDIT.md rule stands) and refreshed their driftNotes; aligned repo-config-carry.md's cross-repo-link rule with AGENTS.md; noted configure.sh's payload-inference fallback in repo-config-carry.md + STANDUP.md. Pre-existing drift from the #383 promotion review.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes the accumulated
developwork since the last promotion (merge-base #327). Merge with a merge commit, never squash; never--delete-branch(head isdevelop). A human merge does not auto-publish — release is a separate gated dispatch.Verified clean: local trial merge is conflict-free, zero files deleted.
What's promoted (33 commits)
Fidelity verification system — the headline. Each carried unit now has a scope (
appliesToselector) and a fidelity (presence/intent/verbatim/interface):Agent write-safety (#367) — repo-scope + never-fabricate-id + never-suppress-write rules, the Copilot-runbook fix, and a portable PreToolUse enforcement kit.
Doc + spec reconciliation from the Financial-Modeling adoption (#379 round: #380-#382) — MD033 now flags inline HTML elements (#378); source-only release model reconciled to the standalone inlined publisher across AGENTS.md/WORKFLOW.md/spec;
repo-config/manifest completed (README.md,configure.shverbatim,settings.jsonintent).Governance hardening — Verification Discipline section, prose/comment style rules, carried-files-carry-no-coordination-references rule + mechanized check,
set -Eeuo pipefaileverywhere, audit run-stamping, Dependabot ecosystem-coverage check, csharp+python repo shape,configure.shmodel inference (#333-#362).Operational-repo conformance — HomeAutomation, HomeAssistant, ESPHome rollouts (#335/#342/#348).
Merge checklist
Check pull request workflow status jobgreen🤖 Generated with Claude Code