A deterministic voice gate for writing — turn 'does this sound like me?' into a number, with no LLM.
Project description
🖋️ Penprint
A deterministic voice gate for writing. Turn "does this sound like me?" into a number — with no LLM.
The problem
Code has tests, so coding agents can run in a loop until the tests pass. Writing has no test. So people gate writing loops with an LLM judge — "score this post 1–10." But an LLM judge is noisy: same draft, different score each run. Loops gated that way oscillate (6 → 7 → 6 → 7) and never converge.
The idea
Penprint is a guitar tuner for your writing voice. It learns a numeric fingerprint from your past posts and scores any new draft against it — the same draft always gets the same score, because there's no model in the loop, just math.
That determinism means a writing loop gated by Penprint converges like a code test-loop.
your past posts ──► penprint fingerprint ──► fingerprint.json (a few numbers)
│
new draft ──► penprint score ──► 0–100 + exactly which metrics are off
What it measures (all computed, no LLM)
| Metric | What it captures |
|---|---|
| sentence rhythm | your average sentence length |
| burstiness | your mix of short + long sentences |
| first-person rate | peer "I / we" energy vs detached prose |
| hook length | is the opening line short enough to stop the scroll |
| build-signal | concrete "I built / ran / shipped" markers |
| banned phrases | generic AI/corporate words you'd never use (hard fail) |
Install
pip install penprint # once published
# or, right now, from source:
git clone https://github.com/<you>/penprint && cd penprint
pip install -e .
Zero dependencies — it's Python stdlib only. No API key. Runs offline.
Quickstart
# 1. learn your voice from your past posts
penprint fingerprint examples/corpus/*.md
# 2. score a draft
penprint score examples/good_draft.md
# -> "SCORE": 79, "FAILS": [ "burstiness ...", "first-person ..." ]
# 3. use it as a CI / loop gate (exit 1 if below threshold)
penprint score draft.md --min 85
# 4. bring your own banned-phrase list (a word normal in YOUR voice shouldn't be banned)
penprint score draft.md --banned examples/banned.txt
The banned-phrase list
A sane default ships in the tool. A fuller, sourced list lives in examples/banned.txt — curated from proselint (BSD), write-good (MIT), anti-ai-slop-writing, and the Max Planck 2024 study on post-ChatGPT word inflation. Override or extend it with --banned <file> — it's your voice, so trim anything that's genuinely yours.
Use it as a loop gate (the fun part)
Penprint has no LLM — but you can put any writing agent in front of it. The agent drafts, Penprint scores, you feed the failing metrics back, the agent fixes, repeat until it passes. Because the gate is deterministic, it converges. See examples/loop.sh for a Claude Code / Codex example.
builder agent ─► draft.md ─► penprint score ─► fails? feed them back ─► fix ─► repeat ─► ✅ ≥ threshold
Does Penprint use an LLM?
No. Penprint itself is pure Python (re, json, statistics). It's a measuring tape, not a brain. The optional writer in a loop can be an LLM of your choice — Penprint just scores the result.
Prior art / credits
Penprint stands on long-standing work — it's a new combination, not a new primitive:
- Stylometry (writing-as-numbers, since the 1880s) — e.g. jpotts18/stylometry, StyloMetrix. Used for forensics; Penprint uses it as a loop gate.
- Vale — prose linter ("style guide as code"). Great rules; no personal voice score.
- conorbronsdon/avoid-ai-writing — preset voice profiles + iterate-to-convergence via an LLM. Penprint differs: a personal, computed, LLM-free score from your own posts.
The gap Penprint fills: a personal computed voice score used as a converging loop's gate.
Author
Built by Sanjay (sanjoxtech) — sanjox.tech · LinkedIn · sanjox.tech@gmail.com
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file penprint-0.1.0.tar.gz.
File metadata
- Download URL: penprint-0.1.0.tar.gz
- Upload date:
- Size: 8.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6065c676843bad60b49ce1b13ab4bc82f451b52c0160e3e31b7b37460a31451
|
|
| MD5 |
632724b3f861cc2a8c723c1f9b12b5bf
|
|
| BLAKE2b-256 |
22514faab6ad281896140e5ff7d9a24659519009e814b075dcd2a8819d577de4
|
Provenance
The following attestation bundles were made for penprint-0.1.0.tar.gz:
Publisher:
publish.yml on sanjoxtech/penprint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
penprint-0.1.0.tar.gz -
Subject digest:
f6065c676843bad60b49ce1b13ab4bc82f451b52c0160e3e31b7b37460a31451 - Sigstore transparency entry: 2009994111
- Sigstore integration time:
-
Permalink:
sanjoxtech/penprint@5f10e24c5f94a81631995d66fc1d1661dcfd2428 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sanjoxtech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5f10e24c5f94a81631995d66fc1d1661dcfd2428 -
Trigger Event:
push
-
Statement type:
File details
Details for the file penprint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: penprint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61ff441c27bb66fb532e37ebf8a6a11ed26182a7d67ff0762049789fd601018f
|
|
| MD5 |
2faa46d784932611e2695513717f03c8
|
|
| BLAKE2b-256 |
fff7953903050eef156ecffb93b9d39ceb2dbc705e8ba48e44984f6c0c904175
|
Provenance
The following attestation bundles were made for penprint-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on sanjoxtech/penprint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
penprint-0.1.0-py3-none-any.whl -
Subject digest:
61ff441c27bb66fb532e37ebf8a6a11ed26182a7d67ff0762049789fd601018f - Sigstore transparency entry: 2009994255
- Sigstore integration time:
-
Permalink:
sanjoxtech/penprint@5f10e24c5f94a81631995d66fc1d1661dcfd2428 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sanjoxtech
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5f10e24c5f94a81631995d66fc1d1661dcfd2428 -
Trigger Event:
push
-
Statement type: