Skip to content

Promote the README Rework, the Tool-Row Repair, and the Line-Ending Gate - #635

Merged
ptr727 merged 6 commits into
mainfrom
develop
Aug 9, 2026
Merged

Promote the README Rework, the Tool-Row Repair, and the Line-Ending Gate#635
ptr727 merged 6 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727ptr727 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Closes#633.

Promotion of develop at 2082547, six squashes since the last one. Merge commit only, no squash and no rebase, and no --delete-branch, since this pull request's head is develop itself.

What is being promoted

SquashChange
9b73d1a (#631)Routes the README by reader, adding a Getting Started table above the Table of Contents and three sections the file lacked, and documents the GH_WRITE_GUARD_ALLOW grant in docs/host-setup.md where a denied cross-owner write puts the reader.
c2ce145 (#632)Moves readme.sections from intent to letter and adds four checks beside it, backed by the new spec/readme-sections.json and spec/third-party-tools.json. Settles the tagline rule, which is #577's first half, and closes the License section over the file.
0bb43c5 (#634)Adds repo_gate.py --check eol-coverage, reading the line-ending pins against the tree rather than only against .editorconfig, in the two directions #633 measured: a shebang file git does not resolve to eol=lf, and a pin matching no tracked file.
1e2a4ba (#636)Repairs the tool-row reader Copilot found on this pull request: both outer table pipes are optional, as GitHub's Markdown makes them, and the header is read for the License column that spec/project-types.json already asserted at verdict letter without anything reading it.
042c853 (#637)Repairs the second reader Copilot found here: a retired badge written as an inline image was invisible to the deprecatedShields scan, which read reference definitions alone, and the finding's own wording then mis-attributed a render to an unused reference.
2082547 (#638)Adds scripts/host_gate.py and spec/host-tools.json, turning the host contract's presence check into a version gate. Retires two gh workarounds that were artifacts of a stale distribution package, re-tested on an upgraded host rather than inferred.

27 files at 2906 insertions against 101 deletions, of which spec/ and scripts/ are 2597, so the prose half of the diff is small.

The closing keyword

Closes #633 is on this pull request rather than on #634, because GitHub fires a closing keyword only when it merges into the default branch, and #634 merged into develop. Merging this closes that issue. The work itself landed in 0bb43c5.

Issue #577 stays open deliberately. Its tagline half shipped in c2ce145, and TODO.md now reuses the issue for what remains, the README-to-About-panel hop that nothing writes.

The two findings this pull request produced

Both are defects in develop, not in the promotion, and both were fixed on develop rather than here, since this pull request's head isdevelop. They share one shape, which is the reason to state them together: a claim wider than the code behind it, where the comment, the finding text and the selftest name all agreed with each other and none agreed with the behavior.

  • Read a Tool Row Without a Trailing Pipe, and Report the License Column #636, the tool-row reader.third_party_tool_findings required a trailing pipe on every row, so a valid Markdown row without one was skipped and its link, description and ordering all went unread. A table written that way reported zero findings, which scores as clean rather than as unread. Round 2 found the symmetric case on the leading pipe. Also makes honest an assert spec/project-types.json already carried at verdict letter, that the section carries no License column, which nothing read.
  • Report a Retired Badge Written Inline, Not Only One Defined #637, the retired-badge scan.readme_shield_findings read reference definitions alone while its comment said the badge is caught "wherever it sits" and its selftest case carried that name while testing one form. An inline badge rendered and reported nothing. Three further rounds corrected the finding's own wording, moved attribution from the URL to the reference name, and settled the four shapes a retired badge can take.

Measured across all 22 cataloged READMEs at every round, in both directions: tool findings go 56 to 57, the addition being Blog's License column; shield findings stay at 7 with no verdict and no wording moving. No fleet README carries either blind spot today, so both fixes close a silent gap rather than correct a live report. Stating that plainly is the point: the 17 repos still owing a 3rd Party Tools section have not written their tables yet.

What this owes downstream

Three carried files changed, and each reaches the fleet differently:

  • GOVERNANCE.md"Repository Details" is verbatim, so every downstream copy is byte-mismatched and the audit reports it as stale. It propagates a rule rather than refreshing a hash: the About description is the tagline alone rather than the paragraph it opens, and Docker Hub receives it from the About panel rather than from the README, so a panel left diverged is carried onward instead of corrected at publish time.

  • CODESTYLE.mditem 4 is intent and whole, so no audit reports it. A repo on the old copy has HISTORY.md mirror the whole intro paragraph, which is wrong for any README that takes the room the tagline rule opens.

  • .gitattributes is intent and whole, and this one couples to the new gate. eol-coverage exempts a pin block marked forward-declared, and a repo holding the pre-0bb43c5 copy carries no such mark, so its uv.lock, Dockerfile and *.Dockerfile pins read as dead the first time the check runs there against a tree with no lockfile and no Dockerfile.

  • .github/copilot-instructions.md is intent, so nothing reports it, and it propagates a correction rather than a refresh. Its gh pr edit entry called the command broken outright; it now names the 2.45.x / 2.46.x range and says to check gh --version first. A repo left on the old copy has its agents routing around a command that works, which is the inverse of the usual staleness: the old text is not merely out of date, it is now wrong.

AUDIT.md is intent and takes one dimension description; cspell.json takes one word; OPERATIONS.md and docs/host-setup.md are this repo's own; spec/ and scripts/ are hub-hosted tooling and carry nothing.

None of these four are recorded in the TODO.md re-vendor entry yet, which still lists the batch ending at #629. That entry needs the three above added before the sweep runs, and saying so here rather than editing TODO.md on this pull request keeps the promotion a merge of what develop already holds.

The conformance backlog this opens

The readme-structure checks were run against all 22 cataloged repos before shipping, and TODO.md records what they report: 73 section and shield findings, 57 third-party-tool findings across four repos, 55 link-naming findings with 27 more on grouping, and the retired byob.yarr.is badge on three repos. No repo is edited by the hub, and each lands on its own next visit. This repo reports zero across all four checks.

🤖 Generated with Claude Code

ptr727and others added 3 commits August 8, 2026 13:32
…631)
The README opened straight into prose written for an agent, so a human
arriving from GitHub had no path to what they actually ask: how to use
this repo, how to deploy the host rules, how to diverge from one, and
where to file an issue. It now forks by reader, and it carries the
write-grant documentation that `TODO.md` recorded as ready in #629
without doing.
## The README, routed by reader
A **Getting Started** table above the Table of Contents states the
human/agent split outright, an agent starting at `AGENTS.md` and a human
starting at the table, then routes six reader shapes to one destination
each. Three new sections fill the gaps, placed in the slots
[`spec/readme-structure.md`](./spec/readme-structure.md) already orders
them in so the redesign does not fight the hub's own spec:
- **Using This Repo** (Installation slot), covering the host guardrails,
carrying the rules into a repository, and an honest note that the
registry does not transfer, so adopting outside this fleet means running
your own hub.
- **Diverging From a Rule** (Configuration slot), which separates the
two kinds of exception that get confused: a repository diverging from a
carried unit, where the fidelity level usually already grants the
freedom and the rest is recorded in `spec/divergences.json`, and a write
the host guard denies.
- **Questions or Issues**, previously absent entirely, saying what an
issue needs and that a downstream agent filing here is the normal path
rather than an exception.
## The cross-owner write grant
The guard denies a `gh` write whose explicit target sits under another
owner and names `GH_WRITE_GUARD_ALLOW` in the denial, and no document
said how to give one. [`docs/host-setup.md`](./docs/host-setup.md) now
carries it where the reader already is when the denial arrives:
- The **fork asymmetry** that raises it, where `origin` is your fork and
only the half of the work aimed at `upstream` denies.
- The channel that works, an `env` block in the checkout's
`.claude/settings.local.json`, which scopes the grant to that checkout
rather than the host, and the **session restart** that loads it.
- The value's exact shape: one string and never a JSON array, split on
any run of whitespace or commas, with `owner/*` granting a whole owner
and a token carrying no `/` silently granting nothing. Verified against
`_granted_targets` and `_target_permitted`.
- The **two forms that fail silently**, an inline `VAR=x gh ...` prefix
and an `export` in a shell call, neither of which reaches a hook running
as its own process. That is the property making a grant a deliberate act
outside the session rather than something an agent does for itself once
blocked.
- A **confirmation that does not involve making the write**. Worth
recording: feeding the hook a synthetic payload is not a usable probe
from inside a session, because the payload text carries the very write
shape the guard matches, so the guard denies the probe command itself.
Observed twice while writing this.
`host-setup/agent-safety/README.md` carries the mechanics beside the
hook description and links across.
## Also here
- The **license shield** moves into the closing License section,
matching the form PlexCleaner carries.
[`spec/readme-structure.md`](./spec/readme-structure.md) still states
the old placement under Build and Distribution, so this README is
deliberately ahead of the spec, and reconciling the two is the
`decision`-state **README Structure Rework** cluster's edit rather than
a loose end here. That cluster's `Settled` bullet asserting the old
placement as fact is rewritten to record it.
- **Proxmox** drops out of every supported-platform list, since it is
Linux. The two remaining mentions name the actual Proxmox host in the
rollout, which is correct.
- `untriaged` joins the cspell word list, since the new Diverging
section names the literal value the divergence report renders.
- `TODO.md` loses the shipped cluster and its orphaned link definitions,
and the stale "Refresh the README" chore becomes the open question this
change leaves behind: whether these human entry points belong in the
spec so fleet repos are measured on them. The per-section index
alternative is recorded as declined, with the reason.
## Verification
markdownlint, cspell, editorconfig-checker, `spec/validate.py`, and
`scripts/prose_lint.py --diff develop` all clean locally. CRLF preserved
in every Markdown file, LF preserved in `install.sh`. Every relative
link target and every in-file anchor resolves, checked mechanically.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README spec was advisory: `readme.sections` sat at verdict `intent`, so
nothing mechanically checked section presence, order, shields, or link
naming. It is now five `letter` checks driven by declared data.
Two wanted changes land with it. The License section closes the file and
carries the license shield, which 15 of the 16 repos with a License
section already did. The first line under the H1 is the tagline, and it
alone carries the 100-character link-free rule and the mirrors, so a
README may say more about itself above the fold.
Settled alongside them from a survey of all 22 non-archived non-fork
repos: a Table of Contents in every README, `Use Cases` renamed to
`Overview` with no aliases, `3rd Party Tools` required, and
`Questions or Issues` required in a public repo. Reference links gain a
closed five-group set, a suffix per kind, and canonical names for the
destinations every repo has. `spec/third-party-tools.json` declares 40
shared tools so a tool two repos use is linked and described identically
in both.
Every rule was measured against the fleet before it shipped, and the
corpus run corrected the model three times before the pull request
opened: shields keyed on alt text flagged three repos for caption
wording, a coverage class would have told 7 repos to add a badge nobody
chose, and requiring both Docker version shields is wrong for NxWitness,
which carries 40 across six images and four channels that are not
`develop`.
This repo's own README reports zero findings. The fleet backlog the
checks report is 214 findings across 22 repos, recorded in `TODO.md`,
and no downstream repo is edited here.
The Copilot loop ran 14 rounds for 21 findings, all accepted, of which
18 were in suppressed blocks and 6 were defects introduced by earlier
fixes on this branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`repo_gate.py --check eol` compares .gitattributes with .editorconfig and never compares either
against the repository, so both documents can agree perfectly and both be wrong. ptr727/Blog
carried two defects in that gap while the gate reported clean: an extensionless shebang script
systemd runs unattended, matched by no pin, and two pins naming paths never tracked there. The
dead pins are what hid the live one, since a pin that binds nothing still reads as coverage.
`eol-coverage` adds the filesystem-facing direction as a separate check, so `--check eol` keeps
meaning exactly what it means today. A pin block marked `forward-declared` is exempt from the
dead reading, because in a carried baseline a pattern matching nothing is a declaration for
whichever consumer adds the file, and the mark travels with the copy.
Closes#633.
CopilotAI lite review requested due to automatic review settings August 9, 2026 03:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Promotes the develop changeset that reworks the README (including tagline/mirror semantics and audited README structure) and adds a new repository gate for line-ending coverage, then carries the corresponding spec/tooling updates into main.

Changes:

  • Updates the README spec and audit tooling to treat the first post-H1 line as the canonical tagline, and adds auditable README structure/link/shield/tool-catalog rules.
  • Introduces a new repo_gate.py --check eol-coverage that validates .gitattributes EOL pins against the tracked tree (shebang LF enforcement + dead-pin detection with forward-declared exemptions).
  • Updates documentation and governance text to align with the new README/tagline and EOL-coverage behaviors.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
TODO.mdUpdates backlog entries to reflect the shipped tagline work and the new downstream-observer idea, plus link ref maintenance.
spec/validate.pyAdds shape checks for spec/readme-sections.json and spec/third-party-tools.json to avoid audit crashes and silent passes.
spec/third-party-tools.schema.jsonAdds JSON schema for the shared 3rd party tool catalog.
spec/third-party-tools.jsonIntroduces the shared 3rd party tool catalog (standardized links/descriptions).
spec/readme-structure.mdUpdates README prose spec (tagline semantics, required sections, link groups, shields, tools catalog rules).
spec/readme-sections.schema.jsonAdds JSON schema for the declared README section/link/shield model.
spec/readme-sections.jsonAdds the declared README model consumed by the audit (sections, link groups/naming, shield classes, canonical links).
spec/project-types.jsonPromotes README checks from intent to letter and adds new README-related checks (sections/shields/links/tools/tagline).
spec/audit.pyImplements README structure/shield/link/tool checks and tagline-based mirroring behavior.
scripts/test_repo_gate.pyExpands tests to cover eol-coverage, forward-declared pins, and gitattributes-matching correctness.
scripts/repo_gate.pyAdds eol-coverage gate (tree-facing EOL pin validation) and supporting helpers.
scripts/README.mdDocuments the new eol-coverage check and its rationale/constraints.
README.mdReworks the README structure (Getting Started routing, new sections, canonical link groups, license shield placement).
host-setup/agent-safety/README.mdDocuments cross-owner write grants (GH_WRITE_GUARD_ALLOW) and removes Proxmox mentions.
host-setup/agent-safety/install.shUpdates platform comment (removes Proxmox mention).
GOVERNANCE.mdUpdates “Repository Details” to define description/tagline semantics and Docker Hub propagation path.
docs/host-setup.mdAdds a detailed “Granting a Write the Guard Denies” section and removes Proxmox mention.
cspell.jsonAdds “untriaged” to the dictionary.
CODESTYLE.mdUpdates HISTORY.md mirroring rule to mirror the tagline (not the full intro paragraph).
AUDIT.mdUpdates readme-structure dimension description to reflect the declared model + new checks.
.gitattributesMarks uv.lock/Dockerfile pins as forward-declared to support the new dead-pin coverage check.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadspec/audit.py Outdated
…cense Column (#636)
The 3rd Party Tools check required both outer pipes on every table row, which GitHub's Markdown makes optional, so a row written without either was skipped outright and its link, description and ordering all went unread. A table written that way scored clean rather than unread.
spec/project-types.json asserted "The section carries no license column" at verdict letter, and nothing read it. The table header is now read for that column, once per table rather than once per row, and it is the only extra column named, since spec/readme-structure.md forbids that one and no other.
table_cells requires a separating pipe rather than a leading one, which is what keeps a thematic break out: `---` alone carries no pipe, where treating it as a one-cell delimiter row would have read the paragraph above it as a table header.
Measured against all 22 cataloged READMEs, in both directions: 56 findings become 57, the addition being Blog's License column, no verdict is lost, and the leading-pipe widening moves nothing. Neither outer-pipe form appears in the fleet today, so both fixes close a silent blind spot rather than a live miss.
Raised by Copilot on promotion pull request #635.
CopilotAI review requested due to automatic review settings August 9, 2026 04:00
@ptr727ptr727 changed the title Promote the README Rework and the Line-Ending Coverage Gate to MainPromote the README Rework, the Tool-Row Repair, and the Line-Ending GateAug 9, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (1)

spec/audit.py:796

  • readme_shield_findings claims retired badge services are reported “wherever it sits”, but the implementation only scans reference definitions (defs.items()). If a repo uses the retired badge as an inline image (![...](https://...)), it won’t appear in defs and will be missed even though the URL is rendered in the README.
 findings = []
defs = {m.group(1): m.group(2) for m in _LINK_DEF.finditer(unfenced_text(text))}
# A retired badge service is scanned across the whole document rather than per section, since a dead badge is wrong wherever it sits.
# It renders broken rather than absent, which a visitor reads as a failing build rather than as a stale badge.
for dep in model.get("deprecatedShields", []):
for ref, url in sorted(defs.items()):
if dep["match"] in url:
findings.append(("LETTER", f"readme: `[{ref}]` renders {dep['label']}, which is retired - {dep['reason']} (spec/readme-structure.md)"))

readme_shield_findings scanned reference definitions alone for a retired badge service, so a repo writing it as an inline image rendered the dead badge and reported nothing. The comment above the loop claimed it was scanned wherever it sits, and the selftest case carried that name while testing the reference form only, so the claim and its evidence agreed with each other and not with the code. shield_endpoints already resolves both forms and its docstring records the same lesson; this was the one site that had not taken it.
Three review rounds refined it. The finding text claimed every matching definition renders the badge, which contradicted the comment directly above it, so the wording now follows which of four shapes it is. Attribution moved from the URL to the reference name, because the same endpoint rendered inline leaves a definition unused and reading the URL credited that render to a reference nothing uses. unfenced_text is computed once, so the definitions and the rendered images cannot be read from two views of the document, and the endpoint set is held as a set named by its namespace.
Measured against all 22 cataloged READMEs at every round: shield findings stay at 7 and no verdict or wording moves, since no repo carries either shape. Every new selftest case was A/B tested against the pre-fix function rather than reasoned about.
Raised by Copilot as a suppressed finding on promotion pull request #635.
CopilotAI review requested due to automatic review settings August 9, 2026 04:23

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

@ptr727

Copy link
Copy Markdown
OwnerAuthor

Closing the record on the suppressed finding raised here on 1e2a4ba, whose fix landed on a separate pull request:

readme_shield_findings claims retired badge services are reported "wherever it sits", but the implementation only scans reference definitions (defs.items()). If a repo uses the retired badge as an inline image (![...](https://...)), it won't appear in defs and will be missed even though the URL is rendered in the README.

Accepted and fixed on develop in #637, now 042c853, which this pull request's head carries. It took four rounds, because the first fix was correct and its reporting was not:

  1. Both image forms are read, via shield_endpoints, which already resolved them for every other shield and whose docstring records this exact lesson. This was the one site that had not taken it.
  2. The finding text claimed every matching definition "renders" the badge, contradicting the comment directly above it. The wording now follows which of four shapes applies.
  3. Attribution moved from the URL to the reference name. The same endpoint rendered inline leaves a definition unused, so reading the URL credited that render to a reference nothing uses, and deleting the named reference would have left the badge on the page.
  4. The endpoint set is held as a set, named by namespace, since this block reasons about reference names and URLs a line apart and step 3 was exactly that confusion.

Measured against all 22 cataloged READMEs at every round: shield findings stay at 7 and no verdict and no wording moves, because all three repos carrying the badge write it as a rendered reference definition. Every new selftest case was A/B tested against the pre-fix function rather than reasoned about.

Both findings this pull request produced share one shape, which is why the description now states them together: a claim wider than the code behind it, where a comment, a finding message and a selftest name all agreed with each other and none agreed with the behavior. Neither was introduced by this promotion; both were in develop and are now fixed in it.

The host contract checked presence, and both host defects this fleet has hit are version facts on a tool that is installed, answers --version, and looks healthy. spec/host-tools.json declares the floors as data and each records the defect it encodes, so most entries carry none deliberately: a floor nobody can justify becomes a host failure nobody can act on. scripts/host_gate.py reads it and replaces the presence-only line in the docs/host-setup.md verification block.
Two floors. A distribution gh in the 2.45.x / 2.46.x range is named by the GitHub CLI maintainers as broken by deprecated GitHub APIs. A git-restore-mtime before 2025.08 calls git whatchanged, which current git refuses, so it restores nothing, prints its ordinary statistics and exits 0, and a deploy keyed on mtimes then ships a full copy and reports success. Note the direction of that second one: a newer git is the trigger rather than the remedy.
A repository layers its own host-tools.json over the hub's, so a repo needing ffmpeg, or needing a tool the fleet calls optional, declares that where it is true. Layering is tighten-only, since lowering a floor from inside the repository a floor protects retires the check, and a rejected relaxation is reported rather than dropped.
The premise was verified during review. The maintainer installed gh 2.97.0 from the official repository and git-restore-mtime 2025.08, the gate went clean, and both gh limitations OPERATIONS.md carried as permanent behavior were re-tested and are gone. That file and .github/copilot-instructions.md are corrected, the latter naming the version range rather than this host since it reaches the fleet. Neither symptom was visible to a presence check, which is the argument for the whole change.
Thirteen review rounds, every finding the same shape: a guard whose stated scope is wider than what it reads.
CopilotAI review requested due to automatic review settings August 9, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit b4b8f2e into mainAug 9, 2026
7 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

repo_gate eol checks the two line-ending documents against each other, never against the tree

2 participants

@ptr727