zero-slop 2.8.3 → 2.8.5

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,10 +5,10 @@
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.3-72528F">
8
+ <img alt="version" src="https://img.shields.io/badge/version-2.8.5-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
- <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>
11
+ <a href="https://zero-slop.ai/try/"><img alt="This README scores 11.6 out of 100 on the slop score, inside the human range" src="https://img.shields.io/badge/slop%20score-11.6%2F100-0f7d55"></a>
12
12
  </p>
13
13
 
14
14
  Less slop, more pop in your writing.
@@ -114,7 +114,7 @@ The badge above is this README's own, from `npx zero-slop score README.md`.
114
114
  Put yours up the same way:
115
115
 
116
116
  ```md
117
- [![Slop score](https://img.shields.io/badge/slop%20score-12.0%2F100-0f7d55)](https://zero-slop.ai/try/)
117
+ [![Slop score](https://img.shields.io/badge/slop%20score-11.6%2F100-0f7d55)](https://zero-slop.ai/try/)
118
118
  ```
119
119
 
120
120
  Colours are the scorer's bands: `0f7d55` under 25, `b8860b` under 60, `b0502c`
@@ -122,7 +122,7 @@ above.
122
122
 
123
123
  ## The slop that Zero Slop catches
124
124
 
125
- 290 weighted patterns and a 96-term lexicon, including:
125
+ 294 weighted patterns and a 96-term lexicon, including:
126
126
 
127
127
  1. Binary contrasts: "It's not X. It's Y."
128
128
  2. Throat-clearing openers: "Here's the thing," "Let me be clear"
@@ -228,12 +228,12 @@ proof of general accuracy.
228
228
 
229
229
  ### Speed
230
230
 
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
231
+ One busy Apple silicon Mac. Meter: 1,000 documents in 1.9929 seconds (501.8 per
232
+ second), 15,201 words in 0.3223 seconds, worst stress case 2.2932 seconds.
233
+ Reading pass: 0.7823 seconds for the same 1,000 (1278.3 per
234
+ second), 0.1092 for the same large document, linear to 96,000 words. Learning
235
+ pass, 8,000 words: 0.1592 seconds. Across 24 interleaved runs against 2.7.7 we measured
236
+ 1.29% lower median throughput; that is machine noise, not a speed claim. Editing
237
237
  time is excluded.
238
238
 
239
239
  ### Reading-pass accuracy
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: zero-slop
3
3
  license: MIT
4
4
  metadata:
5
- version: "2.8.3"
5
+ version: "2.8.5"
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
  ---
@@ -91,8 +91,8 @@ local and AI responsibilities distinct:
91
91
  and directly fix stumbles, repetition, weak transitions, and awkward flow.
92
92
  7. **Verifier — local tools plus the AI assistant.** Check the exact final text
93
93
  against the source for the writing score, facts, meaning, qualifiers, voice,
94
- format, and structure. Any repair returns through roles 5 and 6 before role 7
95
- runs again.
94
+ format, and structure. This is a release gate, not advice: a warning blocks
95
+ delivery. Any repair returns through roles 5 and 6 before role 7 runs again.
96
96
  8. **Fresh-eyes finalizer — a new AI pass.** Read the verified text as a first-time
97
97
  reader, apply only safe final polish, and approve it without changes. A role 8
98
98
  edit restarts roles 5 through 8; the finalizer never bypasses verification.
@@ -192,7 +192,7 @@ Run the heuristic surface scorer on the draft:
192
192
  python3 <skill-root>/scripts/slopscore.py --explain <file> # any cwd; or pipe via stdin
