Found while writing #569, where Copilot flagged a clause-joining semicolon in WORKFLOW.md that prose_lint.py had reported clean, before and after, with the semicolon rule enabled and that file in scope.
The exemption
scripts/prose_lint.py exempts a span it reads as a list, so a genuine enumeration keeps its separators:
listish = span.count(';') > 1 or ':' in span.split(';')[0]
if listish and ',' in span:
continue
The unit is the span, and for a Markdown bullet the span is the whole list item. So a colon anywhere before the first semicolon marks the entire bullet listish, and every semicolon later in it is exempt however plainly it joins two independent clauses. The colon and the semicolon do not have to be near each other, or related at all.
Two bullets differing only by one colon:
EXEMPT - **A rule.** The evidence is three things, and each matters: the first, the second,
and the third. The agent reads them back afterwards; it does not fire it.
FLAGGED - **A rule.** The evidence is three things and each matters. The agent reads them
back afterwards; it does not fire it.
Same trailing clause, same violation. The first is silently exempt because an unrelated enumeration earlier in the bullet supplied a colon and a comma.
What it costs today
Measured over the 32 Markdown files in the gate's own scope on develop 01507a0:
|
|
| Exempted spans carrying a semicolon |
62 |
| Semicolons inside them the rule never examines |
120 |
| Files affected |
9 |
| Reported by the rule |
0 |
So the rule is silent across 120 semicolons in the docs it exists to check, and reports success while doing it. It caught the ones outside a bullet with a colon, which is why the count elsewhere looks healthy and nothing seemed wrong.
Why this is a Gate Honesty item
This is the shape GOVERNANCE.md "Verification Discipline" names: a pattern that silently matches less still exits zero, and the not-noticing case is indistinguishable from a clean tree. It is also the shape recorded twice already, in #476/#478 and again in the markdown/Markdown sweep in #566, where a pattern skipping a match followed by - or . swallowed a hyphenated prose compound and every sentence-final use. The exemption, not the rule, decides what the gate does, and this one carries the whole burden of separating an enumeration from prose.
The real case that exposed it: the violation shipped in a pull request, passed the local gate and CI, and was caught by a reviewer reading the sentence. Fixed there in b1015b6; the gate is untouched.
Three things to settle
Where the span boundary belongs. The narrow fix scopes listish to the clause containing the semicolon rather than the whole bullet, so an enumeration keeps its separators while a later independent clause is still examined. That is the smallest change that restores the rule and it needs the sentence boundary defined, since a bullet is one line here.
Whether a colon should confer the exemption at all. count(';') > 1 is direct evidence of a series. A colon is weaker: it introduces a list, a definition, or an explanation, and only the first is a reason to keep semicolons. Dropping the colon arm and keeping the multi-semicolon arm may be both simpler and stricter, and is worth measuring against the 62 spans before choosing.
Fix the exemption before any sweep. The 120 unexamined semicolons are a work list nobody has read, and the lesson from #566 is that a sweep run through a wrong exemption damages correct documents. Fix and test the exemption, diff old against new verdicts on the same tree, and only then look at what it newly reports.
Related: #519 (the prose backlog these files sit in), and the TODO.md "Gate Honesty" entry asking prose_lint to assert a floor on its own scope, which is the same failure from the other direction, a gate proving it read something rather than proving it looked at everything in what it read.
Found while writing #569, where Copilot flagged a clause-joining semicolon in
WORKFLOW.mdthatprose_lint.pyhad reported clean, before and after, with thesemicolonrule enabled and that file in scope.The exemption
scripts/prose_lint.pyexempts a span it reads as a list, so a genuine enumeration keeps its separators:The unit is the span, and for a Markdown bullet the span is the whole list item. So a colon anywhere before the first semicolon marks the entire bullet listish, and every semicolon later in it is exempt however plainly it joins two independent clauses. The colon and the semicolon do not have to be near each other, or related at all.
Two bullets differing only by one colon:
Same trailing clause, same violation. The first is silently exempt because an unrelated enumeration earlier in the bullet supplied a colon and a comma.
What it costs today
Measured over the 32 Markdown files in the gate's own scope on
develop01507a0:So the rule is silent across 120 semicolons in the docs it exists to check, and reports success while doing it. It caught the ones outside a bullet with a colon, which is why the count elsewhere looks healthy and nothing seemed wrong.
Why this is a Gate Honesty item
This is the shape
GOVERNANCE.md"Verification Discipline" names: a pattern that silently matches less still exits zero, and the not-noticing case is indistinguishable from a clean tree. It is also the shape recorded twice already, in #476/#478 and again in themarkdown/Markdownsweep in #566, where a pattern skipping a match followed by-or.swallowed a hyphenated prose compound and every sentence-final use. The exemption, not the rule, decides what the gate does, and this one carries the whole burden of separating an enumeration from prose.The real case that exposed it: the violation shipped in a pull request, passed the local gate and CI, and was caught by a reviewer reading the sentence. Fixed there in
b1015b6; the gate is untouched.Three things to settle
Where the span boundary belongs. The narrow fix scopes listish to the clause containing the semicolon rather than the whole bullet, so an enumeration keeps its separators while a later independent clause is still examined. That is the smallest change that restores the rule and it needs the sentence boundary defined, since a bullet is one line here.
Whether a colon should confer the exemption at all.
count(';') > 1is direct evidence of a series. A colon is weaker: it introduces a list, a definition, or an explanation, and only the first is a reason to keep semicolons. Dropping the colon arm and keeping the multi-semicolon arm may be both simpler and stricter, and is worth measuring against the 62 spans before choosing.Fix the exemption before any sweep. The 120 unexamined semicolons are a work list nobody has read, and the lesson from #566 is that a sweep run through a wrong exemption damages correct documents. Fix and test the exemption, diff old against new verdicts on the same tree, and only then look at what it newly reports.
Related: #519 (the prose backlog these files sit in), and the
TODO.md"Gate Honesty" entry askingprose_lintto assert a floor on its own scope, which is the same failure from the other direction, a gate proving it read something rather than proving it looked at everything in what it read.