slop-lint
Purpose
Deterministically measure how much a piece of prose relies on AI-writing tells
— negative parallelism, significance inflation, uniform rhythm, formatting
reflexes, era-tagged vocabulary, leaked chatbot artifacts — and report
span-anchored findings plus a 0-100 cluster score. The script is pure Python
stdlib: no network, no LLM, same input → same output. It fixes the observed
failure of prompt-only "humanizer" packs: judgments by feel, no numbers, no CI
gate, and single-tell overreactions (an em dash is not a verdict).
When to use
- "score / scan / lint / check this text for AI tells or slop"
- "how AI-sounding is this?" — anything wanting a number or a report
- Gating docs or marketing copy in CI before publishing
- Producing the findings a rewrite pass will edit against
When NOT to use
- Rewriting or de-slopping text → use
deslop (it runs this lint first).
- Checking whether an edit changed facts/quotes/meaning → use
deslop-verify.
- Judging whether a specific person or student used AI — this is a style lint,
never an authorship detector; refuse accusation framings.
- Linting code, grammar, or spelling.
Workflow
Pick the genre profile: general (default), academic, technical
(docs/READMEs), or casual. When unsure, say which you chose and why.
Run the script (span-anchored findings + score):
python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" FILE... --genre technical
python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" --json < draft.md # machine-readable
Report to the user: the score and band, which families are active, and the
top flagged lines (the report includes line numbers). Always state that the
score reflects a cluster of co-occurring tells — never conclude anything
from one family, and never frame results as proof of AI authorship.
If confidence is low (under 150 words / 8 sentences), say the text is
too short for a reliable score and report values without a verdict.
For CI gating, use the exit-code contract:
python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" docs/*.md --genre technical --fail-above slop-cluster
Exit 0 = pass, 1 = usage/input error, 2 = a confidently-scored file reached
the --fail-above band. Recommend starting with --fail-above heavy-slop
(warn-first rollout) before tightening to slop-cluster.
Sanity check after any script edit: --self-test must print all PASS.
Output spec
A report per file: score (0-100), band (clean / mild / slop-cluster /
heavy-slop), confidence, active_families, and per-family value, band,
and hits with line numbers. JSON mode emits the same as schema: 1 JSON.
Bands: <25 clean, 25-49 mild, 50-74 slop-cluster, 75+ heavy-slop. A single
active family caps the score at 24; two cap it at 49 — except leaked chatbot
artifacts (oaicite, "As an AI", utm_source=chatgpt...), which force ≥50
because their false-positive rate is near zero.
Gotchas
- Em dashes are weak evidence. Human baseline is ~3.23/1k words with a
huge range, and current models are vendor-suppressed below it. The dash
family carries minimal weight; never present dash counts as a conclusion.
- Hedging is only scored when stacked ("may potentially"). Academic prose
is legitimately hedge-dense; flagging raw hedges punishes real scholars.
- Fairness: the underlying signals overlap legitimate ESL, formal, and
neurodivergent writing styles. The cluster gate and genre profiles exist to
protect those writers — do not bypass them, and never report a score as
evidence about the writer.
- Thresholds decay. Lexical tells die when vendors patch them (delve is
gone; em dashes are fading). Thresholds and the era lexicon are dated
engineering priors — see
references/thresholds.md for the 6-12 month
recalibration protocol before trusting them long-term.
- Code blocks are stripped before scoring; a file that is mostly code will
come back low-confidence. Score the prose, not the repo.
- The heuristics are tuned for English prose (ASCII word tokenization);
scores on other languages or heavily accented text are unreliable — say so
rather than reporting them as fact.
Pointers
scripts/slop_lint.py — the scorer (--help, --self-test).
references/thresholds.md — evidence per family, threshold provenance,
era-lexicon sourcing, fairness rationale, recalibration protocol.
1---2name: slop-lint3description: Scores copy 0-100 for AI-writing tells with a deterministic script - measures em dash rate, hedge-stack density and other cluster rates against genre-aware thresholds, with span-anchored findings and CI gating via exit codes. Use to scan, score, lint, measure or gate text for AI-sounding patterns. Detect-only - not for rewriting, verifying an edit, or code linting.4---56# slop-lint78## Purpose910Deterministically measure how much a piece of prose relies on AI-writing tells11— negative parallelism, significance inflation, uniform rhythm, formatting12reflexes, era-tagged vocabulary, leaked chatbot artifacts — and report13span-anchored findings plus a 0-100 cluster score. The script is pure Python14stdlib: no network, no LLM, same input → same output. It fixes the observed15failure of prompt-only "humanizer" packs: judgments by feel, no numbers, no CI16gate, and single-tell overreactions (an em dash is not a verdict).1718## When to use1920- "score / scan / lint / check this text for AI tells or slop"21- "how AI-sounding is this?" — anything wanting a number or a report22- Gating docs or marketing copy in CI before publishing23- Producing the findings a rewrite pass will edit against2425## When NOT to use2627- Rewriting or de-slopping text → use `deslop` (it runs this lint first).28- Checking whether an edit changed facts/quotes/meaning → use `deslop-verify`.29- Judging whether a specific person or student used AI — this is a style lint,30 never an authorship detector; refuse accusation framings.31- Linting code, grammar, or spelling.3233## Workflow34351. Pick the genre profile: `general` (default), `academic`, `technical`36 (docs/READMEs), or `casual`. When unsure, say which you chose and why.372. Run the script (span-anchored findings + score):3839 ```bash40 python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" FILE... --genre technical41 python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" --json < draft.md # machine-readable42 ```43443. Report to the user: the score and band, which families are active, and the45 top flagged lines (the report includes line numbers). Always state that the46 score reflects a **cluster** of co-occurring tells — never conclude anything47 from one family, and never frame results as proof of AI authorship.484. If `confidence` is `low` (under 150 words / 8 sentences), say the text is49 too short for a reliable score and report values without a verdict.505. For CI gating, use the exit-code contract:5152 ```bash53 python3 "${CLAUDE_SKILL_DIR}/scripts/slop_lint.py" docs/*.md --genre technical --fail-above slop-cluster54 ```5556 Exit 0 = pass, 1 = usage/input error, 2 = a confidently-scored file reached57 the `--fail-above` band. Recommend starting with `--fail-above heavy-slop`58 (warn-first rollout) before tightening to `slop-cluster`.596. Sanity check after any script edit: `--self-test` must print all PASS.6061## Output spec6263A report per file: `score` (0-100), `band` (clean / mild / slop-cluster /64heavy-slop), `confidence`, `active_families`, and per-family `value`, `band`,65and `hits` with line numbers. JSON mode emits the same as `schema: 1` JSON.66Bands: <25 clean, 25-49 mild, 50-74 slop-cluster, 75+ heavy-slop. A single67active family caps the score at 24; two cap it at 49 — except leaked chatbot68artifacts (`oaicite`, "As an AI", `utm_source=chatgpt`...), which force ≥5069because their false-positive rate is near zero.7071## Gotchas7273- **Em dashes are weak evidence.** Human baseline is ~3.23/1k words with a74 huge range, and current models are vendor-suppressed below it. The dash75 family carries minimal weight; never present dash counts as a conclusion.76- **Hedging is only scored when stacked** ("may potentially"). Academic prose77 is legitimately hedge-dense; flagging raw hedges punishes real scholars.78- **Fairness**: the underlying signals overlap legitimate ESL, formal, and79 neurodivergent writing styles. The cluster gate and genre profiles exist to80 protect those writers — do not bypass them, and never report a score as81 evidence about the writer.82- **Thresholds decay.** Lexical tells die when vendors patch them (delve is83 gone; em dashes are fading). Thresholds and the era lexicon are dated84 engineering priors — see `references/thresholds.md` for the 6-12 month85 recalibration protocol before trusting them long-term.86- Code blocks are stripped before scoring; a file that is mostly code will87 come back low-confidence. Score the prose, not the repo.88- The heuristics are tuned for English prose (ASCII word tokenization);89 scores on other languages or heavily accented text are unreliable — say so90 rather than reporting them as fact.9192## Pointers9394- `scripts/slop_lint.py` — the scorer (`--help`, `--self-test`).95- `references/thresholds.md` — evidence per family, threshold provenance,96 era-lexicon sourcing, fairness rationale, recalibration protocol.