zero-slop 2.8.0 → 2.8.1
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/README.md +33 -13
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/scripts/register.py +113 -27
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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.
|
|
8
|
+
<img alt="version" src="https://img.shields.io/badge/version-2.8.1-72528F">
|
|
9
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
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
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>
|
|
@@ -191,10 +191,9 @@ with [`scripts/register.py`](scripts/register.py) running the reading pass.
|
|
|
191
191
|
|
|
192
192
|
The saved replay ran Zero Slop, [avoid-ai-writing](https://github.com/conorbronsdon/avoid-ai-writing),
|
|
193
193
|
[no-ai-slop](https://github.com/petergyang/no-ai-slop) and
|
|
194
|
-
[humanizer](https://github.com/blader/humanizer)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
pretend to have regenerated them.
|
|
194
|
+
[humanizer](https://github.com/blader/humanizer) over 18 drafts with GPT-5.4, high
|
|
195
|
+
reasoning, and pinned instructions. Zero Slop's outputs came from v2.5.9; later
|
|
196
|
+
releases rescore those frozen outputs rather than pretend to regenerate them.
|
|
198
197
|
|
|
199
198
|
| Method | Mean writing score ↓ | Passed Zero Slop's local gates | Source check passed | Average length change |
|
|
200
199
|
|---|---:|---:|---:|---:|
|
|
@@ -218,23 +217,44 @@ rates, and a method-hidden quality ranking.
|
|
|
218
217
|

|
|
219
218
|
|
|
220
219
|
This is a small LLM-reviewed regression study. It measures neither field accuracy nor a
|
|
221
|
-
universal ranking. Drafts,
|
|
222
|
-
in [`bench/README.md`](bench/README.md). The separate method-hidden two-way replay used
|
|
220
|
+
universal ranking. Drafts, hashes, and limits are in [`bench/README.md`](bench/README.md). The separate method-hidden two-way replay used
|
|
223
221
|
Zero Slop v2.6.0 and is preserved in
|
|
224
222
|
[`bench/incumbent-blind-replay/`](bench/incumbent-blind-replay/).
|
|
225
223
|
|
|
226
224
|
For the 38-item editorial panel, the current scorer matched the prior 84.2% result.
|
|
227
225
|
All frozen scores stayed unchanged, all 18 human controls remained below the gate,
|
|
228
226
|
and all 18 obvious search cases remained above it. These fixed-sample checks are not
|
|
229
|
-
proof of general accuracy.
|
|
230
|
-
local runs; treat that as machine noise, not a speed claim.
|
|
227
|
+
proof of general accuracy.
|
|
231
228
|
|
|
232
229
|
### Speed
|
|
233
230
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
One busy Apple silicon Mac. Meter: 1,000 documents in 2.5035 seconds (399.4 per
|
|
232
|
+
second), 15,201 words in 0.4301 seconds, worst stress case 3.2518 seconds. Reading
|
|
233
|
+
pass, untimed on record until now: 0.8304 seconds for the same 1,000 (1,204.2 per
|
|
234
|
+
second), 0.1359 for the same large document, linear to 96,000 words. Learning pass,
|
|
235
|
+
8,000 words: 0.2027 seconds. Across 31 interleaved runs against 2.7.7 we measured
|
|
236
|
+
0.07% higher median throughput; that is machine noise, not a speed claim. Editing
|
|
237
|
+
time is excluded.
|
|
238
|
+
|
|
239
|
+
### Reading-pass accuracy
|
|
240
|
+
|
|
241
|
+
The reading pass budgets antithesis pairs by frequency, so the count has to be right
|
|
242
|
+
before the budget means anything. It had never been measured. On 58 labelled pairs in
|
|
243
|
+
[`bench/antithesis/`](bench/antithesis/):
|
|
244
|
+
|
|
245
|
+
| Reading pass | 2.8.0 | now |
|
|
246
|
+
|---|---:|---:|
|
|
247
|
+
| Recall, all shapes | 40.0% | 90.0% |
|
|
248
|
+
| Recall, shapes in reach | 44.4% | 100% |
|
|
249
|
+
| Precision | 80.0% | 100% |
|
|
250
|
+
| False positives | 3 | 0 |
|
|
251
|
+
|
|
252
|
+

|
|
253
|
+
|
|
254
|
+
Bare subject swap and the weak isocolon stay out of reach and count against recall: the
|
|
255
|
+
first is a judgment call in `references/tells.md`, the second is identical to ordinary
|
|
256
|
+
parallel prose on every lexical statistic. Maintainer labels on constructed pairs, so
|
|
257
|
+
this is a regression floor, not field accuracy.
|
|
238
258
|
|
|
239
259
|
### Current models
|
|
240
260
|
|
package/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: zero-slop
|
|
3
3
|
license: MIT
|
|
4
4
|
metadata:
|
|
5
|
-
version: "2.8.
|
|
5
|
+
version: "2.8.1"
|
|
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.8.
|
|
3
|
+
"version": "2.8.1",
|
|
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"
|
package/scripts/register.py
CHANGED
|
@@ -81,22 +81,41 @@ SHORT_FLOORS = {
|
|
|
81
81
|
ANTITHESIS_STOP = frozenset("""
|
|
82
82
|
a an the this that these those it its is are was were be been being am do does did
|
|
83
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
|
|
84
|
+
we you they he she i us our your their his her them me my there here
|
|
85
85
|
one two three first second next last own same very just only also more most less
|
|
86
|
+
every all each any some many few way thing
|
|
86
87
|
""".split())
|
|
87
88
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
89
|
+
# Negation, including the contracted forms. "isn't", "don't" and "won't" carry
|
|
90
|
+
# the figure exactly as "is not" does, and the shipped detector matched none of
|
|
91
|
+
# them: "Slop isn't a vibe. It's measurable." is a documented anchor in
|
|
92
|
+
# references/tells.md and it walked straight past.
|
|
93
|
+
NEGATION = re.compile(r"\b(?:not|never|cannot)\b|\w+n['’]t\b", re.I)
|
|
94
|
+
# A copula in both halves is the "X is A. Y is B." frame, which is the marked
|
|
95
|
+
# figure's usual carrier and is absent from ordinary negated prose ("The server
|
|
96
|
+
# did not respond. We restarted it.").
|
|
97
|
+
COPULA = re.compile(r"\b(?:is|are|was|were|be|been|am)\b|\w+['’]s\b|\w+n['’]t\b", re.I)
|
|
90
98
|
RX_MARKED_OPEN = re.compile(r"^\W*not\b", re.I)
|
|
91
|
-
RX_MARKED_CLOSE = re.compile(
|
|
99
|
+
RX_MARKED_CLOSE = re.compile(
|
|
100
|
+
r"\b(?:was|were|is|are|did|does|do|has|have|had|will|can|could|would)\s+not\W*$", re.I)
|
|
101
|
+
# The two template shapes the meter already anchors (this-is-what-looks-like,
|
|
102
|
+
# no-x-had-to). The meter scores them as spans; the register pass has to COUNT
|
|
103
|
+
# them, because tells.md budgets the family by frequency and a span hit is not
|
|
104
|
+
# a count.
|
|
105
|
+
RX_CONTRASTIVE = re.compile(r"^\W*(?:but|yet|however)\b", re.I)
|
|
106
|
+
RX_STOCK_CLOSER = re.compile(r"\bthis is what\b.{0,60}?\blooks? like\b", re.I)
|
|
107
|
+
RX_UNMARKED_REVERSAL = re.compile(r"^\W*no\s+[\w-]+(?:\s+[\w-]+){0,3}\s+had to\b", re.I)
|
|
92
108
|
|
|
93
109
|
|
|
94
|
-
def
|
|
110
|
+
def _tokens(sent: str) -> list[str]:
|
|
111
|
+
return [w.lower() for w in re.findall(r"[A-Za-z][\w'’-]*", sent)]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _antithesis_content(tokens: list[str]) -> list[str]:
|
|
95
115
|
"""Content words, with a trailing -s folded away so a verb frame still
|
|
96
116
|
matches when only its agreement changed: 'let' and 'lets' are one frame."""
|
|
97
117
|
out = []
|
|
98
|
-
for word in
|
|
99
|
-
word = word.lower()
|
|
118
|
+
for word in tokens:
|
|
100
119
|
if word in ANTITHESIS_STOP:
|
|
101
120
|
continue
|
|
102
121
|
if len(word) > 3 and word.endswith("s") and not word.endswith("ss"):
|
|
@@ -105,35 +124,102 @@ def _antithesis_content(sent: str) -> list[str]:
|
|
|
105
124
|
return out
|
|
106
125
|
|
|
107
126
|
|
|
127
|
+
def _common_prefix(a: list[str], b: list[str]) -> int:
|
|
128
|
+
n = 0
|
|
129
|
+
for x, y in zip(a, b):
|
|
130
|
+
if x != y:
|
|
131
|
+
break
|
|
132
|
+
n += 1
|
|
133
|
+
return n
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# A heading or a list lead-in carries no terminal punctuation, so it arrives
|
|
137
|
+
# from _sentences glued to the paragraph beneath it. Rather than change the
|
|
138
|
+
# splitter or prose_of -- both are shared by every family, and moving either one
|
|
139
|
+
# pushed four to eleven documents over budget on rate alone -- this family
|
|
140
|
+
# rejects the glued span itself. Nothing else sees the change.
|
|
141
|
+
RX_SCAFFOLD = re.compile(r"^\s*(?:[#>|]|[-*+]\s|\d+\.\s)|\*\*|\n\s*(?:[#>|]|[-*+]\s|\d+\.\s)")
|
|
142
|
+
|
|
143
|
+
|
|
108
144
|
def antithesis_pairs(prose: str) -> list[str]:
|
|
109
|
-
"""Adjacent balanced sentences
|
|
145
|
+
"""Adjacent balanced sentences where the second lands the twist.
|
|
146
|
+
|
|
147
|
+
Recall is bounded and the bound is a property of the figure, not of the
|
|
148
|
+
implementation. "A meter reports a number. A reader reports a feeling." and
|
|
149
|
+
"The report lists every vendor. The appendix lists every contract." are the
|
|
150
|
+
same construction to every lexical statistic -- same lengths, same one
|
|
151
|
+
shared word, same 0.33 overlap -- and only the first is antithesis. What
|
|
152
|
+
separates them is semantic opposition, which no word count can see. So the
|
|
153
|
+
marked shapes, where a negation anchors the figure, are matched broadly;
|
|
154
|
+
the unmarked ones are matched only where the parallel is strong enough to
|
|
155
|
+
be structural. Bare subject swap stays the reader's call, as tells.md says.
|
|
156
|
+
"""
|
|
110
157
|
out = []
|
|
111
158
|
sents = _sentences(prose)
|
|
112
|
-
for
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
159
|
+
toks = [_tokens(s) for s in sents]
|
|
160
|
+
cont = [_antithesis_content(t) for t in toks]
|
|
161
|
+
# Matches do not overlap. The figure is a pair, so a sentence that has
|
|
162
|
+
# already landed one twist cannot also be the setup for the next: three
|
|
163
|
+
# short consecutive sentences were producing two pairs out of one figure
|
|
164
|
+
# and inflating a rate the budget reads directly.
|
|
165
|
+
consumed = -1
|
|
166
|
+
for i in range(len(sents) - 1):
|
|
167
|
+
if i <= consumed:
|
|
121
168
|
continue
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if len(head) < 2 or len(tail) < 2:
|
|
169
|
+
first, second = sents[i], sents[i + 1]
|
|
170
|
+
ta, tb = toks[i], toks[i + 1]
|
|
171
|
+
na, nb = len(ta), len(tb)
|
|
172
|
+
if not (2 <= na <= 14 and 1 <= nb <= 14):
|
|
127
173
|
continue
|
|
128
|
-
|
|
129
|
-
# arguments to have changed.
|
|
130
|
-
if head == tail:
|
|
174
|
+
if RX_SCAFFOLD.search(first) or RX_SCAFFOLD.search(second):
|
|
131
175
|
continue
|
|
176
|
+
head, tail = cont[i], cont[i + 1]
|
|
132
177
|
shared = set(head) & set(tail)
|
|
133
|
-
|
|
134
|
-
|
|
178
|
+
prefix = _common_prefix(ta, tb)
|
|
179
|
+
negated = bool(NEGATION.search(first) or NEGATION.search(second))
|
|
180
|
+
|
|
181
|
+
# Marked, announced at the open or the close of the pair.
|
|
182
|
+
if RX_MARKED_OPEN.match(first) and nb <= 10:
|
|
183
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
184
|
+
if RX_MARKED_CLOSE.search(second) and na <= 10:
|
|
185
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
186
|
+
# Marked, carried inside the pair. A negation alone is ordinary prose,
|
|
187
|
+
# so the halves also have to be short AND share a frame. One shared
|
|
188
|
+
# topic word is not a frame: "The AI roles supply judgment. A generating
|
|
189
|
+
# role never certifies its own output." shares "role" and is ordinary
|
|
190
|
+
# prose. The frame is a repeated opening, a copula on both sides, or two
|
|
191
|
+
# words in common.
|
|
192
|
+
if negated and na <= 10 and nb <= 8 and (prefix >= 1 or len(shared) >= 2):
|
|
193
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
194
|
+
# A copula on both sides is the weakest of the three frames, so it only
|
|
195
|
+
# counts when the halves are staccato-short. "Passwords are never stored
|
|
196
|
+
# in plain text. They are hashed with a per-user salt." is two copulas
|
|
197
|
+
# and a negation and no figure at all.
|
|
198
|
+
if (negated and na <= 7 and nb <= 6
|
|
199
|
+
and COPULA.search(first) and COPULA.search(second)):
|
|
200
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
201
|
+
# A contrastive opener on the second half is the twist announced by a
|
|
202
|
+
# conjunction rather than by the negation's position: "That creates
|
|
203
|
+
# speed. But speed is not velocity."
|
|
204
|
+
if (negated and RX_CONTRASTIVE.match(second) and na <= 8 and nb <= 7
|
|
205
|
+
and (shared or COPULA.search(second))):
|
|
206
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
207
|
+
# The two template shapes the meter anchors, counted here.
|
|
208
|
+
if RX_STOCK_CLOSER.search(second) or RX_UNMARKED_REVERSAL.match(first):
|
|
209
|
+
out.append(f"{first} {second}"); consumed = i + 1; continue
|
|
210
|
+
# Isocolon: one frame, both arguments swapped. A repeated opening is
|
|
211
|
+
# restatement rather than a swap, but only when the repeated part
|
|
212
|
+
# carries meaning: "Version one shipped in March. Version two shipped in
|
|
213
|
+
# June." repeats the subject and is enumeration, while "A junior
|
|
214
|
+
# engineer reads the error. A senior engineer reads the stack trace."
|
|
215
|
+
# repeats only the article and is the figure. So the test is whether any
|
|
216
|
+
# CONTENT word opens both halves, not whether any token does.
|
|
217
|
+
shared_open = any(w not in ANTITHESIS_STOP for w in ta[:prefix])
|
|
218
|
+
if shared_open or head == tail or len(head) < 2 or len(tail) < 2:
|
|
219
|
+
continue
|
|
135
220
|
if len(shared) >= 2 and len(shared) / min(len(head), len(tail)) >= 0.5:
|
|
136
221
|
out.append(f"{first} {second}")
|
|
222
|
+
consumed = i + 1
|
|
137
223
|
return out
|
|
138
224
|
|
|
139
225
|
|