zero-slop 2.7.8 → 2.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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Manav Mishra
3
+ Copyright (c) 2026 Garage Capital Ventures
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -5,23 +5,65 @@
5
5
  <img alt="tests" src="https://img.shields.io/badge/tests-passing-227B5B">
6
6
  <img alt="dependencies" src="https://img.shields.io/badge/runtime%20dependencies-0-227B5B">
7
7
  <img alt="privacy" src="https://img.shields.io/badge/learning-private-227B5B">
8
- <img alt="version" src="https://img.shields.io/badge/version-2.7.8-72528F">
8
+ <img alt="version" src="https://img.shields.io/badge/version-2.8.0-72528F">
9
+ <a href="https://hol.org/guard/plugins"><img alt="Listed in the HOL plugin registry" src="https://img.shields.io/badge/HOL%20registry-listed-2C6E8F"></a>
10
+ <a href="https://github.com/hashgraph-online/awesome-ai-plugins#tools--integrations"><img alt="Listed in awesome-ai-plugins" src="https://img.shields.io/badge/awesome--ai--plugins-listed-2C6E8F"></a>
11
+ <a href="https://zero-slop.ai/try/"><img alt="This README scores 12.0 out of 100 on the slop score, inside the human range" src="https://img.shields.io/badge/slop%20score-12.0%2F100-0f7d55"></a>
9
12
  </p>
10
13
 
11
- Less slop, more pop.
14
+ Less slop, more pop in your writing.
12
15
 
13
16
  Zero Slop finds stock phrasing, mechanical rhythm, vague claims, and canned
