Read the Line-Ending Pins Against the Tree, Not Just Each Other - #634
Merged
Conversation
`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. Raised as #633 by a downstream agent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new filesystem-facing line-ending gate (eol-coverage) so the existing eol check is no longer “document-to-document only” and can detect cases where .gitattributes/.editorconfig agree with each other but are still wrong for the tracked tree (including dead pins and unpinned shebang scripts).
Changes:
- Add
eol-coveragetoscripts/repo_gate.py, including forward-declared pin marking, shebang detection, and gitattributes-style glob matching for dead-pin detection. - Expand
scripts/test_repo_gate.pywith unit/integration-style tests covering the new check, theforward-declaredmarker behavior, and matching semantics. - Update documentation and carried baseline prose to make the new behavior and the
forward-declaredconvention explicit (scripts/README.md,.gitattributes,TODO.md).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Records follow-up work and links related to the newly closed gap and future symmetric .editorconfig coverage reading. |
| scripts/test_repo_gate.py | Adds/extends tests for eol-coverage, forward-declared pin parsing, gitattributes glob semantics, and non-vacuity floors. |
| scripts/repo_gate.py | Implements the new eol-coverage check plus shared pin parsing, shebang scanning, and dead-pin detection. |
| scripts/README.md | Documents the new check, its rationale, and the forward-declared exemption mechanism. |
| .gitattributes | Marks the lockfile/Dockerfile LF pins as forward-declared so dead-pin detection can distinguish carried-baseline pins from genuinely dead ones. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
ptr727 added a commit
that referenced
this pull request
Aug 9, 2026
…ate (#635) Promotion of develop at 2082547, six squashes since the last one. Closes#633. #631 routes the README by reader and documents the GH_WRITE_GUARD_ALLOW grant where a denied cross-owner write puts the reader. #632 moves readme.sections from intent to letter with four checks beside it, backed by spec/readme-sections.json and spec/third-party-tools.json, and settles the tagline rule. #634 adds repo_gate.py --check eol-coverage, reading the line-ending pins against the tree rather than only against .editorconfig. #636 and #637 repair two readers Copilot found on this pull request, both defects develop already carried: a tool row required both outer table pipes that GitHub's Markdown makes optional, and a retired badge written as an inline image was invisible to a scan that read reference definitions alone. #638 turns the host contract's presence check into a version gate, and retires two gh workarounds that were artifacts of a stale distribution package, re-tested on an upgraded host rather than inferred. Four carried files owe a downstream re-vendor and none is recorded in the TODO.md entry yet. GOVERNANCE.md Repository Details is verbatim, so the audit reports it, and it propagates a rule: the About description is the tagline alone, and Docker Hub receives it from the About panel rather than from the README. CODESTYLE.md item 4 and .gitattributes are intent, so nothing reports them, and the second couples to the new gate through the forward-declared mark. .github/copilot-instructions.md is intent and propagates a correction rather than a refresh, so a repo left on the old copy is wrong rather than merely stale.
This was referenced Aug 23, 2026
ptr727 added a commit
that referenced
this pull request
Aug 23, 2026
Adds a cluster to TODO.md tracking #931: whether Blog's \`-text\` plus explicit named \`eol=lf\` pins plus a rotted-pin gate is an accepted alternative to the fleet's \`text=auto eol=lf\` default. Reflects the issue's own follow-up comment, which changes the original post's position: option 3 (ask Blog to adopt \`text=auto eol=lf\`) is now ruled out, since it would defeat \`eol-coverage\` (the check #634 built for exactly this failure mode) for the one repo whose bug caused that check to exist. The comment now leans toward option 1 (document the alternative in \`line-endings.md\`) over option 2 (a plain registry driftNote), and raises a secondary open question about whether \`eol-coverage\` runs in Blog's own CI. No decision made here, this is the pointer per TODO.md's own convention, the actual call is explicitly the maintainer's per the issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a decision record documenting Blog’s divergence from the standard `.gitattributes` pattern. * Linked the decision to the relevant tracking issue for future reference. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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 freeto 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.
Closes#633.
The gap
repo_gate.py --check eolcompares.gitattributesagainst.editorconfigand never compares either against the repository. That direction is correct and stays unchanged. What it cannot see is the tree: both documents can agree perfectly and both be wrong about the repository they describe, andspec/files.jsonmarks.gitattributesasintent, so what stood in the gap was an agent reading the file for meaning during an audit.Measured, not argued
ptr727/Blogat392de22carried both shapes. Run against that commit:All three reported defects caught, and Blog's other six pins raise nothing.
ops/vps-backup-pullis run unattended by systemd on a backup host, so a CRLF checkout hands it a broken interpreter line.The dead pin is the worse half. The comment above Blog's two asserted the extensionless case was handled, so the one real instance twenty lines up read as covered by everyone who opened the file. A dead pin does not merely fail to bind, it reads as coverage.
What ships
eol-coverage, a new check rather than a widenedeol. Widening in place would make the docstring,scripts/README.md, andTestEolwrong at once, and would destroy the ability to ask the config question alone.unpinned: a tracked file opening#!that git does not resolve toeol=lf. Delegates togit check-attr, so it cannot disagree with what a checkout applies. The shebang decides rather than the mode bit, since the two move independently. This starts enforcing the GOVERNANCE.md "Line Endings" sentence "any repo whose tooling ships extensionless scripts adds the matching path pin", which had nothing behind it. This repo passes with zero slack: 16 tracked#!files, 16 resolving tolf.dead: a pin matching no tracked file, unless its block is markedforward-declared.eol. Where.editorconfigsets[*] end_of_line = lf, the override the check looks for is satisfied by the global default for any path, an absent one included, so the check asserts nothing about pin content. Blog is shaped that way, as are the repos declaringlineEndings: lfinregistry/repos.json. It does not fail there, it says what it did not read.The exemption decision
Three of this repo's pins match nothing today and all three are correct:
uv.lock,Dockerfile,*.Dockerfile..gitattributesis carried, so those go live the moment a consumer adds a lockfile or a Dockerfile, which the file already said in prose. Theforward-declaredtoken makes that prose machine-readable and travels with the carried copy, so a python repo holding the baseline without a lockfile stays exempt too.Two alternatives rejected: an exception list inside the script restates data the scanned repository already carries, and diffing against the hub's own
.gitattributesmakes a repo gate change verdict when another repository changes and cannot run standalone.What it trades away is stated rather than left to be found: the mark reaches to the next blank line, so a pin appended under a marked block inherits an exemption nobody wrote for it, and that fails open.
TestGitattributesCouplingtherefore names the three pins the marking is for and fails the moment a fourth arrives, rather than the parser growing a second rule.Two implementation notes
Matching is gitattributes matching, not pathspec matching.
git ls-files -- <pattern>is the cheap way to ask whether a pin binds anything and is a different language: there*crosses a/, socapture/*.pyalso matchescapture/sub/x.pyand a dead pin reads as live.attr_globhandles basename-versus-anchored, the leading slash,**as a whole segment, and character classes, with a 20-case table.The shebang floor is a suite case rather than a runtime finding. A source-only configuration repo shipping no scripts is legitimately clean, so failing an empty scan would be a false finding in the common fleet case. What must not go unnoticed is this repo's own scan going quiet, which
TestCoverageFloorsholds instead. Every run still prints its counters, for the reasonsha-pindoes.Deliberately out of scope
The symmetric
.editorconfigreading, a path-specific section naming files that do not exist. This repo's[.github/workflows/*]and[catalog/snippets/workflows/*]sections are legitimately broad, so the exemption needs measuring against the live corpus before a gate is built on it. Recorded inTODO.mdunder Standalone Chores.TODO.mdalso gains areadyWork Cluster for the general case this issue is one instance of: an observer that reads merged fleet pull requests and classifies each changed path against what the hub declares it owns. Nothing detected the downstream editing hub-managed CI files here except the maintainer noticing.Verification
test_repo_gate.pygoing 46 to 70 cases, harness floor raised to 68.spec/validate.py,spec/audit.py --selftest, all three repo gates, whole-treeprose_lint.py,markdownlint-cli2andeditorconfig-checkerall clean. CRLF preserved in the three Markdown and dotfile edits, LF in both scripts.