Skip to main content

unslopify

test PyPI Python License: MIT

Audit and rewrite text to remove AI-writing patterns. A deterministic Python core finds the named failures. An agent skill runs the rewrite loop until a fresh-context judge cannot tell a model touched the text. Calibrated against pre-LLM prose: 0.28 named findings per 1,000 words across Twain, Darwin, Austen, Doyle, and an RFC (numbers below).

Install

pip install unslopify

Or run it without installing:

uvx unslopify draft.md

30 seconds

$ unslopify draft.md
draft.md:1: [scene-setting] "In today's fast-paced digital world"
    A weather-report opener about today's fast-moving world before the actual topic.
draft.md:1: [inflated-contrast] "is not just"
    A plain statement dressed up as a reveal by denying a smaller version of itself first.

FAIL draft.md: 2 findings, 0 mechanics, 0 bank

Before:

In today's fast-paced digital world, this release is not just an
update. It is a testament to our team's unwavering commitment,
ensuring a seamless experience for every user.

After:

This release fixes the checkout crash and cuts page load from 3 s
to 1 s. Both changes came out of the June incident review.

What it checks

Four categories of named types. unslopify types prints all of them with definitions and examples, and RULES.md is the full catalog with a before and after pair per rule.

  • formula: manufactured rhythm. Inflated contrast, negative parallelism, slogan fragments, stock triads, fake authority, canned conclusions, AI vocabulary clusters, significance inflation.
  • substance: claims a reader cannot verify. Empty abstraction, tacked-on benefits, process instead of reason, unsupported claims.
  • wording: buried points. Bureaucratic phrasing, hedge stacks, unexplained jargon, copula avoidance, overlong sentences, abstract noun stacks, and sentences with no concrete anchor.
  • structure: packaging that delays the point. Scene-setting, request restatement, meta-announcements, redundant conclusions, over-structure.

Types are contextual signals with severities and thresholds, not a banned-word list. Mechanical checks run alongside: em and en dashes, curly quotes, sentence length, semicolon density, and 4-word phrases repeated inside one document.

CLI

unslopify DRAFT.md              # audit; exit 0 pass, 1 findings
unslopify docs/ README.md       # many files; directories recurse (.md .txt .rst)
cat draft.txt | unslopify       # audit stdin
unslopify DRAFT.md --json       # full report as JSON
unslopify DRAFT.md --brief      # rewrite instructions for a model or human
unslopify DRAFT.md --fix        # safe mechanical fixes to stdout
unslopify DRAFT.md --fix -w     # apply the fixes in place
unslopify DRAFT.md --bank       # also check the cross-document phrase bank
unslopify commit DRAFT.md --id blog-2026-08   # bank a finished document
unslopify types                 # print the rubric
unslopify DRAFT.md --level strict   # halve soft allowances (or: relaxed)

Color respects NO_COLOR and FORCE_COLOR. Exit codes are stable: 0 pass, 1 findings in any file, 2 usage or IO error. Soft-type thresholds and the repeated-phrase allowance scale with document length, so long reports are judged at their own density, not a memo's. --level (also a config key) halves or doubles the soft allowances; hard rules are unaffected.

Suppression and config

Inline, in any file:

<!-- unslopify:disable -->                    skip this file
<!-- unslopify:disable=fake-authority,dash -->  disable listed types or checks
some sentence  <!-- unslopify:disable-line -->  suppress this line

Pragmas inside code fences are documentation and do nothing. Project-wide, in pyproject.toml:

[tool.unslopify]
disable = ["promo-tone"]
exclude = ["CHANGELOG.md", "vendored/*"]
max-sentence-words = 40

CI gate

Pre-commit:

repos:
  - repo: https://github.com/youngfreezy/unslopify
    rev: v0.4.0
    hooks:
      - id: unslopify

GitHub Actions:

- uses: youngfreezy/unslopify@v0.4.0
  with:
    paths: docs/ README.md

Both fail on findings, so generated slop cannot merge quietly. This repo runs its own audit in CI on every push.

The phrase bank lives at ~/.unslopify/phrase_bank.jsonl (override with UNSLOPIFY_HOME). Committing a finished document banks its 8-word phrases, and future drafts that reuse any of them fail the --bank check. This is what stops a writer, or an agent, from developing stamps.

Claude Code plugin: the in-loop gate with memory

/plugin marketplace add youngfreezy/unslopify
/plugin install unslopify@unslopify