14
- formatting, then gives your AI assistant a guarded editing workflow. Its
15
- MIT-licensed local checks run offline; your existing AI assistant does the editing.
16
- See the benchmark at [zero-slop.ai](https://zero-slop.ai).
17
+ formatting, then gives your AI assistant or agent harness an editing workflow on guardrails. Its
18
+ MIT-licensed local checks run offline; your existing AI assistant or harness does the editing.
19
+ Score a draft at [zero-slop.ai/try](https://zero-slop.ai/try/); benchmark at
20
+ [zero-slop.ai](https://zero-slop.ai).
17
21
 
18
- ![A scored sentence before and after editing](assets/demo.png)
22
+ ```sh
23
+ npx zero-slop install
24
+ ```
25
+
26
+ ![Scoring a launch post at 100.0, rewriting it to 9.5, with the 40% figure held by the fact gate](assets/zero-slop-demo.gif)
27
+
28
+ ### What it does, on a real draft
29
+
30
+ A launch post, as AI wrote it:
31
+
32
+ > We're thrilled to announce that our team has leveraged cutting-edge AI to deliver a seamless onboarding experience. It's not just a redesign, it's a fundamental reimagining of how users engage with our platform. In today's fast-paced landscape, the ability to iterate quickly is crucial. By harnessing the power of machine learning, we've been able to reduce setup time by 40%. Here's the thing nobody tells you: onboarding is where most products lose their users. We're excited to continue this journey with you.
33
+
34
+ `slopscore.py --explain` on that paragraph, unedited:
35
+
36
+ ```text
37
+ Writing score: 100.0/100 [major rewrite]
38
+ Flagged phrases : 11 across 83 words
39
+ Sentence variety: too even
40
+ Main issues : canned LinkedIn phrase, promotional language, manufactured
41
+ stakes, two-part contrast used as a formula, performed
42
+ writer's voice, buzzword used as promotion
43
+
44
+ Flagged phrases (11), strongest first:
45
+ "In today's fast-paced" manufactured stakes; start where the reader needs to start
46
+ "We're thrilled to" canned LinkedIn phrase; say what happened without the stock opening
47
+ "It's not just a redesign, it's"
48
+ two-part contrast used as a formula; state the claim once, plainly
49
+ "Here's the thing" performed writer's voice; say the thing plainly
50
+ 'cutting-edge' promotional language; name what it does; cut the adjectives
51
+ 'leveraged' buzzword used as promotion; use the plain word
52
+ ```
53
+
54
+ The rewrite, limited to the draft's stated claims:
55
+
56
+ > We used machine learning to reduce onboarding setup time by 40%.
57
+
58
+ ```text
59
+ Writing score: 9.5/100 [clear]
60
+ Flagged phrases : 0 across 10 words
61
+ ```
19
62
 
20
63
  ## Problem
21
64
 
22
65
  AI-assisted writing often converges on the same constructions: "It's not X. It's Y."
23
- "Here's the thing nobody tells you." One phrase proves little. Repetition across
24
- wording, rhythm, structure, and formatting is the problem.
66
+ "Here's the thing nobody tells you." One emdash is fine. Multiple emdashes, definitely slop. The same effect can come from repetition in the wording or the structure. Mechanical rhythm and overworked formatting can do it too.
25
67
 
26
68
  Zero Slop is an Agent Skill and ships no model. Claude, GPT, or another compatible
27
69
  model edits; local tools check names, numbers, quotations, links, code, tables, and
@@ -42,7 +84,10 @@ Or install it with `npx`:
42
84
  npx skills add manavmishra/ZeroSlop --global
43
85
  ```
44
86
 
45
- Or from the registry, which also installs the scorer as a command:
87
+ Or from the registry, which also installs the scorer as a command. Zero Slop is
88
+ listed in [awesome-ai-plugins](https://github.com/hashgraph-online/awesome-ai-plugins#tools--integrations)
89
+ and carries a public profile in the [HOL plugin registry](https://hol.org/guard/plugins),
90
+ where its trust score and scanner results are published:
46
91
 
47
92
  ```sh
48
93
  npx zero-slop install # add --harness codex|cursor|opencode|zed
@@ -63,6 +108,18 @@ You get the edited draft, before-and-after scores, and quoted flagged phrases.
63
108
  `/zero-slop inspect (your writing)` reviews without rewriting. For a folder,
64
109
  `slopscore.py --batch drafts/ --gate 25` fails above the threshold.
65
110
 
111
+ ## Show your score
112
+
113
+ The badge above is this README's own, from `npx zero-slop score README.md`.
114
+ Put yours up the same way:
115
+
116
+ ```md
117
+ [![Slop score](https://img.shields.io/badge/slop%20score-12.0%2F100-0f7d55)](https://zero-slop.ai/try/)
118
+ ```
119
+
120
+ Colours are the scorer's bands: `0f7d55` under 25, `b8860b` under 60, `b0502c`
121
+ above.
122
+
66
123
  ## The slop that Zero Slop catches
67
124
 
68
125
  290 weighted patterns and a 96-term lexicon, including:
@@ -94,7 +151,7 @@ Eight roles form one workflow. Each is a job rather than a service, run as its o
94
151
 
95
152
  | Role | Who does it | What happens |
96
153
  |---|---|---|
97
- | 1. Scorer | Local tools | Finds the exact wording, rhythm, readability, and formatting problems that raised the writing score. |
154
+ | 1. Scorer | Local tools | Finds the exact phrases behind the writing score, then checks pacing and readability. It also catches overworked formatting. |
98
155
  | 2. Interpreter | Your AI assistant | Reads the claims, purpose, audience, structure, and voice before changing anything. |
99
156
  | 3. Rewriter | Your AI assistant | Removes stock language and rebuilds order, rhythm, and tone without inventing detail. |
100
157
  | 4. Fact gate | Local tools | Rejects any version that changes names, numbers, quotations, links, code, tables, paths, or structure. |
@@ -169,7 +226,7 @@ Zero Slop v2.6.0 and is preserved in
169
226
  For the 38-item editorial panel, the current scorer matched the prior 84.2% result.
170
227
  All frozen scores stayed unchanged, all 18 human controls remained below the gate,
171
228
  and all 18 obvious search cases remained above it. These fixed-sample checks are not
172
- proof of general accuracy. We recorded 6.51% higher median throughput across 12 interleaved
229
+ proof of general accuracy. We recorded 1.86% lower median throughput across 31 interleaved
173
230
  local runs; treat that as machine noise, not a speed claim.
174
231
 
175
232
  ### Speed
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: zero-slop
3
3
  license: MIT
4
4
  metadata:
5
- version: "2.7.8"
5
+ version: "2.8.0"
6
6
  author: manavmishra
7
7
  description: Turn drafts into sharp, natural prose or inspect them without rewriting. Zero Slop runs inside the user's existing AI assistant; Claude, GPT, or another compatible model reads and edits in context while local tools point to exact phrases and protect the source. Use when the user asks to humanize or de-slop writing, inspect AI-sounding patterns, fix text that reads like ChatGPT, polish outward-facing prose, draft social or LinkedIn content, or apply a final quality check to prose the agent generated. The workflow preserves facts, voice, and format and learns privately from repeated, reason-labelled human edits.
8
8
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-slop",
3
- "version": "2.7.8",
3
+ "version": "2.8.0",
4
4
  "description": "An Agent Skill that scores AI-sounding prose locally, guides the host AI through an editorial rewrite, and checks changed source details. The local tools run offline with zero dependencies.",
5
5
  "bin": {
6
6
  "zero-slop": "bin/zero-slop.mjs"
@@ -50,6 +50,7 @@
50
50
  "!scripts/contextual.py",
51
51
  "!scripts/contextual-signals.md",
52
52
  "!references/contextual-signals.md",
53
+ "!scripts/make-readme-gif.mjs",
53
54
  "!scripts/growth-snapshot.mjs",
54
55
  "!data/voices/",
55
56
  "!**/__pycache__/"
@@ -35,6 +35,7 @@ import sys
35
35
  # certified-human samples would let these budgets be derived rather than argued.
36
36
  MIN_WORDS = 300
37
37
  BUDGETS = {
38
+ "antithesis_pair": (0.0, 2),
38
39
  "subtractive_contrast": (6.0, 3),
39
40
  "comma_series": (26.0, 8),
40
41
  "significance_scaffolding": (0.0, 1),
@@ -61,6 +62,81 @@ SHORT_FLOORS = {
61
62
  "referent_cluster": 2,
62
63
  }
63
64
 
65
+ # eval.md A1 / SKILL.md step 2.1: antithesis pairs -- two balanced statements,
66
+ # the second landing the twist. Budget is ONE per piece, and "three or more under
67
+ # 500 words means the register failed whatever the score said."
68
+ #
69
+ # This report had no row for that family. RX_SUBTRACTIVE below is a different
70
+ # check -- eval.md A2, the corrective appositive, judged on density -- and it was
71
+ # carrying the whole contrast family's name in the report while A1 went
72
+ # unmeasured. A 479-word manifesto with five antithesis pairs in it therefore
73
+ # printed "Binary contrasts: 1 found, ok" and nothing else about contrast.
74
+ #
75
+ # Two of the four shapes are reachable without a parser, and only those two ship:
76
+ # marked staccato -- "Not perfect. Honest."
77
+ # adjacent isocolon -- one verb frame, both arguments swapped: "Speed is moving
78
+ # fast. Velocity is moving fast in the same direction."
79
+ # Bare subject swap and unmarked reversal stay the reader's judgment, exactly as
80
+ # references/tells.md already says they must.
81
+ ANTITHESIS_STOP = frozenset("""
82
+ a an the this that these those it its is are was were be been being am do does did
83
+ to of in on at by for with from as and or but so if then than not no nor yet
84
+ we you they he she i us our your their his her them me my their there here
85
+ one two three first second next last own same very just only also more most less
86
+ """.split())
87
+
88
+ # The negation can open the pair ("Not perfect. Honest.") or land at the end of
89
+ # it ("The draft was cheap. The signal it sent was not."). Both are in tells.md.
90
+ RX_MARKED_OPEN = re.compile(r"^\W*not\b", re.I)
91
+ RX_MARKED_CLOSE = re.compile(r"\b(?:was|were|is|are|did|does|do|has|have|had|will)\s+not\W*$", re.I)
92
+
93
+
94
+ def _antithesis_content(sent: str) -> list[str]:
95
+ """Content words, with a trailing -s folded away so a verb frame still
96
+ matches when only its agreement changed: 'let' and 'lets' are one frame."""
97
+ out = []
98
+ for word in re.findall(r"[A-Za-z][\w'-]*", sent):
99
+ word = word.lower()
100
+ if word in ANTITHESIS_STOP:
101
+ continue
102
+ if len(word) > 3 and word.endswith("s") and not word.endswith("ss"):
103
+ word = word[:-1]
104
+ out.append(word)
105
+ return out
106
+
107
+
108
+ def antithesis_pairs(prose: str) -> list[str]:
109
+ """Adjacent balanced sentences: the marked form, and the swapped-argument one."""
110
+ out = []
111
+ sents = _sentences(prose)
112
+ for first, second in zip(sents, sents[1:]):
113
+ n_first, n_second = len(first.split()), len(second.split())
114
+ # Both halves have to be short enough to read as one figure. A short
115
+ # line beside a paragraph-length sentence is not a balanced pair.
116
+ if not (2 <= n_first <= 14 and 1 <= n_second <= 14):
117
+ continue
118
+ # Marked: the twist is announced, at either end of the pair.
119
+ if RX_MARKED_OPEN.match(first) and n_second <= 10:
120
+ out.append(f"{first} {second}")
121
+ continue
122
+ if RX_MARKED_CLOSE.search(second) and n_first <= 10:
123
+ out.append(f"{first} {second}")
124
+ continue
125
+ head, tail = _antithesis_content(first), _antithesis_content(second)
126
+ if len(head) < 2 or len(tail) < 2:
127
+ continue
128
+ # Identical content is repetition, not antithesis: the figure needs the
129
+ # arguments to have changed.
130
+ if head == tail:
131
+ continue
132
+ shared = set(head) & set(tail)
133
+ # A shared frame carrying most of the shorter sentence, with the
134
+ # arguments changed: that is the figure, not an accidental echo.
135
+ if len(shared) >= 2 and len(shared) / min(len(head), len(tail)) >= 0.5:
136
+ out.append(f"{first} {second}")
137
+ return out
138
+
139
+
64
140
  # "X, not Y." and "A rather than B." The corrective appositive. Each instance is
65
141
  # usually careful writing, which is why no pattern list contains it.
66
142
  RX_SUBTRACTIVE = re.compile(
@@ -142,6 +218,28 @@ RX_INFLATION = re.compile(
142
218
  r"(?:improvement|progress|difference|impact|result|results|value|win|shift"
143
219
  r"|change|benefit|breakthrough|game.?changer)\b", re.I)
144
220
 
221
+ # An imperative is the bare base form, so it carries none of the inflection
222
+ # (-s, -ed, -es) or the auxiliaries FINITE_VERB looks for: "Play to win." and
223
+ # "Build durable growth." were both being reported as verbless fragments. On the
224
+ # seven-line manifesto that surfaced this, two of the five hits were imperatives,
225
+ # so 40% of the document's one finding was wrong. A closed list is the same
226
+ # device FINITE_VERB already is, and it only ever fires at a sentence opening.
227
+ IMPERATIVE_OPENER = re.compile(
228
+ r"^\W*(?:and|but|so|then|now|first|next|finally)?\W*"
229
+ r"(?:add|aim|allow|apply|ask|assume|avoid|begin|book|break|bring|build|buy"
230
+ r"|call|change|check|choose|clean|clear|click|close|collect|come|compare"
231
+ r"|consider|copy|count|cover|create|cut|decide|define|delete|deliver|design"
232
+ r"|do|draw|drive|drop|edit|enter|expect|explain|fill|find|finish|fix|focus"
233
+ r"|follow|forget|get|give|go|grab|grow|handle|help|hire|hold|imagine|include"
234
+ r"|install|invest|join|keep|know|launch|lead|learn|leave|let|listen|look"
235
+ r"|love|make|meet|move|name|note|notice|open|pay|pick|plan|play|point|prefer"
236
+ r"|press|pull|push|put|read|remember|remove|repeat|replace|report|reset"
237
+ r"|return|review|run|save|say|scale|see|select|sell|send|set|share|ship|show"
238
+ r"|sign|skip|solve|sort|speak|spend|stand|start|stay|stop|study|take|talk"
239
+ r"|teach|tell|test|think|throw|touch|track|treat|trust|try|turn|update|use"
240
+ r"|wait|walk|want|watch|win|write)\b",
241
+ re.I)
242
+
145
243
  FINITE_VERB = re.compile(
146
244
  r"\b(?:is|are|was|were|be|been|being|has|have|had|do|does|did|can|could|will"
147
245
  r"|would|shall|should|may|might|must|gets?|goes|comes?|makes?|takes?|gives?"
@@ -161,6 +259,8 @@ def verbless_fragments(prose: str) -> list[str]:
161
259
  continue
162
260
  if sent.rstrip().endswith(":") or sent.lstrip().startswith(("-", "*", "#", "|")):
163
261
  continue
262
+ if IMPERATIVE_OPENER.match(sent):
263
+ continue
164
264
  if not FINITE_VERB.search(sent):
165
265
  out.append(sent)
166
266
  return out
@@ -307,6 +407,7 @@ def measure(text: str) -> dict:
307
407
  triads = [" ".join(m.group(0).split()) for m in NEGATION_TRIAD.finditer(prose)]
308
408
  dangling = dangling_pointers(text)
309
409
  fragments = verbless_fragments(prose)
410
+ antithesis = antithesis_pairs(prose)
310
411
  thin = thin_sections(text)
311
412
  clusters = referent_clusters(text)
312
413
  table_uniformity, column = table_row_uniformity(text)
@@ -321,6 +422,7 @@ def measure(text: str) -> dict:
321
422
  "thin_section": {"count": len(thin), "per_1k": per_k(len(thin)), "hits": thin[:6]},
322
423
  "referent_cluster": {"count": len(clusters), "per_1k": per_k(len(clusters)), "hits": clusters[:3]},
323
424
  "table_uniformity": {"share": table_uniformity, "column": column},
425
+ "antithesis_pair": {"count": len(antithesis), "per_1k": per_k(len(antithesis)), "hits": antithesis[:6]},
324
426
  "subtractive_contrast": {"count": len(subtractive), "per_1k": per_k(len(subtractive)), "hits": subtractive[:12]},
325
427
  "comma_series": {"count": len(series), "per_1k": per_k(len(series))},
326
428
  "significance_scaffolding": {"count": len(significance), "per_1k": per_k(len(significance)), "hits": significance[:6]},
@@ -354,7 +456,8 @@ LABEL = {
354
456
  "verbless_fragment": "Verbless fragments",
355
457
  "thin_section": "Headings over a sentence or two",
356
458
  "referent_cluster": "One thing under several names",
357
- "subtractive_contrast": "Binary contrasts",
459
+ "antithesis_pair": "Antithesis pairs",
460
+ "subtractive_contrast": "Binary contrasts (X, not Y)",
358
461
  "comma_series": "Comma-series density",
359
462
  "significance_scaffolding": "Announced significance",
360
463
  "classifier_scaffolding": "Graded not delivered (stems)",