zero-slop 2.7.0 → 2.7.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 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.7.0-72528F">
8
+ <img alt="version" src="https://img.shields.io/badge/version-2.7.1-72528F">
9
9
  </p>
10
10
 
11
11
  Score your writing 0 to 100 for AI slop, then edit it out without changing a single fact.
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: zero-slop
3
3
  license: MIT
4
4
  metadata:
5
- version: "2.7.0"
5
+ version: "2.7.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
  ---
@@ -449,6 +449,8 @@ the most reader value. `references/rewrite-moves.md` expands each rung.
449
449
  "decided". Kill participial openers ("Leveraging X, …"). Translate internal
450
450
  workflow labels into plain language; never let evaluator or harness language
451
451
  leak into reader-facing prose.
452
+ Strong claims the author owns are content, not register: cut an intensifier
453
+ only for a defect you can name in context, never for strength alone.
452
454
  Prefer an explicit actor and an active verb when responsibility matters. Keep
453
455
  passive voice when the actor is unknown, irrelevant, deliberately withheld, or
454
456
  native to the genre; passive voice alone is not evidence of AI writing.
@@ -595,6 +597,13 @@ format, and non-prose structure. Apply these contextual checks too:
595
597
  `_coverage` map by dispositioning every paragraph; the verdict fails on any
596
598
  paragraph nobody dispositioned, exactly as it fails on an unanswered check.
597
599
  A non-zero exit is a failed check.
600
+ - **The delta.** Run
601
+ `python3 <skill-root>/scripts/register.py --delta <original> <final>` and
602
+ answer for what it prints: every inserted run must restate source meaning,
603
+ every cut emphasis word needs a named defect, and every rewritten span passes
604
+ the three direction tests — purpose has not become outcome, agency has not
605
+ moved, a warned future has not become an asserted present. The fact gate
606
+ cannot see any of these; this is where a reframed claim gets caught.
598
607
  - **Performed register.** Re-run the step 2 performed-register pass on the exact
599
608
  final text and state the counts. An exceeded antithesis budget, or a surviving
