sloplint 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74566267ccb2ee1d4caec464d3098809e24f232f75872a7df2ac61ef7d583153
4
- data.tar.gz: 4e3b6196aeb6604d212d00be14032e530a644d4250820424774244fe7298fa9e
3
+ metadata.gz: 33eff57c67530d0745b1ba5009a97128929a73ec27ae09afaba8d9057f0f997b
4
+ data.tar.gz: 3b76104840a3c2ab471651cbdf18dd4a9027707aaf100bc901430c7f9e121216
5
5
  SHA512:
6
- metadata.gz: 41e707ec52643d3f57abe04867d399fef77c8e567378394e4c1e0e2ca541d8aefaa2cb908b9d0e419fc9af9f0a1aedb55adce9ef87fc187628be4a90641a4809
7
- data.tar.gz: ed1b87c4a32490d582100343d2e7a4b114067737de3761fa3fb768e669f398e69b8c4e3ce4cb33d3138702d533818f5dd53b2b778d8736f848ca71c8166e61f8
6
+ metadata.gz: 4d1a47eae97395fa37dd1fa50b9a4894ccae70064d15542abdce0197cf715956d23c6ba75bee7bb4f56bc6ada8986485aff5bb1e06c9ecbb1ac6b763705588e1
7
+ data.tar.gz: cb1ba2ed058855336b71bdc6e10a743e021a5d5d85e0207b8e0564b0fcd9076c9c45eb021eee6fc15a4eac92709b3440ebb6e1487da3c8981853ff37ebd01419
data/CHANGELOG.md CHANGED
@@ -3,7 +3,335 @@
3
3
  All notable changes to this project are documented here. Format loosely
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
- ## [Unreleased]
6
+ ## [0.7.0] - 2026-09-15
7
+
8
+ ### Added
9
+
10
+ - `--strict` runs the full catalog, including the five rules that ship off
11
+ by default. Before this, the only way to turn one on was to name it in
12
+ `--select`, which also turned off every other rule unless you listed
13
+ those too. `--ignore` still subtracts on top of `--strict`, and
14
+ `--select` is unchanged.
15
+ - `ellipsis-closer` (structure, info) flags the long-sentence-then-short-closer
16
+ kicker `mic-drop-closer` catches, but built on the opposite verb shape: the
17
+ closer's verb phrase is elided down to a bare auxiliary with no object,
18
+ "The agent did." rather than a quantifier plus a full verb. No subject list
19
+ is needed — the tell is the missing verb, not the subject — so "That",
20
+ "This" and "It" are not excluded the way `mic-drop-closer` excludes them.
21
+ A negative lookahead drops a closer that still holds a wh-word, since that
22
+ marks a subordinate clause with its own complement rather than an elided
23
+ one. Reuses `mic-drop-closer`'s long-sentence prefix.
24
+ - `actually-not-x`: the adverb and a trailing "…, not X" in one clause, as in
25
+ "The disclosure actually covered two incidents, not one". Either marker
26
+ alone is ordinary technical English and neither is matched on its own; the
27
+ tell is the two of them doubled up, correcting an alternative the reader was
28
+ never offered. The narrowing is structural, not a word list: the comma in
29
+ front of "not" has to be the first comma of its clause and has to follow a
30
+ word, so a fronted setup ("Despite the name, …") and a parenthetical both
31
+ drop it. Nothing crosses a line break, so two rows of a table cannot weld
32
+ into one correction.
33
+ - `real-x-real-y` (rhetorical-tic, info) flags the same "real" used twice
34
+ attributively in one sentence, in front of two different nouns: "it can
35
+ trigger real API calls or hold real credentials". Bare "real" is an
36
+ ordinary word, so the narrowing is the repetition itself, not a noun
37
+ list. A hyphen on either side takes it out ("real-time", "non-real"), a
38
+ backreference requires the second noun to differ from the first, and a
39
+ short closed list drops the fixed senses of this register (real time,
40
+ real-world, real numbers, real user monitoring, real estate, real money).
41
+ - `phrase-echo` (rhetorical-tic, info, off by default) flags a three-word
42
+ phrase that comes back within a few hundred words, the model reusing a
43
+ phrase it has just minted. Each word must be four characters or more and
44
+ lowercase-led, and one of them six letters with nothing but letters, so "in
45
+ order to", "couldn't have been" and "New York City" never match. The window
46
+ does not cross into a list item or a table row. Off by default: a term of
47
+ art repeats because it must, and in reference prose the pattern fires on
48
+ names by the thousand per million words.
49
+ - `trailing-restatement` (structure, info, off by default) flags the tail
50
+ that says the sentence again in other words: ", which means …", ", which
51
+ is to say …", ", meaning …", and four participles that hang a result off
52
+ the sentence (", making it easier", ", allowing us to", ", giving them
53
+ more", ", leaving you with"). The connective is all a pattern can see, and the
54
+ same connective states a real consequence in careful prose, so the rule
55
+ runs only when selected.
56
+
57
+ ### Fixed
58
+
59
+ - `check`'s options now work without the command word: `sloplint --markdown -`
60
+ and `sloplint --strict -` scan, as the README has said `sloplint -` does.
61
+ Before, the global parser rejected any option it did not own with
62
+ `invalid option`, so `check` had to be spelled out whenever an option
63
+ came first. An option no command knows still exits 2.
64
+ - `--markdown` now blanks `<!-- -->` comment blocks along with fenced code,
65
+ inline code and URLs. Planning notes kept in comments never render, so
66
+ every rule that fired inside one was a permanent false positive.
67
+ - `rule-of-three`'s message, rationale, README and SPEC entries said it
68
+ flagged three parallel comma items. The pattern requires single words in
69
+ the closing two slots, so a triad of phrases never matched; the text now
70
+ says that.
71
+
72
+ ## [0.6.0] - 2026-09-08
73
+
74
+ The catalog grows from 50 rules to 75, and sloplint ships as a Claude Code
75
+ plugin as well as a gem.
76
+
77
+ ### Added
78
+
79
+ - sloplint now ships as a Claude Code plugin as well as a gem. Add the
80
+ marketplace `benjaminjackson/sloplint` and install `sloplint`; the plugin
81
+ carries the Ruby source with it, so nothing is installed from a package
82
+ index and it works where `gem install` cannot reach the network — Claude
83
+ Cowork above all. The plugin adds one skill, `/sloplint:check`, which runs
84
+ the scanner and reports what it returns. The skill refuses to assess prose
85
+ on its own when the scanner cannot run, because a review with no scan
86
+ behind it reads exactly like one with a scan behind it.
87
+ - `not-by-x-but-by-y`: the corrective frame on a repeated preposition, "not
88
+ by luck, but by design". The copula rules cannot see it because nothing
89
+ anchors "not" but a verb or a dash, so the anchor is the preposition, which
90
+ must repeat after "but". Ships at `info`: Thoreau and Melville write it too.
91
+
92
+ ### Fixed
93
+
94
+ - `check` now reads files and stdin as UTF-8 whatever the locale says. A
95
+ sandbox with no `LANG` set — Claude Cowork's, among others — leaves Ruby's
96
+ default external encoding at US-ASCII, and the first em dash in a draft then
97
+ failed the whole scan with `invalid input: invalid byte sequence in
98
+ US-ASCII`. Prose is the only input sloplint takes, so UTF-8 is the
99
+ assumption rather than the locale's guess. Genuinely invalid UTF-8 is still
100
+ exit 2.
101
+
102
+ ### Changed
103
+
104
+ - `thats-the-whole` now takes `value` and `fix` after "that's the whole",
105
+ alongside `point`, `game`, `thing`, `deal` and `story`. The contracted
106
+ closer is the one an agent writes in technical prose ("That's the whole
107
+ value of the `info` tier", "That's the whole fix"), and `is-the-whole-x`
108
+ sees only the uncontracted "is", so the sentence slipped both rules. The
109
+ uncontracted "That is the whole fix." moves with it, from `info` under
110
+ `is-the-whole-x` to `warning`. The two new nouns must end the sentence or
111
+ the paragraph, or run into one of the words a closer trails off on, so
112
+ `thats-the-whole` leaves "value chain", "value-add" and "fix list" alone
113
+ however the line wraps (`is-the-whole-x` still reads the uncontracted
114
+ "That is the whole value chain" at `info`, as it did), and a curly
115
+ apostrophe now counts as a contraction. Both rules interpolate one `WHOLE_CLOSERS`
116
+ fragment, so the lists cannot drift, and a spec walks every noun through
117
+ every form the two wrappers treat differently. Neither noun appears after
118
+ "that's the whole" in 1.55M words of public-domain prose.
119
+
120
+ - Empty or whitespace-only input
121
+ is now exit 2 with `empty input: nothing to
122
+ check in ...`, instead of exit 0. Reporting a scan of nothing as a clean
123
+ scan is the same trap a mistyped rule id sets, and gets the same answer. The
124
+ text is tested before `--markdown` blanks code and URLs, so a file holding
125
+ only a fenced code block still exits 0; and only an all-empty set of sources
126
+ is an error, so one empty file among several named ones is left alone.
127
+
128
+ - The executable moved from `bin/sloplint` to `exe/sloplint`. claude.ai
129
+ rejects a plugin with a top-level `bin/` directory, which would block
130
+ organization distribution. `gem install sloplint` is unaffected.
131
+
132
+ - `exe/sloplint` now checks `RUBY_VERSION` before loading anything and aborts
133
+ with an explanation, rather than raising `NoMethodError` from `Data.define`.
134
+ The Ruby macOS ships at `/usr/bin/ruby` is 2.6 and hits this.
135
+
136
+ ### New rules
137
+
138
+ - `from-x-to-y-chain` (rhetorical-tic, warning) flags two or more
139
+ comma-separated "from X to Y" spans in a row: "from guessing to measuring,
140
+ from hoping to knowing". Each span names two poles and nothing between
141
+ them, and stacking them sweeps across a change without describing it. Two
142
+ human shapes are skipped: the relay, where each span starts where the last
143
+ ended ("from the egg to the worm, from the worm to the fly"), and the
144
+ reduplication, where a span has the same word at both ends ("from hummock
145
+ to hummock"). Operands must open with a letter, so a list of ranges ("from
146
+ 1990 to 1995, from 1997 to 2001") is not a chain. With those out it flags once in 2.4M words of pre-2022
147
+ Hacker News (a geographic sweep) and once in 1.25M words of public-domain
148
+ prose (Joyce).
149
+
150
+ - `one-x-one-y` (rhetorical-tic, warning) flags three or more "one X" items
151
+ in a comma chain that stands on its own: "One owner, one repository, one
152
+ weekly prune." The word does no counting; it sets a rhythm. The chain must
153
+ open a sentence or follow a colon, because after a verb ("the flat has one
154
+ bedroom, one bathroom, one balcony") the word is counting. Items are
155
+ letter-led, so an enumeration over numbers is out; the distributive "one
156
+ for you, one for me, one for the pot" is skipped at any length. Nothing in
157
+ 2.4M words of pre-2022 Hacker News or 1.25M words of public-domain prose.
158
+
159
+ - `everyone-nobody` (structure, warning) flags the comma-spliced antithesis
160
+ on quantifier subjects: "Everyone wants the dashboard, nobody maintains
161
+ it." The
162
+ balance is what makes the diagnosis sound settled. The comma splice is
163
+ required (with "and" it is a sentence, with a period it is two), the two
164
+ subjects must differ in polarity, and the second clause must close the
165
+ sentence. Nothing in 2.4M words of pre-2022 Hacker News or 1.25M words of
166
+ public-domain prose.
167
+
168
+ - `and-what-it-should` (rhetorical-tic, warning) flags the elliptical tail:
169
+ "List what the assistant knows about the client, and what it should." The
170
+ second clause borrows its verb from the first and closes on a bare modal
171
+ or a negated auxiliary, so the sentence ends on a contrast it never
172
+ states. The comma, the conjunction, and the full stop right after the
173
+ modal are all required; the affirmative copula and do-verb ("and what he
174
+ does.") are complete clauses and stay out, and so does a question.
175
+ Nothing in 2.4M words of pre-2022 Hacker News or 1.25M words of
176
+ public-domain prose.
177
+
178
+ - `abstract-lives-in` (rhetorical-tic, info) flags an abstraction given an
179
+ address: "the craft that lives between the two desks", "its context lives
180
+ in a folder nobody else can open", "the value sits in the follow-up". The
181
+ subject list is closed and abstract, so people and dogs living and sitting
182
+ places never match, and a capitalised subject is a proper noun. "with" is
183
+ left out of the prepositions because "the decision sits with the board" is
184
+ ordinary English for who is responsible, "at" because "the value sits at
185
+ ten million" is a quantity, and "lies in" because "the problem lies in
186
+ the assumption" is where a fault is. It ships at info because the same
187
+ shape says where information literally is ("the knowledge lives in our
188
+ heads"), and a sample of pre-2022 Hacker News biased toward the
189
+ construction turns up a few of those per million words, all human. A
190
+ draft that keeps giving ideas addresses should be read as a warning.
191
+
192
+ - `np-fragment-and` (structure, info) flags a whole sentence made of two
193
+ noun phrases and an "and": "A named owner and a quarterly review." It is
194
+ the fix half of a model's problem-then-fix pair with the verb left out,
195
+ and its usual habitat is a bulleted list, so a list marker may open it.
196
+ Each phrase is one to three words, and no auxiliary or modal may appear,
197
+ contractions included. It ships at info because a lexical verb is
198
+ invisible to the pattern: "A car and a truck collided." has the same
199
+ shape and flags. That sentence is rare in the corpora (nothing in 2.4M
200
+ words of pre-2022 Hacker News, once in 1.25M words of public-domain
201
+ prose), but it is a sentence, so one flag is a question; a draft full of
202
+ them should be read as a warning.
203
+
204
+ - `the-x-is-the-x` (rhetorical-tic, warning) flags the repeated-head
205
+ equative: "the reason it holds up is the reason the other half happens",
206
+ "the problem with the tool is the problem with the team". A backreference
207
+ catches the same abstract head noun on both sides of the copula, so the
208
+ sentence equates two things while naming neither. The head list holds
209
+ only nouns that cannot name an object, since "the key to the front door
210
+ is the key on the red fob" is an identity statement; the two heads must
211
+ share a clause, so an earlier "the cost was low, but shipping is the
212
+ cost" never pairs; and the second head must be followed by a preposition,
213
+ determiner, quantifier, pronoun, plural noun or punctuation, so "the
214
+ answer key" is not a repeat. Once in 2.4M words of pre-2022 Hacker News;
215
+ nothing in 1.25M words of public-domain prose.
216
+
217
+ - `same-determiner-chain` (rhetorical-tic, info) is the quiet cousin of
218
+ `one-x-one-y`: three or more comma-separated items opening on the same
219
+ determiner or quantifier, "every file, every branch, every deploy", caught
220
+ with a backreference. The narrative possessives (my, his, her, their, its)
221
+ are left out, since "his fame, his position, his life" is every
222
+ novelist's. It ships at info because the device is one humans use on
223
+ purpose: about 8 per million words of pre-2022 Hacker News and 14 per
224
+ million
225
+ in public-domain prose. One is a question; several in a draft should be
226
+ read as a warning.
227
+
228
+ - `quip-question` (structure, info) flags the verbless question that opens
229
+ a pitch: "No invite?", "New to the tool?", "Still stuck?". It must start a
230
+ sentence, open on one of a short list of words, and close on the question
231
+ mark within four more words with no auxiliary or contraction, so a real
232
+ question stays out; "Need" and "Want" are left off the list because "Need
233
+ help?" is a question with its verb elided. Ships at info: about six per
234
+ million words of pre-2022 Hacker News, all of them replies asking the
235
+ same shape of a person; nothing in public-domain prose outside Joyce's
236
+ dialogue. Several in a draft should be read as a warning.
237
+
238
+ - `is-the-whole-x` (rhetorical-tic, info) is `thats-the-whole` on any
239
+ subject: "That periodicity is the whole tell.", "Consistency is the real
240
+ test." It yields only the exact sentences the two older rules own
241
+ ("that/this is the whole point/game/thing/deal/story" and "is the entire
242
+ point/game/thing/deal/story"), so nothing is reported twice and "This is
243
+ the real test." is not lost. An interrogative subject is out, since a
244
+ question is not a closer; "only", "deal", "thing" and "cost" are left out
245
+ because "is the only thing", "the real deal", "the real thing" and "the
246
+ whole cost" are ordinary speech. Ships at info: about three per million words of
247
+ pre-2022 Hacker News, nothing in public-domain prose. Several in a draft
248
+ should be read as a warning.
249
+
250
+ - `bare-equative` (rhetorical-tic, info) flags a sentence that opens on an
251
+ abstract head noun and equates it with a definite noun phrase: "The tell
252
+ here is the periodicity.", "The lesson is the handoff." The head list is
253
+ the one `the-x-is-the-x` uses, so "The key is the brass thing on the hook"
254
+ is a definition and stays out; the copula (including "isn't") must be
255
+ followed by "the" and a lowercase word, so "The problem is real", "The
256
+ answer is a mess", "the same", "the first" and "the Slack thread" are all
257
+ out; a list marker may open it. Ships at info: about four per million
258
+ words of pre-2022 Hacker News, nothing in public-domain prose. Several in
259
+ a draft should be read as a warning.
260
+
261
+ - `mic-drop-closer` (structure, info) flags the kicker: a sentence of sixty
262
+ or more characters, then a two-to-eight-word closer that ends the
263
+ paragraph and opens on a quantifier or deictic, "Nothing here needs a new
264
+ login.", "Most teams end up with two." A blank line or the end of the
265
+ text must follow the closer, so a bullet followed by another bullet is
266
+ not one; both sentences may be hard-wrapped; and whitespace runs in the
267
+ long sentence are capped, so a URL blanked by `--markdown` cannot make it.
268
+ Ships at info because people end paragraphs this way too, at about 150
269
+ per million words of pre-2022 Hacker News (a comment ends on a verdict);
270
+ one flag means nothing. A draft where it repeats paragraph after
271
+ paragraph is the tell, and the rationale tells the agent to read that as
272
+ a warning.
273
+
274
+ - `short-run` (structure, info) flags three consecutive sentences of thirty
275
+ characters or fewer, each closing on a full stop with no quotation mark:
276
+ "Nobody used it. A named owner. Then a review." The run must start at a
277
+ real sentence boundary, so the short tail of a hard-wrapped sentence never
278
+ opens one; dialogue is out by the boundary, sentences with digits as data,
279
+ initials and abbreviations as not sentence ends, consecutive bullets as a
280
+ list, and questions and exclamations by design. Ships at info: a staccato
281
+ run is a device people use on purpose, at about thirty per million words
282
+ of pre-2022 Hacker News. A draft that keeps doing it is the tell, and the
283
+ rationale tells the agent to read that as a warning.
284
+
285
+ - `epistrophe` (rhetorical-tic, info, off by default) flags two clauses
286
+ that end on the same two-word phrase, the second closing the sentence:
287
+ "built for one desk, and almost no job is done at one desk." Two
288
+ backreferences catch the repeat, so the phrase may be hard-wrapped; an
289
+ article-led phrase, a short second word, a long or punctuated second
290
+ clause, and a clause made of blanked Markdown are all out. It is off by
291
+ default like `rule-of-three`: the figure is one Emerson and Marcus
292
+ Aurelius use on purpose, and on pre-2022 Hacker News most of the 42 hits
293
+ per million words are plain phrase reuse. Select it when a draft is
294
+ suspected of leaning on it; several then should be read as a warning.
295
+
296
+ - `intersection-of` (rhetorical-tic, warning) flags "the intersection of X and
297
+ Y" used as positioning: a writer placed between two fields, saying nothing
298
+ about either. "at" is not required, so "explores the intersection of art and
299
+ technology" flags the same as "sits at the intersection of". Two guards keep
300
+ the literal senses out. A street corner names capitalised streets ("Elm",
301
+ "Broadway", "Highway 12"), so the word after "of" must be lowercase or an
302
+ all-caps acronym; "AI", "UX" and "HCI" are the metaphor's usual operands and
303
+ no street is spelled that way. Geometry and set arithmetic name their operands,
304
+ so a literal noun ("curves", "lines", "arrays", "ranges", "roads") within two
305
+ words of "of" drops the note.
306
+
307
+ - `and-nothing-else` (rhetorical-tic, warning) flags a sentence that closes on
308
+ ", and nothing else", ", nothing more", ", nothing further" or ", and no
309
+ more". A model told to return one thing and nothing else carries the phrasing
310
+ into the prose it writes afterwards, where the exclusion repeats what the
311
+ sentence already said.
312
+ - `nothing-else-frag` (rhetorical-tic, warning) flags the same exclusion built
313
+ as a fragment: "Return the JSON. Nothing else."
314
+ - `honestly`, `honest-x` and `most-honest-x` (rhetorical-tic, warning) answer
315
+ the "honest" habit the way `cleanly`, `clean-x` and `cleanest-x` answer the
316
+ "clean" one. `honestly` takes the manner adverb closing on a full stop or
317
+ comma ("these two rows compare honestly"); `honest-x` takes the adjective in
318
+ front of the writer's own construction ("an honest comparison", "the honest
319
+ framing"); `most-honest-x` takes the self-ranking superlative.
320
+
321
+ `honestly` is anchored on position alone, so a dialogue tag ("said Isabel
322
+ honestly") and the sentence-final hedge of casual speech ("it's beyond boring
323
+ honestly") both match. Both are rare: twice in 3.65M words of public-domain
324
+ prose and ten times in 6.07M words of pre-2022 Hacker News. The two noun
325
+ rules flag nothing in either corpus; "an honest answer" and "an honest
326
+ assessment" are left out because on Hacker News they mean a person telling
327
+ the truth, not a writer praising their own framing.
328
+
329
+ Both rules are wide on purpose. There is no verb list and no imperative
330
+ requirement, so the only narrowing is structural, and English has always used
331
+ this tail: the pair flags three times in 1.92M words of public-domain prose
332
+ and twice in 461k words of pre-2022 Hacker News, and `and-nothing-else` flags
333
+ every refrain in "The Raven". Expect to dismiss it on fiction and on quoted
334
+ verse.
7
335
 
8
336
  ## [0.5.0] - 2026-08-27
9
337
 
data/README.md CHANGED
@@ -10,7 +10,32 @@ A pattern earns a place in the catalog only if it shows up constantly in AI writ
10
10
 
11
11
  ## Installation
12
12
 
13
- Requires **Ruby 3.3+** and nothing else. The runtime is standard library only (`optparse`, `json`, native regex).
13
+ Requires **Ruby 3.3+** and nothing else. The runtime is standard library only (`optparse`, `json`, native regex). The Ruby that macOS ships at `/usr/bin/ruby` is 2.6 and too old; `brew install ruby` gets you a current one.
14
+
15
+ ### In Claude Cowork or the Claude desktop app
16
+
17
+ Open **Customize → Plugins → Add marketplace** and paste exactly this:
18
+
19
+ ```
20
+ benjaminjackson/sloplint
21
+ ```
22
+
23
+ Not the web address of the page you are reading — just those two words with the slash, and no slash on the end. `https://github.com/benjaminjackson/sloplint` works too, but a link copied from a page you were browsing usually has more on the end of it and will fail. Then install **sloplint** from the list.
24
+
25
+ Installing this way needs no package install of any kind, which is why it works in Cowork, where `gem install` cannot reach the internet. Claude Code carries the source along with the plugin and runs it in place.
26
+
27
+ New rules reach you when you click **Update** on the marketplace. Nothing updates on its own.
28
+
29
+ Once it is installed, ask Claude to check a draft, or run `/sloplint:check`.
30
+
31
+ ### In Claude Code
32
+
33
+ ```
34
+ /plugin marketplace add benjaminjackson/sloplint
35
+ /plugin install sloplint@sloplint
36
+ ```
37
+
38
+ ### In a terminal
14
39
 
15
40
  ```bash
16
41
  gem install sloplint
@@ -35,7 +60,7 @@ The recipe sloplint is built around, and the one an agent should use:
35
60
  cat draft.md | sloplint check --markdown -o json -
36
61
  ```
37
62
 
38
- `--markdown` blanks out code and URLs first, `-o json` emits the machine-readable form, and `-` reads stdin. Exit 0 means clean, 1 means notes found, anything higher is an error. `check` is the default command, so `sloplint draft.md`, `sloplint -`, and a bare `sloplint` with piped stdin all scan.
63
+ `--markdown` blanks out code, HTML comments, and URLs first, `-o json` emits the machine-readable form, and `-` reads stdin. Exit 0 means clean, 1 means notes found, anything higher is an error. `check` is the default command, so `sloplint draft.md`, `sloplint -`, and a bare `sloplint` with piped stdin all scan.
39
64
 
40
65
  The human-readable form drops `-o json`:
41
66
 
@@ -73,9 +98,10 @@ version print the sloplint version
73
98
 
74
99
  `check` takes files as arguments, or `-` (or nothing) to read stdin, and these options:
75
100
 
76
- - `--markdown` skips fenced code, inline code, and URLs before scanning. Off by default so it never silently eats prose.
101
+ - `--markdown` skips fenced code, inline code, HTML comments, and URLs before scanning. Off by default so it never silently eats prose.
77
102
  - `--select IDS` runs only these rules. Accepts comma-separated rule ids or category names.
78
103
  - `--ignore IDS` skips these rules. Same id-or-category form.
104
+ - `--strict` runs every rule, including the five that are off by default. `--ignore` still applies on top.
79
105
 
80
106
  `explain` is the command an agent calls to decide whether a flag is worth acting on:
81
107
 
@@ -125,26 +151,28 @@ Three codes carry the contract. A crash exits nonzero on its own.
125
151
  | 1 | ran, notes found |
126
152
  | 2 | bad arguments or usage error |
127
153
 
128
- An unknown id or category in `--select`/`--ignore` is a usage error (exit 2, naming the id) rather than a silent no-op, so a typo can't masquerade as a clean scan.
154
+ An unknown id or category in `--select`/`--ignore` is a usage error (exit 2, naming the id) rather than a silent no-op, so a typo can't masquerade as a clean scan. Input that is empty or only whitespace is exit 2 for the same reason: a pipe that delivered nothing must not read as a clean draft. Only when every source is empty — one empty file among several named ones is taken as deliberate.
129
155
 
130
156
  ## The rule catalog
131
157
 
132
- 50 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
158
+ 80 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
133
159
 
134
- - **rhetorical-tic** (36) the cadence patterns: `no-x-no-y`, `no-x-no-y-frag`, `thats-the-whole`, `thats-how-x`, `announced-takeaway`, `exact-exactly`, `load-bearing`, `you-already-know`, `sit-with-that`, `hold-onto-that`, `cleanly`, `clean-count`, `cleanest-x`, `clean-x`, `not-nothing`, `is-is` (doubled copula), `if-im-being-honest` (the candor preamble, from slopwash.com's "false intimacy"), and more.
135
- - **puffery** (5) Wikipedia's "signs of AI writing": `puffery-words` (vibrant, nestled, groundbreaking, in the heart of), `rich-tapestry`, `vital-role`, `stands-serves-as`, `underscores-highlights`.
136
- - **structure** (8) `not-just-x-but-y`, `not-x-but-y` (the bare corrective), `em-dash` (any em dash), `em-dash-overuse` (three or more in one paragraph), `question-isnt` (the corrective frame in interrogative dress), `less-about-more-about`, `trailing-significance-participle` (the "…, showcasing its importance" clause), and `rule-of-three`.
160
+ - **rhetorical-tic** (56) the cadence patterns: `no-x-no-y`, `no-x-no-y-frag`, `from-x-to-y-chain` ("from guessing to measuring, from hoping to knowing"), `one-x-one-y` ("one reviewer, one queue, one deadline"), `same-determiner-chain` (any other repeated determiner, at `info`), `and-what-it-should` (the elliptical tail: "…, and what it should."), `abstract-lives-in` ("the value sits in the follow-up", at `info`), `the-x-is-the-x` ("the problem with A is the problem with B"), `bare-equative` ("The lesson is the handoff.", at `info`), `thats-the-whole`, `is-the-whole-x` (the same closer on any subject: "Consistency is the real test.", at `info`), `thats-how-x`, `announced-takeaway`, `exact-exactly`, `load-bearing`, `intersection-of`, `you-already-know`, `sit-with-that`, `hold-onto-that`, `cleanly`, `clean-count`, `cleanest-x`, `clean-x`, `not-nothing`, `is-is` (doubled copula), `if-im-being-honest` (the candor preamble, from slopwash.com's "false intimacy"), `and-nothing-else` (the trailing "…, and nothing else"), `honestly`, `honest-x`, `most-honest-x` (the honesty family, built the same way as the four `clean` rules above), `impact-verb` ("the outage impacted four thousand accounts"), `impact-noun-bare` ("the impact of X", at `info` because research prose uses it straight), and more.
161
+ - **puffery** (6) five from Wikipedia's "signs of AI writing" `puffery-words` (vibrant, nestled, groundbreaking, in the heart of), `rich-tapestry`, `vital-role`, `stands-serves-as`, `underscores-highlights` — plus `impact-noun-vague` ("a significant impact", "make an impact").
162
+ - **structure** (17) `everyone-nobody` (the comma-spliced antithesis: "Everyone wants the dashboard, nobody maintains it."), `np-fragment-and` (the verbless "A named owner and a quarterly review.", at `info`), `quip-question` (the verbless "No invite?", at `info`), `mic-drop-closer` (the short quantifier-led sentence that ends a paragraph after a long one, at `info`; a draft full of them is a warning), `ellipsis-closer` (the same shape, but the closer's verb is elided down to a bare auxiliary: "The agent did.", at `info`), `short-run` (three sentences of thirty characters or fewer in a row, at `info`, same caveat), `not-just-x-but-y`, `not-x-but-y` (the bare corrective), `isnt-x-its-y` (the same corrective split across two clauses: "It isn't the tool. It's the habit."), `not-by-x-but-by-y` (the corrective on a repeated preposition: "not by luck, but by design"), `em-dash` (any em dash), `em-dash-overuse` (three or more in one paragraph), `question-isnt` (the corrective frame in interrogative dress), `less-about-more-about`, `trailing-significance-participle` (the "…, showcasing its importance" clause), `trailing-restatement` (the "…, which means …" tail that says the sentence again, off by default), and `rule-of-three`.
137
163
  - **hedging** (1) `vague-attribution`: "some critics argue," "it is widely regarded."
138
164
 
139
165
  Severity is `warning` for strong tells, `info` for weak or contextual ones. No rule currently ships at `error`; the tier is reserved for a pattern with essentially zero false-positive risk, and none has earned that yet.
140
166
 
141
167
  Some tells come in a confident form and an ambiguous one, and those ship as a pair rather than as one rule stretched over both. `no-x-no-y` wants the comma chain a writer clearly authored; `no-x-no-y-frag` takes the same cadence built from sentence fragments, which ordinary prose also produces, and ships at `info`. Same with `not-just-x-but-y` and `not-x-but-y`, and with `notice-what-there` and `notice-what`. The quiet half is still worth flagging — an agent that reads the rationale can judge — but it should not carry the same weight as the half we're sure about.
142
168
 
143
- Two rules ship **off by default**, and both run only when you name them: `sloplint check --select rule-of-three -`. `rule-of-three` flags three parallel comma items closing a sentence, which humans do all the time. `genuinely` flags every occurrence of the word; as an intensifier it rates the writer's sincerity, but it still does real work when it draws a contrast, and nothing in the sentence separates the two.
169
+ `and-nothing-else` and `nothing-else-frag` are a pair of the same shape, but both ship at `warning`. That is a deliberate exception: the fragment half carries a capital letter and a whole-sentence requirement that the comma half has no equivalent of, so it is the *narrower* of the two rather than the quieter one.
170
+
171
+ Five rules ship **off by default**. They run when you name them — `sloplint check --select rule-of-three -` — or when you pass `--strict`, which turns the whole catalog on. `rule-of-three` flags three single words in a comma series closing a sentence, which humans do all the time; the closing two items must be single words, so a triad of phrases does not match, because a regex cannot tell one from an ordinary list. `genuinely` flags every occurrence of the word; as an intensifier it rates the writer's sincerity, but it still does real work when it draws a contrast, and nothing in the sentence separates the two. `epistrophe` flags two clauses ending on the same phrase, a named figure that careful writers use on purpose and that, on Hacker News, is mostly plain phrase reuse. `trailing-restatement` flags the "…, which means …" tail and the participles that hang a result off the sentence ("…, making it easier"); the connective is visible and the restatement is not, so a real consequence flags the same way. `phrase-echo` flags a three-word phrase that comes back within a few hundred words; a term of art comes back because it must, and the pattern cannot tell one from a phrase the writer coined.
144
172
 
145
173
  ### Markdown handling
146
174
 
147
- `--markdown` replaces fenced code, inline code, and URLs with same-length whitespace before scanning, so line and column stay correct. Without it, sloplint treats the whole file as prose and will flag text inside your code fences. Pass `--markdown` whenever the input is Markdown.
175
+ `--markdown` replaces fenced code, inline code, HTML comments, and URLs with same-length whitespace before scanning, so line and column stay correct. Without it, sloplint treats the whole file as prose and will flag text inside your code fences. Pass `--markdown` whenever the input is Markdown.
148
176
 
149
177
  ## Adding a rule
150
178
 
data/docs/SPEC.md CHANGED
@@ -93,13 +93,15 @@ dependencies at runtime.
93
93
  Rationale: rules are regexes; the whole thing is a scanner plus an output
94
94
  formatter. A dependency-free `gem install sloplint` is the robust, boring
95
95
  choice. The name is free on RubyGems (taken on PyPI, so this also sidesteps the
96
- collision). Ships a `sloplint` executable via the gemspec's `bin`.
96
+ collision). Ships a `sloplint` executable from `exe/` (claude.ai rejects a
97
+ plugin with a top-level `bin/`, and sloplint ships as a Claude Code plugin
98
+ too).
97
99
 
98
100
  Package layout (standard gem):
99
101
 
100
102
  ```
101
103
  sloplint/
102
- bin/sloplint # thin shim: require "sloplint/cli"; exit Sloplint::CLI.run(ARGV)
104
+ exe/sloplint # thin shim: require "sloplint/cli"; exit Sloplint::CLI.run(ARGV)
103
105
  lib/sloplint.rb # requires the pieces below
104
106
  lib/sloplint/version.rb
105
107
  lib/sloplint/cli.rb # optparse, subcommands, exit codes
@@ -136,9 +138,10 @@ global options:
136
138
 
137
139
  check options:
138
140
  paths ... files to scan; "-" or no paths reads stdin
139
- --markdown skip fenced/inline code spans
141
+ --markdown skip fenced/inline code spans, HTML comments, and URLs
140
142
  --select IDS only run these rules (comma-separated ids or categories)
141
143
  --ignore IDS skip these rules
144
+ --strict run every rule, including the off-by-default ones
142
145
  ```
143
146
 
144
147
  `check` is the default command. A first argument that is not a command name is
@@ -161,6 +164,11 @@ Three codes carry the contract. A crash just exits nonzero on its own.
161
164
  | 1 | ran, **notes found** |
162
165
  | 2 | bad arguments / usage error |
163
166
 
167
+ Empty or whitespace-only input is exit 2, like a mistyped rule id: a scan of
168
+ nothing must not report as a clean scan. The text is tested before
169
+ `--markdown` blanks code, HTML comments, and URLs, so a file that holds only a fenced code
170
+ block still exits 0. Only when every source is empty.
171
+
164
172
  ## Note (the diagnostic object)
165
173
 
166
174
  One match = one Note. JSON output is an array of these (or an object keyed by
@@ -255,7 +263,7 @@ Categories (for `--select`/`--ignore` by group):
255
263
 
256
264
  - `rhetorical-tic` — the cadence patterns (the user's list below)
257
265
  - `puffery` — Wikipedia "words to watch" (boasts, vibrant, nestled, tapestry…)
258
- - `structure` — rule-of-three, "not just X but Y", "the question isn't X, it's Y", "less about X more about Y", the trailing significance participle, em dash, em-dash overuse
266
+ - `structure` — rule-of-three, "not just X but Y", "the question isn't X, it's Y", "less about X more about Y", the trailing significance participle, the trailing restatement, em dash, em-dash overuse
259
267
  - `hedging` — vague attribution ("some critics argue", "it is widely regarded")
260
268
 
261
269
  Severities: `warning` for strong tells, `info` for weak/contextual ones. No
@@ -270,12 +278,20 @@ false-positive risk, which none has demonstrated.
270
278
  |----|---------|-------|
271
279
  | `no-x-no-y` | 2+ comma-separated "no …" items in a row | counts items |
272
280
  | `no-x-no-y-frag` | the same cadence as sentence fragments ("No fluff. No filler.") | counts items; `info` |
273
- | `thats-the-whole` | "that/this is the whole point/game/thing…" | |
281
+ | `thats-the-whole` | "that/this is the whole point/game/thing…" | also value and fix, the closers agents write in technical prose, when they end the sentence or the paragraph or run into a closer's tail word; either apostrophe; `is-the-whole-x` yields the same list |
282
+ | `is-the-whole-x` | any subject + "is the whole/real/actual/entire N" (tell, point, test, work, …) | opens on the subject word; yields only the exact sentences `thats-the-whole` and `is-the-entire` own; interrogative subjects out; "only", "deal", "thing", "cost" left out; `info` |
274
283
  | `did-not-x-did-not-y` | 2+ "did not …"/"didn't …" in a row | counts items |
284
+ | `from-x-to-y-chain` | 2+ comma-separated "from X to Y" spans | counts spans; operands open with a letter, so ranges are out; skips the relay ("to B, from B") and the reduplication ("from X to X") |
285
+ | `one-x-one-y` | 3+ comma-separated "one X" items standing on their own | counts items; must open a sentence or follow a colon, so a chain after a verb is counting; items letter-led; the distributive "one for …" skipped; a pair never flags |
286
+ | `and-what-it-should` | ", and what it should." — a second "what" clause closing on a bare modal or a negated auxiliary | comma, conjunction and full stop required; the affirmative copula and do-verb ("what he does.") are complete clauses and out; a question is out |
287
+ | `abstract-lives-in` | an abstract noun that "lives/sits in/between/inside/…" | closed subject list; capitalised subjects skipped; "with" (responsibility), "at" (quantity), "lies in" and "resides in" left out; `info` |
288
+ | `the-x-is-the-x` | "the X … is the X …": the same abstract head noun on both sides of the copula | backreference; closed list of heads that cannot name an object (key, cost, unit are out); the clause between is capped at 50 chars and may not hold a comma, semicolon or colon; the second head must be followed by a preposition, determiner, quantifier, pronoun, plural noun or punctuation, so compounds are out; "isn't" counts |
289
+ | `same-determiner-chain` | 3+ comma-separated items opening on the same determiner or quantifier (every, each, your, more, …) | backreference; counts items; "one" and "no" left to their own rules; the narrative possessives (my, his, her, their, its) left out; items lowercase-led, so proper nouns are not a chain; `info` |
290
+ | `bare-equative` | sentence-initial "The N (here) is (not) the …" with an abstract head noun | the-x-is-the-x's head list, so concrete heads are out; the copula (is, is not, isn't) must be followed by "the" and a lowercase word, so predicate adjectives, indefinites, pointing complements ("the same/one/first/…") and proper nouns are out; a list marker may open it; `info` |
275
291
  | `dont-verb-it` | "Don't call it X. Call it Y." (negated verb+it, same verb+it) | |
276
292
  | `sit-with-that` | "sit with that/this/it", "sit with the discomfort" | |
277
293
  | `hold-onto-that` | sentence-initial "hold onto/on to that/this" | imperative only |
278
- | `cleanly` | "cleanly" anywhere | no verb list; the engineering idiom counts too |
294
+ | `cleanly` | "cleanly" into/onto/in two/in half — the partition frame | the preposition is the narrowing; the engineering idiom ("applies cleanly", "separated cleanly", "cleanly compiled") is a checkable fact and stays out, and the clause-final form is left out. `info`: the frame is not the sense, and separating "splits cleanly into two parts" from "retracted cleanly into the well" needs the subject |
279
295
  | `clean-count` | "two/three clean parts/buckets/categories…" | needs a partition noun |
280
296
  | `cleanest-x` | "the cleanest framing/formulation", "cleanest way to put it" | noun list only |
281
297
  | `clean-x` | "a clean abstraction/distinction/framing", "clean line between" | `info` |
@@ -283,12 +299,16 @@ false-positive risk, which none has demonstrated.
283
299
  | `is-the-entire` | "X is the entire point/game/business model" | |
284
300
  | `the-entire-is` | "the entire point/game/… is" (flip of above) | |
285
301
  | `is-real-and-not` | "the X is real, and/not…", "is the real … and it" | skip "real estate/time"; `info` |
302
+ | `real-x-real-y` | the same "real" used twice attributively in one sentence, in front of two different nouns: "real API calls … real credentials" | needs no noun list -- narrowed by requiring two attributive uses of the intensifier naming two different things; a hyphen on either side of "real" (real-time, non-real) takes it out of the running; a closed list drops the fixed senses (real time, real-world, real numbers/roots, real user (monitoring), real estate, real money) and the function words that continue a predicative "is real" |
286
303
  | `the-punchline-is` | "the punchline is/:/?", "the honest answer/version is" | "short version" left out; ordinary writing |
287
304
  | `worth-naming` | "worth naming/flagging/separating/spelling out" | skip "naming names"; yields to the rule below when a manner adverb follows; `info` |
288
305
  | `worth-saying-plainly` | "it's worth saying plainly / better put bluntly…", plus the bare "Put plainly," / "Said bluntly," | sentence-initial; the bare branch drops "simply"/"clearly" so "put simply" and "simply put" stay clean |
289
306
  | `not-nothing` | copula + "not nothing" litotes, any subject | skip personal/there subjects |
290
- | `exact-exactly` | "exact"/"exactly" | allowlist for the checkable uses; `info` |
307
+ | `exact-exactly` | the reflexive intensifier: "that's exactly", "exactly right", "exactly the point", "the exact problem", "know exactly why" | matches the tell as a closed set of frames rather than matching the word and subtracting an allow-list; a measured quantity ("the exact diameter", "the exact CPU time") is silent by construction. `info` |
291
308
  | `load-bearing` | "load-bearing" outside its construction sense | skip building nouns either side |
309
+ | `intersection-of` | "the intersection of X and Y" as positioning | skip street corners, geometry, set arithmetic, airfield surfaces (runway, taxiway, apron), matrix rows and columns, and operands shaped like a US route designator ("US-27A") or a quadrant plus house number ("NE 140th Court") |
310
+ | `impact-verb` | "impact" used as a verb ("the outage impacted 4,000 accounts") | needs an auxiliary or subject pronoun for the base form; "to impact" requires a following object, so the preposition ("prior to impact") stays out; skip the medical and soil sense of "impacted", the struck object of a real collision ("impacted terrain"), the fixed compounds, and hyphenated forms |
311
+ | `impact-noun-bare` | "the impact of X", "measure the impact" — `info` | needs a measuring verb in front or "of" behind; skip the collision sense and the fixed compounds |
292
312
  | `thats-how-x` | sentence-initial "that's how…" | |
293
313
  | `announced-takeaway` | colon-led label: "The pattern/lesson/takeaway…:" | sentence-initial |
294
314
  | `earns-its-place` | "earns its place/keep" (any possessive) | possessive required; `warning` |
@@ -301,8 +321,16 @@ false-positive risk, which none has demonstrated.
301
321
  | `notice-what` | bare sentence-initial "Notice what…" | yields the "there" frame to the rule above; "how" excluded; `info` |
302
322
  | `none-of-this-is-to-say` | "none of this/that/the above is to say" | every other "not to say" phrasing excluded |
303
323
  | `if-im-being-honest` | "if I'm/we're (being) honest", "honestly, the answer/truth" | plain "to be honest" and "I'll be honest" excluded |
324
+ | `honestly` | a word, then "honestly", then a full stop or comma | terminal position required; discourse-marker slot guarded ("and/quite/but honestly,"); no animacy test |
325
+ | `honest-x` | "an honest comparison", "the honest framing" | short noun list; "answer", "assessment", "account" excluded as ordinary; superlative yielded to the rule below |
326
+ | `most-honest-x` | "the most honest framing", "the most honest way to put it" | wider noun list than `honest-x`; no human nouns, so "the most honest person" stays out |
304
327
  | `genuinely` | any "genuinely" | off by default; no narrowing holds |
328
+ | `actually-not-x` | "actually" and a trailing "…, not X" in one clause | the comma before "not" must be the first comma of the clause and must follow a word, so a fronted setup ("Despite the name, …") and a parenthetical both drop it; never crosses a line break; bare "actually" and "not actually" both left out |
329
+ | `epistrophe` | two clauses ending on the same two-word phrase, the second closing the sentence | two backreferences, so the phrase may be hard-wrapped; no article-led phrase, second word 4+ letters, second clause 5–60 chars with no internal punctuation and capped whitespace; off by default; `info` |
330
+ | `phrase-echo` | the same three words again within about 400 words | backreference in a lookahead, so the match is the first occurrence; each word 4+ characters and lowercase-led, one of them 6+ letters with nothing but letters, so function-word runs, contractions, proper nouns and Title Case headings are out; a repeat that opens on a quote mark, backtick, emphasis marker or hyphen is out; the gap crosses paragraph breaks but not a list item, a table row or 80+ non-word characters; off by default; `info` |
305
331
  | `and-thats-fine` | "And that's fine/okay." as a whole sentence | "and" required; must open and close the sentence |
332
+ | `and-nothing-else` | trailing ", and nothing else/more/further", ", and no more" | tail must close the sentence; comma required; bare "no more" needs "and"; "?" excluded |
333
+ | `nothing-else-frag` | the same exclusion as a fragment: "Nothing else." | sentence-initial capital; semicolon excluded; must be the whole sentence; "No more." left out |
306
334
  | `is-is` | doubled copula: "what it is is …", "the thing is, is that …" | comma optional |
307
335
 
308
336
  ### puffery (Wikipedia: Signs of AI writing)
@@ -314,15 +342,25 @@ Single flat rule per word-cluster, matched as whole words:
314
342
  - `vital-role` — "plays a (vital/crucial/pivotal/significant/key) role".
315
343
  - `underscores-highlights` — "underscore(s)" + determiner and "underscored/underscoring" anywhere (the emphasis verb); "highlights/emphasizes its (importance/significance)" stays narrow.
316
344
  - `rich-tapestry` — "rich tapestry", "tapestry of".
345
+ - `impact-noun-vague` — an intensity adjective plus "impact" (significant, real, meaningful, lasting, massive, huge, profound, big and the rest), or "make/have an impact". "positive" and "negative" stay out: they name a direction, which is more than the intensity words do. "statistically significant impact" is skipped — that one is a finding.
317
346
 
318
347
  ### structure
319
348
 
320
349
  - `not-just-x-but-y` — copula + "not just/only/merely/simply/solely X … but (also) Y", plus "not because X, but because Y". Requires the escalation word.
321
350
  - `not-x-but-y` — the bare corrective "is not X but Y" with no escalation word; `info`, because the corrective/concession distinction is syntactic and the pattern can only approximate it.
351
+ - `isnt-x-its-y` — the corrective with no conjunction: a negated copula, then a second clause that supplies the replacement ("It isn't the tool. It's the habit."), joined by a period, semicolon, comma, or dash; `info`. Neither complement may open with a pronoun, possessive, preposition ("about" excepted), degree word, or predicate adjective, which keeps out ordinary two-part contrast; some hits are still the same shape written by a person.
352
+ - `not-by-x-but-by-y` — the corrective anchored on a repeated preposition rather than a copula: "not by A, but by B", "not from A but from B" (by, for, from, with, about, in, on, at, to, of, through, because of, out of); `info`. The B side must repeat the A preposition, A is capped at six words, and B may not open with a pronoun. Every corpus hit is the real shape written by a person, which is why it sits at info rather than being narrowed further.
353
+ - `everyone-nobody` — the comma-spliced antithesis on quantifier subjects: one clause opens on everyone/everybody, the other on nobody/no one/none or "one N", joined by a bare comma, the second closing the sentence ("Everyone wants the dashboard, nobody maintains it."). A conjunction, a period, or the same subject twice is not a hinge; "one of/by/per", a measure, a proper noun, "none of which" and "none louder than" are not second subjects; clauses are capped at eighty characters.
354
+ - `np-fragment-and` — a whole sentence made of two noun phrases and an "and", opening on A/An/One at a sentence start or after a list marker ("A named owner and a quarterly review."). One to three words a side, no auxiliary or modal anywhere (contractions included); a lexical verb is invisible, so "A car and a truck collided." flags, which is why it ships at `info`.
355
+ - `quip-question` — the verbless opening question ("No invite?", "New to the tool?", "Still stuck?"): sentence-initial, one of a short list of opening words, one to four more words, no auxiliary or contraction, closing on the question mark; "Need" and "Want" are left out as elided verbs; `info`, because people ask the same shape in conversation.
356
+ - `mic-drop-closer` — a sentence of 60+ characters, then a paragraph-final closer of two to eight words opening on a **quantifier** (Nothing, Most, None, Everything, Everyone, Nobody, Then, Neither, Both): "Nothing here needs a new login."; `info`. The bare demonstratives (That, This, It) were in the list and are out: procedural writing ends a step with one as a matter of course ("This completes the roughing operations."). A blank line or the end of the text must follow the closer; both sentences may be hard-wrapped; whitespace runs in the long sentence are capped so blanked Markdown cannot make it, and the long-sentence prefix is an atomic group so an unpunctuated stretch cannot send it into catastrophic backtracking. The note points at the closer. One means nothing; a draft where it repeats is the tell.
357
+ - `ellipsis-closer` — the same long-sentence-then-short-closer shape as `mic-drop-closer`, but the tell sits in the verb rather than the subject: the closer's verb phrase is elided down to a bare auxiliary with no object, "The agent did."; `info`. No subject list is needed — a one-to-three word subject runs straight into a bare `did/does/do/was/were/is/are/had/has/can/could/would/will/should/might/must` (contracted forms included) and a period, and the closer must be the last thing in the paragraph, same as `mic-drop-closer`, so a closing quotation mark after the period excludes quoted dialogue the same way `short-run` excludes it. A negative lookahead drops a closer that still holds "what", "that", "which", "who", "why" or "how", since those introduce a subordinate clause supplying its own complement rather than an elided one ("Nobody knew who did." asks who did it). Reuses `mic-drop-closer`'s long-sentence prefix rather than a second copy of it. One means nothing; a draft where it repeats is the tell.
358
+ - `short-run` — three consecutive sentences of thirty characters or fewer, each letter-led and closing on a full stop, no quotation marks or digits, no lone-letter labels (either case, though a possessive is not one) or abbreviations, starting at a real sentence boundary (never on a wrap continuation), crossing a hard wrap but not a paragraph break; `info`. A list marker may open a run but never sit inside one, so consecutive bullets are a list — the marker set covers glyphs, the literal "o" plain-text documents use as a bullet, and numbered and lettered items. Two whole-run exclusions keep document furniture out, each a property of the run rather than of any sentence in it: three single-word sentences in a row is a citation line ("Natl. Inst. Stand. Technol."), and three lowercase-led sentences in a row is transcribed speech ("we all get gas. we go to divert to Albany."). One single-word sentence is the archetypal kicker and stays, and a run that reaches a capital anywhere is prose, so identifier-initial writing ("npm was slow. git blame helped. We moved on.") is untouched. One is a question; a draft that repeats it is the tell.
322
359
  - `question-isnt` — "the question isn't/is not (whether|if|how|what|why|who) X, it's/but Y"; `info`. The resolving clause is required, so a plain rhetorical question never matches; "the real question is" is excluded.
323
360
  - `less-about-more-about` — "it's/this is/that's less about X (and) more about Y", also "… than about Y"; `info`. Both halves of the frame are required, and the subject slot is limited to the pronouns.
324
361
  - `trailing-significance-participle` — comma plus a participle from a closed verb list (highlighting, showcasing, reinforcing, shaping, enhancing, cementing, solidifying, embodying, fostering, facilitating, signalling), the clause a model hangs off a sentence to say what a fact means. Guards drop gerund lists and "signalling to". `driving`, `representing`, `reflecting`, `marking`, `contributing`, `illustrating`, `demonstrating`, `emphasising`, `echoing` and `affirming` stay out: humans write them in the same position, usually with a person as the subject, and the pattern cannot see the subject. `underscoring` is left to `underscores-highlights`.
325
- - `rule-of-three` — three parallel comma items ending a sentence (heuristic; `info` severity, off by default via `--select` since it false-positives).
362
+ - `trailing-restatement` — comma plus "which means", "which is to say", or "meaning" opening on one of a closed set of determiners and pronouns; or one of four participle frames with a pronoun object and a closing word ("making it easier", "allowing us to", "giving them more", "leaving you with"). The closer on "making" is a comparative that ends the clause or leads into "to", "for" or "than". Nothing before the comma is inspected, so a gloss ("_ma_, which means hand") matches; the bare participles ("leaving the door open") never do. Off by default: the pattern sees the connective, not whether the tail restates the head. `info`.
363
+ - `rule-of-three` — three single-word comma items ending a sentence (heuristic; `info` severity, off by default; runs under `--select` or `--strict` since it false-positives).
326
364
  - `em-dash` — any em dash; `info`.
327
365
  - `em-dash-overuse` — 3+ em dashes in one paragraph; `warning`.
328
366
 
@@ -332,8 +370,8 @@ Single flat rule per word-cluster, matched as whole words:
332
370
 
333
371
  ## Markdown handling
334
372
 
335
- `--markdown` blanks out fenced code (```` ``` ````), inline code (`` ` ``), and
336
- URLs before scanning, replacing them with same-length whitespace so line/column
373
+ `--markdown` blanks out fenced code (```` ``` ````), inline code (`` ` ``), HTML
374
+ comments (`<!-- -->`), and URLs before scanning, replacing them with same-length whitespace so line/column
337
375
  stay correct. Off by default (plain-text mode) so it never silently eats prose.
338
376
 
339
377
  ## Agent-first help text