AI & Automation
No Slop — De-Slop Writing Engine
No Slop takes AI-generated text and strips its surface tells — em-dash overload, curly quotes, emoji, filler openers, throat-clearing transitions, and inflated vocabulary — then shows exactly what changed and scores how sloppy the original was. A single engine (deslop, slopScore, flagsFor) powers a browser app, an HTTP API hosted from inside Vite's own dev server, a CLI, and an MCP server, so a rule changed once updates everywhere at once — and a second mode rewrites a draft entirely in a voice learned from the author's own past writing. The same engine also exists as a line-by-line Django/DRF port, built to match a target job posting's stack and checked for parity against the original rather than assumed equivalent.
Stack
Concepts
How it works
The whole flow, traced from your first tap.
- 01
Browser SPA
Paste text, watch it clean live
Typing or pasting runs the same deslop() call the API and CLI use, and a word-level LCS diff renders swaps and removals inline as you go, guarded against the cost of diffing very large pastes.
Live de-slop on inputWord-level LCS diffBlowup-guarded on huge pastes - 02
Slopometer
See exactly how sloppy it was
slopScore() normalizes per 100 words and weights the 'it's not X, it's Y' antithesis pattern far above any other signal, with an exponential saturation curve so one long paste doesn't just max the score out linearly.
Weighted, saturating 0-100 scorePer-category breakdown - 03
flagsFor()
Some tells get flagged, not fixed
Constructions like 'not just X but Y' or 'X isn't Y, it's Z' are deliberately left unedited — each flag carries its own rewrite instruction instead, because safely collapsing that pattern takes a human judgment call the engine won't fake.
Antithesis family regexPer-flag fix guidance - 04
vite.config.js / cli/deslop.mjs / mcp/server.mjs
Same engine, four doors in
There's no separate Express server — a custom Vite plugin's configureServer hook serves the API directly, checks the request's Origin against localhost, the CLI is a thin wrapper around the exact same exported functions, and an MCP server exposes deslop and slop_score as tools any MCP client can call directly — so one rule change updates the browser, API, CLI, and agent tools at once.
API hosted inside Vite's dev serverOrigin-checked CSRF guardIdentical CLI wrapperMCP tool server - 05
django_api/deslop/engine.py
Port the engine to the target stack
engine.py mirrors deslop.js with no Django import, wrapped in DRF views, serializers, and a rate-limit throttle standing in for the Node origin guard. A four-input parity check against the original engine caught a real bug on review — Python's banker's rounding vs. JS's Math.round() at .5 boundaries — fixed and pinned with its own regression test rather than left to chance.
Line-by-line Python portDRF serializers + custom PlainTextParserAnonRateThrottle14 APITestCase tests - 06
/api/rewrite
Ask for the full rewrite
selectExamples() blends a fixed seed pair with a learned, growing style corpus, ranks them by plain term-frequency cosine similarity with no embeddings involved, and builds a few-shot prompt that a local CLI model uses to produce a full voice-matched rewrite.
Lexical cosine retrievalGrowing style corpusLocal Claude/Codex CLI - 07
Examples tab
Test it against real drafts
A library of the author's own past draft captions, each tagged by where it sits in the writing pipeline, gets scored and sorted worst-first — doubling as both a running regression set for the engine and a gallery of what the target voice actually sounds like.
Pipeline-staged corpusWorst-first sort
The problem
AI-written text has recognizable tells — em dashes everywhere, 'it's not just X, it's Y' constructions, hedging opens, hype words — and mechanically swapping them out isn't the whole fix: word-level detectors are already being replaced by ones that read sentence-structure burstiness, so a tool that only launders vocabulary will still read as generated.
What we built
One dependency-free engine in src/deslop.js does the real work — regex-driven word and phrase swap tables, opener and transition detection, and a separate flagsFor() pass that only detects the antithesis family of constructions rather than trying to auto-fix them. That same engine backs a live browser diff view, a POST /api/deslop endpoint served by a custom Vite plugin rather than a separate server, and a CLI for piping text through the terminal.
Rules that know their own limits
Fixes are tiered on purpose: AUTO rules (punctuation, filler openers, vocabulary, hype phrases) get rewritten automatically, FLAG rules (the 'it's not X, it's Y' antithesis family) are only surfaced with a fix instruction because rewriting them safely takes judgment, and a further HUMAN tier covering structural issues is advisory-only, documented in no-slop-rules.md rather than encoded as code. The slopometer score weights that antithesis flip pattern hardest of any signal, since it's treated as the single most reliable tell.
A second mode: rewriting in a real voice
Past the mechanical pass, a Rewrite tab calls a local Claude or Codex CLI to fully rewrite a draft in a learned voice — built from a hand-picked seed example plus a growing corpus of past examples, retrieved by plain lexical cosine similarity rather than embeddings, and assembled into a few-shot prompt. The tool tests itself against a real corpus of the author's own draft captions, tagged by pipeline stage and sorted worst-first by slop score, so the examples browser doubles as both a regression set and a gallery of the voice it's aiming for.
A Django/DRF twin backend
django_api/ ports the same engine line-by-line to pure Python (no Django import in engine.py), fronted by Django REST Framework: a custom PlainTextParser so raw text/plain bodies work like the Node API, a serializer enforcing the same 4MB cap with a real 400, and AnonRateThrottle standing in for the Node origin-header guard. Parity wasn't assumed — four representative inputs were run through both engines and diffed field-by-field (clean text, fix counts/categories, slop score, signal breakdown), and a fresh adversarial review pass caught a real divergence anyway: Python's banker's rounding disagreed with JS's Math.round() at exact .5 boundaries, fixed with a _js_round() helper and pinned with its own regression test. The same review pass also deleted a dead serializer whose docstring falsely claimed test coverage, and fixed a throttle test that would have passed even if throttling were completely broken. 14 DRF tests now guard the port, verified green from a fresh venv install.
Outcome
A single dev-server process serves the browser tool, the API, and the CLI identically at a pinned local port, with an origin check guarding the API against anything but localhost. It runs as a standing part of a personal writing workflow — piped from the clipboard, wired into an editor shortcut, given to a coding agent as a rule to run every final draft through before handing it back, or called as an MCP tool directly from an agent's own context.
Interested in something similar?
Tell us what you need and we'll figure out how to ship it.