zero-slop 2.8.9 → 2.8.10
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 +4 -2
- package/SKILL.md +11 -4
- package/package.json +1 -1
- package/scripts/rescue.py +155 -0
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.10-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 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>
|
|
@@ -163,7 +163,9 @@ is an engineering choice.
|
|
|
163
163
|
Missed editorial targets do not erase a useful edit or start an open-ended loop. Zero
|
|
164
164
|
Slop permits one targeted repair and one local recheck, then returns the safest
|
|
165
165
|
source-preserving edit with a plain warning. On the website, a failed model call falls
|
|
166
|
-
back to
|
|
166
|
+
back to the same conservative local editor shipped with the installed skill instead of
|
|
167
|
+
retrying through more providers. Shared fixtures require the browser and local command
|
|
168
|
+
to return the same fallback edit, character for character.
|
|
167
169
|
|
|
168
170
|
Studies find
|
|
169
171
|
[predictable wording](https://arxiv.org/abs/2301.11305) and
|
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.10"
|
|
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
|
---
|
|
@@ -643,9 +643,13 @@ restart the complete editorial sequence. If the second check still finds a probl
|
|
|
643
643
|
return the safest source-preserving edit and name the remaining issue plainly.
|
|
644
644
|
|
|
645
645
|
If an AI editorial role returns no usable text, record that it was unavailable and
|
|
646
|
-
continue from the last source-preserving text.
|
|
647
|
-
|
|
648
|
-
|
|
646
|
+
continue from the last source-preserving text. For an explicit rewrite request, if that
|
|
647
|
+
text is still the unchanged source, run `python3 scripts/rescue.py -` on the source and
|
|
648
|
+
pass its output through the same scorer and fact gate. This deterministic availability
|
|
649
|
+
editor removes only reviewed stock wrappers and never certifies itself; label its use
|
|
650
|
+
plainly. Unavailability is an abstention, not a reason to retry, switch models, or replay
|
|
651
|
+
earlier roles. A caller with a one-request budget must never make a second remote
|
|
652
|
+
request. Report any role that did not complete.
|
|
649
653
|
|
|
650
654
|
A required repair may raise the writing score from the previous draft as long as it
|
|
651
655
|
stays below the release limit. Source meaning, stated emotion, and factual accuracy
|
|
@@ -967,6 +971,9 @@ the AI model already running in the assistant or rewrite this `SKILL.md`.
|
|
|
967
971
|
requires the section A counts to be written down rather than judged silently.
|
|
968
972
|
- `references/evidence.md` — the research basis: papers, detector mechanics,
|
|
969
973
|
and why each ladder rung is ordered where it is.
|
|
974
|
+
- `scripts/rescue.py` — the conservative, no-network availability editor shared by
|
|
975
|
+
installed skills and the web demo. It returns a changed draft when a known safe edit
|
|
976
|
+
is available, then leaves approval to the scorer and fact gate.
|
|
970
977
|
|
|
971
978
|
## Worked example (LinkedIn)
|
|
972
979
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zero-slop",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.10",
|
|
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"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Conservative, deterministic editing when an AI response is unavailable.
|
|
3
|
+
|
|
4
|
+
This is an availability path, not a substitute for the contextual editor. Every
|
|
5
|
+
rule removes a stock wrapper, contracts a phrase, or repairs paragraph staging.
|
|
6
|
+
Quoted text, code, Markdown links, URLs, names, figures, and claims are left alone.
|
|
7
|
+
|
|
8
|
+
python3 scripts/rescue.py draft.txt
|
|
9
|
+
cat draft.txt | python3 scripts/rescue.py -
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
MAX_INPUT_BYTES = 4 * 1024 * 1024
|
|
20
|
+
PROTECTED = re.compile(
|
|
21
|
+
r"```[\s\S]*?```|`[^`\n]+`|\[[^\]\n]+\]\([^)]+\)|https?://[^\s<]+|"
|
|
22
|
+
r"“[^”\n]*”|‘[^’\n]*’|\"[^\"\n]*\""
|
|
23
|
+
)
|
|
24
|
+
TOKEN = re.compile(r"\ue000(\d+)\ue001")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _sentence_pair(match: re.Match[str], *, past: bool) -> str:
|
|
28
|
+
first, second = match.group(1), match.group(2)
|
|
29
|
+
lead = first[:1].upper() + first[1:]
|
|
30
|
+
return f"{lead} {'mattered' if past else 'matters'}. " + (
|
|
31
|
+
f"More important was {second}." if past else f"The larger gain is {second}."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def rescue_text(text: str) -> str:
|
|
36
|
+
"""Return a bounded, source-preserving edit; return clean text unchanged."""
|
|
37
|
+
original = str(text or "").strip()
|
|
38
|
+
protected: list[str] = []
|
|
39
|
+
|
|
40
|
+
def mask(match: re.Match[str]) -> str:
|
|
41
|
+
protected.append(match.group(0))
|
|
42
|
+
return f"\ue000{len(protected) - 1}\ue001"
|
|
43
|
+
|
|
44
|
+
masked = PROTECTED.sub(mask, original)
|
|
45
|
+
out = re.sub(r"[\u00a0\u202f]", " ", masked)
|
|
46
|
+
rules: list[tuple[str, str | object]] = [
|
|
47
|
+
(
|
|
48
|
+
r"\bwe are thrilled to unveil ([^,\n]+),\s+a transformative release that "
|
|
49
|
+
r"redefines what is possible in ([^.]+)\.",
|
|
50
|
+
lambda m: f"{m.group(1)} updates {m.group(2)}.",
|
|
51
|
+
),
|
|
52
|
+
(r"\bthis release represents a significant milestone in our journey to empower "
|
|
53
|
+
r"teams everywhere\.\s*", ""),
|
|
54
|
+
(r"\bwe have listened carefully to your feedback and are excited to deliver a "
|
|
55
|
+
r"suite of powerful new capabilities\.",
|
|
56
|
+
"We listened to your feedback and added new capabilities."),
|
|
57
|
+
(r"\bour cutting[-\u2010\u2011 ]edge\b", "Our"),
|
|
58
|
+
(r"\bhours of tedious manual configuration\b", "hours of manual configuration"),
|
|
59
|
+
(r"\bwe have completely reimagined\b", "We rebuilt"),
|
|
60
|
+
(r"\bwith robust error handling built in from the ground up\b",
|
|
61
|
+
"with built-in error handling"),
|
|
62
|
+
(r"\bwe believe these improvements will fundamentally transform how your team "
|
|
63
|
+
r"works,\s+and the release is available today\.", "The release is available today."),
|
|
64
|
+
(r"\bwe are incredibly excited to share(?: some news)? about\b", "We're excited about"),
|
|
65
|
+
(r"\bwe(?:['’]re| are) excited to share(?: some news)? about\b", "We're excited about"),
|
|
66
|
+
(r"\bwe are incredibly excited to share\b", "We're sharing"),
|
|
67
|
+
(r"\bwe(?:['’]re| are) excited to share\b", "We're excited about"),
|
|
68
|
+
(r"\bi(?:['’]m| am) incredibly excited to (?:share|announce)\b", "I'm sharing"),
|
|
69
|
+
(r"\bour journey\b", "our work"),
|
|
70
|
+
(r"\bour transformative journey\b", "our work"),
|
|
71
|
+
(r"\bin today'?s rapidly evolving (?:landscape|world)\b", "Today"),
|
|
72
|
+
(r"\bit is important to note that\b", ""),
|
|
73
|
+
(r"\bit is worth noting that\b", ""),
|
|
74
|
+
(r"\bwhat we did not realize was just how deeply it impacted everything downstream\.",
|
|
75
|
+
"We underestimated its effect on the work that followed."),
|
|
76
|
+
(r"\bonboarding is not a checklist\.\s*it is a promise\.",
|
|
77
|
+
"We see onboarding as a promise."),
|
|
78
|
+
(r"\bonboarding isn['’]t a checklist\s*[-—]\s*it['’]s a promise\.",
|
|
79
|
+
"We see onboarding as a promise."),
|
|
80
|
+
(r"\bthe insights were game[-\u2011]changing\.",
|
|
81
|
+
"Those conversations changed our approach."),
|
|
82
|
+
(r"\bthe insights were (?:transformative|clear|significant):\s*",
|
|
83
|
+
"Those conversations showed that "),
|
|
84
|
+
(r"\ba platform that leverages intelligent automation to streamline the entire "
|
|
85
|
+
r"process end to end\b", "a platform that automates onboarding from start to finish"),
|
|
86
|
+
(r"\bthe results speak for themselves:\s*([a-z])",
|
|
87
|
+
lambda m: m.group(1).upper()),
|
|
88
|
+
(r"\bthe results speak for themselves\.\s*", ""),
|
|
89
|
+
(r"\bbut here is the thing nobody talks about\.\s*", ""),
|
|
90
|
+
(r"\bthe real win was not ([^.]+)\.\s*it was ([^.]+)\.",
|
|
91
|
+
lambda m: _sentence_pair(m, past=True)),
|
|
92
|
+
(r"\bthe real win isn['’]t just ([^.]+)\.\s*it['’]s ([^.]+)\.",
|
|
93
|
+
lambda m: _sentence_pair(m, past=False)),
|
|
94
|
+
(r"\bthat is the kind of impact that keeps us going\b", "That result keeps us going"),
|
|
95
|
+
(r"\bunlock(?:ing)? the full potential of\b", "use"),
|
|
96
|
+
(r"\bseamlessly integrates?\b", "integrates"),
|
|
97
|
+
(r"\bjust how deeply\b", "how much"),
|
|
98
|
+
(r"\bgame[-\u2011]changing\b", "useful"),
|
|
99
|
+
(r"\bcutting[-\u2010\u2011 ]edge\b", "current"),
|
|
100
|
+
(r"\bredefines what(?:['’]s| is) possible in\b", "updates"),
|
|
101
|
+
(r"\bin order to\b", "to"),
|
|
102
|
+
(r"\bat the end of the day\b", "ultimately"),
|
|
103
|
+
(r"\bwe are\b", "we're"),
|
|
104
|
+
(r"\bwe did not\b", "we didn't"),
|
|
105
|
+
(r"\bwe do not\b", "we don't"),
|
|
106
|
+
(r"\bi am\b", "I'm"),
|
|
107
|
+
(r"\bit is\b", "it's"),
|
|
108
|
+
(r"\bthey are\b", "they're"),
|
|
109
|
+
(r"\byou are\b", "you're"),
|
|
110
|
+
(r"\bthere is\b", "there's"),
|
|
111
|
+
(r"\bdoes not\b", "doesn't"),
|
|
112
|
+
(r"\bis not\b", "isn't"),
|
|
113
|
+
(r"\bare not\b", "aren't"),
|
|
114
|
+
(r"\bcannot\b", "can't"),
|
|
115
|
+
]
|
|
116
|
+
for pattern, replacement in rules:
|
|
117
|
+
out = re.sub(pattern, replacement, out, flags=re.IGNORECASE)
|
|
118
|
+
out = re.sub(r"[ \t]+\n", "\n", out)
|
|
119
|
+
out = re.sub(r" {2,}", " ", out).strip()
|
|
120
|
+
|
|
121
|
+
if out == masked:
|
|
122
|
+
paragraphs = [part.strip() for part in re.split(r"\n{2,}", out) if part.strip()]
|
|
123
|
+
if len(paragraphs) >= 4 and all(len(part.split()) < 24 for part in paragraphs):
|
|
124
|
+
out = " ".join(paragraphs)
|
|
125
|
+
|
|
126
|
+
def restore(match: re.Match[str]) -> str:
|
|
127
|
+
index = int(match.group(1))
|
|
128
|
+
return protected[index] if index < len(protected) else match.group(0)
|
|
129
|
+
|
|
130
|
+
return TOKEN.sub(restore, out).strip()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _read(path: str) -> str:
|
|
134
|
+
if path == "-":
|
|
135
|
+
raw = sys.stdin.buffer.read(MAX_INPUT_BYTES + 1)
|
|
136
|
+
else:
|
|
137
|
+
raw = Path(path).read_bytes()
|
|
138
|
+
if len(raw) > MAX_INPUT_BYTES:
|
|
139
|
+
raise SystemExit(f"input exceeds {MAX_INPUT_BYTES} bytes")
|
|
140
|
+
try:
|
|
141
|
+
return raw.decode("utf-8")
|
|
142
|
+
except UnicodeDecodeError as exc:
|
|
143
|
+
raise SystemExit(f"input is not valid UTF-8: {exc}") from exc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main(argv: list[str] | None = None) -> int:
|
|
147
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
148
|
+
parser.add_argument("input", nargs="?", default="-", help="UTF-8 file, or - for stdin")
|
|
149
|
+
args = parser.parse_args(argv)
|
|
150
|
+
sys.stdout.write(rescue_text(_read(args.input)) + "\n")
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
raise SystemExit(main())
|