Bucket the backlog and refresh the divergence ledger - #535
Conversation
TODO.md was a flat list of twenty entries that had grown past the point where the next action was visible in it. It is now grouped by the surface each entry changes, since an entry's cost is mostly set by what it touches, and every open issue is named on the entry that covers the same ground. Five issues were verified complete against the tree and are recorded with the commits that fixed them, so closing them cites evidence rather than assertion. Three new entries: the prose_lint scope floor, the rule that an agent never assumes a Docker image is still present locally, and the gh-write-guard newline defect. reports/divergences.md is regenerated. The committed copy predated the router split and still filed three sections under AGENTS.md that now live in GOVERNANCE.md, so it was handing out a work list measured against a tree that no longer exists. The refresh corrected the propagation picture: only two repos carry GOVERNANCE.md at all, so the split never reached the fleet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the repo’s operational tracking docs by reorganizing the backlog into clearer “buckets” and regenerating the fleet divergence ledger to reflect the current governance/router split.
Changes:
- Restructures
TODO.mdinto grouped sections (e.g., verified-complete items, gate-honesty work, carried-content propagation). - Adds an explicit “Verified Complete, Awaiting Close” section with evidence-style notes tied to specific issues/commits.
- Regenerates
reports/divergences.mdto align with the post-splitGOVERNANCE.mdsection layout and updated re-vendor targets.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| TODO.md | Re-buckets and annotates the repo backlog, adding a verified-complete section and regrouping work by affected surface area. |
| reports/divergences.md | Regenerated divergence ledger reflecting the updated governance section ownership and current re-vendor targets. |
The disposition read "in these three" while the report rendered two names, because PhotoCleaner resolved and the generated line drops a resolved repo while the reason text stayed as written. Correcting the number to two would go stale the same way the next time a repo converges, so the count is gone rather than fixed. The fix belongs in spec/divergences.json rather than in the report, since reports/divergences.md is generated and a hand edit there is undone by the next regeneration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
spec/divergences.json:7
- The PR description says this change is "Two files", but this PR also updates the divergence ledger source (
spec/divergences.json) that drives the regeneratedreports/divergences.md. Please update the PR description to mention this file explicitly so reviewers know the report change is sourced and intentional.
{ "path": ".editorconfig-checker.json", "repos": ["HomeAutomation-Config", "HolidayLights"], "disposition": "accepted", "reason": "Both carry a legitimate repo-specific Exclude list (HomeAutomation-Config excludes a Vantage/ subtree, HolidayLights excludes .fseq sequence files). The uniform Disable block is carried intent-equivalent. Exclude is inherently repo-local, which is why the unit is intent, not verbatim.", "tracking": null },
{ "path": ".markdownlint-cli2.jsonc", "repos": ["aiopurpleair", "PhotoCleaner", "AudioCleaner"], "disposition": "re-vendor", "reason": "Verbatim config held as a hand-modified copy rather than a past hub revision. Restore the current canonical.", "tracking": null },
{ "path": "repo-config/configure.sh", "repos": ["NxWitness", "aiopurpleair", "homeassistant-purpleair", "ESPHome-NonRoot", "VSCode-Server-DotNetCore", "LanguageTags"], "disposition": "re-vendor", "reason": "The hub adopted the fleet check mode into the configure.sh canonical (apply plus a payload-driven check). These carry an older design that predates it (five an older check-mode fork with ruleset-*.json filenames and no registry, LanguageTags an older apply-only copy). Re-vendor the new canonical to converge them. The stale copies re-vendor mechanically. NxWitness may keep a thin repo-specific wrapper for its hardcoded Docker Hub secret checks, which the repo-agnostic canonical leaves as a manual-verify note.", "tracking": null }
AUDIT.md section 8 governs a note's end of life, saying it records a current deviation and is deleted once resolved, and says nothing about its content at the moment an agent authors one. PhotoCleaner is the worked example: asked to self-audit and reconcile, the agent wrote a completion record, the date a past version published, and a description of the repo conforming to spec. None is a current deviation, and nothing it was given forbids any of the three, so this is an incomplete instruction rather than an agent ignoring one. The detector has the matching gap. It flags a stale note by matching words that assert outstanding work, which is the opposite polarity to all three of those notes, and it only runs when the repo audits clean, which is the state where the problem has already gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The re-bucketing rewrote the whole file rather than editing it in place, and the write emitted LF into a repo whose editorconfig sets crlf as the default for every file with no override. editorconfig-checker caught it in CI, which is the check working, but the cause is worth naming: a whole-file rewrite discards the line endings an in-place edit would have preserved, so the risk scales with how much of a file is replaced rather than with how much of it changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
reports/divergences.md:27
reports/divergences.mdreports "GOVERNANCE.md > Representative Data in Agent-Authored Text" as "hand-modified" for Financial-Modeling/PhotoCleaner, butspec/fidelity_honesty.pycurrently puts a downstream missing verbatim section into the samediffersbucket as a true hand-edit (region is None->spread["differs"], then rendered as "hand-modified"). For newly introduced verbatim sections, any downstream repo that simply hasn't received the section yet will always show up as "hand-modified" (not "stale") because absent sections are skipped when buildinghistory. Consider splitting this case out (e.g.,missing_section) and rendering it as "missing section" (or treating it as stale) so the report doesn't imply manual edits when the section is merely absent.
- **AGENTS.md > Context and Delegation Discipline** - hand-modified in 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 (verbatim canonical)
- **AGENTS.md > Where the Rules Live** - hand-modified in 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 (verbatim canonical)
- **GOVERNANCE.md > Representative Data in Agent-Authored Text** - hand-modified in Financial-Modeling, PhotoCleaner (verbatim canonical)
The disposition said NxWitness may keep a thin repo-specific wrapper for its hardcoded secret checks. Measured rather than left open: the fork asserts four secret names, and spec/audit.py checks the spec/secrets.json baseline plus that repo's registry requiredSecrets, which is a strict superset of the four and additionally covers CODECOV_TOKEN that the fork misses. Re-vendoring loses no coverage and gains some, so no wrapper is owed. The disposition also now records what actually blocks these repos, which the sweep found the hard way. Five of the six carry ruleset-develop.json and ruleset-main.json against the canonical's develop.json and main.json, so each needs the payload migration before it can take the script at all, and a re-vendor without it leaves apply and check aborting on payloads that do not exist. NxWitness is the fifth, missed at first because its configure.sh was never in its pull request so its payload names went unchecked. Report regenerated so it matches its source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Answering the suppressed finding on
Correct, and it matters more than a label. The report currently says "hand-modified in AudioCleaner, DevKitCIoT, ESPHome-Config, ..." for Independently confirmed while this pull request was being prepared, by a direct read rather than from the report: Fixing it is a generator change rather than a data change, so it is not folded in here. This pull request regenerates the report faithfully from the current code, and the mislabel lives in Queued as a follow-up, and worth stating in the entry: a newly introduced verbatim section will report as "hand-modified" in every repo on the day it lands, which is precisely the case the new |
…541) Follow-up to the suppressed finding on #535, which is now merged. That finding was correct and pointed at a defect with a measurable cost. ## The defect `spec/fidelity_honesty.py` put a verbatim section whose heading is missing downstream into the same `differs` bucket as a genuine local edit (`region is None` -> `spread["differs"]`), and the report rendered both as **"hand-modified"**. The two states want opposite responses. A hand-edit is drift to reconcile against a local decision someone made. An absence is a section that never arrived, which is carried. Calling the second one the first sends a reader looking for decisions that were never taken. ## What it actually produced Seventeen repos were reported as having hand-modified `AGENTS.md > Context and Delegation Discipline` and `Where the Rules Live`. None of them edited anything: they still hold the pre-split monolithic `AGENTS.md`, so the sections are simply not there. **That claim was quoted as evidence in this repo's own backlog work before a reviewer caught it.** The conclusion drawn from it happened to be right, because absence was separately confirmed by direct reads against the live repos, but the word it was drawn from was wrong, which is the more dangerous shape. There is a second-order effect the finding also named: an absent section contributes nothing to the `history` set, so it can never be classified `stale`. That means **every newly added verbatim section reports as hand-modified across the whole fleet on the day it lands**. The new `GOVERNANCE.md > Representative Data in Agent-Authored Text` row was showing exactly that. ## The change A new `absent` bucket, with three consequences handled beyond the label itself: - **It no longer disqualifies an `intent` unit from promotion.** A missing section is not evidence of a local decision, so it should not block a unit from being promoted to `verbatim`. - **It still counts as a live divergence for burn-down**, because the section is genuinely owed to that repo. - **The untriaged section reports the two kinds separately**, since one is fixed by reconciling and the other by carrying. ## Result ```diff -- **AGENTS.md > Where the Rules Live** - hand-modified in AudioCleaner, DevKitCIoT, ... +- **AGENTS.md > Where the Rules Live** - **not carried** by AudioCleaner, DevKitCIoT, ..., so the section never arrived rather than being edited ``` Three rows change, covering nineteen repo-section pairs. ## Verification - `spec/validate.py`: OK, 21 cataloged - `prose_lint --diff`: clean - editorconfig: clean, CRLF preserved on the regenerated report - Report regenerated from a live fleet pass so it matches its source --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ndor groundwork (#542) Six commits, all from one session. Merged as a **merge commit**, never squashed, per the branching model. | Commit | PR | What it does | | --- | --- | --- | | `1ab9b32` | #537 | Deletes three stale PhotoCleaner `driftNotes` that described finished work | | `bf746aa` | #535 | Buckets `TODO.md` by the surface each entry changes, records five issues verified complete, refreshes the divergence ledger | | `69276d1` | #536 | Adds the byte-locked `AGENTS.md` "Fleet Bootstrap" section, the host-wide `fleet-bootstrap` block, and the `AUDIT.md` routing that sends an un-stood-up repo to `STANDUP.md` first | | `530dc0d` | #539 | Records two `gh-write-guard` limits found by running it: a write inside a script is unseen, and a push followed by a newline over-blocks | | `5f4c17f` | #540 | Makes `configure.sh check` verify what the payloads declare, closing a false clean where a `jq` failure left every setting reported as passing while nothing was compared | | `a549572` | #541 | Reports a verbatim section that never arrived as **absent** rather than as hand-modified | ## Why this promotion matters more than most Two of these change what the fleet is measured against, so until they reach `main` every downstream audit compares against ground truth that predates them. **#536 is the one with reach.** A downstream agent had no way to discover this repository. `AGENTS.md` is carried at `intent`, and the hub's only self-reference described *the hub*, which is false about a downstream repo, so an agent adapting the file correctly deleted it. Four repos now hold no hub reference at all and one holds no `AGENTS.md` either. The replacement is `verbatim` so adaptation cannot remove it, declared in `spec/files.json` and classified in `spec/section-model.md` so the audit reports its absence as drift rather than losing it silently. **#540 closes a false clean.** `check_settings` streamed its payload from a process substitution, and a `jq` failure there leaves the loop body unexecuted without tripping `set -e`. Every static setting reported as checked and passing while nothing was compared. Verified by negative test rather than by a clean pass: a malformed `settings.json` now fails with exit 1, an extra live rule fails the rule-set assertion, and a changed `copilot_code_review` parameter fails the parameters assertion. **#541 corrects a report that was actively misleading.** It accused seventeen repos of hand-editing `AGENTS.md` sections they had never been given. They still hold the pre-split monolithic file, so the sections are simply absent. A hand-edit is reconciled against a local decision, an absence is carried, and the report was naming the wrong one. ## Fleet state behind this Seventeen downstream re-vendor pull requests merged alongside this work, each gate-checked at merge time and each verified after. Four repos had `configure.sh` withdrawn from their re-vendor when review found they carry `ruleset-*.json` payload names the canonical does not resolve, which would have left `apply` and `check` aborting on files that do not exist. That payload migration is still owed and is tracked in `TODO.md`. ## Expected immediately after this merges The divergence ledger currently reports the hub itself as not carrying "Fleet Bootstrap" and as owing a `configure.sh` re-vendor. That is the ledger reading each repo's `main`, honestly, before this promotion. Regenerating after the merge is what makes the report meaningful again, and the `configure.sh` re-vendor list will collapse as repos take the corrected script. ## Verification `spec/validate.py` OK (21 cataloged), diff-scoped `prose_lint` clean, `markdownlint-cli2` clean, editorconfig clean, `gh-write-guard --selftest` PASS, 157 `prose_lint` unit tests pass.
## Why Selecting the next work item meant re-deriving the grouping every session. Entries were grouped by the surface they change, which is #535's convention and answers what a change costs, while the working question is what fits one pull request. That analysis is what the file should carry forward and did not. Three defects made it unsafe to select from, each verified against `develop` at `3d1a0b1`: - **Three open issues appeared nowhere in it.** A pass reading only this file could not see #538, #550 or #557. - **Two open issues were already fixed.** Both are now closed with their evidence quoted, see below. - **Two research artifacts contradicted the tree.** The re-vendor entry called `repo-config/configure.sh` carried `verbatim` with `appliesTo: "*"`, while `spec/files.json` no longer declares it and `spec/divergences.json` dispositions it `retire`. `reports/divergences.md` still rendered it under `re-vendor` across six repos, so the committed report disagreed with its own ledger. ## What changed A `###` heading under `Work Clusters` is one pull request. 20 clusters, 28 entries, after the follow-up commit retired one. The #535 rationale is not discarded, it moves from an organizing axis to a per-cluster `Touches` and `Cost` field, which states more than the old heading implied: a cluster confined to one surface says so in a clause and a cluster spanning two states both. An entry carries `Blocked by`, `Issue` and `Checked` exactly once each, in that order, and never omits one, because an omitted field reads as unknown rather than as none. `Checked` is a per-entry freshness anchor naming the branch, commit and date a claim was last read against the tree, replacing a file-level anchor that already carried two commits for two passes and could not carry a third. `Open` states a decision a future session makes, `Settled` states a finding it does not re-derive, and a `Settled` bullet carries a number, a proper name, or a rejected alternative. Two structural slots are new: - **`Fleet Sweeps`** holds work landing on a downstream visit. It had no home, which left #353, #379 and #489 under a heading promising a close they explicitly could not take. Each item carries `Hub state`, `Outstanding` and `Rides with`, so six repetitions of naming the same visit become one manifest. It records the batching preference outright, that the fleet is caught up periodically rather than after every hub change, and names Blog as the pilot. - **`Recorded for the Maintainer`** holds issue actions that are the maintainer's to take, each carrying the evidence so it is one action rather than a re-derivation. `How to Select the Next Item` states the procedure. Step 1 is confirming every open issue has an entry, which is the honest maximum a hand-maintained file gives and the reason it comes first. ## Two overlaps folded in - **#577 and the registry-description entry are one decision.** Declaring the description in `registry/repos.json` makes every mirror read a field rather than parse a paragraph, so taking #577 first means writing an extraction rule the registry change then deletes. They are now one cluster with the sequencing stated. - **#365 and #483 are one host visit.** The rollout needs the tooling matrix to be repeatable and the matrix is only worth filling if the rollout uses it. ## A generated ledger was considered and rejected `spec/divergences.json` earns its generator by joining a curated ledger against live fleet reality and rendering `UNTRIAGED` for anything live the ledger missed. A backlog has no such reality to join against, and the only mechanizable join is issue open or closed state, which is step 1 of the procedure. What is given up is stated in the file rather than glossed: no enum enforcement, and no automatic surfacing of an open issue with no entry, which is exactly the #538, #550 and #557 failure. The bold-label template stays parseable, so a later checker asserting every `Checked` commit resolves is buildable without re-shaping the file. ## Sweep obligations `spec/readme-structure.md` said a `TODO.md` "opens with a `# TODO` heading and one line saying what it holds, then a flat bullet list". #535 already contradicted both clauses. It now states the minimum rather than the shape, and says this repo's file is a worked example rather than a template every repo adopts, since a five-line downstream backlog needs no selection procedure. That file is not in `spec/files.json`, so the edit costs no re-vendor. `spec/section-model.md`, `STANDUP.md` and `README.md` describe the file's purpose without asserting its shape, verified clean and recorded here so the sweep is done rather than skipped. `host-setup/agent-safety/README.md` ends the write-guard note with "Tracked in `TODO.md`", still true. ## Ledger regenerated `reports/divergences.md` now renders `repo-config/configure.sh` under `retire` rather than `re-vendor`, resolving the contradiction. Regenerating it also **disproved a claim written during this pass**: Blog is one of two repos carrying `AGENTS.md` "Fleet Bootstrap", not the only one. HomeAutomation-Config carries it too, and being `operational` it exercises the direct-to-`develop` path rather than the pull request one, which the pilot note now records as the second visit worth making rather than the first. A second correction, to #521's own claim: three merge-bot jobs carry both `contents: write` and `pull-requests: write` and the fourth carries `pull-requests: write` alone, so the issue's "all four" is one job wide. The entry records this rather than repeating the issue. ## Issues closed - **#552** closed. #572 landed `template_ref_outside_verbatim` in `spec/audit.py`, excising verbatim regions by position before the coordination-reference scan, with eight self-test cases. Positional excision rather than content removal is required by the fenced-copy case, since removing extracted text would delete a fenced copy along with the real region and the scan would fail open. - **#538** closed. Complete on all three points, checked one at a time: the whole `parameters` object of every parameterized rule is compared with keys and set-like arrays sorted on both sides, the rule-type set is compared in both directions, and the header states what check verifies and names what stays unaudited. ## Follow-up: the file's own procedure applied to itself `6bed2d9` is the first exercise of step 9, and it is also what gave this pull request the CI run it never had, since the whole thing opened during the Actions outage and a rollup does not re-fire on its own. `9d85941` merged "Reaching the Hub in Your Own Checkout" whole, so the cluster is deleted rather than annotated and nothing moves out of it. Each of its four entries was re-read against `develop` before the deletion rather than trusted to the merge: the checkout-isolation rule and the stale-clone read rule in `GOVERNANCE.md`, the hub definition in `AGENTS.md` "Fleet Bootstrap", the six terms in `README.md`, and the hub-script pointers in `.github/copilot-instructions.md`. #557 and #579 take its place under "Verified Complete, Awaiting Close" citing that commit, since both close on the `develop` to `main` promotion rather than on the merge that completed them. The two "Recorded for the Maintainer" comments they subsume go with them, because a recorded action saying an issue stays open until a cluster lands is a stale instruction once the cluster has landed. That section now holds two items rather than four. Two entries left the same section for the opposite reason. #509 and #490 were closed on 2026-08-03, three days before the clustering commit, so a section whose entire subject is what still awaits a close was carrying two entries that had already had one. Their link definitions stay, since other clusters still cite both issues. Four definitions the deletions orphaned go too, `issue-552`, `pr-572`, `pr-588` and `workflow`, along with the pull request block left holding none. MD053 is enabled by default, so an unused definition is a lint failure rather than a spare, and the reference-and-definition audit in the verification list above was re-run rather than assumed to still hold. ## Verification - `python3 scripts/prose_lint.py . --diff origin/develop` clean, and `TODO.md` holds 0 violations under the default rules as it did before. - `python3 spec/validate.py` reports 22 cataloged, 0 backlog. - markdownlint-cli2 and editorconfig-checker clean on all three files, CRLF preserved, and both re-run on `TODO.md` after `6bed2d9`. - Every link reference is defined and every definition is cited, with each block sorted by reference name. - All 28 cluster entries carry the three mandatory fields in order, checked mechanically. - The selection procedure was walked end to end against the file, and names a cluster from 5 KB of headings and `State` lines without opening any research. Note the file grew, 61 KB to 73 KB, rather than compressing. Three issues that had no entry were added along with the procedure and the per-entry anchors. The metric that moved is the one selection depends on, which is the 5 KB a re-select actually reads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two files, one session's findings.
TODO.mdA flat list of twenty entries had grown past the point where the next action was visible in it. It is now grouped by the surface each entry changes, since an entry's cost is mostly set by what it touches, and every open issue is named on the entry covering the same ground so the two are read together rather than worked twice.
A new opening section records five issues verified complete against the tree, each with the commit that fixed it, so closing them cites evidence rather than assertion. Those five are #509, #490, #489, #379 and #353, and each was re-checked line by line rather than taken at its filed state.
Three new entries:
prose_lint.pyscope floor, promoted to lead its bucket, because every verdict below it is only worth reading once the gate can prove it read something.docker runre-pulls an absent registry tag by itself, so the cases that actually break are a locally built tag and any command that branches on the image being present.gh-write-guardnewline defect found while running this sweep._git_subcommand_arglistsends agit pushargument list at&&but not at a newline, so a push followed by any later line resolves that line's tokens as refspecs. Measured: one branch alone, five with a followinggh pr createwhose base isdevelop. It over-blocks rather than under-blocks, so it is not a safety hole, but the denial tells the agent it is bypassing a branch rule with admin power when it is pushing an ordinary feature branch, and a safety hook that cries wolf stops being read.reports/divergences.mdRegenerated. The committed copy was dated 2026-07-22 and predated the router split, so it still filed "Repository Boundaries and Write Safety", "Git and Commit Rules" and "Verification Discipline" under
AGENTS.mdwhen all three now live inGOVERNANCE.md. It was handing out a work list measured against a tree that no longer exists.The refresh corrected the propagation picture materially: only two repos carry
GOVERNANCE.mdat all, verified against the live repos rather than inferred. The router split never reached the fleet, so what reads as a section re-vendor is, for seventeen repos, adopting the split itself. That is #305's problem rather than a sweep, and the entry says so.