From e48d64d15f8f481a1dee2945774a5da9dc99ee7f Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 14:51:12 -0700 Subject: [PATCH 1/3] Regenerate the divergence ledger against the promoted main (#544) Generated output only, no hand edits. `reports/divergences.md` reads each repo's ground-truth `main`, so before #542 promoted it was measuring a hub `main` that predated this session and reporting the hub as not carrying a section it had just authored. ## What moved, and how each was established **Predicted and correct.** `AGENTS.md > Fleet Bootstrap` no longer lists ProjectTemplate. The hub's own `main` carries it now, so that row was pre-promotion state rather than fleet drift. **Predicted and wrong.** I expected `repo-config/configure.sh` to fall from 9 repos to 7. It fell to **8**. ProjectTemplate dropped off correctly, but PhotoCleaner is **genuinely stale** rather than an artifact: #540 moved the canonical, so a copy that was current yesterday is behind today. That is the intended consequence of changing a `verbatim` file, flagged in #540 itself and then forgotten when predicting here. **Not predicted, and checked rather than assumed.** PhotoCleaner dropped off five carried-section stale rows and off the `GOVERNANCE.md > Representative Data` not-carried row. The cause is external to this work: that repo merged its own promotion `c457ff3` earlier today and its `main` now carries the section. An unexplained improvement deserves the same scrutiny as an unexplained regression, so it was verified against that repo's commits rather than accepted. ## What the report now says about the fleet - **19 repos owe `AGENTS.md > Fleet Bootstrap`.** The bootstrap shipped and nothing downstream has it. That is the propagation job #536 exists to make possible, and it is the honest measure of where the fleet stands. - **Financial-Modeling is the only repo still stale on the carried governance sections**, at 18 rows. - **`.markdownlint-cli2.jsonc` still lists 16 repos**, because this session's fleet merges landed on each repo's `develop` and have not promoted to their own `main`. The ledger reads `main`, correctly, so these clear as those repos promote. - **`repo-config/configure.sh` at 8**, five of which additionally need the `ruleset-*.json` payload migration before they can take the canonical at all. ## Verification Regenerated by `python3 spec/fidelity_honesty.py --report` from a live fleet pass after the promotion merged. editorconfig clean, CRLF preserved, diff-scoped `prose_lint` clean. Co-authored-by: Claude Opus 5 (1M context) --- reports/divergences.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/reports/divergences.md b/reports/divergences.md index a4ededcb..4a2307a1 100644 --- a/reports/divergences.md +++ b/reports/divergences.md @@ -22,34 +22,35 @@ Generated by `python3 spec/fidelity_honesty.py --report` - do not hand-edit. Cur ## Untriaged - add a disposition to `spec/divergences.json` +- **AGENTS.md > Fleet Bootstrap** - **not carried** by AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PhotoCleaner, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair, so the section never arrived rather than being edited (verbatim canonical) - **AGENTS.md > Context and Delegation Discipline** - **not carried** by AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair, so the section never arrived rather than being edited (verbatim canonical) - **AGENTS.md > Where the Rules Live** - **not carried** by AudioCleaner, DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, aiopurpleair, homeassistant-purpleair, so the section never arrived rather than being edited (verbatim canonical) -- **GOVERNANCE.md > Representative Data in Agent-Authored Text** - **not carried** by Financial-Modeling, PhotoCleaner, so the section never arrived rather than being edited (verbatim canonical) +- **GOVERNANCE.md > Representative Data in Agent-Authored Text** - **not carried** by Financial-Modeling, so the section never arrived rather than being edited (verbatim canonical) ## Mechanical re-vendor (verbatim stale copies) A past hub revision, not the current canonical - the audit already flags these as DRIFT. Copy the current file down. No judgment needed. -- **AGENTS.md > Context and Delegation Discipline** (2): Financial-Modeling, PhotoCleaner -- **AGENTS.md > Where the Rules Live** (2): Financial-Modeling, PhotoCleaner +- **AGENTS.md > Context and Delegation Discipline** (1): Financial-Modeling +- **AGENTS.md > Where the Rules Live** (1): Financial-Modeling - **GOVERNANCE.md > Foundational Principles** (1): Financial-Modeling - **GOVERNANCE.md > Durable Knowledge and Self-Improvement** (1): Financial-Modeling - **GOVERNANCE.md > Repository Boundaries and Write Safety** (1): Financial-Modeling -- **GOVERNANCE.md > Git and Commit Rules** (2): Financial-Modeling, PhotoCleaner +- **GOVERNANCE.md > Git and Commit Rules** (1): Financial-Modeling - **GOVERNANCE.md > Branching Model** (1): Financial-Modeling - **GOVERNANCE.md > Release Model** (1): Financial-Modeling - **GOVERNANCE.md > Operational Repositories** (1): Financial-Modeling - **GOVERNANCE.md > Pull Request Title and Commit Message Conventions** (1): Financial-Modeling - **GOVERNANCE.md > Documentation Style Conventions** (1): Financial-Modeling -- **GOVERNANCE.md > Verification Discipline** (2): Financial-Modeling, PhotoCleaner -- **GOVERNANCE.md > PR Review Etiquette** (2): Financial-Modeling, PhotoCleaner +- **GOVERNANCE.md > Verification Discipline** (1): Financial-Modeling +- **GOVERNANCE.md > PR Review Etiquette** (1): Financial-Modeling - **GOVERNANCE.md > Communicating with the User** (1): Financial-Modeling - **GOVERNANCE.md > Workflow YAML Conventions** (1): Financial-Modeling - **GOVERNANCE.md > Supported Development Platforms** (1): Financial-Modeling - **GOVERNANCE.md > Editor and Tasks** (1): Financial-Modeling - **GOVERNANCE.md > Repository Details** (1): Financial-Modeling - **.markdownlint-cli2.jsonc** (16): DevKitCIoT, ESPHome-Config, ESPHome-NonRoot, Financial-Modeling, HolidayLights, HomeAssistant-Config, HomeAutomation-Config, KiCadLibrary, LanguageTags, MediaTools, NxWitness, PlexCleaner, Utilities, VSCode-Server-DotNetCore, Vantage-Config, homeassistant-purpleair -- **repo-config/configure.sh** (7): ESPHome-Config, Financial-Modeling, HomeAssistant-Config, HomeAutomation-Config, PlexCleaner, Utilities, Vantage-Config +- **repo-config/configure.sh** (8): ESPHome-Config, Financial-Modeling, HomeAssistant-Config, HomeAutomation-Config, PhotoCleaner, PlexCleaner, Utilities, Vantage-Config ## Promote candidates (intent uniform -> verbatim) From 90e3255daf3876477c8bc02a8048f1d2f72382c5 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:28:18 -0700 Subject: [PATCH 2/3] Take the ruleset bypass list out of the fleet config entirely (#545) Who may bypass a ruleset decides who the rules do not apply to. That is a per-repository human decision, not a value the fleet config hands out. All three payloads declared `bypass_actors: [{ actor_id: 5, actor_type: RepositoryRole, bypass_mode: always }]`, so applying them granted repository admins a standing exemption on `main` and `develop` in every repo. Verified live: every fleet repo sampled carries exactly that entry on both branches. ## It was a real grant, not a restatement GitHub's documentation is explicit that a ruleset **applies to administrators by default** and that the bypass list **starts empty**. Admins are *eligible* for bypass and must be added. The maintainer's UI screenshot confirms the same from the other side: the entry offers a **Delete bypass** action, so it is a deliberate list member rather than an inherent property of the role. Whether these repos first got it from this config or from hand-made rulesets that predate the tooling is unknown, and it does not matter. The config should not be the thing maintaining it either way. ## Removing the declaration alone would have made it worse `apply` sends the payload with `gh api --method PUT`, which **replaces the whole document**. A payload with no `bypass_actors` would therefore have cleared the live list on every run. That is code deleting a bypass, which is the specific thing that must never happen. So `apply` now reads the live list and writes it back unchanged, and **aborts** rather than proceeding if that read fails, since applying without it would silently clear what it was meant to preserve. On create there is no live list and none is sent, so a new ruleset starts at GitHub's own empty default and nothing is deleted, because nothing existed. ## `check` reports and never asserts No payload declares a bypass value, so there is nothing to compare against, and inventing an expectation would put code back in charge of the decision. `check` now prints the live list on every run, the way it already prints the secrets manual-verify note: ```text ruleset 'develop' bypass list: RepositoryRole 5 always (not managed by this script) ``` Visible on every run, judged by nothing. ## Verification The preserve path was proven by composing the PUT body against this repo's live ruleset **without sending it**: ```text live bypass: [{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}] payload declares: ABSENT composed PUT body would send: [{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}] ``` `bash -n` clean, `prose_lint` clean, editorconfig clean, live read-only `check` against this repo passes. ## What this does not do It does not remove the bypass entries that already exist on the fleet's rulesets. Removing those would itself be code deleting a bypass. Clearing them is a deliberate per-repo act in the UI, and `check` now makes each one visible so they can be reviewed rather than forgotten. Co-authored-by: Claude Opus 5 (1M context) --- repo-config/configure.sh | 24 ++++++++++++++++++++++-- repo-config/develop.json | 7 ------- repo-config/main.json | 7 ------- repo-config/operational/develop.json | 7 ------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/repo-config/configure.sh b/repo-config/configure.sh index 41a3a535..4842702e 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -111,7 +111,7 @@ ruleset_id() { # =============================== apply =============================== apply_ruleset() { # payload-file - create-or-update the ruleset by name - local file="$1" rname id + local file="$1" rname id live_bypass body if [ ! -e "$file" ]; then echo "Ruleset payload $file not found. Aborting to avoid a partially-applied configuration." >&2 exit 1 @@ -124,9 +124,23 @@ apply_ruleset() { # payload-file - create-or-update the ruleset by name id="$(ruleset_id "$rname")" if [ -n "$id" ]; then echo "Updating ruleset '$rname' (id $id) on $repo" - gh api --method PUT "repos/$repo/rulesets/$id" --input "$file" >/dev/null + # The bypass list is a human decision, so this script neither grants nor revokes it. + # A PUT replaces the whole document, so omitting the field would delete the live list rather than leave it alone. + # The live value is therefore read and written back unchanged, which is what "hands off" has to mean against a replacing API. + # A read failure aborts rather than proceeding, since applying without it would silently clear the list. + if ! live_bypass="$(gh api "repos/$repo/rulesets/$id" --jq '.bypass_actors // []')"; then + echo "Could not read the live bypass list for ruleset '$rname' on $repo. Aborting rather than applying a payload that would clear it." >&2 + exit 1 + fi + if ! body="$(jq --argjson b "$live_bypass" '.bypass_actors = $b' "$file")"; then + echo "Could not compose the ruleset payload for '$rname'. Aborting." >&2 + exit 1 + fi + gh api --method PUT "repos/$repo/rulesets/$id" --input - <<<"$body" >/dev/null else echo "Creating ruleset '$rname' on $repo" + # No bypass list is sent on create, so a new ruleset starts with GitHub's own empty default. + # Nothing is deleted here, because nothing existed to delete. gh api --method POST "repos/$repo/rulesets" --input "$file" >/dev/null fi } @@ -210,6 +224,12 @@ check_ruleset() { # payload-file - the live ruleset must match the committed pol if [ -z "$want_types" ]; then fail "ruleset payload $file declares no rules"; return; fi got_types="$(jq -r '[.rules[].type] | sort | join(",")' <<<"$live")" assert "'$rname' rule set = $want_types" test "$got_types" = "$want_types" + # The bypass list is reported and never asserted, because no payload declares one. + # Who may bypass a ruleset is a human decision taken in the UI, so code states what is there and judges nothing. + # It is surfaced on every run rather than left invisible, since it is the field that decides who the rules do not apply to. + local bypass + bypass="$(jq -r '[.bypass_actors[]? | "\(.actor_type) \(.actor_id) \(.bypass_mode)"] | join("; ")' <<<"$live")" + note "ruleset '$rname' bypass list: ${bypass:-none} (not managed by this script)" # Every parameterized rule is compared on its whole parameters object rather than on selected fields. # Naming fields one at a time meant a payload could declare a parameter the check never read. # Review-thread resolution, stale-review dismissal, and the status-check policy flags all went unverified that way. diff --git a/repo-config/develop.json b/repo-config/develop.json index efc4262b..d34d9f8f 100644 --- a/repo-config/develop.json +++ b/repo-config/develop.json @@ -1,11 +1,4 @@ { - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "always" - } - ], "conditions": { "ref_name": { "exclude": [], diff --git a/repo-config/main.json b/repo-config/main.json index 5d8d7b2d..57bd647d 100644 --- a/repo-config/main.json +++ b/repo-config/main.json @@ -1,11 +1,4 @@ { - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "always" - } - ], "conditions": { "ref_name": { "exclude": [], diff --git a/repo-config/operational/develop.json b/repo-config/operational/develop.json index 0d16930e..9cab7415 100644 --- a/repo-config/operational/develop.json +++ b/repo-config/operational/develop.json @@ -1,11 +1,4 @@ { - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "always" - } - ], "conditions": { "ref_name": { "exclude": [], From 362aec8d691ed7f4f07764f34ef7f18276f39d89 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:31:40 -0700 Subject: [PATCH 3/3] Queue the agent-isolation rule and the vendored-tooling surface (#546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two backlog entries, both raised by the maintainer, both with worked examples from the session that produced them. ## An agent works in its own checkout Own directory, own feature branch, and never writes to a repository because it happened to be on disk. The reason care is not enough is that **the operations which cross the boundary are the ordinary ones**. A blanket `git add -A` sweeps another agent's uncommitted work into your commit. A `git reset --hard` deletes it. A branch switch carries it into a change it has nothing to do with. Each command is correct in isolation and wrong only because of who else is in the directory. This session produced the first of those. A blanket add swept a second agent's in-progress onboarding work into an unrelated commit and pushed it. It survived only because it was rescued and restored by hand afterwards, and the same directory had already seen a `reset --hard` and two branch switches that would have destroyed it outright rather than merely misfiling it. ## The same rule extends to reading, which is the commoner case A clone on disk is not the branch it names. It is whatever that clone was last fetched to. Reading it to answer *what does this repository carry* answers instead *what did this clone last see*, and **the failure is silent**: the read succeeds and returns a plausible answer. Two instances, both from this session, both wrong in a way nothing flagged: - A detection pass over local clones reported a repository as still drifted on a file whose fix had already merged, because that clone's refs predated the merge. The correction came from a `fetch`, not from the read. - A second pass reported a repository as missing a file it does carry, because the local checkout sat on a branch behind the one being measured. The entry states the practice alongside the rule: read the live ref through the API where a claim will be acted on, or fetch immediately before reading, and name the ref and commit in any finding derived from a local read so a reader can tell what was actually measured. It also keeps the honest exception, that a local clone is the right tool for anything needing history or a build, since an API read cannot give either. ## Every carried file is a copy that breaks when it is not current `repo-config/configure.sh` is the worked example, and it happened twice today. A defect in its check mode was fixed once in the hub and immediately became a re-vendor owed by every repository carrying it. Until each takes the fix they run different logic while reporting the same command, and a repository that misses the sweep **does not fail loudly** — it quietly audits itself with an older script. Hosting the tool in the hub and having downstream call it removes the stale copy rather than detecting it. This is the same argument as the existing reusable-workflow entry, so the entry says to settle them together rather than separately. Three things to decide are recorded: what genuinely needs vendoring against what only needs to be reachable, how a consumed tool is pinned, since calling hub code at run time is a floating dependency unless it names a commit, and what a repository does when the hub is unreachable, since a vendored copy still works offline and a called one does not. ## Verification `prose_lint` clean, `markdownlint-cli2` clean, CRLF preserved. One file in the commit, staged by explicit path rather than a blanket add, for the reason the first entry describes. --------- Co-authored-by: Claude Opus 5 (1M context) --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.md b/TODO.md index 4d4ea957..a99ead4a 100644 --- a/TODO.md +++ b/TODO.md @@ -35,6 +35,7 @@ Everything here changes a file the fleet holds a copy of, so each entry costs a - State the rule that an agent never assumes a Docker image is present locally, however recently it pulled one, because a background prune can remove an image between two commands of the same session and the run then fails on something that worked minutes earlier. The prescription is the always-pull flag by default, and an explicit `docker pull :` before use where the flag does not apply. The four documented lint invocations already carry `--pull=always`, in [`GOVERNANCE.md`][governance] "Running the Linters Locally", mirrored in [`OPERATIONS.md`][operations] and both VS Code task snippets, so what is missing is the rule rather than the one-liners: an agent composing an ad-hoc `docker run` drops the flag precisely because it believes the image is already cached. Two things to settle when writing it. Where it lives, since that section is scoped to the four lint tools while the rule covers any container an agent starts, which argues for placing it with the Docker or verification guidance instead, and whether it is carried to the fleet, since every repo runs the same images from the same instructions. Note the limit before writing it, so the flag does not read as the whole answer: `docker run` against a registry tag re-pulls an absent image on its own, so the cases that actually break are a locally built tag with no registry to pull from, and any command that branches on the image being present (`docker image inspect`, `docker images`) rather than simply running it. - Decide where a carried file may name hub-only machinery, since `GOVERNANCE.md` "PR Review Etiquette" points at `scripts/pr_review.py` and the fleet carries the section but not the script. A downstream reader follows that pointer to a path their repo does not have. Either the script joins the carried set, or the rule states the behavior and drops the tool name the way the coordination-reference rule already requires for the template repo itself. - Revisit [#305][issue-305], which asks for a propagation path for the rule docs and is the oldest open issue here, because most of the machinery it asked for has since been built and the issue has not been re-scoped to match. The fidelity model, the `spec/files.json` manifest, [`spec/divergences.json`][divergences] with its generated [reports/divergences.md][divergences-report], and [`AUDIT.md`][audit-doc] section 10 together give the canonical-versus-adapted split and the audit path the issue proposed. What is genuinely still missing is the push half, since every one of those detects drift while the sweep that fixes it is manual, which is exactly the entry above. Either re-scope the issue to the push half or close it against the entry above and the audit-automation entry below. +- Reduce the surface that is copied downstream at all, since every carried file is a copy that breaks when it is not the current one. `repo-config/configure.sh` is the worked example from this session: a defect in its check mode was fixed once in the hub and immediately turned into a re-vendor owed by every repository carrying it, and until each takes the fix they run different logic while reporting the same command. A repository that misses the sweep does not fail loudly, it quietly audits itself with an older script. The alternative is for the hub to host the tool and for each downstream repository to call it rather than hold a copy, so one edit reaches the fleet and no stale copy exists to detect, re-vendor, or classify. This is the same argument as the reusable-workflow entry below and should be settled with it rather than separately, since both are about consuming hub code at runtime instead of vendoring it. Three things to settle. What the fleet actually needs vendored, which is the content a repository is audited against and could not run without, against what only needs to be reachable. How a consumed tool is pinned, because consuming hub code at runtime is a floating dependency unless it names a commit, and the action-pinning rule already requires exactly that. And what a repository does when the hub is unreachable, since a vendored copy still works offline and a called one does not, which is the real cost being traded away. - Investigate replacing copy-pasted workflow content with cross-repo reuse, now that this repo is public. A public repository's composite actions and reusable workflows can be consumed by any other repository regardless of owner type, so the organization account this pattern was assumed to require is not needed, and the constraint that shaped the current vendor-everything model no longer holds. The catalog under [`catalog/snippets/workflows/`][workflows] is copied into each repo today, which means a fix to a shared job is a sweep across the fleet rather than one edit here, and it is the mechanism by which a defect in a snippet seeds itself into every repo that adopted it. Scope the investigation to which jobs are genuinely identical across repos against which only look similar, since a reusable workflow that needs a long input list to cover per-repo variation is worse than the copy it replaces. Settle the ref policy in the same pass, because consuming hub code at CI time is a floating dependency unless it is pinned, and [`GOVERNANCE.md`][governance] "Action pinning" requires a commit SHA for every action with one documented exception. Note that `uses:` does not accept expressions, so a per-branch ref cannot be selected in the workflow file and any branch-dependent behavior belongs inside the consumed action instead. ## Audit and Spec Model @@ -59,6 +60,9 @@ Everything here changes a file the fleet holds a copy of, so each entry costs a ## Host and Environment +- State the rule that an agent works only in its own checkout, in its own directory, on its own feature branch, and never writes to a repository because it happened to be on disk. A clone left in a scratch directory is convenient exactly when it is most dangerous, since the operations that cross the boundary are the ordinary ones rather than the reckless ones. A blanket `git add -A` sweeps another agent's uncommitted work into your commit, a `git reset --hard` deletes it outright, and a branch switch carries it along into a change it has nothing to do with. This session produced the first of those: a blanket add swept a second agent's in-progress registration work into an unrelated commit and pushed it, and the work survived only because it was rescued and restored by hand afterwards. Care does not make a shared working tree safe, because every one of those commands is correct in isolation and wrong only because of who else is in the directory. Two things to settle when writing it. Whether the rule is stated as one checkout per agent or one checkout per task, since a single agent moving between two repositories in one directory hits the same hazard as two agents sharing one. And what an agent does when it finds a foreign checkout anyway, where the answer is to leave it alone and clone its own rather than to work carefully in it. +- Extend the same rule to **reading**, which is the more common case and the easier one to think is harmless. A clone on disk is not the branch it names. It is whatever that clone was last fetched to, so reading it to answer what a repository carries answers instead what that clone last saw. The failure is silent, because the read succeeds and returns a plausible answer. This session produced two instances. A detection pass over local clones reported one repository as still drifted on a file whose fix had already merged, because that clone's refs predated the merge, and the correction came from a fetch rather than from the read. A second pass then reported a repository as missing a file it does carry, because the local checkout sat on a branch behind the one being measured. State the working practice with the rule: read the live ref through the API where a claim will be acted on, or fetch immediately before reading, and name the ref and commit in any finding derived from a local read so a reader can tell what was actually measured. Note the exception worth keeping, which is that a local clone is the right tool for anything needing history or a build, since those are what an API read cannot give. + - Finish the [#365][issue-365] rollout, which has two halves and one of them is invisible from here. Four machines are still unchecked, WSL2 Ubuntu, the MacBook Air, and both ThinkPads, plus any headless or cron environment that runs Claude Code with the token. A ticked row means the `CLAUDE.md` text and not the hook, since only running `host-setup/agent-safety/install.sh` deploys both layers, and the proxmox host proved that distinction the hard way by carrying the documentary half alone for eight days on the very machine where the incident originated. Note also the follow-up left open on this host and never applied, that the superseded `## Safety rules` section from [#364][issue-364] still sits above the canonical `agent-safety v1` block in `~/.claude/CLAUDE.md`, so the two overlap. Removing the superseded section is a judgment call on a per-machine file, which is why it was surfaced rather than applied. - Fill in the remaining cells of the [#483][issue-483] host tooling matrix, which is the reference that makes the per-machine chore above repeatable, so the two close together rather than either alone. Linux, Windows and WSL2 are filled in and verified. **macOS** needs someone on that platform, the **Proxmox** question is whether that host is a shell for agent work or also runs containers, which decides whether `docker` is required there at all, and the engine-inside-the-distro variant of the WSL2 `docker` cell is still unverified since that host only exercised the Docker Desktop integration. Honor the issue's own rule when filling a cell, that an unverified install command is worse than a blank, because a blank prompts a question while a wrong command produces a broken host and a false sense that setup succeeded.