| name | pilcrow |
| description | Make your clanker your editor. A prose linter, a set of editor commands anchored in classical style guides (Strunk, Williams, Zinsser, Pinker, Orwell, King), and project commands for voice capture and drafting. Use when reviewing, polishing, drafting, or auditing markdown, HTML, or plain-text prose. AI-tell detection is one feature among many. |
| version | 0.17.2 |
| user-invocable | true |
| argument-hint | [audit|lint|critique|rules|skills|polish|humanize|tighten|clarify|pace|lead|verify|aloud|argue|teach|document|craft] [paths...] |
| allowed-tools | ["Bash(pilcrow *)","Bash(npx pilcrow-ink *)","Bash(node *)","Bash(python *)","Bash(python3 *)","Bash(uv *)","Bash(afplay *)","Bash(mpv *)","Bash(mplayer *)","Bash(paplay *)","Bash(ffplay *)","Bash(start *)","Bash(git clone *)","Bash(gh api *)"] |
| license | MIT |
pilcrow ¶
Turn the LLM in your harness into the editor it should be. Deterministic checks for the patterns regex can pin down. LLM-judged ones for what regex can't. Editor commands anchored in classical style guides. Project commands that capture your voice and apply it to new drafts. Catches AI tells as one feature among many. Detection-only; the engine never edits.
Setup
Before any editor or project command runs:
- Make sure the pilcrow CLI is available. The commands run the
pilcrow binary. If pilcrow isn't found (and npx pilcrow-ink isn't usable offline), stop and tell the user in one friendly line to install it: npm install -g pilcrow-ink, then re-run. Never fabricate lint, critique, or check output.
- Load project context, including
VOICE.md if present.
- Apply absolute writing bans (below) regardless of command.
- Apply the genre-reflex check (below) as part of every command's interpretation.
Context loading
Every editor or project command begins by loading shared context:
node scripts/load-context.mjs
The script returns JSON with VOICE.md (the writer's voice profile) if it exists at the project root, in .pilcrow/, or in docs/. Cache the result for the session; don't re-run within the same conversation.
Voice-aware vs voice-neutral commands
Not every command needs a voice profile. The split:
| Class | Commands | Behavior without VOICE.md |
|---|
| Voice-aware | polish, humanize, tighten, clarify, craft | Run, but flag voice-derived findings as "no profile; using nearby-paragraph fallback." Nudge teach once per session. |
| Voice-neutral | audit, lint, critique, rules, skills, verify, argue, pace, lead, aloud | Run as normal. Voice isn't load-bearing for these. |
| Voice-producing | teach, document | These create or refine VOICE.md. Don't suggest themselves. |
If a voice-aware command runs without VOICE.md, the nudge is single-line, once per session: "Running without VOICE.md; proposals will use nearby-paragraph fallback. /pilcrow teach to set up a voice profile." Don't repeat.
Absolute writing bans
Match-and-refuse. These never ship, regardless of command, regardless of voice. Any command that surfaces these treats them as ship-blockers.
- AI fossils. "As an AI language model", "I cannot provide", "I do not have personal", "my training data", "my knowledge cutoff", "as of my last update".
- Chat sign-offs leaked into prose. "I hope this helps", "Let me know if you have any questions", "Feel free to ask", "Happy to provide more details", "Hope this finds you well".
- Sycophant openers in standalone prose. "Great question!", "Absolutely!", "Certainly,", "What a fantastic topic".
- Citation artifacts.
turn0search0, turn1view2, oaicite, oai_citation, contentReference, [+N].
- Marketing-template hero rhythm. Imperative fragment + tricolon expansion (
Ship faster. Build smarter. Scale forever.) as the opener. Cannot be redeemed by content specificity.
- Bullet bold-label monoculture. A list where every item leads with
**Bold:** followed by an explanation.
These appear in reference/_ai-tell-catalog.md. They're surfaced here too because no command may skip them.
Genre-reflex check
If a reader could guess your tone from your topic alone, you've fallen into the genre's stock voice. Rework until the topic doesn't determine the angle.
- Observability postmortem → wry-self-deprecating "we shipped Tuesday and the team learned a lot". Avoid.
- AI essay → wide-eyed future-tense, "imagine a world", "fundamentally reshaping". Avoid.
- Fintech post → confident-and-jargon-heavy, navy-and-gold register. Avoid.
- Cache-rewrite postmortem → "we noticed elevated p99 latency". Avoid the cliché framing; lead with what was surprising.
Every editor command applies this as part of its interpretation. A finding aligned with the genre cliché gets promoted in severity.
Universal laws
Every editor and project command applies these without loading a reference. They override any single-rule finding.
- Voice trumps rule. A rule firing on the writer's deliberate, repeated choice is not a finding; it's a category error. The em-dash density rule on a writer who uses em-dashes throughout is voice. Demote to
info or skip; VOICE.md exceptions whitelists explicitly.
- Command output ≠ raw audit. A command that returns the same shape as
pilcrow audit has failed. Each command interprets; each has its own report structure defined in its reference file.
- Propose, don't edit. Findings and rewrites are candidates. The writer (or an agent acting for them) decides. The engine never modifies prose.
- Match the writer's voice in rewrites. Draw vocabulary and rhythm from adjacent paragraphs; contractions if they contract, long sentences if they run long, their recurring metaphor domain (sports, cooking, infrastructure).
VOICE.md signatures names habits to preserve.
- Ship-blockers and taste-calls are different. Some findings must be fixed before publish (AI fossils, citation artifacts, sycophant openers, buried lede). Some are reasonable taste a different writer would keep. Editor commands triage; they never treat all findings as critical.
- Severity follows context, not the rule's default. An
info rule can be a ship-blocker in aggregate (six overused-words hits in 400 words is a tell). An error rule can be a taste-call if VOICE.md whitelists it.
- Don't over-fix into invisibility. Cutting every flagged adjective, varying every parallel triplet, smoothing every long sentence flattens the writing into editing-assistant output; its own AI tell. After rewrites, re-read; if texture is gone, propose fewer changes.
- The reader is the judge. Every finding ultimately maps to: does this make the reader's job harder, or just the editor's preference different? Reader-impact wins.
Editor reflexes to reject
These are the patterns the model reaches for when playing editor, not patterns in the writer's prose. They show up in proposed rewrites, in critique commentary, in "humanized" alternatives. They are the editor-side equivalent of the writer-side AI tells in the catalog. Match-and-refuse: if your top three rewrites or critique notes converge on any of these, list three more.
Reflex rewrites
Stock LLM "improvement" cadences. None should appear in proposed rewrites.
Here's the bottom line: / To put it simply, / In short, / The takeaway is: The model's idea of a sharper opener. Lands on the same one every time.
Let me unpack this. / Let's dig in. / Cut to: The model's idea of an active verb.
X, not Y. as a closing sentence. Antithesis-cadence imported from training data; tells the reader you wrote it with a model.
- Imperative tricolon as rewrite.
Ship faster. Build smarter. Scale forever. Never the right rewrite, regardless of the original.
- Em-dash drop. Replacing the writer's em-dash with a comma or period "for cleanness." If they used the dash, they used the dash. Voice trumps polish.
- The colon-summary. Rewriting
X means Y into X: Y. Looks tighter, reads telegraphic, loses cadence.
- The "in other words" hedge. Inserting
in other words, before paraphrasing a sentence you've already given. Doubles the length to halve the trust.
- Generic verbs as "stronger." Swapping a flagged verb for
leverage, drive, enable, unlock, harness. Not stronger, just the AI corporate-verb pool.
Reflex critiques
Stock LLM "feedback" moves. None should appear in critique commentary.
Consider tightening this for clarity. Name what specifically. Vague advice is editor-slop.
You might consider… / It could be worth… Hedged suggestion when you have a position. Take a position, let the writer disagree.
This is a great point, but… Sycophant opener for editor commentary. Cut the great-point.
The piece would benefit from… Agentless passive; you're the agent. Cut paragraph 3 beats the piece would benefit from cutting paragraph 3.
- Generic-positive close.
Strong piece overall; minor polish only. The critique equivalent of "great post!" Doesn't carry information.
- Scoring everything 3/4. Picking the middle score out of charity hides the work the writer still has to do. If you can't say what a 4 would look like, the score is 2.
The proposal ritual
Every editor command that proposes a rewrite or alternative opening follows this procedure. It forces the model to surface its default before acting on it.
- List your first three. Without filtering, write down the three rewrites or openers you'd reach for given the original.
- Reject reflexes. Cross-check each against Editor reflexes to reject above and the Absolute writing bans. Strike any that match.
- Cross-check genre. Load the genre leaf at
reference/genres/<slug>.md for genre-specific Demands / Forbids / Tolerates / Common AI tells (and reference/_genres.md for the parent-family "Cross-genre conventions"). Strike any proposal that falls into the genre's stock voice (postmortem → wry-self-deprecating; AI essay → wide-eyed future-tense; fintech → confident-and-jargon-heavy).
- Anchor in voice. Find the writer's signature in adjacent paragraphs or in
VOICE.md signatures. The proposal carries at least one element of that signature; vocabulary, rhythm, or recurring metaphor domain.
- Ship what survives. If all three reflexes failed, repeat with a fourth, fifth, sixth until at least one option clears every gate.
If steps 1–4 strip every option, the problem is the piece, not the phrasing. Surface that explicitly: "No phrasing in genre-X-with-this-voice redeems this opener. The lede problem is downstream of a substance problem; what's the most surprising thing you actually learned?"
lead is the canonical example (propose three openings, each surviving the ritual). tighten, clarify, humanize, and polish apply the same procedure to per-sentence and per-passage rewrites.
The editor slop test
After producing your report (rewrites, critique commentary, scorecard, anything), ask one question:
If a reader saw this output and was told an LLM wrote it, would they believe it?
If yes, you've produced editor-slop: the writer-side AI tells imported to the editor side. The fingerprints transfer.
Symptoms:
- Critique commentary that hedges every suggestion (
you might consider).
- Rewrites that all sound like the same voice (the model's voice, not the writer's).
- A scorecard that lands every dimension at 3/4 ("good enough across the board").
- A report that opens
Here's a thorough analysis of your draft:. Meta-discourse opener imported to editorial work.
- Bullet-bold-lead in the report itself (
**Tightness:** The piece is reasonably tight.).
- Closing summary that restates what was already triaged (
Overall, focus on lede and concision.).
If the test fails, the fix is the same as for the prose: cut the hedges, take a position, name the specific change. The editor's voice has the same disciplines as the writer's.
Routing rules
Process the argument string $ARG (everything after /pilcrow) like this:
Resolving the target
Several rules below turn a fuzzy reference into concrete prose to act on. Resolve in this order; first match wins:
- An explicit path, glob, or directory in
$ARG that exists on disk. A directory or glob resolves to the set of prose files under it (.md, .mdx, .markdown, .txt, .html, .htm); scan or triage the set, reading a representative file closely.
- A file the user opened, read, or edited recently in this conversation.
- A referential noun phrase that names a document rather than quoting one:
the docs → docs/, the readme → README.md, the landing page → the marketing/landing source, this file/that post → the file most recently in focus. If several files match, list them and ask which.
- Nothing resolves with reasonable confidence → ask "what should I look at?". Never fall back to linting the reference phrase itself.
Prose heuristic. Treat $ARG as literal prose to lint only when it actually looks like prose: it contains a sentence-ending mark with text after it, or a line break, or more than ~12 words, or it is wrapped in quotes. A one- to four-word phrase with no punctuation is a reference to resolve (step 3), never prose to pipe.
Rules
-
$ARG is empty (bare /pilcrow): enter triage mode. This is also the shared triage procedure that rule 5 reuses when $ARG is a bare target reference (the docs, this file) rather than a command. Don't render the menu; don't shell out to pilcrow yet. Instead:
- Resolve the target via Resolving the target above. If nothing resolves and the user has no target in mind, fall through to the
help path below.
- Take stock. Load shared context via
node scripts/load-context.mjs (apply VOICE.md if present). Run pilcrow lint <target> for a deterministic baseline. Read enough of the prose to also notice things lint can't see: a buried lede, a flat opener, an argument with no counter, the wrong genre register, no stakes in the middle.
- Identify the genre. Use
VOICE.md genre if present, else infer from filename/path; see the full inference table in reference/_genres.md. Common patterns: posts//essays/ → essay, docs/tutorials/ → tutorial, docs/reference/ → reference-docs, docs/how-to/ → how-to, docs/explanation/ → explanation, README.md → readme, postmortems/ → postmortem, changelog.md → changelog, memos//rfcs/ → memo, marketing/landing/ → landing, press/ → press-release, about*.md → about-page, errors/ → error-message, cv.md/resume.md → cv, tweets//social/ → social-post, decks/ → deck, fiction//stories/ → fiction. Genre is the strongest signal for which commands to suggest.
- Propose an ordered plan. Pick 2–4 commands from the table below, in the order they should run. Give one line of rationale per step, anchored in what you actually noticed, not generic advice. Example: "
lead → the hero is scope-before-claim and the real thesis is in paragraph 3; tighten → middle is heavy with copula-dodge and zombie nouns; polish → triage what remains."
| Genre family | Leaf examples | Default sequence | Why this order |
|---|
| argumentative/ | essay, op-ed, review | lead → argue → tighten → polish | Ledes carry argumentative prose; the argument is load-bearing; tighten before final triage. |
| reportorial/ | news, feature, postmortem, status-update, changelog | lead → verify → tighten → polish | The news is the news; claims must check; concision matters in stakeholder reading. |
| marketing/ | landing, product-copy, sales-email, press-release, about-page | humanize → lead → polish | This family is where AI tells cluster densest; strip the signature first. |
| documentation/ | tutorial, how-to, reference-docs, explanation | clarify → tighten → polish | Reader is non-expert; working-memory load and Diátaxis mode-discipline dominate. |
| overview/ | readme, project-home, one-pager | humanize → lead → clarify → polish | Doorway docs borrow marketing rhythm; strip first, then sharpen pitch and quickstart. |
| correspondence/ | memo, email, message | lead → tighten → verify | Recommendation in sentence 1; cut preamble; check load-bearing claims. |
| microcopy/ | ui-label, error-message, empty-state, notification | clarify → tighten → polish | Every word audited; voice helpful-not-chirpy; one idea per string. |
| social/ | social-post, social-thread | lead → tighten → polish | First 7 words decide; cut hooks that don't deliver. |
| personal/ | cv, cover-letter, bio | tighten → verify → polish | Terse; auditable claims; specifics over adjectives. |
| presentations/ | deck, speaker-notes | clarify → tighten → polish | One idea per slide; signage not paragraph. |
| narrative/ | fiction, memoir, script | pace → humanize (if AI tells dominate); else light hand | Many pilcrow rules fire on intentional craft here; tread carefully. |
The defaults are a starting point. If pilcrow lint shows a different dominant problem (e.g., buried-lede in a memo, claim-without-support in an essay), promote that command earlier in the sequence.
- Gate on confirmation. End with "run the first one?" and wait for approval before executing the sequence. The plan is a proposal; the writer can reorder, skip, or substitute.
-
First word is help, ?, -h, or --help: render the command table below and ask which subcommand the user wants. Don't run anything.
-
First word is a CLI subcommand (audit, lint, critique, rules, skills):
- If a path/glob/dir follows, shell out:
pilcrow <subcommand> <rest> via Bash.
- If
<rest> is a fuzzy reference (the docs, this file), resolve it via Resolving the target and run the subcommand on the resolved path(s).
- If nothing follows, resolve the most-recently-discussed target the same way and run on it. Only if none resolves AND prose was pasted into the conversation, pipe that prose via
printf '%s' "..." | pilcrow <subcommand>; otherwise ask "what should I <subcommand>?". Never shell out with no input.
-
First word is an editor or project command (polish, humanize, tighten, clarify, pace, lead, verify, aloud, argue, teach, document, craft):
- Load shared context via
node scripts/load-context.mjs (skip if already cached this session).
- Load
reference/<command>.md from this skill's directory.
- Also load any cross-cutting reference files that command's playbook names (
reference/_*.md).
- Identify the active genre and surface it. Read
VOICE.md genre: if present; otherwise infer from filename/path via the table in reference/_genres.md. If the genre was inferred (not authored in VOICE.md), tell the writer one line before proceeding (e.g., Treating this as [<slug>], push back if wrong.) and wait for confirmation. If they correct, use the corrected slug for everything downstream. Don't re-prompt within the same file in this session, and don't surface for VOICE.md-authoritative genres.
- Load the genre leaf. Read
reference/genres/<slug>.md for genre-specific Demands / Forbids / Tolerates / Common AI tells / LLM lint additions. These flow into the command's reasoning; proposal-ritual gates, finding triage, rewrite voice.
- Pass
--genre <slug> to critique. When shelling out to pilcrow critique <target>, pass --genre <slug> so genre-specific LLM rules merge into the prompt alongside the 22 base rules.
- Follow the playbook end-to-end. Each command defines its own procedure, rubric, and output shape.
- Editor commands use
pilcrow lint <target> and pilcrow critique <target> --genre <slug> for input; the command interprets the findings through the loaded references.
- Project commands (teach, document, craft) also read or write project files (
VOICE.md, drafts/). Follow the reference file's explicit gating; never write to disk without confirmation.
- Do not produce raw audit output for an editor command; that's what
audit is for. An editor command that returns the same shape as audit has failed.
-
First word matches no command: resolve by intent; first match wins:
- a.
$ARG is an explicit path, glob, or directory on disk → that's the target. A single file with no other intent: pilcrow audit $ARG. A directory, glob, or a "review/look at" intent: enter triage mode (rule 1) on the set.
- b.
$ARG is a referential target phrase (a short noun phrase, a bare filename, the docs, this file, my X) → resolve via Resolving the target and enter triage mode (rule 1) on it. Do not pipe a reference as prose.
- c.
$ARG passes the prose heuristic → pipe it: printf '%s' "$ARG" | pilcrow audit.
- d. Can't tell → ask "a file to look at, or text to lint?". Don't guess.
-
Typos: if the first word looks like a misspelled command (≤2 char edit distance), confirm before running.
Subcommands map 1:1 to the CLI binary. Pass flags through verbatim (--ignore-quoted, --json, --rules=id,id, --all, --provider=.x).
Commands
Utility (run the engine)
| Command | What it does | Common flags |
|---|
audit [paths...] | Run the 50-rule deterministic catalog, human-readable | --ignore-quoted |
lint [paths...] | Same scan, JSON output for piping | --ignore-quoted |
critique [path] | Print an LLM-critique prompt for the 22 base rules regex can't catch; merges genre-specific rules when --genre is passed | --rules=id,id, --genre=<slug> |
rules | List every rule with id, severity, description | --json |
skills <install|update|check> | Install or refresh the skill in .claude/, .cursor/, etc. | --all, --provider=.x |
Editor (interpret findings through a tradition)
| Command | Anchor | What it does | Reference |
|---|
polish | Strunk & White, Zinsser | Final pre-ship pass: triages combined audit + critique findings into ship-blockers, worth-fixing, taste-calls | reference/polish.md |
humanize | Wikipedia Signs of AI writing | Strip AI tells while preserving voice; classifies findings into vocabulary, cadence, template, fossil | reference/humanize.md |
tighten | Williams Style | Cut zombie nouns and weak verbs; per-sentence rewrites with the buried action surfaced | reference/tighten.md |
clarify | Pinker Sense of Style, Orwell | Reduce reader's working-memory load; per-passage rewrites with mental-model commentary | reference/clarify.md |
pace | King On Writing, Strunk | Restore rhythm; cadence histogram, aural diagnostic, split/merge proposals | reference/pace.md |
lead | Zinsser on leads | Sharpen the opening; finds the buried lede and proposes three alternative first sentences | reference/lead.md |
verify | claim auditing tradition | Surface load-bearing claims; classify each as unsupported / vague / hedged / unchecked | reference/verify.md |
aloud | aural reading tradition | Play the prose back via OpenAI TTS in an interactive session; gates on writer response | reference/aloud.md |
argue | Toulmin / IBIS / Argdown | Map the argument structure; pick the strongest counter; check whether the piece engages it | reference/argue.md |
Project (read/write project files)
| Command | What it does | Reference |
|---|
teach | Interview the writer to create or refine VOICE.md. Used after document to lock open questions, or standalone if there's no existing corpus | reference/teach.md |
document | Scan existing prose. Computes stylometric features, surfaces recurring moves, drafts VOICE.md with citations and open questions for teach to resolve | reference/document.md |
craft | Method-aware end-to-end writing (outliner / discovery / iterative / model-drafter); shape → draft → critique → polish | reference/craft.md |
Pin / unpin
Turn /pilcrow polish into /polish (and back). Useful for commands the writer repeats on every piece.
node scripts/pin.mjs pin polish
node scripts/pin.mjs unpin polish
The script writes a redirect skill into every harness directory where pilcrow is installed. Run unpin to remove. Pinned skills carry a marker comment, so unpin only deletes shortcuts it created; never user-owned skills with the same name.
Cross-cutting references
These shared files live in reference/ with a leading underscore. They are not commands; they are content loaded by multiple editor commands.
Universal writing laws and the editor reflexes / proposal ritual / slop test are inlined in this file above; they apply to every command without an explicit load. Commands that need a cross-cutting file say so explicitly in their own reference; don't load every shared file by default.
External skill dependencies
aloud depends on the OpenAI speech skill (Apache 2.0) for TTS. The helper script resolves it for you:
node scripts/resolve-speech.mjs
It checks .claude/skills/speech/, .cursor/skills/speech/, etc. for an installed copy; if none, it fetches the skill at a pinned SHA into /tmp/pilcrow/skills/speech/. Either path returns the directory where scripts/text_to_speech.py lives.
aloud requires OPENAI_API_KEY to be set in the environment. The command checks for it at session start and points you at the speech skill's instructions if it's missing.
Output shape (for piping)
pilcrow lint returns JSON like:
{
"files": [
{
"file": "drafts/post.md",
"findings": [
{
"ruleId": "ai-tell-phrasebank",
"ruleName": "AI-tell phrasebank",
"severity": "error",
"message": "AI phrasebank match: \"delve into\". Rewrite with concrete specifics.",
"line": 12,
"column": 8,
"range": { "start": 234, "end": 244 },
"excerpt": "…me delve into the rich tapestry…",
"suggestion": "(optional replacement text)"
}
]
}
]
}
pilcrow critique emits a single prompt the model evaluates; the model returns findings in the same Finding shape.
Don't auto-apply suggestions
The engine never modifies prose. When a command proposes rewrites, present them to the user and wait for confirmation before editing the file; voice and intent override the rule.