Skip to content

Read a URI-only comment as a reference, not as a sentence - #500

Merged
ptr727 merged 3 commits into
developfrom
feature/bare-uri-comment-exemption
Aug 1, 2026
Merged

Read a URI-only comment as a reference, not as a sentence#500
ptr727 merged 3 commits into
developfrom
feature/bare-uri-comment-exemption

Conversation

@ptr727

@ptr727 ptr727 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

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-case reported it anyway:

.github/dependabot.yml:1: comment-case: comment sentence opens in lowercase -> capitalize, or restructure so it does not open on a lowercase name

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 .editorconfig opens with a block of eight.

The fix

BARE_URI joins the existing NOT_PROSE exemption at the one place both comment rules consult, so a body that is only a URI is skipped by comment-case and comment-wrap alike.

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 inside prose_lint.py itself.

before 526  ->  after 516
Removed Rule
.github/dependabot.yml:1 comment-case (the reported case)
.editorconfig:1, :3, :7, :10 comment-case
.editorconfig:3, :4, :7, :10 comment-wrap (the false continuation)
catalog/snippets/configs/dependabot.yml:2 comment-case

One finding this exposed, fixed rather than left

Removing the false continuation on .editorconfig:10 revealed .editorconfig:11, a bare command (dotnet format style --verify-no-changes ...) that the old rule had been hiding inside a comment-wrap on 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. .editorconfig is intent fidelity, so this does not force a downstream re-vendor.

This is one line of the larger .editorconfig comment-shape sweep still owed in TODO.md. It is fixed here only because this change is what surfaced it.

Asserting the floor

  • Six new test cases fail against the old rule and pass against the new one, so they are not vacuous.
  • test_a_uri_inside_a_sentence_is_still_prose proves 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_continuation pins the continuation behavior, which is the subtle half.
  • Cases cover #, ;, // and <!-- --> syntaxes, plus the angle-bracketed and ftp:// forms.

Verification

Every step of the CI job run locally:

python3 scripts/test_prose_lint.py     139 tests, OK  (136 before, 3 added)
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
prose_lint . --diff HEAD                clean, all default rules

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings August 1, 2026 13:31

Copilot AI 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

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_URI exemption to the shared comment rule path so URI-only comment bodies are skipped by comment-wrap and comment-case.
  • Add targeted tests covering multiple comment syntaxes, URI forms, and the continuation edge case.
  • Update documentation and adjust .editorconfig to 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.

Comment thread scripts/prose_lint.py Outdated
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>
Copilot AI review requested due to automatic review settings August 1, 2026 13:35

Copilot AI 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 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/prose_lint.py:525

  • BARE_URI only 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 the comment-wrap continuation 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.IGNORECASE and 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>
Copilot AI review requested due to automatic review settings August 1, 2026 13:40
@ptr727

ptr727 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

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 350ab22.

Reproduced against the rule before changing anything:

# HTTPS://example.com/one     ->  comment-wrap on line 1   (the false positive this PR removes)
# https://example.com/one     ->  clean

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. BARE_URI now carries re.IGNORECASE.

2. Missing case-insensitive test — real, added in 350ab22.

test_the_scheme_is_case_insensitive asserts the continuation case the comment named (an uppercase-scheme reference line followed by a lowercase comment), which fails without re.IGNORECASE and passes with it, plus a mixed-case Https:// form.

Tree-wide verdicts are unchanged by both fixes: 526 -> 516, ten removed, none added, and prose_lint.py's own findings stay at the baseline 5.

Full local CI: 141 tests OK (was 139), repo_gate clean, blocking prose gate clean, --diff HEAD clean.

Copilot AI 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 4 out of 4 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit c3ad6db into develop Aug 1, 2026
7 checks passed
@ptr727
ptr727 deleted the feature/bare-uri-comment-exemption branch August 1, 2026 13:51
ptr727 added a commit that referenced this pull request Aug 1, 2026
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>
ptr727 added a commit that referenced this pull request Aug 1, 2026
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)
ptr727 added a commit that referenced this pull request Aug 1, 2026
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>
Sign up for free to 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.

2 participants