Plugin · Tools · AI · Claude · Claude Code
AI Writing Rules
A Claude Code plugin that stops Claude writing like an AI. It loads a 270-word ruleset into every session, then runs a checker after every file write that flags the tells by line number and refuses to let the model move on. The rules come from 29 documented patterns, each researched into its own file with a measurable threshold: burstiness under 0.4 where human prose sits at 0.6 to 1.2, more than 20 em dashes per 1,000 words where humans use 3.2 to 10, more than one polished three-item list per 200 words. The checker is 17 regex rules and 3 statistical ones in Python with no dependencies. Tested against a sample with 14 planted tells, it caught all 14, stayed silent on clean prose, and found a real tricolon in the research files themselves.
- Python
- Role
- Research, design and development
- Timeline
- 1 day
- Year
- 2026
- Status
- Completed
01 / The overview
I had a writing-rules document I pasted into project after project. It worked while it stayed in context and stopped working the moment a session got long or I forgot to include it. A document that has to be remembered is a document that gets ignored.
This plugin turns that document into something the harness enforces. Rules load at session start whether or not anyone remembers them, and a checker reads every markdown file Claude writes and reports what it finds. When it finds something, the model fixes it before continuing.
The rules themselves got rebuilt from research rather than from instinct. Wikipedia maintains a page on signs of AI writing that catalogues the tells by era, which matters because the list drifts as models change. "Delve" and "tapestry" dominated 2023. By mid-2025 the common ones were "emphasizing", "enhance", "highlighting" and "showcasing". Alongside that, several groups have measured the patterns rather than only listing them, which is where the thresholds come from.
The finding that changed the design: em dashes are the famous tell and the least useful one now, because everyone scrubs them. Sentence-length uniformity is the tell nobody bothers to fix. Human prose has a burstiness of 0.6 to 1.2, measured as sentence-length standard deviation over the mean. Model output sits at 0.2 to 0.4. That is a number a script can compute, which makes it enforceable in a way that "write with more personality" never was.
02 / The challenge
Enforcement is the hard half. A skill only fires when the model decides it is relevant, and a model deep in a task does not decide that reliably. The rules had to sit somewhere the model does not get a vote.
Claude Code's hook system solves it, but the exit codes matter more than they look. A PostToolUse hook that exits 0 writes to the transcript and the model never sees it. Exit 2 sends stderr back into the conversation as something to act on. The whole enforcement mechanism is that one difference, so the checker had to be confident enough to justify interrupting a task on every finding.
Confidence meant fighting false positives. The first version flagged code samples, because a shell heredoc full of the word "utilize" is not prose. Fenced blocks and inline code now get blanked before anything is scanned. It also flagged the research files, which is a genuine problem: a catalogue of bad examples is nothing but bad examples, so those files carry an opt-out marker.
Several patterns resisted regex on the first pass. Negative parallelism is the clearest AI sentence shape there is, and "This isn't just a tool, it's a paradigm shift" slipped straight through because the pattern only handled "is not" and never the contraction. Transition stacking failed because "Furthermore" and "Moreover" often open a sentence mid-paragraph rather than a line. The three-item-list threshold of one per 200 words under-fires on a short document, so it scales with word count now.
The last problem was the checker's own credibility. A tool that lints prose has to survive its own linting or nobody will trust it. The first clean run over the research folder found a tricolon I had written two hours earlier, in the file arguing against tricolons.
03 / The solution
Three surfaces, each covering what the others cannot.
A SessionStart hook prints 270 words of hard rules into context on every session. No em dashes, straight quotes, sentence case headings, no "not just X, it's Y", break the three-item-list reflex, name the source or drop the claim, plus the vocabulary to avoid and the plain-word substitutions. It costs almost nothing per session and it is there before the first prompt.
A PostToolUse hook runs the checker after every Write and Edit on markdown, mdx, txt and rst. It reports the rule, the line number, the matched text and the fix, then exits 2 so the model has to deal with it. Seventeen regex rules cover em dashes, curly quotes, negative parallelism, the vocabulary list, inflated verbs, dodged copulas, puffed significance, vague attribution, empty openers, wrap-up endings, hedge stacks, filler, assistant scaffolding, leaked model artifacts, Title Case headings, inline-header bullets, and trailing participle commentary. Three statistical rules handle what regex cannot: three-item-list density, transition stacking, and burstiness.
The skill carries the full ruleset and an index into the research, so the model can open the one file it needs instead of loading 29. A /deslop command audits a file, rewrites it, then re-runs the checker to confirm it comes back clean.
The research folder is one file per pattern: what it is, why models produce it, the threshold where one exists, a bad example, the rewrite, and the fix. Keeping them separate means the skill stays small and the detail is there when a specific tell needs explaining.
It runs on Python 3 with nothing from PyPI, and exits quietly if Python is missing so the session-start rules still work. Verified on a sample carrying 14 deliberate tells, where it found all 14 and stayed silent on clean prose. The README passes its own checker, which felt like the minimum bar.
FAQ
About this project
Does it lint my code?
No. It only reads .md, .mdx, .markdown, .txt and .rst files, and inside those it blanks out fenced code blocks and inline code before scanning. A README quoting a shell command full of banned words comes back clean. Code identifiers, variable names and API surfaces are never touched, because the rules are about prose a human reads rather than about naming things.
What happens when it finds something?
The hook writes the findings to stderr and exits 2, which puts them back into the conversation as something Claude has to act on. Each finding names the rule, the line number, the matched text and the fix, for example "negative parallelism, L3: isn't just a tool, it's, delete the negation and keep the positive half". Findings are capped at four examples per rule so a messy file does not flood the transcript.
How does it detect sentence rhythm?
Burstiness, which is the standard deviation of sentence length divided by the mean. Human writing measures 0.6 to 1.2 and model output measures 0.2 to 0.4, so anything under 0.4 gets flagged. It only runs on documents of ten sentences or more, since the statistic is meaningless on a short file. Several analyses now call cadence uniformity the strongest remaining tell, ahead of em dashes, because everyone scrubs em dashes and nobody varies their sentence length on purpose.
What if a file needs to break the rules?
Add the marker slop-check: off inside an HTML comment anywhere in the file. Every file in the research folder carries it, since a catalogue of bad examples is made entirely of bad examples. It is a whole-file switch rather than per-line, which is deliberate: a file needing line-by-line exemptions is usually a file that should be split.
Are these rules just personal taste?
Some are. The em dash ban is stricter than the evidence requires, since human prose does use 3.2 to 10 per 1,000 words and the research only supports flagging above 20. I ban them outright because they read as machine-written to most people now, and the cost of writing a comma instead is zero. The rest come from Wikipedia's catalogue of AI writing signs and from published measurement work, with thresholds taken from the source rather than invented.
Does it work without Python?
The checker does not, but it fails quietly rather than breaking your session. The wrapper script checks for python3 and exits 0 if it is missing, so the session-start rules and the skill keep working and you lose only the automatic checking. Python 3 ships with macOS and most Linux distributions, and there are no packages to install because the checker uses the standard library alone.
Why one file per pattern instead of one long document?
Context cost. A single document covering all 29 patterns with examples runs long, and loading it to fix one tell wastes most of what it loads. Split up, the skill holds a short index and the model opens the one file matching what the checker flagged. It also made the research better, since a file existing only to explain trailing participle phrases has nowhere to hide a vague paragraph.