sloplint 0.7.0 → 0.8.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 +65 -0
- data/README.md +27 -21
- data/docs/SPEC.md +165 -108
- data/lib/sloplint/cli.rb +15 -10
- data/lib/sloplint/engine.rb +3 -2
- data/lib/sloplint/rules.rb +239 -164
- 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: 0aabc7314411211ed5adcbb5e09bb81a4291b2ffb7748e8d07d15f0150e6c43c
|
|
4
|
+
data.tar.gz: f8c9af283d490a6acb2d4cd6260ccc776e868542be3f1b10066ec6232e856ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 508f10329c7698ab4cce5b1dd09902984975ed2baeaa5646b7c288d33ee913f1a8f19e2aba36ab860a77a7b2ea2acde07748ce11db39367958c6ec86af3b4b1f
|
|
7
|
+
data.tar.gz: b75def69bddf045ddab9f99798985cf210ae7b3997266f9199600cf77638b5db8759738879b51949f31945b2ad183706b34654736cfdd7420f292c5442d2445d
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,71 @@
|
|
|
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.8.0] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed (breaking)
|
|
11
|
+
|
|
12
|
+
- The four old categories are gone: `rhetorical-tic`, `structure`, `puffery`
|
|
13
|
+
and `hedging`. Nine new categories replace them, each named for the move
|
|
14
|
+
the construct makes: `self-rating` (the writer grades their own prose or
|
|
15
|
+
claim), `closer` (closes by restating or announcing the point), `cadence`
|
|
16
|
+
(rhythm: repetition, parallelism, the long-then-short kicker), `puffery`
|
|
17
|
+
(inflates the subject), `false-correction` (corrects a reading nobody
|
|
18
|
+
offered), `false-concession` (performs balance or candor and gives nothing
|
|
19
|
+
up), `reader-address` (instructs or flatters the reader),
|
|
20
|
+
`borrowed-metaphor` (an engineering term applied to an argument), and
|
|
21
|
+
`punctuation` (the mark itself). Every note, `--select`, and `--ignore` now
|
|
22
|
+
use these nine names; the four old names are gone, not aliased, so a
|
|
23
|
+
script still passing one gets a usage error instead of a silent no-op.
|
|
24
|
+
- The rule `ellipsis-closer` is renamed `bare-auxiliary-closer`. Nothing
|
|
25
|
+
about its pattern, message, or fixtures changed, only the id.
|
|
26
|
+
- `severity` now rates only what the construct costs the prose (`error`,
|
|
27
|
+
`warning`, `info`). A new field, `confidence`, rates how likely a match is
|
|
28
|
+
a false positive (`high`, `medium`, `low`), and a `low` rule replaces what
|
|
29
|
+
used to be `default_on: false` — it stays out of a normal run and needs
|
|
30
|
+
`--strict` or its own id named directly to turn on.
|
|
31
|
+
- `sloplint rules --json` drops the `default_on` field and adds `confidence`
|
|
32
|
+
and `rationale` for every rule. Each note in `check` output also gains a
|
|
33
|
+
`confidence` field alongside `severity`.
|
|
34
|
+
- `--select <category>` no longer turns on that category's low-confidence
|
|
35
|
+
rules. Naming a category now selects only its default-on rules; to run a
|
|
36
|
+
low-confidence rule, pass `--strict` or name the rule's own id.
|
|
37
|
+
- Every rule's severity was re-rated for what the construct costs the prose,
|
|
38
|
+
now that confidence carries the false-positive question on its own. The
|
|
39
|
+
catalog reads 11 `error`, 56 `warning`, 13 `info`. Nothing shipped at
|
|
40
|
+
`error` before: the eleven that do now are the ones that hurt a sentence in
|
|
41
|
+
any register, the puffery family (`puffery-words`, `rich-tapestry`,
|
|
42
|
+
`vital-role`, `impact-noun-vague`,
|
|
43
|
+
`trailing-significance-participle`), the tautology and self-ranking moves
|
|
44
|
+
(`the-x-is-the-x`, `cleanest-x`, `most-honest-x`), the fake balance
|
|
45
|
+
(`two-things-true`, `none-of-this-is-to-say`) and `vague-attribution`.
|
|
46
|
+
Twenty rules moved from `info` to `warning`, among them `em-dash-overuse`:
|
|
47
|
+
one em dash is a style choice, three in a paragraph is the tell, so the
|
|
48
|
+
dense form now costs more than `em-dash`. Any tool that keys off severity
|
|
49
|
+
sees different values for the same text.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- The test suite failed with no locale set, because `spec/plugin_spec.rb`
|
|
54
|
+
read files without forcing UTF-8. Fixed to read as UTF-8 regardless of the
|
|
55
|
+
environment's locale.
|
|
56
|
+
- The README and SPEC quoted an outdated rationale for `no-x-no-y` and an
|
|
57
|
+
outdated sample of `sloplint check` output. Both are corrected and now
|
|
58
|
+
pinned by tests, so a future drift between the docs and the code fails the
|
|
59
|
+
build instead of sitting unnoticed.
|
|
60
|
+
- SPEC's description of the `Rule` model and of the package's file layout
|
|
61
|
+
had fallen out of step with the code. Both are corrected to match.
|
|
62
|
+
- `--help` was missing `count` from its list of note keys, even though a
|
|
63
|
+
note already includes it for rules that tally items. Added.
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- A test asserting that no rule id ever matches a category name, since
|
|
68
|
+
`--select` and `--ignore` resolve a name against both and a collision
|
|
69
|
+
would make one shadow the other.
|
|
70
|
+
|
|
6
71
|
## [0.7.0] - 2026-09-15
|
|
7
72
|
|
|
8
73
|
### Added
|
data/README.md
CHANGED
|
@@ -66,18 +66,18 @@ The human-readable form drops `-o json`:
|
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
$ printf 'The report is a rich tapestry of vibrant detail.\nThat is exactly the point I keep making about it.\n' | sloplint check -
|
|
69
|
-
-:1:17:
|
|
69
|
+
-:1:17: error rich-tapestry "rich tapestry"/"tapestry of" is a signature AI cliché.
|
|
70
70
|
excerpt: The report is a [rich tapestry] of vibrant detail. That is exactly the…
|
|
71
71
|
why: 'tapestry of' is one of the most reliable single-phrase model tells.
|
|
72
72
|
fix: Cut the metaphor; name the actual things.
|
|
73
73
|
|
|
74
|
-
-:1:34:
|
|
74
|
+
-:1:34: error puffery-words Wikipedia-style puffery word/phrase — a common AI tell.
|
|
75
75
|
excerpt: The report is a rich tapestry of [vibrant] detail. That is exactly the point I…
|
|
76
76
|
why: Travel-brochure adjectives and phrases that models reach for and careful writers avoid.
|
|
77
77
|
fix: Replace with a concrete, specific detail or cut it.
|
|
78
78
|
|
|
79
|
-
-:2:
|
|
80
|
-
excerpt: …tapestry of vibrant detail. That is
|
|
79
|
+
-:2:1: info exact-exactly "exact/exactly" is reflexive emphasis unless it names something checkable.
|
|
80
|
+
excerpt: …is a rich tapestry of vibrant detail. [That is exactly] the point I keep making about it.
|
|
81
81
|
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.
|
|
82
82
|
fix: Cut it, or replace with the number, name, or match it's supposed to be precise about.
|
|
83
83
|
```
|
|
@@ -107,11 +107,11 @@ version print the sloplint version
|
|
|
107
107
|
|
|
108
108
|
```
|
|
109
109
|
$ sloplint explain no-x-no-y
|
|
110
|
-
no-x-no-y (
|
|
110
|
+
no-x-no-y (cadence, warning, high confidence)
|
|
111
111
|
|
|
112
112
|
"No X, no Y" chain (%{count} items) reads as AI cadence.
|
|
113
113
|
|
|
114
|
-
Why: Asyndetic negation chains are a signature model cadence, near-absent from human prose at any length
|
|
114
|
+
Why: Asyndetic negation chains are a signature model cadence, near-absent from human prose at any length. A careful writer occasionally stacks two (and, rarely, more), but a model reaches for the pattern constantly.
|
|
115
115
|
Fix: Cut the chain or make it one plain sentence.
|
|
116
116
|
|
|
117
117
|
Flags: No fluff, no filler, no jargon.
|
|
@@ -128,18 +128,19 @@ One match is one note. JSON output is an array of these, or an object keyed by p
|
|
|
128
128
|
"line": 12,
|
|
129
129
|
"column": 5,
|
|
130
130
|
"severity": "warning",
|
|
131
|
+
"confidence": "high",
|
|
131
132
|
"rule": "no-x-no-y",
|
|
132
|
-
"category": "
|
|
133
|
+
"category": "cadence",
|
|
133
134
|
"message": "\"No X, no Y\" chain (3 items) reads as AI cadence.",
|
|
134
135
|
"excerpt": "No fluff, no filler, no jargon",
|
|
135
136
|
"context": "The report was blunt. [No fluff, no filler, no jargon]. Nothing held back at all.",
|
|
136
137
|
"count": 3,
|
|
137
|
-
"rationale": "Asyndetic negation chains are a signature model cadence, near-absent from human prose at any length
|
|
138
|
+
"rationale": "Asyndetic negation chains are a signature model cadence, near-absent from human prose at any length. A careful writer occasionally stacks two (and, rarely, more), but a model reaches for the pattern constantly.",
|
|
138
139
|
"suggestion": "Cut the chain or make it one plain sentence."
|
|
139
140
|
}
|
|
140
141
|
```
|
|
141
142
|
|
|
142
|
-
`line` and `column` are 1-indexed and point at the start of the match. `excerpt` is the bare match; `context` is the same match bracketed inside about 40 characters of surrounding prose, which is what you want when the match is a single word or a lone em dash. A match already 40 characters long carries its own context, so `context` returns it alone rather than padding it further. `count` appears only when the rule tallies items (a "no X, no Y" chain, a "did not, did not" chain). `rationale` is why the pattern is a tell — the same text `sloplint explain` prints — so an agent deciding whether
|
|
143
|
+
`severity` is what the construct costs the prose; `confidence` is how likely the match is a false positive. The two are separate, so a cheap tell we are sure about and an expensive one we are guessing at no longer collapse into the same word. `line` and `column` are 1-indexed and point at the start of the match. `excerpt` is the bare match; `context` is the same match bracketed inside about 40 characters of surrounding prose, which is what you want when the match is a single word or a lone em dash. A match already 40 characters long carries its own context, so `context` returns it alone rather than padding it further. `count` appears only when the rule tallies items (a "no X, no Y" chain, a "did not, did not" chain). `rationale` is why the pattern is a tell — the same text `sloplint explain` prints — so an agent deciding whether a flag is worth acting on doesn't have to run `explain` separately to find out. `suggestion` is a short fix hint.
|
|
143
144
|
|
|
144
145
|
## Exit codes
|
|
145
146
|
|
|
@@ -155,20 +156,25 @@ An unknown id or category in `--select`/`--ignore` is a usage error (exit 2, nam
|
|
|
155
156
|
|
|
156
157
|
## The rule catalog
|
|
157
158
|
|
|
158
|
-
80 rules across
|
|
159
|
+
80 rules across nine categories, each named for the rhetorical move the construct makes. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
|
|
159
160
|
|
|
160
|
-
- **
|
|
161
|
-
- **
|
|
162
|
-
- **
|
|
163
|
-
- **
|
|
161
|
+
- **self-rating** (15) the writer grades their own prose or claim: `clean-x`, `clean-count`, `cleanest-x`, `cleanly`, `honest-x`, `most-honest-x`, `honestly` (the honesty family, built the same way as the four `clean` rules), `worth-naming`, `worth-saying-plainly`, `earns-its-place`, `does-a-lot-of-work`, `exact-exactly`, `genuinely` (off by default), `the-punchline-is`, `announced-takeaway`.
|
|
162
|
+
- **closer** (12) closes by restating or announcing the point: `thats-the-whole`, `is-the-whole-x` (the same closer on any subject: "Consistency is the real test.", at `medium` confidence), `is-the-entire`, `the-entire-is`, `thats-how-x`, `thats-the-tension`, `right-up-until`, `and-nothing-else` (the trailing "…, and nothing else"), `nothing-else-frag`, `bare-equative` ("The lesson is the handoff.", at `medium` confidence), `trailing-restatement` (the "…, which means …" tail that says the sentence again, off by default), `and-what-it-should` (the elliptical tail: "…, and what it should.").
|
|
163
|
+
- **cadence** (17) rhythm: repetition, parallelism, and the long-then-short kicker: `no-x-no-y`, `no-x-no-y-frag`, `did-not-x-did-not-y`, `one-x-one-y` ("one reviewer, one queue, one deadline"), `from-x-to-y-chain` ("from guessing to measuring, from hoping to knowing"), `same-determiner-chain` (any other repeated determiner, at `medium` confidence), `real-x-real-y`, `epistrophe` (off by default), `phrase-echo` (off by default), `is-is` (doubled copula), `the-x-is-the-x` ("the problem with A is the problem with B"), `rule-of-three` (off by default), `everyone-nobody` (the comma-spliced antithesis: "Everyone wants the dashboard, nobody maintains it."), `short-run` (three sentences of thirty characters or fewer in a row, at `medium` confidence), `mic-drop-closer` (the short quantifier-led sentence that ends a paragraph after a long one, at `medium` confidence), `bare-auxiliary-closer` (the same shape, but the closer's verb is elided down to a bare auxiliary: "The agent did.", at `medium` confidence), `np-fragment-and` (the verbless "A named owner and a quarterly review.", at `medium` confidence).
|
|
164
|
+
- **puffery** (8) inflates the subject: `puffery-words` (vibrant, nestled, groundbreaking, in the heart of), `rich-tapestry`, `vital-role`, `stands-serves-as`, `underscores-highlights`, `impact-noun-vague` ("a significant impact", "make an impact"), `trailing-significance-participle` (the "…, showcasing its importance" clause), `abstract-lives-in` ("the value sits in the follow-up", at `medium` confidence).
|
|
165
|
+
- **false-correction** (8) corrects a reading nobody offered: `not-just-x-but-y`, `not-x-but-y` (the bare corrective), `not-by-x-but-by-y` ("not by luck, but by design"), `isnt-x-its-y` (the same corrective split across two clauses: "It isn't the tool. It's the habit."), `question-isnt` (the corrective frame in interrogative dress), `less-about-more-about`, `actually-not-x`, `dont-verb-it`.
|
|
166
|
+
- **false-concession** (7) performs balance or candour and gives nothing up: `two-things-true`, `none-of-this-is-to-say`, `is-real-and-not`, `not-nothing`, `vague-attribution` ("some critics argue", "it is widely regarded"), `if-im-being-honest` (the candor preamble, from slopwash.com's "false intimacy"), `and-thats-fine`.
|
|
167
|
+
- **reader-address** (6) instructs or flatters the reader: `you-already-know`, `sit-with-that`, `hold-onto-that`, `notice-what`, `notice-what-there`, `quip-question` (the verbless "No invite?", at `medium` confidence).
|
|
168
|
+
- **borrowed-metaphor** (5) an engineering term applied to an argument: `load-bearing`, `failure-mode-here`, `intersection-of`, `impact-verb` ("the outage impacted four thousand accounts"), `impact-noun-bare` ("the impact of X", at `medium` confidence because research prose uses it straight).
|
|
169
|
+
- **punctuation** (2) the mark itself: `em-dash` (any em dash), `em-dash-overuse` (three or more in one paragraph).
|
|
164
170
|
|
|
165
|
-
Severity is `
|
|
171
|
+
Every rule carries two ratings, and they answer different questions. **Severity** is what the construct costs the prose: `error` when the sentence is worse for it in any register (`rich-tapestry`, `puffery-words`, `vague-attribution`), `warning` when it dates the draft as model output but the sentence still says something, `info` when it is mostly harmless and worth knowing (`em-dash`). **Confidence** is how likely the match is a false positive: `high` when almost every hit is the real tell, `medium` when ordinary prose produces the same shape often enough that an agent should read the rationale first, `low` when the pattern cannot tell the tell from the ordinary use at all.
|
|
166
172
|
|
|
167
|
-
|
|
173
|
+
The two used to be one word, so a cheap tell we were sure about and an expensive one we were guessing at both came out as `warning`. They no longer do. `error` is in use: the puffery family, the tautology closers, and the self-ranking superlatives all cost the sentence something wherever they appear, however sure or unsure we are of the match.
|
|
168
174
|
|
|
169
|
-
`and-nothing-else` and `nothing-else-frag` are a pair of the same shape
|
|
175
|
+
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, so it ships at `medium` confidence. 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. `and-nothing-else` and `nothing-else-frag` are a pair of the same shape and both sit at `high`, because 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
176
|
|
|
171
|
-
|
|
177
|
+
The five `low` rules are the ones that run **off by default**. They run when you name them — `sloplint check --select rule-of-three -` — or when you pass `--strict`, which runs the whole catalog when you give it no `--select`. `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. Naming a category in `--select` only turns on that category's non-low rules; naming the rule's own id runs it regardless. `--strict` on its own runs the whole catalog, but alongside `--select` it only widens the named categories to include their low-confidence members. `sloplint rules --json` lists every rule's `severity`, `confidence` and `rationale`, so an agent can tell which rules are off by default without reading this file.
|
|
172
178
|
|
|
173
179
|
### Markdown handling
|
|
174
180
|
|
|
@@ -181,14 +187,14 @@ Rules are data, not code. Each is a `Data.define` object in `lib/sloplint/rules.
|
|
|
181
187
|
```ruby
|
|
182
188
|
Rule.new(
|
|
183
189
|
id: "rule-id",
|
|
184
|
-
category: "
|
|
185
|
-
severity: "warning", #
|
|
190
|
+
category: "cadence", # one of the nine in the catalog above
|
|
191
|
+
severity: "warning", # what it costs the prose: error, warning, info
|
|
192
|
+
confidence: "high", # false-positive risk: high, medium, low
|
|
186
193
|
pattern: /.../i,
|
|
187
194
|
message: "What the reader sees. %{count} interpolates the tally.",
|
|
188
195
|
suggestion: "One short fix hint.",
|
|
189
196
|
count_group: /.../i, # optional: a regex tallied over the match
|
|
190
197
|
skip: [/.../i], # optional: drop the note if these match
|
|
191
|
-
default_on: false, # optional: runs only when named in --select
|
|
192
198
|
examples_bad: ["A sentence the rule must flag."],
|
|
193
199
|
examples_ok: ["A sentence it must leave alone."],
|
|
194
200
|
rationale: "Why this is a tell, and what it costs when it's wrong."
|