# AI isn't slop. It's just the generic average. And not you

_My agent's PR description had 10 AI tells in 99 words. My version of the same PR had 0._

## What's in it for you?

**An agent that sounds like you. Not the generic average of everyone.**

voiceprint is my framework for that, a Claude Code plugin, a few dozen iterations in. It finds how you wrote before AI, and checks every PR, commit and Slack message the agent sends against it.

It can't do everything. What it will try to do is find your own voice. What makes you you. And make you sound a little better.

I love Claude. I love AI and the power it brings to my hectic ADHD brain. The main problem I have is how other people understand what I wrote. My goal is always to make it sound human. Or more exactly, to sound like me.

Sometimes it feels impossible. You ask Claude to write a text a human will want to read, and it writes bullshit. Not slop. Just the generic average. And not you.

## What the average sounds like

This is what the agent wanted to post, word for word:

```text
This PR fundamentally addresses a subtle but load-bearing gap in how the hook
parses shell commands — specifically, how it handles comments. Previously, an
apostrophe inside a comment (e.g. "# Don't push yet") would quietly open a
single quote that was never closed, which meant every subsequent command
substitution was treated as plain text. As a result, the PR body was never
actually scored. This change deliberately strips unquoted comments before
tokenizing, ensuring that the guard remains robust against this common
pattern. It's not just a bug fix; it's a meaningful step toward a more
comprehensive and reliable guard.
```

Nothing in it is wrong. It's just nobody. Same PR, the way I write it:

```text
hook missed PR bodies when a comment came first
"# Don't push yet" on the line before opened a single quote that never closed
so the $(cat <<EOF) body after it was never read and never scored
now comments are dropped before parsing
```

Same facts. Zero AI tells. The agent's version had 10 in 99 words: fundamentally, load-bearing, quietly, deliberately, robust, comprehensive, an em dash, "It's not just", and 2 words from my own list (more on that below).

voiceprint turns that into a score. Every AI word is 1 point, an em dash or "It's not just X" is 2. Only the 3 worst count, per 100 words. The agent's draft scores 5. Mine scores 0. My own writing almost never passes 2, so 2 is my line.

