Read a URI-only comment as a reference, not as a sentence - #500
Conversation
A comment whose whole body is a URI cannot be capitalized or restructured without corrupting the address it exists to carry, so `comment-case` reported a finding no edit could answer. The hub's own `.github/dependabot.yml` opens with one, as does `.editorconfig`, so every repo carrying a reference block inherited it. Neither comment rule now applies to a body that is only a URI. The exemption also stops the line below a URI from reading as its continuation, since consecutive reference lines are separate addresses rather than one sentence wrapping. A URI inside a sentence is still prose, so the whole body has to be the address and nothing else. Exposed and fixed in passing: `.editorconfig` line 11 is a bare command that the old rule hid inside a false `comment-wrap` on the URI above it. It is restructured to open on a capital rather than left as a new finding. Reported by the Blog agent as finding 4 of #489. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This pull request updates the prose_lint comment rules so a comment whose entire body is a bare URI is treated as a reference (not prose), preventing false positives from comment-case / comment-wrap and avoiding “continuation” misclassification on the following line.
Changes:
- Add a
BARE_URIexemption to the shared comment rule path so URI-only comment bodies are skipped bycomment-wrapandcomment-case. - Add targeted tests covering multiple comment syntaxes, URI forms, and the continuation edge case.
- Update documentation and adjust
.editorconfigto avoid introducing a net-new real finding once the false continuation is removed.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
scripts/prose_lint.py |
Adds BARE_URI and applies it to the shared comment-wrap/comment-case findings logic. |
scripts/test_prose_lint.py |
Adds tests for URI-only comment bodies, continuation behavior, and “URI inside a sentence” still being prose. |
scripts/README.md |
Documents the new URI-only comment exemption and its intended boundaries. |
.editorconfig |
Restructures one comment line so it remains compliant once URI-only comments stop affecting continuation logic. |
BARE_URI accepted `<https://x` and `https://x>`, because the opening bracket was optional and `\S+` consumed a trailing one. An unbalanced bracket is a typo, so exempting it hid the typo rather than reporting it. Both ends of the alternation now exclude `>`, so a delimited URI needs both brackets and an undelimited one carries neither. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (2)
scripts/prose_lint.py:525
BARE_URIonly matches lowercase schemes (https?/ftp). URI schemes are case-insensitive, so a URL-only comment like# HTTPS://example.com/...would not be exempted and can still participate in thecomment-wrapcontinuation logic (reintroducing the false-positive this PR is trying to eliminate).
BARE_URI = re.compile(r'^(?:<(?:https?|ftp)://[^>\s]+>|(?:https?|ftp)://[^>\s]+)$')
scripts/test_prose_lint.py:395
- The new URI exemption behavior isn’t currently asserted for case-insensitive schemes. Adding a case where an uppercase-scheme URI line precedes a lowercase non-URI comment would fail without
re.IGNORECASEand pass with it, preventing regressions.
def test_a_uri_block_does_not_make_the_next_line_a_continuation(self) -> None:
"""Consecutive reference lines are separate addresses, not one sentence wrapping."""
self.assertEqual([], self.flag('a.yml', '# https://example.com/one\n'
'# https://example.com/two\n'))
RFC 3986 makes the scheme case-insensitive, so `HTTPS://example.com` is the same reference as the lowercase form. The exemption missed it, which put the reference line back into the wrap logic and reproduced the exact false `comment-wrap` this exemption removes. Found as a suppressed finding in the second Copilot round, verified against the rule before accepting it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Closing the loop on the two suppressed findings from the second round, since a suppressed finding still gets verified rather than accepted on sight. 1. Case-insensitive scheme — real, fixed in Reproduced against the rule before changing anything: RFC 3986 makes the scheme case-insensitive, so the uppercase form is the same reference. Missing it put the reference line back into the continuation logic and reproduced exactly the defect this PR exists to remove. 2. Missing case-insensitive test — real, added in
Tree-wide verdicts are unchanged by both fixes: 526 -> 516, ten removed, none added, and Full local CI: 141 tests OK (was 139), |
The `repo-config/configure.sh` sweep listed in `TODO.md`, and finding 6 of #489. ## Why the hub sweeps this one `configure.sh` is `verbatim` fidelity with `appliesTo: "*"`, so every repo in the fleet carries it byte-matched and **cannot correct it locally**. GOVERNANCE already names this case: > In carried verbatim content the hub sweeps a class rather than waiting for the next edit. Correct-as-you-next-edit assumes someone able to edit the file, and a downstream repo cannot edit a verbatim section. Blog is onboarding now, so fixing it here means Blog carries the corrected file from the start rather than inheriting 54 findings it has no way to answer. ## Result **54 findings, all gone.** Verified under `develop`'s current rule, not a modified one. ``` before: comment-wrap 43, comment-case 11 (54) after: 0 ``` ## What was actually wrong, by shape I classified the 54 before touching anything, because a sweep driven by a bad work list damages a correct document: | Count | Shape | Treatment | | --- | --- | --- | | 35 | Ordinary wrapped prose | Rewritten one sentence per line | | 17 | Opens on a lowercase API or JSON key (`apply:`, `check:`, `has_discussions:`, `per_page=100`, `pull_request:`, `required_status_checks:`, `vulnerability-alerts:`, plus the `assert`/`jq_has`/`gh_ok` helper docs) | Restructured so the sentence does not open on the name, which is what GOVERNANCE prescribes rather than capitalizing a tool name against the CODESTYLE tooling-casing rule | | 2 | The usage synopsis (`repo-config/configure.sh apply ...`) | Given an `Apply:` / `Check:` label so the line no longer reads as a lowercase sentence | The `ruleset_id` doc comment moved from a trailing position to above the function, because a trailing comment carrying two sentences cannot be split where it sits. ## No behavior change This is a comments-only change to an operational script that writes GitHub rulesets, so that claim is evidenced rather than asserted: - **Every non-comment line is byte-identical**, with the single intended exception of `ruleset_id() { # ...` becoming `ruleset_id() {`. - **All three `shellcheck disable=SC2016` directives survive** and still sit directly above the lines they suppress (93, 199, 223). A directive that drifted off its target would silently stop suppressing. - **shellcheck passes clean** (`koalaman/shellcheck:latest`, exit 0). - **`bash -n` parses.** - **LF endings preserved**, as `[*.sh]` requires. ## One thing worth a follow-up The two synopsis findings were fixable here with a label, but the underlying shape is general: a comment whose body is a **command invocation** is not prose, the same way a bare URI is not (#500). Every repo that documents a script's usage in a comment block will hit this. A synopsis exemption in `prose_lint.py` is probably the better long-term answer than labeling each block, and I did not add one here because #500 is already changing that file. ## Downstream consequence, stated plainly `configure.sh` is verbatim, so **every repo carrying it now differs from the hub until re-vendored**. That is the documented cost of a verbatim sweep, not a surprise, but it does mean a fleet re-vendor pass is owed after this merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Promotes the three prose-gate fixes to `main`, so a repo carrying the baseline from `main` gets the corrected gates and the corrected `configure.sh` rather than the versions that reject correct work. This is the unblocking step for the `Blog` onboarding in #456: `Blog` carries from `main`, and today's `main` hands it two false-positive gates and 54 unfixable findings. | PR | Squash | What | | --- | --- | --- | | #499 | `d68b2ca` | `dupword` reads the comments outside markdown, not the raw source line (#489 finding 3) | | #500 | `c3ad6db` | A URI-only comment is a reference, not a sentence (#489 finding 4) | | #501 | `67aaa6c` | The `verbatim`-carried `repo-config/configure.sh` swept to one sentence per line (#489 finding 6) | ## What this changes for a repo carrying from `main` - `class="gallery gallery-cols-1"` and any other repeated token outside a comment stops being a **blocking** CI failure. That was the reported case with no legal fix in the HTML. - A comment whose whole body is a URI stops producing a `comment-case` finding no edit can answer. Every config file opening with a reference block inherited one. - `repo-config/configure.sh` arrives clean. It is `verbatim` with `appliesTo: "*"`, so a downstream copy is byte-matched and could never have been fixed locally. ## Consequence, stated plainly `configure.sh` is `verbatim`, so **every repo already carrying it now differs from `main` until re-vendored**. A fleet re-vendor pass is owed after this merges. A repo onboarding after this point carries the corrected file from the start. ## Verification Run on `develop` at `67aaa6c`, which is what this promotes: ``` python3 scripts/test_prose_lint.py 141 tests, OK python3 scripts/test_repo_gate.py 23 tests, OK python3 scripts/test_pr_review.py 27 tests, OK python3 spec/audit.py --selftest SELFTEST PASS python3 scripts/repo_gate.py eol 0, sha-pin 0 prose_lint --check charset --check dupword --check spelling clean ``` Warn-only backlog moved 526 -> 516 on the comment rules from #500, and `configure.sh` went 54 -> 0 from #501. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The `.editorconfig` / `.gitattributes` / `.gitignore` entry from `TODO.md`, taken now because `Blog` is carrying the baseline this week and these are the first files it copies. `TODO.md` states the reason itself: until they are fixed, a new repo learns the shape the rules forbid. ## Result **36 findings gone.** `.gitignore` was already clean, so the 44 that `TODO.md` claimed was stale, most likely because #500 removed several `.editorconfig` findings on the way past. | File | Before | After | Fidelity | | --- | --- | --- | --- | | `.editorconfig` | 22 | 0 | `intent` | | `.gitattributes` | 14 | 0 | `intent` | | `.gitignore` | 0 | 0 | repo-owned | Both swept files are `intent` fidelity, so a downstream copy is **adapted rather than byte-matched**. Unlike #501 this obliges no re-vendor. ## A recommendation I am reversing In #501 I said a **command-synopsis exemption** in `prose_lint.py` was probably the better long-term answer than labeling each block. Having done this sweep, I no longer think so, and this PR labels instead. Two bare commands appear here: ``` # git config --global core.autocrlf false # git ls-files --eol ``` The difference from the bare-URI case in #500 is that **relabeling a command is easy and improves it**, where relabeling a URI is impossible without corrupting the address: ``` # Configure with: git config --global core.autocrlf false # Inspect with: git ls-files --eol ``` The URI needed an exemption because there was no legal fix. A command has one, and it reads better. On top of that, "a comment body that is a command invocation" has no crisp form the way `^scheme://...$` does, and a fuzzy exemption inside a gate silently swallows real prose. So the rule stays as it is. ## What was corrected, by shape - **Wrapped prose** rewritten one sentence per line, which is the bulk of the 36. - **Comments opening on `git` or `uv`** restructured rather than capitalized, per GOVERNANCE's tooling-casing rule. For example `# uv regenerates uv.lock ...` became `# The uv tool regenerates uv.lock ...`. - **Bare commands** labeled, as above. ## Fixed in passing, since these files were open A mid-sentence semicolon in `.gitattributes` and several spaced hyphens in both files. GOVERNANCE bans both in agent-authored prose, and neither was reported here because the `semicolon` and `dash` rules are still markdown-only. Leaving them would have carried the banned construction into every repo copying these files. ## No setting or pattern changed The whole point is that this is comment-only, so it is evidenced rather than asserted: - **Every non-comment line in both files is byte-identical**, checked by diffing the files with comment lines stripped. - **`repo_gate`'s `eol` check still passes.** That check cross-validates the two files against each other, asserting every path pinned LF in `.gitattributes` has the matching `.editorconfig` override, so it would catch a mangled pattern or section header. ## Verification ``` prose_lint .editorconfig .gitattributes --check comment-wrap --check comment-case clean prose_lint . --check charset --check dupword --check spelling clean prose_lint . --diff HEAD clean, all default rules python3 scripts/repo_gate.py eol 0, sha-pin 0 python3 scripts/test_prose_lint.py 141 tests, OK python3 spec/validate.py OK, 21 cataloged ``` Tree-wide warn-only backlog moves **1152 -> 1116**. ## TODO.md Drops the two swept entries, and adds the **fleet re-vendor of `configure.sh`** that #501 obliges, which was previously recorded only in the PR body. The `README.md` entry is reworded: it was deferred because two edits to that file were in flight, and both have since landed, so it is now free to take. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Finding 4 of #489, reported by the Blog agent. Same class as #499, which fixed finding 3.
The defect
A comment whose entire content is a documentation URI is not a sentence, and it cannot be capitalized or restructured without corrupting the address it exists to carry.
comment-casereported it anyway:That line is
# https://docs.github.com/..., the reference the file opens with. A reference block opening a config file is the ordinary shape, so every repo carrying one inherited a finding no edit could answer. The hub's own.editorconfigopens with a block of eight.The fix
BARE_URIjoins the existingNOT_PROSEexemption at the one place both comment rules consult, so a body that is only a URI is skipped bycomment-caseandcomment-wrapalike.Skipping it also stops the line below a URI from reading as its continuation. That mattered more than expected: consecutive reference lines are separate addresses, and the old rule was reporting
comment-wrap("sentence wraps into the next line") on a URL that wrapped into nothing.A URI inside a sentence is still prose, so the exemption requires the whole body to be the address and nothing else.
Verdict diff, tree-wide
Ten findings removed, none added. Counted with
prose_lint.py . --check comment-wrap --check comment-case, ignoring pure line-number shifts insideprose_lint.pyitself..github/dependabot.yml:1.editorconfig:1,:3,:7,:10.editorconfig:3,:4,:7,:10catalog/snippets/configs/dependabot.yml:2One finding this exposed, fixed rather than left
Removing the false continuation on
.editorconfig:10revealed.editorconfig:11, a bare command (dotnet format style --verify-no-changes ...) that the old rule had been hiding inside acomment-wrapon the URL above it. It is a genuine lowercase opening, not a URI, so the exemption does not cover it.Rather than ship a net-new finding, it is restructured to
# Verify with: dotnet format ..., which is what GOVERNANCE's own guidance prescribes for a comment opening on a lowercase tool name..editorconfigisintentfidelity, so this does not force a downstream re-vendor.This is one line of the larger
.editorconfigcomment-shape sweep still owed inTODO.md. It is fixed here only because this change is what surfaced it.Asserting the floor
test_a_uri_inside_a_sentence_is_still_proseproves the other direction, that the exemption did not swallow prose that merely mentions a URL.test_a_uri_block_does_not_make_the_next_line_a_continuationpins the continuation behavior, which is the subtle half.#,;,//and<!-- -->syntaxes, plus the angle-bracketed andftp://forms.Verification
Every step of the CI job run locally:
🤖 Generated with Claude Code