sloplint 0.2.0 → 0.3.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 +30 -0
- data/README.md +6 -6
- data/docs/SPEC.md +3 -2
- data/lib/sloplint/rules.rb +215 -31
- data/lib/sloplint/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2dff756c71ef10086a95d721978cb5bd0f3a73ae28f46637f91b13b016aa461b
|
|
4
|
+
data.tar.gz: 543c0b9005b4e7b90d35c5595ee812f77d849513163d222b1ae04cb8fa48b8c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 537e1d987a39cb4b9c488b69ea4aa7e5381b3594d9565eb78324640389e6eae7fa9cf47c6c93921048b396de24178b969f6ac36685dec2606845c9e00694b543
|
|
7
|
+
data.tar.gz: 9d4d9845f6a540954446dd2a5513cd0dc0fd9c28a8e689ac6a64a975f03bf68e1478c62be96e2d09ac1784e83bb421c667898f88e89aceb6a63d729ffce17270
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
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]
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-08-02
|
|
9
|
+
|
|
10
|
+
- New rule `load-bearing` (`rhetorical-tic`, `warning`): "load-bearing" used
|
|
11
|
+
as a borrowed metaphor for anything important, outside its literal
|
|
12
|
+
construction sense (a wall, a column, a beam). Guards on both sides —
|
|
13
|
+
a building noun right after it, or the predicate form ("the wall is
|
|
14
|
+
load-bearing") — leave the construction sense clean.
|
|
15
|
+
- New rule `is-is` (`rhetorical-tic`, `warning`): the doubled copula, "what it
|
|
16
|
+
is is a mistake" / "the thing is, is that...". No anchor needed -- the bare
|
|
17
|
+
pattern scored 0 hits across ~1.9M words of public-domain and modern prose.
|
|
18
|
+
- `exactly-the` replaced by the broader `exact-exactly` (`rhetorical-tic`,
|
|
19
|
+
now `info`): flags "exact/exactly" generally, not just the fixed "exactly
|
|
20
|
+
the point/kind/problem/…" phrase shape, with an allowlist for the places
|
|
21
|
+
it's doing real work (numbers, times, same/opposite/way/etc.). Severity
|
|
22
|
+
drops to `info` because a common word will still slip through on cases
|
|
23
|
+
the allowlist hasn't seen yet.
|
|
24
|
+
- `thats-not-nothing` replaced by the broader `not-nothing`
|
|
25
|
+
(`rhetorical-tic`, `warning`): catches the "is not nothing" litotes
|
|
26
|
+
regardless of subject ("Fifty basis points is not nothing"), not only
|
|
27
|
+
the demonstrative-subject form ("that's not nothing"). Personal-subject
|
|
28
|
+
litotes ("he was not nothing to her") and the "there is not nothing"
|
|
29
|
+
philosophy frame are allowlisted as legitimate human use.
|
|
30
|
+
- `underscores-highlights` broadened (`puffery`, still `info`): "underscores
|
|
31
|
+
the need for", "underscored how fragile", and "underscoring the urgency"
|
|
32
|
+
now match, not only the fixed "underscores/highlights/emphasizes its/the/
|
|
33
|
+
their importance/significance" phrase. The character-noun sense
|
|
34
|
+
("a leading underscore", snake_case) stays excluded.
|
|
35
|
+
|
|
6
36
|
## [0.2.0] - 2026-07-28
|
|
7
37
|
|
|
8
38
|
- `is-real-and-not` and `worth-naming` move from `warning` to `info`. Both
|
data/README.md
CHANGED
|
@@ -51,10 +51,10 @@ $ printf 'The report is a rich tapestry of vibrant detail.\nThat is exactly the
|
|
|
51
51
|
why: Travel-brochure adjectives and phrases that models reach for and careful writers avoid.
|
|
52
52
|
fix: Replace with a concrete, specific detail or cut it.
|
|
53
53
|
|
|
54
|
-
-:2:9:
|
|
55
|
-
excerpt: …tapestry of vibrant detail. That is [exactly the point
|
|
56
|
-
why: 'exactly
|
|
57
|
-
fix:
|
|
54
|
+
-:2:9: info exact-exactly "exact/exactly" is reflexive emphasis unless it names something checkable.
|
|
55
|
+
excerpt: …tapestry of vibrant detail. That is [exactly] the point I keep making about it.
|
|
56
|
+
why: Models reach for 'exact/exactly' as filler emphasis on a claim with nothing to check; it earns its place only next to a number, a name, or a stated identity.
|
|
57
|
+
fix: Cut it, or replace with the number, name, or match it's supposed to be precise about.
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
The brackets mark the match; the rest is there so you can see what you're fixing without opening the file — including *why*, so an agent doesn't have to run `explain` separately to decide whether a flag is worth acting on.
|
|
@@ -128,9 +128,9 @@ An unknown id or category in `--select`/`--ignore` is a usage error (exit 2, nam
|
|
|
128
128
|
|
|
129
129
|
## The rule catalog
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
29 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
|
|
132
132
|
|
|
133
|
-
- **rhetorical-tic** (
|
|
133
|
+
- **rhetorical-tic** (18) 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`, `not-nothing`, `is-is` (doubled copula), and more.
|
|
134
134
|
- **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`.
|
|
135
135
|
- **structure** (5) `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), and `rule-of-three`.
|
|
136
136
|
- **hedging** (1) `vague-attribution`: "some critics argue," "it is widely regarded."
|
data/docs/SPEC.md
CHANGED
|
@@ -275,7 +275,8 @@ false-positive risk, which none has demonstrated.
|
|
|
275
275
|
| `is-real-and-not` | "the X is real, and/not…", "is the real … and it" | skip "real estate/time"; `info` |
|
|
276
276
|
| `the-punchline-is` | "the punchline is/:/?" | |
|
|
277
277
|
| `worth-naming` | "worth naming", "it's worth naming that…", "Worth naming:" | skip "naming names"; `info` |
|
|
278
|
-
| `
|
|
278
|
+
| `not-nothing` | copula + "not nothing" litotes, any subject | skip personal/there subjects |
|
|
279
|
+
| `is-is` | doubled copula: "what it is is …", "the thing is, is that …" | comma optional |
|
|
279
280
|
|
|
280
281
|
### puffery (Wikipedia: Signs of AI writing)
|
|
281
282
|
|
|
@@ -284,7 +285,7 @@ Single flat rule per word-cluster, matched as whole words:
|
|
|
284
285
|
- `puffery-words` — boasts a, vibrant, rich (history/cultural/tapestry), nestled (gated to a following in/among/between, so the literal verb — a head nestled against a shoulder — doesn't count), in the heart of (gated to a place object), groundbreaking, renowned, diverse array, breathtaking, natural beauty, stands as a testament, indelible mark, deeply rooted.
|
|
285
286
|
- `stands-serves-as` — "stands as / serves as", "is a testament/reminder to".
|
|
286
287
|
- `vital-role` — "plays a (vital/crucial/pivotal/significant/key) role".
|
|
287
|
-
- `underscores-highlights` — "
|
|
288
|
+
- `underscores-highlights` — "underscore(s)" + determiner and "underscored/underscoring" anywhere (the emphasis verb); "highlights/emphasizes its (importance/significance)" stays narrow.
|
|
288
289
|
- `rich-tapestry` — "rich tapestry", "tapestry of".
|
|
289
290
|
|
|
290
291
|
### structure
|
data/lib/sloplint/rules.rb
CHANGED
|
@@ -33,9 +33,8 @@ module Sloplint
|
|
|
33
33
|
examples_bad: ["No fluff, no filler, no jargon."],
|
|
34
34
|
examples_ok: ["No parking on Sundays."],
|
|
35
35
|
rationale: "Asyndetic negation chains are a signature model cadence, near-absent from " \
|
|
36
|
-
"human prose at any length
|
|
37
|
-
"
|
|
38
|
-
"two (and, rarely, more), but a model reaches for the pattern constantly."
|
|
36
|
+
"human prose at any length. A careful writer occasionally stacks two (and, " \
|
|
37
|
+
"rarely, more), but a model reaches for the pattern constantly."
|
|
39
38
|
),
|
|
40
39
|
Rule.new(
|
|
41
40
|
id: "no-x-no-y-frag",
|
|
@@ -76,11 +75,9 @@ module Sloplint
|
|
|
76
75
|
],
|
|
77
76
|
rationale: "The fragment form of the same cadence, and the weaker signal of the two: the " \
|
|
78
77
|
"comma chain is one authored sentence, while this is just short sentences in " \
|
|
79
|
-
"sequence, which human prose also does. It
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"staccato run ('No one moved. No one spoke.') has the identical shape and is " \
|
|
83
|
-
"not a tell. Treat a flag here as a question, not a verdict."
|
|
78
|
+
"sequence, which human prose also does. It's rare in careful writing, but a " \
|
|
79
|
+
"deliberate staccato run ('No one moved. No one spoke.') has the identical " \
|
|
80
|
+
"shape and is not a tell. Treat a flag here as a question, not a verdict."
|
|
84
81
|
),
|
|
85
82
|
Rule.new(
|
|
86
83
|
id: "thats-the-whole",
|
|
@@ -215,27 +212,141 @@ module Sloplint
|
|
|
215
212
|
"which sense it's in."
|
|
216
213
|
),
|
|
217
214
|
Rule.new(
|
|
218
|
-
id: "
|
|
215
|
+
id: "not-nothing",
|
|
219
216
|
category: "rhetorical-tic",
|
|
220
217
|
severity: "warning",
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
# Two branches: spelled-out copula with an optional captured subject, and
|
|
219
|
+
# the contracted "X's not nothing". The optional subject + skip: is the
|
|
220
|
+
# load-bearing idiom -- skip: only sees matched text, so the personal
|
|
221
|
+
# subjects a human litotes takes (I/he/she/we/you/they) must be inside
|
|
222
|
+
# the match to be dropped there. The contracted branch captures the word
|
|
223
|
+
# before 's for the same reason ("she's not nothing" must reach the
|
|
224
|
+
# skip). "there" is guarded too: "there is/there's not nothing" is the
|
|
225
|
+
# philosophy frame or dialect, not the closer. "not for nothing" and
|
|
226
|
+
# "something and not nothing" never match at all -- the copula must sit
|
|
227
|
+
# adjacent to "not nothing". Token gaps cross a hard-wrapped line but
|
|
228
|
+
# never a paragraph break, so a paragraph ending "...was not" cannot weld
|
|
229
|
+
# onto one starting "Nothing ...".
|
|
230
|
+
pattern: /\b(?:(?:i|he|she|we|you|they|there)(?:[ \t]|\r?\n(?![ \t]*\r?\n))+)?
|
|
231
|
+
(?:am|is|are|was|were)(?:[ \t]|\r?\n(?![ \t]*\r?\n))+
|
|
232
|
+
not(?:[ \t]|\r?\n(?![ \t]*\r?\n))+nothing\b
|
|
233
|
+
|\b[\w-]+['’]s(?:[ \t]|\r?\n(?![ \t]*\r?\n))+
|
|
234
|
+
not(?:[ \t]|\r?\n(?![ \t]*\r?\n))+nothing\b/ix,
|
|
235
|
+
skip: [/\A(?:i|he|she|we|you|they|there)\b/i],
|
|
236
|
+
message: '"…is not nothing" is a stock LLM understatement.',
|
|
223
237
|
suggestion: "State the magnitude directly instead of the litotes.",
|
|
224
|
-
examples_bad: [
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
examples_bad: [
|
|
239
|
+
"We cut latency in half, and that's not nothing.",
|
|
240
|
+
"Fifty basis points is not nothing.",
|
|
241
|
+
"Three years of runway is not nothing.",
|
|
242
|
+
"A million users is not nothing.",
|
|
243
|
+
"The margin was not nothing.",
|
|
244
|
+
# Hard-wrapped Markdown: same paragraph, still the tell.
|
|
245
|
+
"The gain was not\nnothing, the report said."
|
|
246
|
+
],
|
|
247
|
+
examples_ok: [
|
|
248
|
+
"That is not enough to matter.",
|
|
249
|
+
# Personal-subject litotes is a human literary move, not the closer
|
|
250
|
+
# tic -- spelled out and contracted.
|
|
251
|
+
"She told him he was not nothing to her.",
|
|
252
|
+
"She's not nothing to me, whatever they say.",
|
|
253
|
+
# "not for nothing": the intervening word breaks adjacency.
|
|
254
|
+
"It was not for nothing that he trained all winter.",
|
|
255
|
+
# The philosophy frame, both shapes it takes.
|
|
256
|
+
"Why is there something and not nothing?",
|
|
257
|
+
"There is not nothing; being persists.",
|
|
258
|
+
# Dialect double negative, not the litotes.
|
|
259
|
+
"There's not nothing we can do.",
|
|
260
|
+
# A paragraph break never welds a match.
|
|
261
|
+
"The answer was not\n\nNothing prepared us for it."
|
|
262
|
+
],
|
|
263
|
+
rationale: "The 'not nothing' litotes is a recognizable model closer; the subject " \
|
|
264
|
+
"varies ('that's not nothing', 'fifty basis points is not nothing') but " \
|
|
265
|
+
"the move is the same. Personal subjects are carved out: 'he was not " \
|
|
266
|
+
"nothing to her' is a human literary litotes, not the closer."
|
|
227
267
|
),
|
|
228
268
|
|
|
229
269
|
Rule.new(
|
|
230
|
-
id: "exactly
|
|
270
|
+
id: "exact-exactly",
|
|
271
|
+
category: "rhetorical-tic",
|
|
272
|
+
severity: "info",
|
|
273
|
+
pattern: /\bexact(?:ly)?\b(?!\s*(?:(?:the\s+)?(?:same|opposite|science|change|replica|copy|location|coordinates|way)\b|[$\d]|noon\b|midnight\b|o'?\s*clock\b))/i,
|
|
274
|
+
message: '"exact/exactly" is reflexive emphasis unless it names something checkable.',
|
|
275
|
+
suggestion: "Cut it, or replace with the number, name, or match it's supposed to be precise about.",
|
|
276
|
+
examples_bad: [
|
|
277
|
+
"That's exactly the point.", "We proved exactly the point we needed.",
|
|
278
|
+
"This is exactly the kind of thing we warned about.",
|
|
279
|
+
"That's the exact problem with the old system.",
|
|
280
|
+
"That's exactly right.", "I know exactly why this happened."
|
|
281
|
+
],
|
|
282
|
+
examples_ok: [
|
|
283
|
+
"She folded it exactly the way he showed her.",
|
|
284
|
+
"The bill came to exactly $42.",
|
|
285
|
+
"We agreed on the exact same design.",
|
|
286
|
+
"It's not an exact science.",
|
|
287
|
+
"Please bring exact change for the bus.",
|
|
288
|
+
"The museum built an exact replica of the ship.",
|
|
289
|
+
"This is an exact copy of the original.",
|
|
290
|
+
"He wanted the exact opposite of what she suggested.",
|
|
291
|
+
"Rescue teams pinpointed the exact location of the wreck.",
|
|
292
|
+
"GPS gave us the exact coordinates of the site.",
|
|
293
|
+
"The train left at exactly noon.",
|
|
294
|
+
"They agreed to meet at exactly midnight.",
|
|
295
|
+
"The meeting starts at exactly 3 o'clock.",
|
|
296
|
+
"She has exacting standards for her students."
|
|
297
|
+
],
|
|
298
|
+
rationale: "Models reach for 'exact/exactly' as filler emphasis on a claim with nothing to " \
|
|
299
|
+
"check; it earns its place only next to a number, a name, or a stated identity."
|
|
300
|
+
),
|
|
301
|
+
Rule.new(
|
|
302
|
+
id: "load-bearing",
|
|
231
303
|
category: "rhetorical-tic",
|
|
232
304
|
severity: "warning",
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
305
|
+
# Two guards, both structural, over the same noun list so they can't
|
|
306
|
+
# drift apart. Forward: a physical building part right after it is the
|
|
307
|
+
# literal sense, checked with a negative lookahead. Backward: the
|
|
308
|
+
# predicate form ("the wall is load-bearing") is literal too, but a
|
|
309
|
+
# fixed-width lookbehind covering this many noun x tense combinations
|
|
310
|
+
# trips a real Ruby/Onigmo lookbehind bug on some inputs (RegexpError
|
|
311
|
+
# at match time, not compile time -- reproduced on em-dash's own
|
|
312
|
+
# fixtures). Pulling the noun+copula into the pattern itself as an
|
|
313
|
+
# optional leading group sidesteps lookbehind entirely: when present,
|
|
314
|
+
# it's captured as part of the match, and skip: (which only ever sees
|
|
315
|
+
# matched text, never surrounding context) drops it there instead.
|
|
316
|
+
pattern: /\b(?:(?:wall|column|beam|post|pillar|joist|stud|masonry|partition|
|
|
317
|
+
structure|frame|footing|foundation|member)s?\s+
|
|
318
|
+
(?:is|are|was|were)\s+)?
|
|
319
|
+
load[-\s]?bearing\b
|
|
320
|
+
(?!\s+(?:wall|column|beam|post|pillar|joist|stud|masonry|partition|
|
|
321
|
+
structure|frame|footing|foundation|member)s?\b)/ix,
|
|
322
|
+
skip: [/\A(?:wall|column|beam|post|pillar|joist|stud|masonry|partition|
|
|
323
|
+
structure|frame|footing|foundation|member)s?\s+
|
|
324
|
+
(?:is|are|was|were)\s+load/ix],
|
|
325
|
+
message: '"load-bearing" outside construction is a borrowed metaphor.',
|
|
326
|
+
suggestion: "Say what the thing holds up, or what breaks without it.",
|
|
327
|
+
examples_bad: [
|
|
328
|
+
"That comma is load-bearing.",
|
|
329
|
+
"The load-bearing assumption is that users read the docs.",
|
|
330
|
+
"Half the argument rests on one load-bearing word.",
|
|
331
|
+
"This paragraph is the load-bearing part of the essay.",
|
|
332
|
+
"Trust was the load-bearing element of the whole deal.",
|
|
333
|
+
"The qualifier is doing load bearing work here."
|
|
334
|
+
],
|
|
335
|
+
examples_ok: [
|
|
336
|
+
"They knocked out a load-bearing wall during the remodel.",
|
|
337
|
+
"The inspector flagged a cracked load-bearing column.",
|
|
338
|
+
"Steel load-bearing beams replaced the old timber.",
|
|
339
|
+
"The load-bearing masonry dates to 1890.",
|
|
340
|
+
"Those columns are load-bearing.",
|
|
341
|
+
"The interior wall is load-bearing, so it stays.",
|
|
342
|
+
"The beam is load-bearing.",
|
|
343
|
+
"That stud was load-bearing, so removing it needed a header beam.",
|
|
344
|
+
"Those joists were load-bearing, engineers confirmed after inspection.",
|
|
345
|
+
"The masonry was load-bearing in the original 1890 structure."
|
|
346
|
+
],
|
|
347
|
+
rationale: "'Load-bearing' is a construction term for a wall or column that holds up the " \
|
|
348
|
+
"structure. Models borrow it as a metaphor for anything important, which just " \
|
|
349
|
+
"restates the sentence's importance without saying what actually holds it up."
|
|
239
350
|
),
|
|
240
351
|
Rule.new(
|
|
241
352
|
id: "thats-how-x",
|
|
@@ -271,6 +382,41 @@ module Sloplint
|
|
|
271
382
|
rationale: "Labelling a claim as the portable lesson does the persuading that the claim " \
|
|
272
383
|
"should be doing — a model habit borrowed from thought-leader prose."
|
|
273
384
|
),
|
|
385
|
+
Rule.new(
|
|
386
|
+
id: "is-is",
|
|
387
|
+
category: "rhetorical-tic",
|
|
388
|
+
severity: "warning",
|
|
389
|
+
# No anchor needed -- the doubled copula alone scored 0 across ~1.9M words.
|
|
390
|
+
# The comma is allowed because "What it is, is a mystery" grates the same
|
|
391
|
+
# way. Sentence and clause punctuation still block the weld ("what it is.
|
|
392
|
+
# Is that…", "here's what it is: is anyone…"), and \b on both ends keeps
|
|
393
|
+
# the pattern out of "his island". \s+ needs no paragraph-break guard: a
|
|
394
|
+
# false match would need a paragraph that begins with "is ".
|
|
395
|
+
pattern: /\bis,?\s+is\b/i,
|
|
396
|
+
message: 'Doubled copula ("is is" / "is, is") — spoken cadence on the page.',
|
|
397
|
+
suggestion: "Drop the cleft and say it straight, or delete the second 'is'.",
|
|
398
|
+
examples_bad: [
|
|
399
|
+
"What this really is is a bet on distribution.",
|
|
400
|
+
"What it is is a rounding error with a press release.",
|
|
401
|
+
"What it is, is a failure of nerve.",
|
|
402
|
+
"The thing is, is that nobody checked the logs."
|
|
403
|
+
],
|
|
404
|
+
examples_ok: [
|
|
405
|
+
# Walden: the cleft with a single copula, which is ordinary English.
|
|
406
|
+
"We have heard of this virtue, but we know not what it is.",
|
|
407
|
+
# Sentence and clause punctuation can't be welded across.
|
|
408
|
+
"I know what it is. Is that a problem?",
|
|
409
|
+
"Here's what it is: is anyone actually reading this?",
|
|
410
|
+
# Both \b anchors matter -- "his island" contains the literal string "is is".
|
|
411
|
+
"He sailed to his island at dawn."
|
|
412
|
+
],
|
|
413
|
+
rationale: "Three things produce a doubled copula and all three read as unedited. The " \
|
|
414
|
+
"wh-cleft ('what it is is a mistake') is grammatical -- 'what it is' is the " \
|
|
415
|
+
"subject and the second 'is' is the verb -- and it is the model's version, a " \
|
|
416
|
+
"frame that stages a definition instead of asserting one, the same move as " \
|
|
417
|
+
"'the punchline is'. The NP form ('the thing is, is that') is the true double " \
|
|
418
|
+
"copula and a spoken disfluency. The third is a typo."
|
|
419
|
+
),
|
|
274
420
|
|
|
275
421
|
# ── puffery ───────────────────────────────────────────────────────────
|
|
276
422
|
Rule.new(
|
|
@@ -335,12 +481,50 @@ module Sloplint
|
|
|
335
481
|
id: "underscores-highlights",
|
|
336
482
|
category: "puffery",
|
|
337
483
|
severity: "info",
|
|
338
|
-
|
|
339
|
-
|
|
484
|
+
# "underscored/underscoring" are unambiguously the verb and flag bare.
|
|
485
|
+
# "underscore/underscores" is also the character noun (a leading
|
|
486
|
+
# underscore, snake_case docs), so those forms require a following
|
|
487
|
+
# determiner/wh-word -- the frame the emphasis verb takes, which the
|
|
488
|
+
# noun never precedes. That guard also skips bare-noun objects
|
|
489
|
+
# ("underscores concerns about"), an accepted miss: widening the frame
|
|
490
|
+
# to catch them readmits the noun sense. Token gaps cross a
|
|
491
|
+
# hard-wrapped line but never a paragraph break. highlights/emphasizes
|
|
492
|
+
# stay narrowed to the importance/significance frame -- both verbs are
|
|
493
|
+
# too common in ordinary prose to match broadly.
|
|
494
|
+
pattern: /\bunderscor(?:ed|ing)\b
|
|
495
|
+
|\bunderscores?(?:[ \t]|\r?\n(?![ \t]*\r?\n))+
|
|
496
|
+
(?:a|an|the|its|his|her|their|our|your|my|this|that|these|those|what|why|how|just)\b
|
|
497
|
+
|\b(?:highlights|emphasizes)\s+(?:its|the|their)\s+(?:importance|significance)\b/ix,
|
|
498
|
+
message: '"underscores" as emphasis (or "highlights/emphasizes its importance") is stock AI framing.',
|
|
340
499
|
suggestion: "Show why it matters rather than asserting that it does.",
|
|
341
|
-
examples_bad: [
|
|
342
|
-
|
|
343
|
-
|
|
500
|
+
examples_bad: [
|
|
501
|
+
"This underscores its importance to the field.",
|
|
502
|
+
"The delay underscores the need for better tooling.",
|
|
503
|
+
"The outage underscored how fragile the pipeline was.",
|
|
504
|
+
"Underscoring the urgency, the board met twice.",
|
|
505
|
+
"The results underscore a deeper problem with the method.",
|
|
506
|
+
"The report underscores just how far behind we are.",
|
|
507
|
+
"It underscores that the market has moved on.",
|
|
508
|
+
"The essay highlights its importance at length.",
|
|
509
|
+
"This emphasizes the significance of early testing."
|
|
510
|
+
],
|
|
511
|
+
examples_ok: [
|
|
512
|
+
"Replace each space with an underscore.",
|
|
513
|
+
"Variable names use underscores instead of dashes.",
|
|
514
|
+
"The underscore character separates words in snake case.",
|
|
515
|
+
"Prefix private methods with a leading underscore.",
|
|
516
|
+
"Ruby numeric literals accept underscores for readability.",
|
|
517
|
+
"Two underscores mark a dunder method in Python.",
|
|
518
|
+
"She highlights the key line in yellow.",
|
|
519
|
+
"The paper emphasizes the method, not the results.",
|
|
520
|
+
# A paragraph break never welds the noun onto the next sentence.
|
|
521
|
+
"Numbers accept underscores\n\nThe next section covers floats."
|
|
522
|
+
],
|
|
523
|
+
rationale: "Models reach for 'underscore' as an all-purpose emphasis verb -- findings " \
|
|
524
|
+
"underscore, outages underscore -- asserting significance without earning it. " \
|
|
525
|
+
"Sincere journalistic and academic use exists, hence info: a flag means the " \
|
|
526
|
+
"move is present, not that it's slop. The character noun never takes the " \
|
|
527
|
+
"verb's frame and stays out."
|
|
344
528
|
),
|
|
345
529
|
Rule.new(
|
|
346
530
|
id: "rich-tapestry",
|
|
@@ -397,8 +581,8 @@ module Sloplint
|
|
|
397
581
|
"copula, which is the specific shape models overuse. Correlative 'not only… " \
|
|
398
582
|
"but' joining two verb phrases or clauses, without a preceding copula, is " \
|
|
399
583
|
"ordinary and common in formal human prose; requiring the escalation word " \
|
|
400
|
-
"keeps those out. 'not because
|
|
401
|
-
"
|
|
584
|
+
"keeps those out. The same logic covers 'not because A, but because B': the " \
|
|
585
|
+
"escalation word is what marks it deliberate."
|
|
402
586
|
),
|
|
403
587
|
Rule.new(
|
|
404
588
|
id: "not-x-but-y",
|
|
@@ -464,9 +648,9 @@ module Sloplint
|
|
|
464
648
|
],
|
|
465
649
|
rationale: "The bare 'is not A but B' corrective is the 'not just… but' move with the " \
|
|
466
650
|
"escalation word dropped, and models reach for it constantly. It is the " \
|
|
467
|
-
"noisiest rule in the catalog by design: a loose version scored 174 hits
|
|
468
|
-
"
|
|
469
|
-
"
|
|
651
|
+
"noisiest rule in the catalog by design: a loose version scored 174 hits " \
|
|
652
|
+
"before narrowing, cut to 15 here -- but not all 15 are correctives. Some " \
|
|
653
|
+
"are concessions with an elided subject (Walden's " \
|
|
470
654
|
"'It was not lonely, but made all the earth lonely beneath it'), which no " \
|
|
471
655
|
"surface pattern can tell apart from the real thing. Hence info: a flag here " \
|
|
472
656
|
"means 'this has the shape', not 'this is slop'."
|
data/lib/sloplint/version.rb
CHANGED