<style>
.vp-fig{margin:2.2rem 0;font-family:var(--sans)}
.vp-fig figcaption{font-size:.8rem;color:var(--muted);text-align:center;margin-top:.8rem;line-height:1.5}
.vp-card{background:var(--card);border:1px solid var(--rule);border-radius:12px;box-shadow:var(--shadow)}
.vp-bars{padding:1.2rem 1.4rem 1rem}
.vp-row{display:grid;grid-template-columns:6.5rem 1fr 1.6rem;gap:.8rem;align-items:center;margin:.7rem 0;font-size:.85rem;color:var(--fg)}
.vp-track{position:relative;height:1.5rem;background:var(--bg-soft);border-radius:999px}
.vp-bar{height:100%;border-radius:999px;background:var(--muted)}
.vp-bad{background:var(--accent);min-width:1.5rem}
.vp-line{position:absolute;top:-.4rem;bottom:-.4rem;width:2px;background:var(--fg);opacity:.55;border-radius:2px}
.vp-row b{font-size:1.05rem;text-align:right;font-variant-numeric:tabular-nums}
.vp-scale{position:relative;margin-left:7.3rem;margin-right:2.4rem;height:1.1rem;font-size:.72rem;color:var(--muted)}
.vp-scale span{position:absolute;transform:translateX(-50%);white-space:nowrap}
figure.vp-fig ol.vp-flow{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(4,1fr);gap:1rem}
figure.vp-fig .vp-flow>li{position:relative;padding:1rem .95rem;margin:0}
.vp-flow li:not(:last-child)::after{content:"";position:absolute;right:-.72rem;top:50%;width:.45rem;height:.45rem;border-top:2px solid var(--muted);border-right:2px solid var(--muted);transform:translateY(-50%) rotate(45deg)}
.vp-n{display:inline-grid;place-items:center;width:1.7rem;height:1.7rem;border-radius:999px;background:var(--accent-soft);color:var(--accent);font-weight:700;font-size:.8rem}
.vp-flow strong{display:block;margin:.6rem 0 .3rem;font-size:.95rem;color:var(--fg)}
.vp-flow p{margin:0;font-size:.8rem;line-height:1.45;color:var(--muted)}
.vp-chips{display:flex;gap:.3rem;flex-wrap:wrap}
.vp-chips span{border:1px solid var(--rule);background:var(--bg-soft);border-radius:999px;padding:.1rem .55rem;font-size:.75rem;color:var(--fg)}
.vp-down{width:.5rem;height:.5rem;margin:1rem auto;border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:rotate(45deg)}
.vp-out{display:flex;gap:.5rem;align-items:center;justify-content:center;flex-wrap:wrap;background:var(--accent-soft);border:1px solid var(--accent);border-radius:12px;padding:1rem}
.vp-out b{color:var(--accent);margin-right:.3rem;font-size:.95rem}
.vp-out span{background:var(--card);border:1px solid var(--rule);border-radius:999px;padding:.25rem .8rem;font-size:.82rem;color:var(--fg)}
.vp-use{display:grid;grid-template-columns:1fr 2rem 1fr 2rem 1fr;align-items:stretch}
.vp-col{padding:1rem;display:flex;flex-direction:column;gap:.45rem}
.vp-col small{font-size:.72rem;font-weight:600;color:var(--muted);margin-bottom:.15rem}
.vp-col span{background:var(--bg-soft);border:1px solid var(--rule);border-radius:8px;padding:.4rem .65rem;font-size:.82rem;color:var(--fg)}
.vp-col .vp-opt{border-style:dashed;color:var(--muted)}
.vp-col .vp-go{font-weight:600}
.vp-col .vp-ask{background:var(--accent-soft);border-color:var(--accent);color:var(--accent);font-weight:600}
.vp-arrow{align-self:center;justify-self:center;width:.6rem;height:.6rem;margin-left:-.2rem;border-top:2px solid var(--muted);border-right:2px solid var(--muted);transform:rotate(45deg)}
@media (max-width:640px){
figure.vp-fig ol.vp-flow{grid-template-columns:1fr 1fr}
.vp-flow li::after{display:none}
.vp-use{grid-template-columns:1fr}
.vp-arrow{margin:.7rem 0;transform:rotate(135deg)}
.vp-row{grid-template-columns:5.2rem 1fr 1.4rem}
.vp-scale{margin-left:6rem;margin-right:2.2rem}
}
</style>
<figure class="vp-fig"><div class="vp-card vp-bars">
<div class="vp-row"><span>agent draft</span><div class="vp-track"><div class="vp-bar vp-bad" style="width:83%"></div><i class="vp-line" style="left:33%"></i></div><b>5</b></div>
<div class="vp-row"><span>my rewrite</span><div class="vp-track"><div class="vp-bar" style="width:0"></div><i class="vp-line" style="left:33%"></i></div><b>0</b></div>
<div class="vp-scale"><span style="left:33%">my line: 2</span></div>
</div><figcaption>AI tells, 3 worst per 100 words. Over my line, it asks me first.</figcaption></figure>

<sub>The line is personal. Mine is 2: 99% of the 742 texts of 25+ words in my baseline, mostly emails I wrote from 2006 to 2022, score at or under it.</sub>

## The secret sauce? How I write without AI

The data is there. Every Google Doc, WhatsApp message, Slack thread and email. Every old publication and project. It's all there.

Recent text is a trap though. In October the agent drafted a WhatsApp reply for me. Four paragraphs. My median WhatsApp message is 4 words. By 2026, 60 to 70% of my long Slack messages went through an LLM. Build a profile from that and the agent learns its own habits as mine.

So it had to go back. WhatsApp failed first. The desktop app won't load old history, it only lives on my Pixel. Now the skill tells you how to export it. Then I asked if it searched my Drive. It didn't. It found 65 docs and kept 30, 2015 to 2022. I pulled two that weren't mine: a goals doc someone shared with me and an onboarding plan my manager wrote. Then email. The first pass took 200. I asked for all of it. 4,450 sent emails back to 2006, 3,017 left after cutting quotes, signatures and auto-replies.

In a 2020 doc of mine I found this:

> לא מוותר- שיפור מתמיד

("Not giving up- constant improvement".) Totally me. Down to the hyphen stuck to the word. I never type em dashes. I do type that. So a hyphen pause passes, and an em dash gets flagged.

Chat, email and blog are different. Each one gets its own baseline. A one-line reply is never judged against an email, and a blog post is never judged against a chat.

