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 +1 -1
- package/README.md +68 -11
- package/SKILL.md +1 -1
- package/package.json +2 -1
- package/scripts/register.py +104 -1
package/LICENSE
CHANGED
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.
|
|
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
|
|
15
|
-
MIT-licensed local checks run offline; your existing AI assistant does the editing.
|
|
16
|
-
|
|
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
|
-
|
|
22
|
+
```sh
|
|
23
|
+
npx zero-slop install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+

|
|
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
|
|
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
|
+
[](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
|
|
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
|
|
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.
|
|
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.
|
|
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__/"
|
package/scripts/register.py
CHANGED
|
@@ -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
|
-
"
|
|
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)",
|