193
193
  ```
194
194
 
195
- Every channel runs on every draft: the pattern meter (290 weighted tells plus
195
+ Every channel runs on every draft: the pattern meter (294 weighted tells plus
196
196
  a 96-term lexicon and 26 context-gated riders), rhythm and burstiness,
197
197
  long-form word variety, followability, formatting
198
198
  densities, and register. Each one is interpretable: pattern-meter hits come
@@ -933,7 +933,7 @@ the AI model already running in the assistant or rewrite this `SKILL.md`.
933
933
  - `references/tells.md` — the master taxonomy (113 tells, 6 families) with fixes.
934
934
  It is the human-readable catalogue; `data/patterns.json` is its machine
935
935
  implementation. Together with the reviewed shared overlay, the current
936
- release carries 290 weighted regexes because some tells need more than one.
936
+ release carries 294 weighted regexes because some tells need more than one.
937
937
  - `references/rewrite-moves.md` — the positive program: the six ladder rungs
938
938
  expanded, with before/after pairs and voice calibration.
939
939
  - `references/platforms.md` — LinkedIn, X/Twitter, email, blog, newsletter,
@@ -1,6 +1,6 @@
1
1
  # performed-register — should-flag corpus
2
2
 
3
- Thirty-five spans a human editor flagged as slop on sight while the scorer
3
+ Thirty-six spans a human editor flagged as slop on sight while the scorer
4
4
  passed every one clean (9.5–10.9, zero pattern hits). All come from
5
5
  zero-slop.ai blog drafts written in August 2026 — first-party text, so no
6
6
  external registry entry applies. They share one register: **performed-writer
@@ -0,0 +1,3 @@
1
+ The channel feels less like a workshop and more like a stage. I know I'm not
2
+ alone. Not in a planning meeting. Not in a team review. But in a private group.
3
+ That is where the real conversations are starting to happen.
@@ -390,3 +390,14 @@ example that motivated it.
390
390
  alone: paragraph-order dependence, unsupported novelty, self-labelled
391
391
  significance, moral-adjective category errors, recap-flattery, and
392
392
  wall-of-text replies.
393
+ - 2026-09-03 — v2.8.4 social-stage false negative. A long promotional social
394
+ post scored 15.0 because its only machine-readable signal was one candour
395
+ phrase; repeated validation, staged negative fragments, a less-like/more-like
396
+ comparison, and a generic "real conversations" close were left entirely to
397
+ the contextual reader. Added four narrow anchors whose combined weight finds
398
+ that stack without making any one ordinary phrase decisive. Also fixed the
399
+ reading pass so curly contractions, irregular past-tense verbs, and Unicode
400
+ dash bullets are not misreported as verbless prose. The reproduced miss moves
401
+ from 15.0 to 50.2, the edited version remains below the 25-point line, every
402
+ known-human control remains below it, and the exact finishing pipeline now
403
+ treats a verifier warning as a blocked release rather than advisory copy.
@@ -162,6 +162,15 @@
162
162
  "first_seen": "2026-08-03",
163
163
  "last_confirmed": "2026-08-15"
164
164
  },
165
+ {
166
+ "name": "social-validation",
167
+ "cat": "performed",
168
+ "rx": "\\b(?:i (?:know|realize) i(?:'|\u2019)m not alone|you(?:'|\u2019)re not (?:alone|imagining it)|if you(?:'|\u2019)ve (?:felt|noticed) [\\w '\u2019-]{2,36}, you(?:'|\u2019)re not (?:alone|imagining it))\\b",
169
+ "hints": ["not alone", "not imagining it"],
170
+ "w": 2.5,
171
+ "first_seen": "2026-09-03",
172
+ "last_confirmed": "2026-09-03"
173
+ },
165
174
  {
166
175
  "name": "faux-insight",
167
176
  "cat": "performed",
@@ -226,6 +235,15 @@
226
235
  "first_seen": "2026-08-03",
227
236
  "last_confirmed": "2026-08-03"
228
237
  },
238
+ {
239
+ "name": "staged-negative-ladder",
240
+ "cat": "contrast",
241
+ "rx": "(?:^|[.!?]\\s+|\\n)[\u2013\u2014*+\u2022-]*\\s*not in (?:an? |the )?[\\w '\u2019-]{2,32}\\.\\s*(?:[\u2013\u2014*+\u2022-]+\\s*)?not in (?:an? |the )?[\\w '\u2019-]{2,32}\\.\\s*(?:[\u2013\u2014*+\u2022-]+\\s*)?but in (?:an? |the )?[\\w '\u2019-]{2,48}",
242
+ "hints": ["not in a", "not in the"],
243
+ "w": 5,
244
+ "first_seen": "2026-09-03",
245
+ "last_confirmed": "2026-09-03"
246
+ },
229
247
  {
230
248
  "name": "this-isnt-about",
231
249
  "cat": "contrast",
@@ -515,6 +533,15 @@
515
533
  "first_seen": "2026-08-14",
516
534
  "last_confirmed": "2026-08-14"
517
535
  },
536
+ {
537
+ "name": "less-like-more-like",
538
+ "cat": "contrast",
539
+ "rx": "\\b(?:feel(?:s|ing)?|seem(?:s|ing)?) less like (?:an? |the )?[\\w '\u2019-]{2,48}(?:,| and)? (?:an? )?(?:little )?more like (?:an? |the )?[\\w '\u2019-]{2,48}",
540
+ "hints": ["less like", "more like"],
541
+ "w": 4,
542
+ "first_seen": "2026-09-03",
543
+ "last_confirmed": "2026-09-03"
544
+ },
518
545
  {
519
546
  "name": "thats-not-thats",
520
547
  "cat": "contrast",
@@ -1177,6 +1204,15 @@
1177
1204
  "first_seen": "2026-08-14",
1178
1205
  "last_confirmed": "2026-08-14"
1179
1206
  },
1207
+ {
1208
+ "name": "real-conversation-closer",
1209
+ "cat": "performed",
1210
+ "rx": "\\bwhere the (?:real|genuine|meaningful) (?:conversation|conversations|connection|connections) (?:is|are) (?:starting to |finally )?(?:happen|happening|taking place)\\b",
1211
+ "hints": ["real conversation", "genuine conversation", "meaningful conversation"],
1212
+ "w": 3,
1213
+ "first_seen": "2026-09-03",
1214
+ "last_confirmed": "2026-09-03"
1215
+ },
1180
1216
  {
1181
1217
  "name": "recipe-for",
1182
1218
  "cat": "cliche",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-slop",
3
- "version": "2.8.3",
3
+ "version": "2.8.5",
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"
@@ -47,6 +47,7 @@
47
47
  "!scripts/build_plugin.py",
48
48
  "!scripts/build_skill_zip.py",
49
49
  "!scripts/check_svg.py",
50
+ "!scripts/check_release_surfaces.py",
50
51
  "!scripts/contextual.py",
51
52
  "!scripts/contextual-signals.md",
52
53
  "!references/contextual-signals.md",
@@ -380,6 +380,26 @@ FINITE_VERB = re.compile(
380
380
  r"|gate[sd]?|gives?|runs?|gets?|gave|gone)\b"
381
381
  r"|\b\w+(?:s|ed|es)\b", re.I)
382
382
 
383
+ # Curly apostrophes are ordinary prose, not token boundaries. A contracted
384
+ # auxiliary makes the clause finite even when the main verb is irregular or a
385
+ # participle ("I’ve found", "you’re seeing"). The old tokeniser split those
386
+ # into ``I``/``ve`` and ``you``/``re`` and then reported complete sentences as
387
+ # fragments. Keep this conservative: a possible possessive false negative is
388
+ # safer than telling a writer that a grammatical sentence has no verb.
389
+ CONTRACTED_FINITE = re.compile(r"\b[A-Za-z][\w-]*['’](?:m|re|ve|s|d|ll)\b", re.I)
390
+ # Only forms that are rarely ordinary nouns or adjectives belong here. Ambiguous
391
+ # forms such as ``cut``, ``left``, ``read``, ``set``, and ``thought`` made real
392
+ # fragments look grammatical ("Nothing left in the queue"). Contractions are
393
+ # handled separately above, so this small list only closes clear past-tense
394
+ # holes in uncontracted sentences.
395
+ IRREGULAR_FINITE = frozenset("""
396
+ arose became began blew broke brought bought caught chose came dealt did drew
397
+ drank drove ate fell fed felt fought found flew forgot forgave froze gave got
398
+ grew had heard held hid kept knew laid led lent lost made meant met paid rode
399
+ ran said saw sold sent shook showed sang sat slept spoke spent stood stole
400
+ struck swam took taught told threw understood woke wore won wrote was were went
401
+ """.split())
402
+
383
403
  # The -s half of that catch-all cannot tell a verb from a plural noun, and a
384
404
  # plural noun in a fragment made the whole fragment invisible: "Same compound,
385
405
  # three identifiers." and "Same assay, two units." both read as verbed on
@@ -434,6 +454,11 @@ def _looks_like_noun(sent: str, start: int) -> bool:
434
454
 
435
455
 
436
456
  def _has_finite_verb(sent: str) -> bool:
457
+ if CONTRACTED_FINITE.search(sent):
458
+ return True
459
+ if any(word.lower() in IRREGULAR_FINITE
460
+ for word in re.findall(r"[A-Za-z][\w'’-]*", sent)):
461
+ return True
437
462
  for match in FINITE_VERB.finditer(sent):
438
463
  word = match.group(0)
439
464
  # An explicit auxiliary or an -ed form is a verb wherever it appears.
@@ -456,10 +481,10 @@ def _sentences(prose: str) -> list[str]:
456
481
  def verbless_fragments(prose: str) -> list[str]:
457
482
  out = []
458
483
  for sent in _sentences(prose):
459
- words = re.findall(r"[A-Za-z][\w'-]*", sent)
484
+ words = re.findall(r"[A-Za-z][\w'’-]*", sent)
460
485
  if not (3 <= len(words) <= 12):
461
486
  continue
462
- if sent.rstrip().endswith(":") or sent.lstrip().startswith(("-", "*", "#", "|")):
487
+ if sent.rstrip().endswith(":") or sent.lstrip().startswith(("-", "–", "—", "•", "*", "#", "|")):
463
488
  continue
464
489
  if IMPERATIVE_OPENER.match(sent):
465
490
  continue
@@ -562,7 +587,7 @@ def prose_of(text: str) -> str:
562
587
  return "\n".join(
563
588
  line
564
589
  for line in text.split("\n")
565
- if not line.strip().startswith(("|", "![", ">", " "))
590
+ if not line.strip().startswith(("|", "![", ">", "-", "–", "—", "•", "*", " "))
566
591
  )
567
592
 
568
593
 
@@ -900,7 +900,7 @@ def render_portfolio(result):
900
900
  # broetry is a slop tell, not a machine tell: LinkedIn writers invented it
901
901
  # years before GPT-3, and it demonstrably performs on the platform. Whether to
902
902
  # trade reach for a human voice is the author's call, not the meter's.
903
- STRUCT_MARK = re.compile(r"^\s*(?:[-*+•>#]|\d+[.)]|\|)")
903
+ STRUCT_MARK = re.compile(r"^\s*(?:[-–—*+•>#]|\d+[.)]|\|)")
904
904
  DIALOGUE_OPEN = re.compile("^[\"“‘']")
905
905
 
906
906