Skip to content

Promote the prose sweeps and the suppressed-findings digest to main - #479

Merged
ptr727 merged 4 commits into
mainfrom
develop
Jul 31, 2026
Merged

Promote the prose sweeps and the suppressed-findings digest to main#479
ptr727 merged 4 commits into
mainfrom
develop

Conversation

@ptr727

Copy link
Copy Markdown
Owner

Forward promotion of four commits. main carries no content beyond the merge-base (f7c77d8, #473), so this is a clean forward merge with no conflicts.

What promotes

  • Gate US English spelling where cspell does not reach #475 - Gate US English spelling where cspell does not reach. The cspell gate reads README and HISTORY only, so a British spelling anywhere else in the tree had nothing checking it. The spelling rule generates its banned words from stems rather than listing them one by one, since an inflected spelling is as wrong as its base and a hand-listed family drifts.
  • Sweep the spaced-hyphen prose class out of the carried docs #476 - Sweep the spaced-hyphen prose class out of the carried docs. All seven carried documents to zero: GOVERNANCE.md 219, CODESTYLE.md 53, WORKFLOW.md 51, .github/copilot-instructions.md 40, AUDIT.md 29, repo-config/README.md 20, AGENTS.md 3. The remaining ~538 are hub-only (reports/, docs/, spec/*.md, STANDUP.md, catalog/), stay warn-only, and are corrected as each file is next edited, which works because nothing vendors them.
  • Report the suppressed findings the thread poll cannot see #477 - Report the suppressed findings the thread poll cannot see. The merge gate counts a review body's collapsed low-confidence block as an outstanding finding, but the digest read reviewThreads alone, where that block never appears, so the one command written to answer "is this PR clean" reported clean while findings stood.
  • Sweep the semicolon prose class out of the carried docs #478 - Sweep the semicolon prose class out of the carried docs. The exemption is fixed first (list-wide comma, per-table-cell scoping, bullet-label colon), then 44 genuine clause joins recast across five files. All six remaining carried files reach zero, the bar GOVERNANCE.md already met.

Verification on the merged develop

Both prose classes report zero across all seven carried documents. The full 180-case script suite, scripts/repo_gate.py, spec/validate.py, and spec/audit.py --selftest pass. The blocking prose run (charset, dupword, spelling) is clean tree-wide, markdownlint reports 0 issues over 39 files, and editorconfig-checker is clean. cspell's gated scope (README + HISTORY) is clean; the tree-wide backlog is unchanged.

Release

A human merge never auto-publishes, so this fires no release, as designed. The latest release stays 2.0.108.

Re-vendor debt

This promotion changes prose in all seven carried documents, so every downstream repo goes further out of date against canonical. That debt is pre-existing and still untracked - no issue covers it and filing one has not been authorized. spec/audit.py --branch <ref> checks a repo's convergence before it promotes.

🤖 Generated with Claude Code

ptr727and others added 4 commits July 31, 2026 09:14
The cspell gate reads README.md and HISTORY.md only, deliberately: gating every
markdown file would mean endlessly padding cspell.json with technical terms. That
left a British spelling anywhere else in the tree with nothing checking it, which
is how `labelled`, `behaviour` and `judgement` reached main. The new `spelling`
rule closes the gap and joins the blocking CI prose run, since all three are fixed
here and the tree is clean.
The banned words generate from stems rather than list one by one, since an
inflected spelling is as wrong as its base and a hand-listed family drifts as soon
as one form is added without the others. Two words are deliberately absent:
`analyses` is the US plural of `analysis` as much as a British verb form, and
`cancelled` is a GitHub Actions job status rather than prose.
Outside markdown the rule reads the comments rather than the source lines, reusing
the extraction comment-wrap already does. Judging a source line as prose had this
script report its own table of banned words.
Also leads YAML['quote_after'] with the dash. Membership is a set test, so the
order carries no behavior, but ':-,' reads as a regex range and cost a review
round on #474 when it was read as missing the dash.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dash rule reports without gating, and a carried file has no correct-as-you-next-edit trigger downstream, so the class only leaves the fleet by being swept at the hub. Takes every carried document to zero: GOVERNANCE.md, CODESTYLE.md, WORKFLOW.md, AUDIT.md, AGENTS.md, .github/copilot-instructions.md, and repo-config/README.md.
Each instance is rewritten by hand as a comma, a colon, parentheses, or two sentences. No meaning changes, and every file stays line-for-line balanced so no line endings moved. Two headings now read with a colon, neither referenced by an anchor, a spec section list, or audit.py.
LABEL_DASH exempted the bulleted definition list but not the ordered form of the same construct, so it now accepts either marker, with a case for both halves. test_pr_review asserted the runbook sentence whose dash this changed, so it moves with the prose it reads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merge gate counts a review body's collapsed low-confidence block as an outstanding finding, but the digest read reviewThreads alone, where that block never appears, so the one command written to answer "is this PR clean" reported clean while findings stood.
Q_FULL now carries each review's body, and the digest reports suppressed=N for the blocks on the current head. Q_LIVE is untouched, so a liveness poll still fetches two scalars. Each block prints whole where a thread body truncates at 160, since a thread can be re-read at its id and a suppressed finding has no thread.
Two defects were found and fixed inside the review loop. Copilot caught that the field counted blocks rather than findings, so a heading reading "Suppressed comments (2)" reported 1; the count now comes from the heading's (N), summed across the rounds on the head and floored at one. Running the tool on its own PR caught the second: the phrase was matched against the whole body, so a review whose overview prose discusses suppressed findings while carrying none reported one. The match is now anchored to the block's summary heading, and the markup-moved fallback reads only the text outside the details blocks and requires a heading with a count.
Verified against live review bodies rather than fixtures alone: #473 reads one finding from its real block and #477 reads zero, agreeing with the runbook's independent REST query. The scripts README documents the behavior, and its own spaced hyphens are corrected in passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The semicolon sweep only ever cleared GOVERNANCE.md. The other six carried
files still ran the class, and they vendor downstream on the same
no-trigger argument the dash sweep ran on.
Two parts, because sweeping the corpus first meant restructuring lists the
exemption exists to protect. The rule keeps a semicolon separating items in
a list that already carries commas, but the check read the comma
positionally, so an enumeration whose commas fall in a later item had its
openers flagged and its tail exempted, splitting one series in two. The
comma now qualifies the list as a whole. A table row is judged one cell at
a time, since a row is a record of fields and a comma in one column cannot
excuse a semicolon in another, and a bullet's `**Label**:` is dropped
before the line is read, because it opens the bullet rather than announcing
a list, the same construct the label dash is already exempted for.
That leaves 44 genuine clause joins, now recast as a comma or two
sentences: CODESTYLE.md 24, .github/copilot-instructions.md 13, AUDIT.md
10, WORKFLOW.md 12, repo-config/README.md 1. The 5A guarantees and the 5B
trace table keep their semicolons, which is the standard series
punctuation the rule already allows.
All six files now report zero, the bar GOVERNANCE.md already meets. The
paren-internal asides those files still carry are the same construction
GOVERNANCE.md kept through its own sweep, so they stay.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings July 31, 2026 19:34

CopilotAI 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 PR forward-promotes a set of governance/documentation prose sweeps and supporting tooling updates into main, including a new US-English spelling enforcement outside cspell’s limited CI scope and improved PR-review digesting of Copilot “suppressed / low confidence” findings.

Changes:

  • Add a spelling rule to scripts/prose_lint.py, gate it in CI, and add unit coverage to keep the banned-word table and behavior correct.
  • Extend scripts/pr_review.py (and tests) to surface Copilot review-body suppressed findings that are invisible to reviewThreads polling.
  • Apply prose-only rewrites across carried governance docs (dash/semicolon class reductions, punctuation/clarity adjustments) plus a few related doc/test updates.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
WORKFLOW.mdProse-only punctuation/clarity tweaks; heading punctuation normalization.
spec/validate.pyUS spelling fix in a comment (“labeled”).
scripts/test_prose_lint.pyAdds tests for ordered label dash exemption, semicolon exemption fixes, and comprehensive spelling-rule coverage.
scripts/test_pr_review.pyAdds review-body body support and tests for suppressed-findings reporting; updates runbook-string assertions.
scripts/README.mdDocuments the new spelling rule and the suppressed-findings digest behavior.
scripts/prose_lint.pyImplements spelling rule, improves semicolon exemption logic, expands label-dash exemption, and adjusts YAML quote_after.
scripts/pr_review.pyAdds suppressed-findings detection/printing in digest; keeps a minimal liveness query for polling.
reports/vscode-server-dotnetcore/audit.mdUS spelling fix in report prose (“behavior”).
repo-config/README.mdProse-only punctuation/clarity tweaks.
README.mdRemoves now-completed backlog item for the dash-class sweep.
host-setup/agent-safety/gh-write-guard.pyUS spelling fix in a comment (“judgment”).
CODESTYLE.mdProse-only punctuation/clarity tweaks; consistent US spelling guidance wording.
AUDIT.mdProse-only punctuation/clarity tweaks; heading punctuation normalization.
AGENTS.mdProse-only punctuation/clarity tweaks.
.github/workflows/validate-task.ymlGates spelling alongside existing charset and dupword.
.github/copilot-instructions.mdProse-only punctuation/clarity tweaks; aligns phrasing relied on by tests/runbook parsing.

@ptr727
ptr727 merged commit 52bcd7a into mainJul 31, 2026
7 checks passed
ptr727 added a commit that referenced this pull request Aug 8, 2026
Closes#607.
## What was wrong
A Copilot review body states how many of the pull request's changed
files it read, and nothing parsed that line. A round that read part of
the diff carries the correct `commit.oid`, raises no inline threads, and
reports "generated no comments", so it is the clean pass byte for byte
in everything the loop checks, and `status` printed `review_on_head=yes`
over it and exited `0`.
Measured over **332 Copilot review bodies** on this repository, five
rounds across three pull requests reported reading fewer files than were
changed, and all three merged. #592 is the sharpest: three changed
files, one never read, across *both* rounds, both reporting no comments.
## What changed, and why it is wider than the issue asked
Partial coverage is the third instance of a shape this script already
answers twice, and the generalization is the point. Every reader here
keys on a structural marker, so a marker that changes spelling is a
section the reader stops finding and reports as absent. All three
failures on record have that shape:
| Drift | Result |
| --- | --- |
| Suppressed heading reworded | `suppressed=0` over a body carrying
findings |
| Suppressed section moved inside another wrapper | `suppressed=0` again
|
| Coverage line never parsed | a partial round reported as a covered
head |
Each was found by the maintainer after it had landed, rather than by the
gate. So the digest now vets the reviewer's output as a whole and
**fails closed**:
- `coverage=full` / `PARTIAL` / `unstated`, with exit **42** on a
partial round.
- `shapes=ok` / `UNRECOGNIZED`, with exit **43** on any heading,
`<summary>`, metadata label, coverage wording or reviewer login the
script has no vetted spelling for. It outranks 42, because a reader that
does not understand the output cannot be believed about what it read of
the diff.
The `43` message states the remedy in two parts: **file an issue on the
repository hosting the reader**, quoting the body the shape came from,
and **the merge decision is the maintainer's**. An unrecognized shape
does not say the pull request is bad, only that nothing here can vouch
for the review of it.
## The inventory is measured, not imagined
With fenced blocks dropped and text reduced to ASCII, all 332 bodies
reduce to **7 headings, 6 `<summary>` texts and 3 metadata labels**, and
every body carries at least one. Counts normalize to `(N)` and the
verdict headings' colored circle is dropped before comparing, since both
change on every review without the section changing, and dropping the
emoji is also what keeps the source inside the charset rule.
Two exemptions, both required by the corpus:
- **A body stating no coverage** reads as `unstated`, never as pass or
failure. 28 of the 332 are an overview and a change list, that shape is
current and interleaves with the counted one, and one pull request
carries both across its two rounds. Failing on it would cry wolf on
about one review in twelve.
- **A refusal** is exempt, being a bare paragraph by design and already
classified. The exemption is the pattern rather than a carve-out, so a
refusal reworded stops being exempt and blocks, which is the refusal
check's own failure mode caught one rewording later.
The quietest reading is the reviewer **login**: a rename leaves every
filter here matching nothing, so a review that landed reads as
`rounds=0` and a wait polls out its timeout against it.
## Evidence
Both readers were swept over the full corpus before this was written,
and over it again after:
- Shape inventory: **0** items raised across all 332 bodies, and **0**
across every review and comment author on those 120 pull requests
(`copilot-pull-request-reviewer` and `ptr727`).
- Coverage: `full 299 / unstated 28 / partial 5`, the five being exactly
#476 (x2), #479 and #592 (x2).
- Live: #592 reports `coverage=PARTIAL shapes=ok` and exits `42`. #595,
#604 and #606 report `shapes=ok` and exit `0`.
166 tests pass, up from 132. The two fixtures the issue named as
unasserted filler are promoted to assertions, and a case reads the
vetted coverage spellings out of the runbook and hands them to the
script's own parser, so the pair fails in both directions on drift. The
old fixtures crafted review bodies with no heading, which no real body
has, so they were made realistic rather than the check loosened.
## Also carried
- `.github/copilot-instructions.md` - the verify step checked
`commit.oid` only, which is what this shows to be insufficient, plus a
new section stating that an unrecognized shape blocks and earns an
issue.
- `GOVERNANCE.md` - merge-gate precondition 4, and the "all four
preconditions" restatement swept to five.
- `scripts/README.md` - both readings.
- `TODO.md` - a `measure` cluster to survey whether GitHub publishes
anything structured about a Copilot review, since this whole design
rests on prose being the only surface. Introspection says it is:
`PullRequestReview` exposes `body`, `bodyText` and `bodyHTML` and no
field naming a finding or a file count, and the only Copilot-named
GraphQL types configure review-on-push in a ruleset.
## The accepted cost
The shape scan reads every round rather than the head's, so once Copilot
changes format, every open pull request blocks at once until the
inventory is updated. That is deliberate and the maintainer's call: it
is well balanced against a reviewer silently missing a raised finding.
🤖 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 8, 2026
Follows the #609 promotion, which went partial at 61 of 62 files and
stayed there through two re-requests. That prompted measuring whether
the remedy the gate recommends has ever worked.
It has not.
## The measurement
Over the **332 Copilot review bodies** on the newest 120 pull requests,
read beside `gh pr list --json number,changedFiles,additions,deletions`:
| Question | Answer |
| --- | --- |
| Pull requests that went partial | **4** - #476, #479, #592, and the
#609 promotion |
| Rounds those four produced | **7** |
| Rounds that recovered | **0** - every later round repeated the
identical ratio |
| Files skipped, each time | exactly **1** |
```
PR 592: round 1 = 2/3 round 2 = 2/3
PR 476: round 1 = 10/11 round 2 = 10/11
PR 479: round 1 = 16/17
PR 609: round 1 = 61/62 round 2 = 61/62 (after a re-request on a new head)
```
So the digest was telling a reader to spend a round on a retry that
nothing has ever retried out of.
## Two hypotheses tested and rejected, so they are not re-derived
**Size does not predict it.** The partials changed 502, 629 and 961
lines. Fully covered pull requests on this repository reach **33 files
and 2,219 changed lines**, well past all three.
**The denominator is not itself lossy.** I suspected the reviewer might
drop a file before counting, which would make `N out of N` a lie. It
does not: the stated denominator equals the API's own `changedFiles` on
**103 of 104** pull requests, and the single exception is one whose
branch shrank between rounds. The reviewer counts the file and does not
read it.
## What changed
The three places that stated the remedy now state what the record
supports, swept by grepping the phrasing rather than by fixing the one
the finding pointed at:
- `scripts/pr_review.py` - the `status=COVERAGE_IS_PARTIAL` line, the
digest's `COVERAGE IS PARTIAL` block, and the `status` exit-code
docstring.
- `.github/copilot-instructions.md` - the "Counts unequal" bucket.
- `scripts/README.md` - the coverage paragraph, carrying the
measurements.
The new wording says a re-request has never cleared one, that splitting
is real for a feature branch and unavailable for a promotion whose head
is `develop`, and that the merge decision is the maintainer's, taken
knowing one file has no review. **No exit code or verdict changes**,
since the gate was reporting the state correctly. Only the advice
attached to it was wrong.
## What is left open
`TODO.md` gains an entry under the programmatic-reading cluster, with
the measurements attached: **which** file is skipped, and why. The
reviewer names no file list in these rounds, so it cannot be recovered
from the API, and the pull request page may be the only place it
appears. Whether it is worth escalating to GitHub needs the file first.
🤖 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 8, 2026
)
Closes#623.
**Disposition** `Amends "Find out which file a partial round skips, and
why re-requesting never clears it."` in the "A Programmatic Reading of a
Copilot Review" cluster. #623 was filed after this repository's last
`TODO.md` edit and appeared nowhere in the file, so selection step 1 was
false until this change.
## What was wrong
The `COVERAGE_IS_PARTIAL` caveat, `scripts/README.md` and the runbook
all said the reviewer names no file list in these rounds, so which file
went unread cannot be read from the API. Measured over 348 Copilot
review bodies on the newest 120 pull requests here and 121 on Blog, that
is false. 91 bodies carry a `| File | Description |` table, and every
table row in the corpus belongs to one of those tables.
## What the measurement says the table is worth
The issue proposed reading a table that names every changed file as
evidence that the count is a reporting artifact. It cannot carry that,
and the corpus is what says so.
| round | states | names | reading |
| --- | --- | --- | --- |
| #476 | 10 of 11 | all 11 | table contradicts the count |
| #592 | 2 of 3 | all 3 | table contradicts the count |
| #479 | 16 of 17 | 16, omitting `GOVERNANCE.md` | table corroborates
the count and names the file |
| #609 | 61 of 62 | 50 of 62 | short by 12 where the count is short by 1
|
| #574 | 33 of 33, full | 32, omitting `TODO.md` | a short table on a
fully covered round |
| #606 | 7 of 7 | names `GOVENANCE.md` | a path no diff carries |
On Blog every table names exactly the changed set, on all seven partial
rounds and on every full round alike. A reading identical under both
outcomes discriminates neither, so a full table is reported as
corroborating nothing rather than as a miscount. #574 kills the
converse, since a short table sits on a round that read everything.
## What ships
- `Q_FULL` selects the pull request's own `files`, windowed at 100 with
`hasNextPage` carried, since a path outside a short window reads exactly
like a path the reviewer left out and the record holds a pull request of
301 changed files.
- `file_table` reads the table, quotations dropped for the reason the
coverage line's are.
- `head_table` takes it from any round covering the current head and
never from a round before a push. Three of the four partials here carry
their table on the pre-push round, describing a diff that push replaced,
and comparing that against the current changed files would name a file
unreviewed on a stale list. Thirteen commits here carry more than one
round, and on one of them a round with a table sits beside a round
without, so which of the two the verdict reads must not decide whether a
table is found.
- `table_against_diff` prints one sentence and decides nothing. It names
an omitted file only where the table is short by exactly what the counts
leave unread **and** names nothing outside the diff, that second guard
existing because #606's typo would otherwise push the real
`GOVERNANCE.md` into the omissions and report it as the file nobody
reviewed.
- Exit `42` is unchanged in every arm, and the caveat keeps its
maintainer-decision framing, which is the issue's third suggestion.
## Verified against live data
#479 the reviewer's own file table omits exactly the 1 file the counts
leave unread, naming GOVERNANCE.md
Blog #60 the reviewer's own file table names all 6 changed files, ... so
it corroborates nothing
#476/#592/#609 no round covering this head carries a file table
## Gates
228 `test_pr_review` cases, plus `test_prose_lint`, `test_repo_gate`,
`spec/audit.py --selftest`, `spec/validate.py`, both `prose_lint.py`
invocations, `markdownlint-cli2` and `editorconfig-checker`, all green
locally.
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 8, 2026
One commit, `a706ddb` from #625, promoting the answer to #623 to `main`.
## What is promoted
`scripts/pr_review.py` reads the Copilot review's own `| File |
Description |` table and reports it beside a partial-coverage verdict,
and the docs drop a claim that was false.
- **The removed claim.** "The reviewer names no file list in these
rounds, so which file went unread cannot be read from the API" was
wrong. Measured over 348 review bodies on the newest 120 pull requests
here and 121 on Blog, 91 carry a table and every table row in either
corpus belongs to one.
- **The table decides nothing, and the measurement is why.** It names
the whole changed set on partial and fully covered rounds alike,
including all seven partials on Blog, so it is identical under both
outcomes. One round here states 33 of 33 and names 32, another states 61
of 62 and names 50, and a third names `GOVENANCE.md`, a path no diff
carries.
- **The one arm that locates a file.** #479 states 16 of 17 and names
16, omitting `GOVERNANCE.md`, and that is the only evidence on record
that the unread file is real rather than an artifact of counting.
`status` names an omitted file only in that shape, where the table is
short by exactly what the counts leave unread and names nothing outside
the diff.
- **Head-scoped like the counts.** Three of the four partials here carry
their table on the round before a push, describing the diff that push
replaced, so those report as no table rather than as a stale list of
unreviewed files.
Exit `42` is unchanged in every arm, and the merge decision stays the
maintainer's.
## State
#623 is already closed by hand, since the keyword sat on a pull request
into `develop`. Review on #625 was one round, `coverage=full shapes=ok
threads=0 unresolved=0 suppressed=0 checks=6/6`, and the body was read
rather than the digest trusted.
**Merge as a merge commit, never a squash**, so `develop` and `main`
stay level.
Generated with [Claude Code](https://claude.com/claude-code)
Sign up for freeto 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

@ptr727