600
609
  significance-scaffolding sentence, is a failed check: the text returns through
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-slop",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "An Agent Skill that scores AI-sounding prose 0-100 locally and rewrites it without losing a fact. Runs inside Claude Code, Codex, Cursor, Warp, Zed and other SKILL.md agents. Offline, zero dependencies, MIT.",
5
5
  "keywords": [
6
6
  "agent-skill",
@@ -187,7 +187,13 @@ the report even when they are zero.
187
187
  51. **Qualifiers survive.** Hedges, scope limits, and caveats carry the same strength
188
188
  as the source.
189
189
  52. **Claims not reframed.** Same names and numbers can still carry a changed
190
- emphasis or implication. Compare meaning, not tokens.
190
+ emphasis or implication. Compare meaning, not tokens, and run three direction
191
+ tests on every span `register.py --delta` reports as inserted or rewritten:
192
+ purpose has not become outcome ("changed tactics to achieve its objective" is
193
+ not "changed tactics until it reached its objective"); agency has not moved
194
+ ("helps teams generate fixes" is not "generates fixes"); a warned future has
195
+ not become an asserted present. These are contextual comparisons against this
196
+ source, never a word list.
191
197
  53. **Non-prose untouched.** Code, front matter, tables, blockquotes, identifiers,
192
198
  paths, and heading hierarchy intact.
193
199
 
@@ -207,44 +213,55 @@ the report even when they are zero.
207
213
  59. **Proportional cutting.** No compression that stripped character. Density is
208
214
  information per word, not fewer words.
209
215
  60. **Read aloud.** Would this sound natural read to a sharp colleague?
210
- 61. **No over-correction.** AI slop was not traded for edgy slop: forced hot takes,
216
+ 61. **Emphasis survives.** `register.py --delta` lists every intensifier or
217
+ absolute the rewrite cut from this source. Each cut needs a defect named from
218
+ this file, judged in its own context: puffery, hyperbole universal, filler.
219
+ "Too strong" is not a defect, and a falsifiable claim the author owns keeps
220
+ its full strength whatever word carries it. Cutting "changed overnight" from
221
+ a claim the author would defend cost a blind head-to-head once.
222
+ 62. **Earned length.** `--delta` lists every run of three or more words the rewrite
223
+ added. Each must restate meaning already in this source for this audience; a
224
+ bridge or an unpacked definition can qualify, scaffolding the author never
225
+ wrote cannot. A rewrite longer than its original defends every insertion or
226
+ loses them.
227
+ 63. **No over-correction.** AI slop was not traded for edgy slop: forced hot takes,
211
228
  fake first person, performed candor, staccato drama.
212
229
 
213
230
  ## E. Form
214
231
 
215
- 62. **Returned in the format it arrived in.** A .docx comes back a .docx.
216
- 63. **Reader language.** No evaluator or harness vocabulary leaked into the copy:
232
+ 64. **Returned in the format it arrived in.** A .docx comes back a .docx.
233
+ 65. **Reader language.** No evaluator or harness vocabulary leaked into the copy:
217
234
  candidate, artifact, overlay, gate, scorecard, burstiness.
218
- 64. **Formatting slop.** No emoji headings, decorative bold, or bullets that should be
235
+ 66. **Formatting slop.** No emoji headings, decorative bold, or bullets that should be
219
236
  prose. Count the headings over one or two sentences: four consecutive H3s each
220
237
  wrapping a code block and one line is a section that wants to be one section.
221
- 65. **Em dashes.** None in short copy. One or two in a long draft only where they
238
+ 67. **Em dashes.** None in short copy. One or two in a long draft only where they
222
239
  clearly beat a comma, period, or parenthesis.
223
- 66. **Hyphenated modifier stacking.** "AI-powered cloud-native data-driven platform".
240
+ 68. **Hyphenated modifier stacking.** "AI-powered cloud-native data-driven platform".
224
241
  Two stacked compound modifiers in one noun phrase is a tell; keep the hyphens
225
242
  grammar requires and cut the rest.
226
- 67. **List-label periods and bare-noun bullets.** Bullets that are fragments ending in
243
+ 69. **List-label periods and bare-noun bullets.** Bullets that are fragments ending in
227
244
  full stops, or a list of bare noun phrases with no predicate. Either make them
228
245
  sentences or make them a real list.
229
- 68. **Numbered-list inflation.** A numbered list where order carries no meaning, or a
246
+ 70. **Numbered-list inflation.** A numbered list where order carries no meaning, or a
230
247
  list padded to a round number. Use a bulleted list, or prose.
231
- 69. **Chatbot artifacts.** Citation markup leaks, "Regenerate response", tool URL
248
+ 71. **Chatbot artifacts.** Citation markup leaks, "Regenerate response", tool URL
232
249
  parameters such as utm_source, and any assistant scaffolding left in the copy.
233
- 70. **Attribution accurate.** The report names who did what without guessing which
250
+ 72. **Attribution accurate.** The report names who did what without guessing which
234
251
  model is running, and never implies a Zero Slop service read the draft.
235
252
 
236
253
  ## F. Process integrity
237
254
 
238
255
  Role 8 answers this section. It is the check no single-agent eval can make.
239
256
 
240
- 71. **Roles stayed separate.** The copy desk, read-aloud pass, verification, and
257
+ 73. **Roles stayed separate.** The copy desk, read-aloud pass, verification, and
241
258
  fresh-eyes review each ran as a distinct pass.
242
- 72. **No self-certification.** No role graded text it generated.
243
- 73. **Counts reported.** Every count in section A appears in the summary, including
259
+ 74. **No self-certification.** No role graded text it generated.
260
+ 75. **Counts reported.** Every count in section A appears in the summary, including
244
261
  the zeros.
245
- 74. **The exact final text cleared every check.** Not an earlier draft, not a version
262
+ 76. **The exact final text cleared every check.** Not an earlier draft, not a version
246
263
  that was repaired afterward.
247
- 75. **Role 8 approved without changes.** If it changed anything, roles 5 through 8
264
+ 77. **Role 8 approved without changes.** If it changed anything, roles 5 through 8
248
265
  ran again on the revision.
249
- 76. **Fallbacks named honestly.** If the three-round limit was reached, the report
266
+ 78. **Fallbacks named honestly.** If the three-round limit was reached, the report
250
267
  says which check failed and does not describe the result as fully verified.
@@ -611,6 +611,68 @@ def verdict(text: str, answers: dict) -> tuple[int, str]:
611
611
  return 0, "\n".join(out)
612
612
 
613
613
 
614
+ # Emphasis words: cutting one is sometimes right (puffery) and sometimes voice
615
+ # flattening ("changed overnight" is a falsifiable claim the author owns). The
616
+ # tool cannot tell which, so it reports every cut and the eval demands a defect
617
+ # name for each. "Too strong" is not a defect.
618
+ EMPHASIS = {
619
+ "overnight", "never", "always", "every", "entire", "all", "nothing",
620
+ "worst", "best", "first", "only", "immediately", "instantly", "forever",
621
+ "massive", "enormous", "catastrophically", "obsessively", "extraordinary",
622
+ "unprecedented", "remarkable", "completely", "exactly",
623
+ }
624
+
625
+
626
+ def delta(original: str, rewrite: str) -> dict:
627
+ """Word-level diff: what the rewrite added that the author never wrote, and
628
+ what emphasis it took away. Insertions are never free; each run must carry
629
+ meaning already in the source. A rewrite 27 words longer than the original
630
+ once lost a blind head-to-head on exactly this."""
631
+ import difflib
632
+ a = original.split()
633
+ b = rewrite.split()
634
+ sm = difflib.SequenceMatcher(a=[w.lower().strip(".,;:!?\"'()") for w in a],
635
+ b=[w.lower().strip(".,;:!?\"'()") for w in b])
636
+ inserted, deleted, cut_emphasis = [], [], []
637
+ for op, i1, i2, j1, j2 in sm.get_opcodes():
638
+ if op in ("insert", "replace") and j2 - j1 >= 3:
639
+ inserted.append(" ".join(b[j1:j2])[:90])
640
+ if op in ("delete", "replace"):
641
+ for w in a[i1:i2]:
642
+ if w.lower().strip(".,;:!?\"'()") in EMPHASIS:
643
+ ctx = " ".join(a[max(0, i1 - 4):min(len(a), i2 + 4)])
644
+ cut_emphasis.append(f"{w} ({ctx[:70]})")
645
+ if op == "delete" and i2 - i1 >= 3:
646
+ deleted.append(" ".join(a[i1:i2])[:90])
647
+ return {
648
+ "original_words": len(a), "rewrite_words": len(b),
649
+ "net": len(b) - len(a),
650
+ "inserted_runs": inserted[:12], "deleted_runs": deleted[:12],
651
+ "cut_emphasis": cut_emphasis[:12],
652
+ }
653
+
654
+
655
+ def render_delta(d: dict) -> str:
656
+ out = [f"Length: {d['original_words']} -> {d['rewrite_words']} words "
657
+ f"({'+' if d['net'] >= 0 else ''}{d['net']})"]
658
+ if d["net"] > 0:
659
+ out.append(" The rewrite is LONGER than the original. Every inserted run below")
660
+ out.append(" must carry meaning already in the source, or it goes.")
661
+ out.append("")
662
+ out.append(f" Inserted runs the author never wrote ({len(d['inserted_runs'])}):")
663
+ for r in d["inserted_runs"] or ["(none)"]:
664
+ out.append(f" + {r}")
665
+ out.append(f" Cut emphasis, each needs a defect name, not 'too strong' ({len(d['cut_emphasis'])}):")
666
+ for r in d["cut_emphasis"] or ["(none)"]:
667
+ out.append(f" - {r}")
668
+ if d["deleted_runs"]:
669
+ out.append(f" Deleted runs ({len(d['deleted_runs'])}):")
670
+ for r in d["deleted_runs"]:
671
+ out.append(f" - {r}")
672
+ return "\n".join(out)
673
+
674
+
675
+
614
676
  MUST_FLAG = EVAL_PATH.resolve().parent.parent / "data" / "corpus" / "must-flag"
615
677
 
616
678
 
@@ -736,6 +798,8 @@ def main() -> int:
736
798
  ap.add_argument("--gate", action="store_true", help="exit 1 when any rate is over budget")
737
799
  ap.add_argument("--calibrate", metavar="DIR", help="recompute budgets from a human corpus")
738
800
  ap.add_argument("--selftest", action="store_true", help="check the gate against the checklist")
801
+ ap.add_argument("--delta", nargs=2, metavar=("ORIGINAL", "REWRITE"),
802
+ help="what the rewrite inserted, and what emphasis it cut")
739
803
  ap.add_argument("--recall", action="store_true", help="verify every recorded miss in data/corpus/must-flag still gets caught")
740
804
  ap.add_argument("--read", action="store_true", help="emit the reading brief for the host model")
741
805
  ap.add_argument("--verdict", metavar="ANSWERS_JSON", help="gate on the measured rates plus the model's answers")
@@ -745,6 +809,11 @@ def main() -> int:
745
809
  return _selftest()
746
810
  if args.recall:
747
811
  return recall()
812
+ if args.delta:
813
+ a = pathlib.Path(args.delta[0]).read_text(encoding="utf-8", errors="ignore")
814
+ b = pathlib.Path(args.delta[1]).read_text(encoding="utf-8", errors="ignore")
815
+ print(render_delta(delta(a, b)))
816
+ return 0
748
817
  if args.calibrate:
749
818
  calibrate(args.calibrate)
750
819
  return 0