zero-slop 2.8.2 → 2.8.3

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 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.2-72528F">
8
+ <img alt="version" src="https://img.shields.io/badge/version-2.8.3-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>
@@ -228,33 +228,35 @@ proof of general accuracy.
228
228
 
229
229
  ### Speed
230
230
 
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 24 interleaved runs against 2.7.7 we measured
236
- 0.08% lower median throughput; that is machine noise, not a speed claim. Editing
231
+ One busy Apple silicon Mac. Meter: 1,000 documents in 1.9949 seconds (501.3 per
232
+ second), 15,201 words in 0.3200 seconds, worst stress case 2.3077 seconds.
233
+ Reading pass: 0.7712 seconds for the same 1,000 (1296.7 per
234
+ second), 0.1054 for the same large document, linear to 96,000 words. Learning
235
+ pass, 8,000 words: 0.2832 seconds. Across 24 interleaved runs against 2.7.7 we measured
236
+ 0.22% higher median throughput; that is machine noise, not a speed claim. Editing
237
237
  time is excluded.
238
238
 
239
239
  ### Reading-pass accuracy
240
240
 
241
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
242
+ before the budget means anything. On 75 labelled pairs in
243
243
  [`bench/antithesis/`](bench/antithesis/):
244
244
 
245
245
  | Reading pass | 2.8.0 | now |
246
246
  |---|---:|---:|
247
- | Recall, all shapes | 40.0% | 90.0% |
247
+ | Recall, all shapes | 40.0% | 91.2% |
248
248
  | Recall, shapes in reach | 44.4% | 100% |
249
249
  | Precision | 80.0% | 100% |
250
250
  | False positives | 3 | 0 |
251
251
 
252
- ![Antithesis detection before and after, on 58 labelled pairs](assets/bench-antithesis.png)
252
+ 2.8.3 added the families the 58-pair corpus never tested, where 2.8.2 scores 67.5%
253
+ precision.
253
254
 
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.
255
+ ![Antithesis detection before and after, on 75 labelled pairs](assets/bench-antithesis.png)
256
+
257
+ Bare subject swap and the weak isocolon stay out of reach and count against recall: both
258
+ are identical to ordinary prose on every lexical statistic. Maintainer labels on
259
+ constructed pairs, so this is a regression floor, not field accuracy.
258
260
 
259
261
  ### Current models
260
262
 
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: zero-slop
3
3
  license: MIT
4
4
  metadata:
5
- version: "2.8.2"
5
+ version: "2.8.3"
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.2",
3
+ "version": "2.8.3",
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"
@@ -95,9 +95,13 @@ NEGATION = re.compile(r"\b(?:not|never|cannot)\b|\w+n['’]t\b", re.I)
95
95
  # figure's usual carrier and is absent from ordinary negated prose ("The server
96
96
  # did not respond. We restarted it.").
97
97
  COPULA = re.compile(r"\b(?:is|are|was|were|be|been|am)\b|\w+['’]s\b|\w+n['’]t\b", re.I)
98
- RX_MARKED_OPEN = re.compile(r"^\W*not\b", re.I)
98
+ RX_MARKED_OPEN = re.compile(
99
+ r"^\W*not\s+(?!all\b|every\b|only\b|just\b|much\b|many\b|most\b|enough\b"
100
+ r"|yet\b|quite\b|nearly\b|entirely\b|\w+ly\b)", re.I)
99
101
  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)
102
+ r"\b(?:was|were|is|are|did|does|do|has|have|had|will|can|could|would)\s+not\W*$"
103
+ r"|\b(?:wasn|weren|isn|aren|didn|doesn|don|hasn|haven|hadn|won|can|couldn|wouldn)"
104
+ r"['\u2019]t\W*$", re.I)
101
105
  # The two template shapes the meter already anchors (this-is-what-looks-like,
102
106
  # no-x-had-to). The meter scores them as spans; the register pass has to COUNT
103
107
  # them, because tells.md budgets the family by frequency and a span hit is not
@@ -138,7 +142,20 @@ def _common_prefix(a: list[str], b: list[str]) -> int:
138
142
  # splitter or prose_of -- both are shared by every family, and moving either one
139
143
  # pushed four to eleven documents over budget on rate alone -- this family
140
144
  # 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)")
