Chapter I
Preface
Every harness ships with an LLM that could be the editor sitting next to you when you write. Mostly it isn’t. It cheerleads instead of cutting, paraphrases instead of pruning, hallucinates citations instead of flagging missing ones, and quietly produces prose that all sounds the same.
Pilcrow turns it into the editor. A small CLI for the mechanical checks Strunk and Williams have been running for sixty years — 49 deterministic rules covering AI vocabulary, antithesis cadence, em-dash density, throat-clearing openers, and the surface patterns regex can pin down. Plus 20 LLM-judged rules for what regex can’t catch: buried ledes, mixed metaphors, missing stakes, unsupported claims.
On top, nine interpretive lenses anchored in the classical style guides. polish (Strunk, Zinsser). tighten (Williams). clarify (Pinker, Orwell). pace (King). lead (Zinsser). verify (journalism fact-check). aloud (read it aloud). argue (Toulmin, Argdown). And humanize for the AI-specific work. Four project commands — teach, document, extract, craft — capture your voice and apply it to a new draft.
Works whether you wrote it, the model wrote it, or you co-wrote it. AI-tell detection is in there because it’s one of the things a good editor catches — alongside the rest.
Credit to Paul Bakaus’s impeccable, which does the same job for frontend design. Same idea, applied to prose.
Detection-only. The engine never edits your text. Findings carry line, column, excerpt, and an optional suggestion; the LLM (or you) decides what to change.
Chapter II
Install
Two paths. Pick one.
Global (recommended)
npm install -g pilcrow-ink
The package on npm is pilcrow-ink; the binary it installs is pilcrow.
Per-project, no install
npx pilcrow-ink audit drafts/
npx pilcrow-ink skills install
Works from any project without touching node_modules.
Add the skill to your AI harness
Once pilcrow is available, drop the skill into the right place for whichever harness you use:
cd your-project
pilcrow skills install # auto-detects .claude / .cursor / .gemini / .agents / etc.
pilcrow skills install --all # install into every supported provider
pilcrow skills update # re-sync after `npm update -g pilcrow-ink`
pilcrow skills check # show installed versions vs the package
Supported harnesses: Claude Code, Cursor, Gemini CLI, Codex / Agents, OpenCode, Kiro, Pi, Qoder, Trae, GitHub Copilot.
Chapter III
The CLI
Five commands.
pilcrow audit [paths...] # human-readable findings (default)
pilcrow lint [paths...] # JSON output for LLM consumption
pilcrow critique [path] # print the LLM-critique prompt
pilcrow rules [--json] # list every rule
pilcrow skills <sub> # install/update/check the harness skill
If you pass no paths, the CLI reads stdin. If you pass a directory, it walks the tree and scans every .md, .mdx, .markdown, and .txt file. audit exits non-zero when an error-severity rule fires; lint exits the same way and prints JSON instead of pretty output.
A first run
$ echo "Let me delve into the rich tapestry of features." | pilcrow audit
<stdin> 3 findings
ERROR 1:8 ai-tell-phrasebank — "delve into" (≈ "delve into")
ERROR 1:23 ai-tell-phrasebank — "rich tapestry" (≈ "rich tapestry")
ERROR 1:28 ai-tell-phrasebank — "tapestry of" (≈ "tapestry of")
Each finding includes a short excerpt of the surrounding text and a one-line suggestion when the rule has one.
Piping to an LLM
The critique command emits a single prompt the model reads against the 20 higher-level rules. Pipe it into your model of choice:
pilcrow critique drafts/post.md | claude
pilcrow critique drafts/post.md | llm -m gpt-5
The expected JSON response shape is documented inside the prompt itself.
Chapter IV
Commands
Pilcrow exposes eighteen commands today. Five run the engine; nine load an interpretive lens; four act on the project as a whole. The engine is the what — deterministic findings, milliseconds per file. The lens is the how — a reference file anchored in a classical style guide that tells the model which findings matter under that lens and what rewrite shape to propose. Lenses are not rule filters; each has its own methodology, its own output, its own anti-patterns.
Utility — run the engine
audit | run the deterministic catalog; human-readable findings |
lint | same scan; JSON output for piping |
critique | emit the LLM-critique prompt for the 20 higher-level rules |
rules | list every rule with id, severity, description |
skills | install or refresh the skill in your harness |
Lenses — interpret findings through a tradition
Each lens loads a reference file (skill/reference/<name>.md) when invoked. The reference encodes a methodology drawn from a named style guide, plus rewrite patterns, an output shape, and anti-patterns. None of them is a subset of audit; each produces a different report.
| Lens | Anchor | What it does |
|---|---|---|
polish | Strunk & White, Zinsser | final pre-ship pass; triages findings into ship-blockers, worth-fixing, taste-calls |
humanize | Wikipedia Signs of AI writing | strip AI tells while preserving voice; classifies into vocabulary, cadence, template, fossil |
tighten | Williams Style | cut zombie nouns and weak verbs; per-sentence rewrites with the buried action surfaced |
clarify | Pinker Sense of Style, Orwell | reduce reader’s working-memory load; per-passage rewrites with mental-model commentary |
pace | King On Writing, Strunk | restore rhythm; cadence histogram plus split/merge proposals |
lead | Zinsser on leads | sharpen openings; finds the buried lede, proposes three alternative first sentences |
verify | journalism fact-check tradition | surface load-bearing claims; classify each as unsupported, vague, hedged, or unchecked |
aloud | aural reading tradition | play the prose back via OpenAI TTS in an interactive session; gates on writer response |
argue | Toulmin, IBIS, Argdown | map the argument structure; surface supports, objections, and load-bearing premises; generate the strongest counter and check coverage |
Lenses don’t prescribe what to run next. Most writers run one lens at a time, on one piece at a time. polish is the meta-lens for a final pre-ship sweep; the others stand alone.
Lens references share five cross-cutting files (the underscore-prefixed entries in skill/reference/): _style-laws.md, _ai-tell-catalog.md, _readers.md, _cadence-theory.md, _genres.md. Each lens loads the shared files it actually needs, so the per-lens reference stays short.
Project — voice context and drafting
Four commands act on the repo, not on a single passage. They capture or apply the writer’s voice so lens proposals sound like the writer, not like a generic editor.
| Command | What it does |
|---|---|
teach | multi-round interview to capture the writer’s voice; writes VOICE.md |
document | infer voice from existing prose in the repo; writes VOICE.md with citations and open questions |
extract | mine the corpus for recurring AI tells and personal tics; writes PILCROW.md |
craft | method-aware end-to-end essay writing (outliner / discovery / iterative / model-drafter); shape → draft → critique → polish |
Each project command writes to disk only after explicit confirmation. The flow is gated, not autonomous. Once VOICE.md or PILCROW.md exist, every lens reads them on invocation and weights findings accordingly.
teach captures four axes: genre, audience, stance, method. craft reads method: and runs one of four phase-2 variants — outliners outline first, discovery writers draft freely, iterative writers gate paragraph by paragraph, and model-drafter users get the model’s draft as raw material to rewrite. Different writers draft differently; the flow respects that rather than forcing one shape.
Pin a lens you repeat — node skill/scripts/pin.mjs pin polish turns /pilcrow polish into /polish. The pinned shortcut carries a marker comment, so unpin polish only removes shortcuts pilcrow created.
Chapter V
What audit catches
Forty-nine deterministic rules, no LLM, no API key, milliseconds per file. They fall into eight families.
For the full catalog with every rule’s id, severity, and description, see The catalog.
| Family | Rules |
|---|---|
| AI phrasebank | ai-tell-phrasebank, overused-words, antithesis-cadence, throat-clearing-openers, cliche-closers, meta-discourse, copula-dodge |
| AI fossils | signoff-chatbot, sycophant-opener, disclaimer-tail, citation-artifact |
| Phrase | corporate-cliche, cliche-list, wordy-phrases, redundant-pairs, weasel-hedges, vague-quantifiers |
| Density | em-dash-density, adverb-density, nominalization-density, boosters, passive-voice, pronoun-density-low, parenthetical-aside-density, inline-bold-emphasis |
| Cadence | sentence-length-monotony, sentence-too-long, paragraph-monotony, parallel-triplet-density, transition-stacking, repeated-words-window, noun-stacking, anaphora-cadence, fragment-cadence, hero-tagline-imperative, from-x-to-y, present-participle-tail |
| Consistency | dash-style-inconsistency, quote-style-inconsistency, oxford-comma-inconsistency |
| Weak constructions | there-is-there-are, expletives, negation-of-negation, pronoun-it-vague |
| Markdown shape | bullet-bold-lead, title-case-headers, colon-headline, decorative-emoji, false-precision-headline |
The phrasebank carries the strongest AI tells (delve, tapestry of, navigate the landscape, plays a crucial role) at severity error. The single-word list (transformative, groundbreaking, seamless, foster, unleash, robust, …) sits at severity info because any one word can be defensible in context; the volume is the tell. The antithesis rule wants two or more matches in the document before it fires, since one instance is rhetoric and repetition is the fingerprint.
Density rules report only when a threshold is crossed: more than 1.5 em-dashes per 100 words, more than 5 unfortunate adverbs per 100, more than 8 nominalizations. Below that, prose breathes.
Run pilcrow rules to see every rule with its description and severity. Run pilcrow rules --json to get a machine-readable copy.
Chapter VI
What critique catches
Twenty higher-level patterns regex can’t catch. These rules describe shapes of argument and rhetoric, not strings of characters. The engine ships them as a catalog plus a prompt builder; the model reads the prose against the catalog and returns structured JSON findings the CLI can consume.
| Rule | What it watches for |
|---|---|
| buried-lede | the real point doesn’t appear in the first paragraph |
| voice-consistency | register drifts across sections without narrative cause |
| mixed-metaphor | two incompatible images in one sentence |
| claim-without-support | a non-trivial claim with no evidence, reasoning, or example |
| missing-stakes | the reader can’t answer so what after reading |
| distinctive-vs-generic | could’ve been written by anyone (or any model) |
| abstract-without-concrete | extended theory with no grounding example or scene |
| showing-vs-telling | tells the reader what to feel instead of showing why |
| transition-coherence | paragraphs that don’t connect |
| register-mismatch | formal and casual jammed together without intent |
| excessive-balance | every claim earns a but or however |
| redundant-thesis | opening and closing repeat the same line |
| marketing-template-cadence | imperative-fragment-plus-tricolon hero rhythm, regardless of content specificity |
| sycophantic-tone | passage-level flattery of reader, prompt, or topic |
| stakes-inflation | civilizational stakes attached to small things |
| false-reframe | not X, it’s Y with no semantic content behind it |
| invented-concept-label | Capitalized Compound treated as established term without definition |
| listicle-disguise | numbered list flowed into parallel paragraphs as prose |
| one-point-dilution | single idea restated under five framings, no new argument |
| unsupported-claim | a load-bearing number, date, comparative, or attribution with no citation the reader can audit (the lens behind verify) |
Each rule in the catalog ships with a description, a positive example, and a negative example, all embedded directly in the prompt. The model returns JSON that the engine parses back into the same Finding shape the deterministic engine uses, so the two passes can be merged into one report.
Chapter VII
Fuzzy matching
Literal regex matches the wrong things. The phrasebank rules tokenize the prose, strip apostrophes, and stem each token before comparison. One phrase entry catches all the variants the model will produce in the wild.
| Phrase | Also catches |
|---|---|
delve into | delves, delving, delved, delve deeply into |
foster | fosters, fostering, fostered |
let’s dive in | Let’s dive in, lets dive in |
One inserted word is permitted between phrase tokens, so delve deeply into matches delve into. Apostrophes are stripped from both sides before matching, so Let’s matches lets.
For the complete list of phrases, words, and patterns the engine watches, see The catalog — auto-generated from the source on every deploy.