A Stop hook audits every completed response (needs pip install unslopify on the hook's python3). On a fail it requests exactly one clearer rewrite with the findings as the brief; the stop_hook_active guard makes a retry loop impossible. Responses that pass are banked, and that is the part nothing else does: when the agent starts reusing the same 8-word phrasing across responses, the hook quotes the repeated spans back and demands different words, not reshuffled ones. Clean-each-time is not the bar; clean-and-not-a-stamp is.

Where it sits

humanizer is a prompt-only skill: it rewrites by instruction, with nothing deterministic to gate on. nopus gates coding-agent responses in-loop with statistical prose measures, and does that well; it has no document mode, no named rules, and no memory. Vale and proselint lint prose against style rules but know nothing about AI-writing patterns. unslopify is the document linter and CI gate with named, sourced rules, plus the same in-loop enforcement, plus the one thing none of them have: the phrase bank, which remembers how everything before was worded and fails repetition across documents and responses. A linter tells you this text is bad. This one also tells you that you are starting to sound like yourself on autopilot.

Agent skill

SKILL.md turns any harness that supports skills into the full rewrite loop: audit, rewrite against named findings, judgment pass, uniqueness gate, fresh-context judge, final PASS. It also defines an always-on mode that applies the writing rules to every reply.

Install:

  • Any agent with the skills CLI: npx skills add youngfreezy/unslopify.
  • Claude Code: copy SKILL.md into ~/.claude/skills/unslopify/, then invoke with /unslopify.
  • Cursor: paste the rules from SKILL.md Mode 1 into a project or user rule, and use the CLI in the terminal for audits.
  • Codex / other: include SKILL.md in the system context and expose the unslopify CLI.

Using it once installed:

  • /unslopify followed by pasted text, or by a file path, runs the rewrite pipeline on that draft and returns the clean text plus a short log of findings fixed and the judge's verdict.
  • "use unslopify in every reply" turns on always-on mode for the session: the agent writes under the skill's rules from then on. Put that sentence in a standing rule to make it permanent.
  • The agent shells out to this package's CLI for the audit and the phrase bank, so pip install unslopify makes the gates real instead of self-graded.

Library

from unslopify import audit_text, build_brief, profile_from_sample

report = audit_text(open("draft.md").read(), source="draft.md")
print(report.verdict, report.counts_by_category)
print(build_brief(report))          # instructions for the rewrite pass
voice = profile_from_sample(open("my_writing.md").read())

All models are Pydantic v2. report.model_dump_json() gives a stable, versioned record of every audit, which is also the hook for analytics in hosted deployments.

Pipeline

pipeline

The always-on mode is simpler: a standing rule loads the skill, and the skill's writing rules apply to every outgoing message.

always-on

Design notes

  • The audit is deterministic. Same text in, same report out, no model calls, no network. Judgment-only types (meaning loss, jargon the regexes miss) are the agent's job and are marked as such in the rubric.
  • Edits are events, not vibes. The mechanical pass records every change as a typed RewriteEvent (line, rule id, before, after) under a StylePolicy. The skill has the agent log its own edits the same way, so a finished rewrite ships with the exact record of what moved.
  • The judge must be fresh. A model that watched the rewrite approves its own choices, so the skill requires a separate context for the final read.
  • Voice is preserved, not replaced. The optional VoiceProfile measures the author's sentence lengths, contraction rate, and first-person rate, and the rewrite aims at those numbers.

Detection

Calibration answers what the audit flags in old human prose; detection answers whether it separates model output from human writing at all. scripts/detection.py runs the named-type verdict (mechanics disabled) over fixtures/llm/, eight genuine LLM-written samples labeled as such in each file, against the five pre-LLM human sources:

detection rate: 8/8 LLM samples flagged
false-positive rate: 0/5 human sources flagged

Add your own labeled samples to fixtures/llm/ and fixtures/human/ and rerun it on your material. The fixtures are default-assistant register; adversarially plain model output will beat a static rubric, which is why the agent skill adds the fresh-context judge on top.

Calibration on pre-LLM prose

The obvious question for any tool like this: what does it flag in text written before language models existed? scripts/calibrate.py fetches five public sources (Twain 1883, Darwin 1859, Austen 1813, Doyle 1892, RFC 7231 from 2014), audits 5,000 words of each, and prints the table. Run it yourself; the numbers below are from 2026-08-18.

source                                     words  named  mech
Twain, Life on the Mississippi (1883)       5011      1    92
Darwin, On the Origin of Species (1859)     5003      3   206
Austen, Pride and Prejudice (1813)          5002      0   192
Doyle, Adventures of Sherlock Holmes (1892) 5001      2   289
RFC 7231, HTTP/1.1 Semantics (2014)         5004      1   235

named findings per 1,000 words: 0.28 (7 in 25,021)

The two columns are different claims and should be read differently.

Named types claim "this is a generated-text pattern", so hits on 1813 prose are false positives. There were 7 in 25,021 words. The script lists each one: "in order to" three times (Twain, Doyle, the RFC), two stacked hedges Darwin genuinely wrote, and one true artifact, Darwin's chapter-contents listing tripping the slogan-fragment shape. No pre-LLM source hit inflated contrast, fake authority, AI vocabulary, tacked-on benefits, or scene-setting.

Mechanics are style gates for modern professional writing, not AI claims. They fire exactly where you would expect: Victorian sentence lengths, typographic quotes in the Gutenberg files, dashes, and the RFC's repeated boilerplate (page headers repeat as n-grams). If you are linting literature rather than a work document, raise --max-sentence-words and read the mechanics column as description, not verdict.

Further reading on plain technical writing: Google developer documentation style guide.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unslopify-0.4.0.tar.gz (465.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unslopify-0.4.0-py3-none-any.whl (33.0 kB view details)

Uploaded Python 3

File details

Details for the file unslopify-0.4.0.tar.gz.

File metadata

  • Download URL: unslopify-0.4.0.tar.gz
  • Upload date:
  • Size: 465.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for unslopify-0.4.0.tar.gz
Algorithm Hash digest
SHA256 ff9cb466ea0fcee4ecfe035f7eb32bb88658053188b137d0f366e902e3fdc10a
MD5 a8d4471dfa672f180d501ddbec602478
BLAKE2b-256 39be09f000f98b870a8b7eefd49a31f997b0fa4e3badca61e15c7193641f1ecf

See more details on using hashes here.

File details

Details for the file unslopify-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: unslopify-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 33.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for unslopify-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4a7e652644848d3c3e3bf248a1ea4ea99e93a2e4e8e431e5c0aad68c4ff17a0c
MD5 e23e18384f66c38c27614090358ab1e2
BLAKE2b-256 bb9fa307853d35f197ce17ba8df3ec500b11e3ca6e597fab52cda3f92a58f7b8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page