Conversation
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>
Contributor
There was a problem hiding this comment.
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_lintto (a) treat URI-only comments as references (excluded fromcomment-wrap/comment-case) and (b) scopedupwordto comments outside markdown. - Add/expand unit tests and documentation describing the new
dupword/URI-reference behaviors and their tradeoffs. - Rewrite
repo-config/configure.shcomments to one sentence per line (and restructure.editorconfig’sdotnet formatreference 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
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>
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 free
to 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.
Promotes the three prose-gate fixes to
main, so a repo carrying the baseline frommaingets the corrected gates and the correctedconfigure.shrather than the versions that reject correct work.This is the unblocking step for the
Blogonboarding in #456:Blogcarries frommain, and today'smainhands it two false-positive gates and 54 unfixable findings.d68b2cadupwordreads the comments outside markdown, not the raw source line (#489 finding 3)c3ad6db67aaa6cverbatim-carriedrepo-config/configure.shswept to one sentence per line (#489 finding 6)What this changes for a repo carrying from
mainclass="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.comment-casefinding no edit can answer. Every config file opening with a reference block inherited one.repo-config/configure.sharrives clean. It isverbatimwithappliesTo: "*", so a downstream copy is byte-matched and could never have been fixed locally.Consequence, stated plainly
configure.shisverbatim, so every repo already carrying it now differs frommainuntil 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
developat67aaa6c, which is what this promotes:Warn-only backlog moved 526 -> 516 on the comment rules from #500, and
configure.shwent 54 -> 0 from #501.🤖 Generated with Claude Code