zero-slop 2.8.1 → 2.8.2

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.1-72528F">
8
+ <img alt="version" src="https://img.shields.io/badge/version-2.8.2-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>
@@ -232,8 +232,8 @@ One busy Apple silicon Mac. Meter: 1,000 documents in 2.5035 seconds (399.4 per
232
232
  second), 15,201 words in 0.4301 seconds, worst stress case 3.2518 seconds. Reading
233
233
  pass, untimed on record until now: 0.8304 seconds for the same 1,000 (1,204.2 per
234
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
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
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.1"
5
+ version: "2.8.2"
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
  ---
@@ -1,142 +1,155 @@
1
1
  {
2
- "_comment": "The ratchet: every miss an audit or a competitor catches becomes a fixture here, in the same change. metric entries are verified by register.py --recall; check entries name the eval.md family a reading pass must catch, and --recall verifies the span exists and the family is a real check.",
3
- "fixtures": [
4
- {
5
- "file": "adjective-inflation.md",
6
- "expect": [
7
- {
8
- "metric": "adjective_inflation",
9
- "span": "a real improvement"
10
- },
11
- {
12
- "metric": "adjective_inflation",
13
- "span": "a genuine breakthrough"
14
- }
15
- ]
16
- },
17
- {
18
- "file": "monument-negation.md",
19
- "expect": [
20
- {
21
- "metric": "monument_verb",
22
- "span": "stands as a testament"
23
- },
24
- {
25
- "metric": "negation_triad",
26
- "span": "no runbook, no owner, no rollback"
2
+ "_comment": "The ratchet: every miss an audit or a competitor catches becomes a fixture here, in the same change. metric entries are verified by register.py --recall; check entries name the eval.md family a reading pass must catch, and --recall verifies the span exists and the family is a real check.",
3
+ "fixtures": [
4
+ {
5
+ "file": "adjective-inflation.md",
6
+ "expect": [
7
+ {
8
+ "metric": "adjective_inflation",
9
+ "span": "a real improvement"
10
+ },
11
+ {
12
+ "metric": "adjective_inflation",
13
+ "span": "a genuine breakthrough"
14
+ }
15
+ ]
27
16
  },
28
17
  {
29
- "metric": "negation_triad",
30
- "span": "Not reviewed, not staged, not announced"
31
- }
32
- ]
33
- },
34
- {
35
- "file": "dangling-thin.md",
36
- "expect": [
37
- {
38
- "metric": "thin_section",
39
- "span": "Install"
18
+ "file": "monument-negation.md",
19
+ "expect": [
20
+ {
21
+ "metric": "monument_verb",
22
+ "span": "stands as a testament"
23
+ },
24
+ {
25
+ "metric": "negation_triad",
26
+ "span": "no runbook, no owner, no rollback"
27
+ },
28
+ {
29
+ "metric": "negation_triad",
30
+ "span": "Not reviewed, not staged, not announced"
31
+ }
32
+ ]
40
33
  },
41
34
  {
42
- "metric": "thin_section",
43
- "span": "Configure"
35
+ "file": "dangling-thin.md",
36
+ "expect": [
37
+ {
38
+ "metric": "thin_section",
39
+ "span": "Install"
40
+ },
41
+ {
42
+ "metric": "thin_section",
43
+ "span": "Configure"
44
+ },
45
+ {
46
+ "metric": "dangling_pointer",
47
+ "span": "download the installer"
48
+ }
49
+ ]
44
50
  },
45
51
  {
46
- "metric": "dangling_pointer",
47
- "span": "download the installer"
48
- }
49
- ]
50
- },
51
- {
52
- "file": "referent-cycling.md",
53
- "expect": [
54
- {
55
- "metric": "referent_cluster",
56
- "span": "local tools"
57
- },
58
- {
59
- "check": "Synonym cycling",
60
- "span": "the local checker"
61
- }
62
- ]
63
- },
64
- {
65
- "file": "mic-drop.md",
66
- "expect": [
67
- {
68
- "metric": "verbless_fragment",
69
- "span": "The review queue."
70
- },
71
- {
72
- "check": "Antithesis pairs",
73
- "span": "Different teams, different fixes, same bottleneck."
74
- }
75
- ]
76
- },
77
- {
78
- "file": "caveat-template.md",
79
- "expect": [
80
- {
81
- "check": "Robotic symmetry",
82
- "span": "Neither figure includes bot traffic"
83
- }
84
- ]
85
- },
86
- {
87
- "file": "label-period-list.md",
88
- "expect": [
89
- {
90
- "check": "List-label periods and bare-noun bullets",
91
- "span": "**Latency.**"
92
- }
93
- ]
94
- },
95
- {
96
- "file": "unnamed-validator.md",
97
- "expect": [
98
- {
99
- "check": "Weasel attribution",
100
- "span": "An external audit"
101
- },
102
- {
103
- "check": "Weasel attribution",
104
- "span": "A\nthird-party checker"
105
- }
106
- ]
107
- },
108
- {
109
- "file": "classifier-scaffolding.md",
110
- "expect": [
111
- {
112
- "metric": "classifier_scaffolding",
113
- "span": "The economics are simple"
52
+ "file": "referent-cycling.md",
53
+ "expect": [
54
+ {
55
+ "metric": "referent_cluster",
56
+ "span": "local tools"
57
+ },
58
+ {
59
+ "check": "Synonym cycling",
60
+ "span": "the local checker"
61
+ }
62
+ ]
114
63
  },
115
64
  {
116
- "metric": "classifier_scaffolding",
117
- "span": "The dangerous part is"
65
+ "file": "mic-drop.md",
66
+ "expect": [
67
+ {
68
+ "metric": "verbless_fragment",
69
+ "span": "The review queue."
70
+ },
71
+ {
72
+ "check": "Antithesis pairs",
73
+ "span": "Different teams, different fixes, same bottleneck."
74
+ }
75
+ ]
118
76
  },
119
77
  {
120
- "metric": "classifier_scaffolding",
121
- "span": "has a simple cause"
78
+ "file": "caveat-template.md",
79
+ "expect": [
80
+ {
81
+ "check": "Robotic symmetry",
82
+ "span": "Neither figure includes bot traffic"
83
+ }
84
+ ]
122
85
  },
123
86
  {
124
- "metric": "classifier_scaffolding",
125
- "span": "limits worth stating"
87
+ "file": "label-period-list.md",
88
+ "expect": [
89
+ {
90
+ "check": "List-label periods and bare-noun bullets",
91
+ "span": "**Latency.**"
92
+ }
93
+ ]
126
94
  },
127
95
  {
128
- "metric": "classifier_scaffolding",
129
- "span": "One honest caveat"
96
+ "file": "unnamed-validator.md",
97
+ "expect": [
98
+ {
99
+ "check": "Weasel attribution",
100
+ "span": "An external audit"
101
+ },
102
+ {
103
+ "check": "Weasel attribution",
104
+ "span": "A\nthird-party checker"
105
+ }
106
+ ]
130
107
  },
131
108
  {
132
- "metric": "classifier_scaffolding",
133
- "span": "the ones that matter"
109
+ "file": "classifier-scaffolding.md",
110
+ "expect": [
111
+ {
112
+ "metric": "classifier_scaffolding",
113
+ "span": "The economics are simple"
114
+ },
115
+ {
116
+ "metric": "classifier_scaffolding",
117
+ "span": "The dangerous part is"
118
+ },
119
+ {
120
+ "metric": "classifier_scaffolding",
121
+ "span": "has a simple cause"
122
+ },
123
+ {
124
+ "metric": "classifier_scaffolding",
125
+ "span": "limits worth stating"
126
+ },
127
+ {
128
+ "metric": "classifier_scaffolding",
129
+ "span": "One honest caveat"
130
+ },
131
+ {
132
+ "metric": "classifier_scaffolding",
133
+ "span": "the ones that matter"
134
+ },
135
+ {
136
+ "check": "Interpretive metadiscourse",
137
+ "span": "It counts what it can see, and the rest is the reader's judgment."
138
+ }
139
+ ]
134
140
  },
135
141
  {
136
- "check": "Interpretive metadiscourse",
137
- "span": "It counts what it can see, and the rest is the reader's judgment."
142
+ "file": "plural-noun-fragment.md",
143
+ "expect": [
144
+ {
145
+ "metric": "verbless_fragment",
146
+ "span": "Same compound, three identifiers."
147
+ },
148
+ {
149
+ "metric": "verbless_fragment",
150
+ "span": "Same assay, two units."
151
+ }
152
+ ]
138
153
  }
139
- ]
140
- }
141
- ]
154
+ ]
142
155
  }
@@ -0,0 +1,4 @@
1
+ Every dataset you add widens the space of things that look relevant. Same
2
+ compound, three identifiers. Same assay, two units. Two sources that disagree
3
+ and no rule for which one wins. The model stays exactly as smart. The retrieval
4
+ gets ambiguous, and fluent ambiguity reads as confidence.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-slop",
3
- "version": "2.8.1",
3
+ "version": "2.8.2",
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"
@@ -332,6 +332,74 @@ FINITE_VERB = re.compile(
332
332
  r"|gate[sd]?|gives?|runs?|gets?|gave|gone)\b"
333
333
  r"|\b\w+(?:s|ed|es)\b", re.I)
334
334
 
335
+ # The -s half of that catch-all cannot tell a verb from a plural noun, and a
336
+ # plural noun in a fragment made the whole fragment invisible: "Same compound,
337
+ # three identifiers." and "Same assay, two units." both read as verbed on
338
+ # `identifiers` and `units`. Since fragments are mostly noun phrases, and noun
339
+ # phrases are mostly plural, the detector was blind to its own commonest shape.
340
+ #
341
+ # A determiner, number or quantifier immediately before an -s word makes it a
342
+ # noun ("three identifiers", "no rules"), not a verb ("the model runs" keeps
343
+ # its verb because "model" is not in this list). Same closed-list device as
344
+ # IMPERATIVE_OPENER above, and measured the same way: on data/corpus/
345
+ # must-not-flag it adds no findings at all.
346
+ NOUN_MARKER = frozenset("""
347
+ a an the this that these those my your his her its our their no some any
348
+ many several few both all each every another other more most much little
349
+ two three four five six seven eight nine ten dozen hundred thousand
350
+ one first second third next last same own other
351
+ of at in on for by with from to into onto over under about across through
352
+ without within per via against between during after before
353
+ """.split())
354
+ # The prepositions are there for the same reason as the determiners: what
355
+ # follows one is a noun, not a verb. "In innovation at scale." has two words
356
+ # that are verbs elsewhere ("scale", and "innovation" is safe), and without
357
+ # this the fragment reads as a sentence. An infinitive after "to" is not a
358
+ # finite verb either, so listing it here is correct rather than convenient.
359
+
360
+
361
+ # The base forms IMPERATIVE_OPENER already knows, reused away from the sentence
362
+ # opening. A plural subject takes a bare verb -- "our engineers ship weekly" --
363
+ # which carries no inflection for the catch-all to find, so without this the
364
+ # fix above turns every such sentence into a fragment.
365
+ BASE_VERBS = frozenset(
366
+ IMPERATIVE_OPENER.pattern
367
+ .split("(?:add|", 1)[1]
368
+ .split(")\\b", 1)[0]
369
+ .replace("|", " ")
370
+ .split()
371
+ ) | {"add"} | frozenset("""
372
+ work need mean seem feel matter differ vary exist remain happen occur tend
373
+ cost fail pass fit last agree apply depend belong arrive land stick
374
+ """.split())
375
+ # The extras are the stative and intransitive verbs a plural subject takes and
376
+ # the imperative list has no reason to carry -- you do not tell someone to
377
+ # "matter". "The tools work." was the fragment this produced without them. Each
378
+ # one is also a noun in some context ("the work", "the cost"), which the
379
+ # determiner test in _looks_like_noun already handles.
380
+
381
+
382
+ def _looks_like_noun(sent: str, start: int) -> bool:
383
+ """Is the word at `start` sitting in a noun phrase rather than a verb slot?"""
384
+ prev = re.findall(r"[A-Za-z][\w'-]*", sent[:start])
385
+ return bool(prev) and prev[-1].lower() in NOUN_MARKER
386
+
387
+
388
+ def _has_finite_verb(sent: str) -> bool:
389
+ for match in FINITE_VERB.finditer(sent):
390
+ word = match.group(0)
391
+ # An explicit auxiliary or an -ed form is a verb wherever it appears.
392
+ if not re.fullmatch(r"\w+(?:s|es)", word, re.I):
393
+ return True
394
+ if not _looks_like_noun(sent, match.start()):
395
+ return True # "the model runs"
396
+ # "three identifiers" is a noun, "one report" is a noun, but "engineers
397
+ # ship" is a verb: the same determiner test decides both.
398
+ for match in re.finditer(r"\b[A-Za-z][\w'-]*\b", sent):
399
+ if match.group(0).lower() in BASE_VERBS and not _looks_like_noun(sent, match.start()):
400
+ return True
401
+ return False
402
+
335
403
 
336
404
  def _sentences(prose: str) -> list[str]:
337
405
  return [x.strip() for x in re.split(r"(?<=[.!?])\s+", prose) if x.strip()]
@@ -347,7 +415,7 @@ def verbless_fragments(prose: str) -> list[str]:
347
415
  continue
348
416
  if IMPERATIVE_OPENER.match(sent):
349
417
  continue
350
- if not FINITE_VERB.search(sent):
418
+ if not _has_finite_verb(sent):
351
419
  out.append(sent)
352
420
  return out
353
421