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 +4 -4
- data/CHANGELOG.md +329 -1
- data/README.md +38 -10
- data/docs/SPEC.md +48 -10
- data/exe/sloplint +13 -0
- data/lib/sloplint/cli.rb +45 -12
- data/lib/sloplint/engine.rb +7 -8
- data/lib/sloplint/rules.rb +2273 -22
- data/lib/sloplint/version.rb +1 -1
- metadata +4 -4
- data/bin/sloplint +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 33eff57c67530d0745b1ba5009a97128929a73ec27ae09afaba8d9057f0f997b
|
|
4
|
+
data.tar.gz: 3b76104840a3c2ab471651cbdf18dd4a9027707aaf100bc901430c7f9e121216
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
## [
|
|
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
|
-
|
|
158
|
+
80 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
|
|
133
159
|
|
|
134
|
-
- **rhetorical-tic** (
|
|
135
|
-
- **puffery** (
|
|
136
|
-
- **structure** (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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
|
-
- `
|
|
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 (`` ` ``),
|
|
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
|