145
+ RX_SCAFFOLD = re.compile(r"^\s*(?:[#>|]|[-*+]\s|\d+\.\s|\*\*)|\n\s*(?:[#>|]|[-*+]\s|\d+\.\s|\*\*)")
146
+
147
+
148
+ # Cardinals and calendar words. Their presence in the part of a pair that
149
+ # differs marks a specification rather than a rhetorical figure.
150
+ ENUMERATED_VALUE = frozenset("""
151
+ one two three four five six seven eight nine ten eleven twelve twenty thirty
152
+ forty fifty sixty seventy eighty ninety hundred thousand million billion
153
+ first second third fourth fifth
154
+ monday tuesday wednesday thursday friday saturday sunday
155
+ january february march april may june july august september october november
156
+ december hour hours minute minutes day days week weeks month months year years
157
+ am pm noon midnight
158
+ """.split())
142
159
 
143
160
 
144
161
  def antithesis_pairs(prose: str) -> list[str]:
@@ -189,7 +206,24 @@ def antithesis_pairs(prose: str) -> list[str]:
189
206
  # role never certifies its own output." shares "role" and is ordinary
190
207
  # prose. The frame is a repeated opening, a copula on both sides, or two
191
208
  # words in common.
192
- if negated and na <= 10 and nb <= 8 and (prefix >= 1 or len(shared) >= 2):
209
+ # `prefix >= 1` counted a shared stopword as a frame, so "It does not
210
+ # run on Windows. It runs on Linux and macOS." qualified on "it" alone.
211
+ # Requiring a CONTENT frame instead was too strict: "We do not guess. We
212
+ # measure." shares only "we" and is the figure.
213
+ #
214
+ # What separates them is the payoff, not the frame. The figure lands its
215
+ # twist in a breath -- "We measure." "She proved it." -- while ordinary
216
+ # negated prose just carries on at normal length. So a stopword frame is
217
+ # allowed, but only when the second half is that short.
218
+ content_prefix = any(w not in ANTITHESIS_STOP for w in ta[:prefix])
219
+ shared_content = {w for w in shared if w not in ANTITHESIS_STOP}
220
+ # A repeated CONTENT opening is anaphora -- "The report ... The report
221
+ # ..." -- which the isocolon branch below already refuses for exactly
222
+ # this reason. Here it was being read as a frame, i.e. as evidence for
223
+ # the figure rather than against it.
224
+ strong_frame = len(shared_content) >= 2 and not content_prefix
225
+ weak_frame = prefix >= 1 or len(shared) >= 2
226
+ if negated and na <= 10 and ((strong_frame and nb <= 8) or (weak_frame and nb <= 5)):
193
227
  out.append(f"{first} {second}"); consumed = i + 1; continue
194
228
  # A copula on both sides is the weakest of the three frames, so it only
195
229
  # counts when the halves are staccato-short. "Passwords are never stored
@@ -217,6 +251,20 @@ def antithesis_pairs(prose: str) -> list[str]:
217
251
  shared_open = any(w not in ANTITHESIS_STOP for w in ta[:prefix])
218
252
  if shared_open or head == tail or len(head) < 2 or len(tail) < 2:
219
253
  continue
254
+ # Enumeration wears the same clothes as isocolon: one frame, both
255
+ # arguments swapped, the same word overlap. "The free tier includes ten
256
+ # seats. The pro tier includes fifty seats." is structurally identical
257
+ # to "A junior engineer reads the error. A senior engineer reads the
258
+ # stack trace." -- four shared words and 0.67 overlap in both.
259
+ #
260
+ # What separates them is what varies. A specification varies a VALUE:
261
+ # a quantity, a weekday, a time. The figure varies a CONCEPT. So a
262
+ # cardinal or a calendar word in the part that differs means this is a
263
+ # table written as prose, and the pass stays quiet.
264
+ differing = set(head) | set(tail)
265
+ if differing & ENUMERATED_VALUE or any(
266
+ any(ch.isdigit() for ch in w) for w in differing):
267
+ continue
220
268
  if len(shared) >= 2 and len(shared) / min(len(head), len(tail)) >= 0.5:
221
269
  out.append(f"{first} {second}")
222
270
  consumed = i + 1
@@ -226,7 +274,7 @@ def antithesis_pairs(prose: str) -> list[str]:
226
274
  # "X, not Y." and "A rather than B." The corrective appositive. Each instance is
227
275
  # usually careful writing, which is why no pattern list contains it.
228
276
  RX_SUBTRACTIVE = re.compile(
229
- r"[^.\n]{3,90}?,\s+not\s+[^.\n]{3,60}[.\n]"
277
+ r"[^.\n]{3,90}?[,\u2014\u2013]\s*(?:not|never)\s+[^.\n]{3,60}[.\n]"
230
278
  r"|[^.\n]{3,70}\brather than\b[^.\n]{3,50}[.\n]",
231
279
  re.I,
232
280
  )