<figure class="vp-fig"><ol class="vp-flow">
<li class="vp-card"><span class="vp-n">1</span><strong>My writing, before AI</strong><p>Docs, email, WhatsApp, Slack, old posts. 3,068 texts, 2006 to 2022.</p></li>
<li class="vp-card"><span class="vp-n">2</span><strong>Keep only me</strong><p>Cut quotes, signatures, forwards and AI-era text.</p></li>
<li class="vp-card"><span class="vp-n">3</span><strong>Split by kind</strong><p class="vp-chips"><span>chat</span><span>email</span><span>blog</span></p></li>
<li class="vp-card"><span class="vp-n">4</span><strong>Measure</strong><p>Sentence length, word rates, dashes.</p></li>
</ol><div class="vp-down"></div>
<div class="vp-out"><b>My voiceprint</b><span>3 baselines</span><span>a profile</span><span>my drift words</span></div>
<figcaption>Built once from my own writing. Rebuilt when I add more.</figcaption></figure>

| Email baseline | |
|---|---|
| Texts | 3,068 (3,017 emails, 30 docs, 20 PR descriptions, 1 blog post; the PRs are about 1% of the words) |
| Words | 74,653 |
| Median sentence | 5 words |
| Starts lowercase | 43% |

## What I have here? Weird words = bad score

We all know the classic AI tells. No human ever wrote spearheaded or load-bearing on their own. voiceprint ships a list: the 2023 ChatGPT words, and the newer ones from a study of 461,121 pull requests. It also learns my own drift, words that got 4x more common in my writing once AI showed up. Mine are passes, stays, step, inferred, plain. Normal English. A pile of them and the hook asks.

The formula is simple. Weird words, bad score. Only the 3 worst count, so a long post isn't punished for being long. And every word is measured against how often I used it before AI, so my own old habits don't count against me. No model call, so it's FAST and runs before every send.

Blog posts get a second number: how many sentences run longer than mine do. My docs have a median of 7 words a sentence. A post with way more long ones than my docs ever had gets flagged.

<figure class="vp-fig"><div class="vp-use">
<div class="vp-card vp-col"><small>The agent writes</small><span>a PR or commit</span><span>a Slack or mail message</span><span>a blog post</span></div>
<div class="vp-arrow"></div>
<div class="vp-card vp-col"><small>Before it goes out</small><span>AI words per 100</span><span>long sentences</span><span class="vp-opt">Haiku: sounds like me? (optional)</span></div>
<div class="vp-arrow"></div>
<div class="vp-card vp-col"><small>Against my own line</small><span class="vp-go">under: it goes</span><span class="vp-ask">over: it asks me</span></div>
</div><figcaption>Every send, against the baseline for that kind of text.</figcaption></figure>

What a word list can't see, an optional Haiku hook can. It reads every `gh` and `git` command and every chat or mail send before it goes out. It blocks analogies, weird prose, spelling mistakes and the "What do you think?" closer. And it judges against a short summary of my voice: does this sound like me on a good day? Funny thing, my own Haiku hooks never checked for any of that. I found out writing this.

## I don't take everything

The profile isn't a copy of me. My DMs have typos and one-word asks. Nobody needs that in a PR body. I want the AI to talk like I strive to. Clear, concise, excited. Polite but firm. Empathy is important to me. Claude is here to be who I am and who I strive to be. Fix my typos. Make it clearer. Just better, but definitely me.

## Try it

To make it work you'll have to pour a LOT of personal data into it. That's on you. I'm not building the integrations for you, use your own MCPs. It asks before it reads each source. You decide how far the agent goes: which sources, how far back, and if Haiku checks every send. Everything stays in `~/.voiceprint` on your machine, though building the profile does pass samples through Claude. It's a writing check, not a security boundary. A body from a shell variable gets past it.

```bash
claude plugin marketplace add NahumLitvin/voiceprint && claude plugin install voiceprint@voiceprint
```

It needs Python 3.10+. Start a new session and run `/voiceprint:voice-init`. Until you do, the hook scores against general limits, not your voice.

Before it went public, Claude Opus and Codex reviewed it. Codex said hold, twice. Opus found the bug from the top of this post: an apostrophe in a shell comment hid every PR body after it from the hook. The description with 10 AI tells was the agent's write-up of that fix.

Some bliss is expected.

voiceprint is open source: https://github.com/NahumLitvin/voiceprint

Nahum Litvin, Lead Platform Engineer.