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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33eff57c67530d0745b1ba5009a97128929a73ec27ae09afaba8d9057f0f997b
4
- data.tar.gz: 3b76104840a3c2ab471651cbdf18dd4a9027707aaf100bc901430c7f9e121216
3
+ metadata.gz: 0aabc7314411211ed5adcbb5e09bb81a4291b2ffb7748e8d07d15f0150e6c43c
4
+ data.tar.gz: f8c9af283d490a6acb2d4cd6260ccc776e868542be3f1b10066ec6232e856ea0
5
5
  SHA512:
6
- metadata.gz: 4d1a47eae97395fa37dd1fa50b9a4894ccae70064d15542abdce0197cf715956d23c6ba75bee7bb4f56bc6ada8986485aff5bb1e06c9ecbb1ac6b763705588e1
7
- data.tar.gz: cb1ba2ed058855336b71bdc6e10a743e021a5d5d85e0207b8e0564b0fcd9076c9c45eb021eee6fc15a4eac92709b3440ebb6e1487da3c8981853ff37ebd01419
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: warning rich-tapestry "rich tapestry"/"tapestry of" is a signature AI cliché.
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: warning puffery-words Wikipedia-style puffery word/phrase — a common AI tell.
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:9: info exact-exactly "exact/exactly" is reflexive emphasis unless it names something checkable.
80
- excerpt: …tapestry of vibrant detail. That is [exactly] the point I keep making about it.
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 (rhetorical-tic, warning)
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 -- 24 hits in 1.02M words across Austen, Melville, Madison, Thoreau, and Emerson combined. A careful writer occasionally stacks two (and, rarely, more), but a model reaches for the pattern constantly.
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": "rhetorical-tic",
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 -- 24 hits in 1.02M words across Austen, Melville, Madison, Thoreau, and Emerson combined. A careful writer occasionally stacks two (and, rarely, more), but a model reaches for the pattern constantly.",
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 an `info` flag is worth acting on doesn't have to run `explain` separately to find out. `suggestion` is a short fix hint.
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 four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
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
- - **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`.
163
- - **hedging** (1) `vague-attribution`: "some critics argue," "it is widely regarded."
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 `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.
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
- 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.
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, 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.
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
- 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.
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: "rhetorical-tic", # or puffery, structure, hedging
185
- severity: "warning", # or info
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."