Skip to content

Promote the prose-gate fixes and the configure.sh sweep to main - #502

Merged
ptr727 merged 3 commits into
mainfrom
develop
Aug 1, 2026
Merged

Promote the prose-gate fixes and the configure.sh sweep to main#502
ptr727 merged 3 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

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

ptr727 and others added 3 commits August 1, 2026 06:23
Finding 3 of #489, reported by the Blog agent while carrying the
baseline into the first Hugo repo.

## The defect

`dupword` is one of three rules in the **blocking** CI step (`--check
charset --check dupword --check spelling`). Outside markdown it read the
raw source line, so it judged code as prose and rejected correct work.

The reported case has no legal fix:

```html
<div class="gallery gallery-cols-1">
```

Two class names sharing a prefix is the ordinary CSS idiom. Editing it
changes the rendered page, so the only way to pass the gate was to break
the styling.

## The fix

`spelling` already solved this in the same function: outside markdown it
reads the extracted comments rather than the source line. `dupword` now
selects its text the same way, reusing the same extraction.

Narrowing to the comment is preferred over exempting the `class`
attribute, since `rel`, `srcset`, `sizes` and the `data-*` attributes
all take value lists of the same shape, and an exemption list covers
only what its author thought of.

Each comment on a line is judged on its own rather than joined with its
neighbors, because two comments are two sentences and joining them reads
the second's opening word as a repeat of the first's last.

## Measured, both directions

Every case below was run against the rule before and after. Nothing else
changed.

| Case | Before | After |
| --- | --- | --- |
| `<div class="gallery gallery-cols-1">` | FIRES | quiet |
| `<a rel="nofollow nofollow-ugc" href="#">` | FIRES | quiet |
| `x = "the the"` (Python string literal) | FIRES | quiet |
| `key: the the` (YAML scalar) | FIRES | quiet |
| `{ "a": "the the" }` (JSON string value) | FIRES | quiet |
| `<p>the the</p>` (HTML body text) | FIRES | quiet |
| `<!-- The the thing. -->` (HTML comment) | FIRES | FIRES |
| `# The the thing.` (Python, shell, YAML) | FIRES | FIRES |
| `// The the thing.` and `/* ... */` (C#) | FIRES | FIRES |
| `x = 1  # The the thing.` (trailing comment) | FIRES | FIRES |
| `the the thing` (markdown prose) | FIRES | FIRES |

## The cost, stated rather than hidden

A duplicated word in HTML body text, or in a YAML or JSON string value,
is no longer caught. That trade was accepted deliberately when the
approach was chosen. The narrower alternative, stripping only HTML
attribute values, was considered and declined for the reason above.

## Asserting the floor

This narrows a blocking gate, so the floor is asserted rather than
assumed.

- Tree-wide `dupword` was clean before the change and is clean after.
- Six new test cases fail against the old rule and pass against the new
one, so they are not vacuous. The comment-syntax cases pass against
both, which is the floor holding.
- `test_the_repo_is_clean_of_duplicated_words` pins the tree-wide floor
the way the spelling rule already does, so the gate cannot silently stop
gating.
- `DUP_ALLOW` (`that that`, `had had`) is untouched. It is the
pre-existing band-aid, still needed for markdown prose, and now the only
remaining allowlist in the rule.

Test bait for the attribute cases is assembled from two literals,
following the convention this module already states, so the file never
holds the pattern it feeds the gate.

## Verification

Every step of the CI job run locally:

```
python3 scripts/test_prose_lint.py     136 tests, OK  (130 before, 6 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
```

The warn-only step reports the same 5 pre-existing `comment-wrap`
findings in `prose_lint.py` as before, and no new ones. Two comments
this PR touches were rewritten to one sentence per line so the change
adds no backlog.

## Docs

`scripts/README.md` documented the comments-only scope for `spelling`
alone. It now covers both rules, states why an attribute value is not
prose, and states the cost.

🤖 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-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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>

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

Promotes the previously landed prose-gate fixes and the carried repo-config/configure.sh comment sweep to main, so downstream repos carrying from main receive corrected gates (no false positives on correct code or URI-only comment references) and a clean verbatim configure.sh.

Changes:

  • Update prose_lint to (a) treat URI-only comments as references (excluded from comment-wrap / comment-case) and (b) scope dupword to comments outside markdown.
  • Add/expand unit tests and documentation describing the new dupword/URI-reference behaviors and their tradeoffs.
  • Rewrite repo-config/configure.sh comments to one sentence per line (and restructure .editorconfig’s dotnet format reference comment) to eliminate unfixable carried findings.

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
scripts/prose_lint.py Implements URI-only comment exemption for comment rules and scopes dupword to extracted comments outside markdown.
scripts/test_prose_lint.py Adds targeted tests covering the new dupword scope and URI-only comment handling, plus a whole-tree dupword cleanliness assertion.
scripts/README.md Updates prose-lint documentation to describe the narrowed dupword scope and the URI-only comment exemption.
repo-config/configure.sh Comment-only sweep to one sentence per line (and restructuring) to remove carried comment-wrap/comment-case findings.
.editorconfig Restructures the dotnet format comment to avoid comment-case while preserving the reference content.

@ptr727
ptr727 merged commit 6501479 into main Aug 1, 2026
7 checks passed
ptr727 added a commit that referenced this pull request Aug 1, 2026
Restores the forward-only invariant after I merged #508 without checking
its base branch.

## What happened

#508 was opened against **`main`**, not `develop`. I verified its
content, its checks, and its review threads, and merged it without ever
reading `baseRefName`. It landed as `048a442` on `main` alone.

The branching model is feature to `develop` by squash, then `develop` to
`main` by promotion merge. A feature PR merged straight to `main` puts
content on `main` that `develop` does not have, which is exactly the
divergence the forward-only rule exists to prevent. Left alone, the next
promotion either conflicts or silently reverts the audit refresh, since
`develop` would carry the older `reports/photocleaner/audit.md`.

## The fix

`048a442` cherry-picked onto `develop`. Confirmed it is the **only**
content commit `main` holds that `develop` lacks:

```
$ git log --oneline origin/develop..origin/main
048a442 Refresh the PhotoCleaner audit report and driftNotes (#508)
6501479 Promote the prose-gate fixes and the configure.sh sweep to main (#502)
... 100+ further entries, all promotion merges
```

Everything below `048a442` is a promotion merge commit, main-only by
construction, which is topology rather than drift. After this merges,
the two branches carry identical content again and the next promotion is
clean.

## Two separate errors, worth naming

**Mine**: I ran the full merge gate, checks, review coverage, unresolved
threads, and an independent re-audit of the report's claims, and never
checked which branch the PR targeted. A base-branch check belongs in
that gate, and it was not in it.

**The PR's**: a downstream conformance PR against the hub targeted
`main`. That is the same class as the report-authorship question #508
raised, an agent filling a gap the instructions never addressed, and it
argues for `AUDIT.md` stating the base branch explicitly alongside who
may author a report.

## Verification

```
python3 spec/validate.py                 OK, 21 cataloged
registry/repos.json parses
prose_lint reports/photocleaner/audit.md --check charset --check dupword --check spelling   clean
editorconfig-checker (docker, canonical) clean
```

Content-identical to `048a442`, so no review of the report's substance
is re-opened here. It was verified against a live `spec/audit.py
PhotoCleaner` run before #508 merged, and every substantive claim was
corroborated.

🤖 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