sloplint 0.4.0 → 0.5.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: f1bc057d93ebef8a56816f986a286ce160461f8d3004c037a2f3b35ab764105e
4
- data.tar.gz: 7c2e243d1795b3d417a815ea3cc0b2e2abe7d5954c7cdbfbd9cfeeeee3120213
3
+ metadata.gz: 74566267ccb2ee1d4caec464d3098809e24f232f75872a7df2ac61ef7d583153
4
+ data.tar.gz: 4e3b6196aeb6604d212d00be14032e530a644d4250820424774244fe7298fa9e
5
5
  SHA512:
6
- metadata.gz: a213224903d49426954ede602803f941cc11617084f5cf93fd9990e003872424fac5ef18a65e615b737eb58c5f7b816252f8fcba303d50f09d037661e98badbe
7
- data.tar.gz: 48761bf8884bd38d8494a4010c706ddf9f332b21df434d3c730654ee09e0ef0a401048df14d7118999575a538d9058ec511ccb9782fab655ddacf4b41953ce2b
6
+ metadata.gz: 41e707ec52643d3f57abe04867d399fef77c8e567378394e4c1e0e2ca541d8aefaa2cb908b9d0e419fc9af9f0a1aedb55adce9ef87fc187628be4a90641a4809
7
+ data.tar.gz: ed1b87c4a32490d582100343d2e7a4b114067737de3761fa3fb768e669f398e69b8c4e3ce4cb33d3138702d533818f5dd53b2b778d8736f848ca71c8166e61f8
data/CHANGELOG.md CHANGED
@@ -5,6 +5,38 @@ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.5.0] - 2026-08-27
9
+
10
+ The catalog grows from 49 rules to 50, and `check` becomes the default
11
+ command.
12
+
13
+ ### Changed
14
+
15
+ - A first argument that is not a command name is now taken as a path, so
16
+ `sloplint draft.md` and `sloplint -` scan instead of failing with
17
+ `unknown command`. A mistyped command fails as a missing file, still exit 2.
18
+
19
+ ### New rules
20
+
21
+ - `trailing-significance-participle` (`structure`, `warning`): the participle
22
+ clause hung off the end of a sentence to say what a fact means, "the sign
23
+ carries both names, showcasing the range of travellers it draws". An event
24
+ cannot showcase anything, so the claim belongs to a narrator who never
25
+ appears in the text.
26
+
27
+ The verb list is closed and narrow: `highlighting`, `showcasing`,
28
+ `reinforcing`, `shaping`, `enhancing`, `cementing`, `solidifying`,
29
+ `embodying`, `fostering`, `facilitating`, `signalling`. Verbs humans write
30
+ in the same position -- `driving`, `representing`, `reflecting`, `marking`,
31
+ `contributing`, `illustrating`, `demonstrating`, `emphasising`, `echoing`,
32
+ `affirming` -- stay out, because they usually take a person as the subject
33
+ and a regex cannot see the subject. `underscoring` is left to
34
+ `underscores-highlights`.
35
+
36
+ Two guards drop gerund lists: a preceding -ing word means the match is a
37
+ middle list item, and a following comma, "and" or "or" means it is not the
38
+ last item. "signalling to" is the physical gesture and does not flag.
39
+
8
40
  ## [0.4.0] - 2026-08-26
9
41
 
10
42
  The catalog grows from 29 rules to 49. Every new rule was probed against
data/README.md CHANGED
@@ -35,7 +35,7 @@ The recipe sloplint is built around, and the one an agent should use:
35
35
  cat draft.md | sloplint check --markdown -o json -
36
36
  ```
37
37
 
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. A bare `sloplint` with piped stdin is the same as `sloplint check -`.
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.
39
39
 
40
40
  The human-readable form drops `-o json`:
41
41
 
@@ -62,9 +62,10 @@ The brackets mark the match; the rest is there so you can see what you're fixing
62
62
  ## Commands
63
63
 
64
64
  ```
65
- sloplint [-o full|json] <command> [args]
65
+ sloplint [-o full|json] [command] [args]
66
66
 
67
67
  check scan paths (or stdin) for AI-slop tells and report notes [default]
68
+ anything that is not a command name is taken as a path: `sloplint draft.md`
68
69
  rules list the rule catalog (add --json for the machine-readable form)
69
70
  explain ID print one rule's message, rationale, and a bad/ok example
70
71
  version print the sloplint version
@@ -128,11 +129,11 @@ An unknown id or category in `--select`/`--ignore` is a usage error (exit 2, nam
128
129
 
129
130
  ## The rule catalog
130
131
 
131
- 49 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
132
+ 50 rules across four categories. `sloplint rules` prints them; `sloplint rules --json` gives an agent the enumerable form.
132
133
 
133
134
  - **rhetorical-tic** (36) the cadence patterns: `no-x-no-y`, `no-x-no-y-frag`, `thats-the-whole`, `thats-how-x`, `announced-takeaway`, `exact-exactly`, `load-bearing`, `you-already-know`, `sit-with-that`, `hold-onto-that`, `cleanly`, `clean-count`, `cleanest-x`, `clean-x`, `not-nothing`, `is-is` (doubled copula), `if-im-being-honest` (the candor preamble, from slopwash.com's "false intimacy"), and more.
134
135
  - **puffery** (5) Wikipedia's "signs of AI writing": `puffery-words` (vibrant, nestled, groundbreaking, in the heart of), `rich-tapestry`, `vital-role`, `stands-serves-as`, `underscores-highlights`.
135
- - **structure** (7) `not-just-x-but-y`, `not-x-but-y` (the bare corrective), `em-dash` (any em dash), `em-dash-overuse` (three or more in one paragraph), `question-isnt` (the corrective frame in interrogative dress), `less-about-more-about`, and `rule-of-three`.
136
+ - **structure** (8) `not-just-x-but-y`, `not-x-but-y` (the bare corrective), `em-dash` (any em dash), `em-dash-overuse` (three or more in one paragraph), `question-isnt` (the corrective frame in interrogative dress), `less-about-more-about`, `trailing-significance-participle` (the "…, showcasing its importance" clause), and `rule-of-three`.
136
137
  - **hedging** (1) `vague-attribution`: "some critics argue," "it is widely regarded."
137
138
 
138
139
  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.
data/docs/SPEC.md CHANGED
@@ -141,8 +141,11 @@ check options:
141
141
  --ignore IDS skip these rules
142
142
  ```
143
143
 
144
- Bare `sloplint` with piped stdin behaves as `sloplint check -`. This is the
145
- common agent path: `cat draft.md | sloplint check --markdown -o json -`.
144
+ `check` is the default command. A first argument that is not a command name is
145
+ taken as a path, so `sloplint draft.md` and `sloplint -` both scan; a mistyped
146
+ command therefore fails as a missing file. Bare `sloplint` with piped stdin
147
+ also behaves as `sloplint check -`. This is the common agent path:
148
+ `cat draft.md | sloplint check --markdown -o json -`.
146
149
 
147
150
  Deliberately left out of v1 (add when a real need shows up, not before):
148
151
  `compact` output, `--min-severity`, `--max-notes`, color, `-q/--quiet`, and a
@@ -252,7 +255,7 @@ Categories (for `--select`/`--ignore` by group):
252
255
 
253
256
  - `rhetorical-tic` — the cadence patterns (the user's list below)
254
257
  - `puffery` — Wikipedia "words to watch" (boasts, vibrant, nestled, tapestry…)
255
- - `structure` — rule-of-three, "not just X but Y", "the question isn't X, it's Y", "less about X more about Y", em dash, em-dash overuse
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
256
259
  - `hedging` — vague attribution ("some critics argue", "it is widely regarded")
257
260
 
258
261
  Severities: `warning` for strong tells, `info` for weak/contextual ones. No
@@ -318,6 +321,7 @@ Single flat rule per word-cluster, matched as whole words:
318
321
  - `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.
319
322
  - `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.
320
323
  - `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
+ - `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`.
321
325
  - `rule-of-three` — three parallel comma items ending a sentence (heuristic; `info` severity, off by default via `--select` since it false-positives).
322
326
  - `em-dash` — any em dash; `info`.
323
327
  - `em-dash-overuse` — 3+ em dashes in one paragraph; `warning`.
data/lib/sloplint/cli.rb CHANGED
@@ -33,9 +33,9 @@ module Sloplint
33
33
  when "version" then out.puts(VERSION); 0
34
34
  when "help" then out.puts(parser.help); 0
35
35
  else
36
- err.puts("sloplint: unknown command #{command.inspect}")
37
- err.puts(parser.help)
38
- 2
36
+ # Anything else is a path or "-": `sloplint FILE`, `sloplint -`.
37
+ # A mistyped command lands here too, and fails as a missing file.
38
+ cmd_check(argv.unshift(command), opts, out:, err:, stdin:)
39
39
  end
40
40
  rescue OptionParser::ParseError => e
41
41
  err.puts("sloplint: #{e.message}")
@@ -190,10 +190,11 @@ module Sloplint
190
190
  # exit 0 = clean, 1 = notes found, >1 = error
191
191
  # each note: {path,line,column,severity,rule,category,message,excerpt,context,rationale,suggestion}
192
192
 
193
- usage: sloplint [-o full|json] <command> [args]
193
+ usage: sloplint [-o full|json] [command] [args]
194
194
 
195
195
  commands:
196
196
  check scan paths (or stdin) for AI-slop tells and report notes [default]
197
+ a first argument that is not a command name is taken as a path
197
198
  rules list the rule catalog (add --json for the machine-readable form)
198
199
  explain ID print one rule's message, rationale, and a bad/ok example
199
200
  version print the sloplint version
@@ -1358,6 +1358,84 @@ module Sloplint
1358
1358
  "half is chosen for the contrast, not because a reader proposed it, and " \
1359
1359
  "the second half stands on its own."
1360
1360
  ),
1361
+ Rule.new(
1362
+ id: "trailing-significance-participle",
1363
+ category: "structure",
1364
+ severity: "warning",
1365
+ # The verb list is closed and short on purpose. Wikipedia's "signs of AI
1366
+ # writing" names eight watch words for this construction; half of them
1367
+ # did not survive probing. In 1.85M words of pre-2022 Hacker News and
1368
+ # 2.66M words of public-domain prose, "driving" appears in this position
1369
+ # 14 times, "representing" 16, "reflecting" 10, "marking" 5 -- all of it
1370
+ # ordinary English ("driving the price down", "representing a majority of
1371
+ # the voting power"), and all of it the same shape a model produces, so
1372
+ # no narrowing separates the two. Those verbs are out. The ones that ship
1373
+ # are used in both corpora but never here: "highlighting" appears 17
1374
+ # times in the HN sample and not once after a comma. "underscoring" is
1375
+ # left out because underscores-highlights already flags it bare.
1376
+ #
1377
+ # "emphasizing", "echoing" and "affirming" are out for the same reason,
1378
+ # found late: the shipped pattern flagged four sentences in the
1379
+ # public-domain corpus and all four had a person as the subject. A person
1380
+ # can emphasize or echo something; an event cannot. That is the animacy
1381
+ # distinction the construction really turns on, and a regex cannot see
1382
+ # it, so the verbs that carry a human subject in ordinary prose stay out
1383
+ # rather than being approximated. The narrowed list flags nothing in
1384
+ # either corpus.
1385
+ #
1386
+ # "ensuring" is also out, despite scoring 1 hit in each corpus: neither
1387
+ # corpus contains software documentation, where "the mutex is held,
1388
+ # ensuring no two writers collide" states a real consequence rather than
1389
+ # claiming significance.
1390
+ #
1391
+ # Two guards keep gerund lists out. A preceding -ing word means the match
1392
+ # is the second item of a list ("cutting, shaping stone"), and a
1393
+ # following comma, "and", or "or" means it is not the last ("cutting,
1394
+ # shaping and sanding"). "signaling to" is the physical gesture.
1395
+ pattern: /\w+(?<!ing),[ \t]+
1396
+ (?:further[ \t]+|thereby[ \t]+|thus[ \t]+|ultimately[ \t]+)?
1397
+ (?:highlighting|showcasing|reinforcing|shaping|enhancing|
1398
+ cementing|solidifying|embodying|fostering|facilitating|
1399
+ signall?ing(?![ \t]+to\b))
1400
+ (?![ \t]*(?:,|and\b|or\b))
1401
+ [ \t]+\S/ix,
1402
+ message: "Trailing -ing clause claiming significance -- a stock AI move.",
1403
+ suggestion: "Cut the clause, or state the consequence as its own sentence with a subject.",
1404
+ examples_bad: [
1405
+ "The bridge reopened in March, cementing its place in the city's skyline.",
1406
+ "Attendance doubled that year, highlighting the appeal of the new format.",
1407
+ "Its sign carries both languages, showcasing the range of visitors it draws.",
1408
+ "Trade routes crossed here for centuries, shaping the food people cook.",
1409
+ "The grant was renewed, further solidifying the lab's standing.",
1410
+ "The team shipped every week, reinforcing the sense that momentum mattered.",
1411
+ "The colour was mixed by hand, embodying the care the shop is known for.",
1412
+ "The lab was refitted last year, enhancing what the group can measure.",
1413
+ "The station sits beside the port, facilitating the movement of goods inland."
1414
+ ],
1415
+ examples_ok: [
1416
+ # The verb in its ordinary position, with a person or document as subject.
1417
+ "The report highlights the two findings that changed our minds.",
1418
+ "She emphasises the method rather than the results.",
1419
+ # Gerund lists: the match is a middle item, not a trailing clause.
1420
+ "The work involves cutting, shaping and polishing the stone.",
1421
+ "They spent the morning cutting, shaping stone for the wall.",
1422
+ "The class covers drawing, shaping, then firing the clay.",
1423
+ # The physical gesture, not a claim about meaning.
1424
+ "He turned toward the bar, signaling to the waiter.",
1425
+ # War and Peace (Maude translation, public domain): a person as the
1426
+ # subject, which is the case the verb list cannot distinguish, so
1427
+ # "emphasizing", "echoing" and "affirming" are excluded outright.
1428
+ "recounted Bitski, emphasizing certain words and opening his eyes",
1429
+ "\u201cOu-rou-rou!\u201d yelled the crowd, echoing the crash of the roof",
1430
+ # A paragraph break is not a comma.
1431
+ "The kiln runs hot\n\nShaping the clay comes first."
1432
+ ],
1433
+ rationale: "A participle hung off the end of a sentence, with a fact or an event as " \
1434
+ "its subject, asserts what something means without anyone having to say it. " \
1435
+ "An event cannot highlight or showcase anything; the claim belongs to a " \
1436
+ "narrator who never appears. Careful writers put the verb in its own clause " \
1437
+ "with a subject, or leave the significance to the reader."
1438
+ ),
1361
1439
  # ── hedging ───────────────────────────────────────────────────────────
1362
1440
  Rule.new(
1363
1441
  id: "vague-attribution",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sloplint
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sloplint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Jackson