I let a model draft my docs. Here is the leash I keep it on.
A Claude Code setup for writing documentation that stays accurate: Context7 for real API signatures, a fact-checker subagent that deletes anything unsourced, and link checks on every save.
Here is the thing about documentation: the writing is the easy part. The hard part is being right. A confident paragraph about a function that takes three arguments, when it actually takes two, is worse than no paragraph at all. It costs someone an afternoon, and it costs you their trust forever.
So when I started handing first drafts to Claude Code, my fear was not that the prose would be bad. The prose is usually fine, a little eager, a little fond of the word "simply," but fine. My fear was that it would invent a parameter that does not exist and say it with a straight face. This whole setup is built around that one fear. Think of it less as a writing assistant and more as a very fast intern who is never, ever allowed to guess.
Why Sonnet, and why not the big model
I draft on Claude Sonnet 4.6. People expect me to reach for the heaviest model available, and for planning a gnarly refactor I would. But docs writing is a high-volume, low-drama job. I am generating a lot of short passages, rerunning them, throwing half away. Sonnet is fast and cheap enough that I do not flinch at regenerating a section five times, and the quality gap on plain explanatory prose is small. The accuracy does not come from the model being clever. It comes from the rest of the rig.
The rules file is short on purpose
My CLAUDE.md is the spine. Everyone wants to stuff theirs with a thousand lines of style guide. Mine has three rules I actually enforce, because a rule the model ignores is just decoration.
# Docs writing rules
This repo publishes developer documentation. You write first drafts.
I edit them. Follow these without exception.
## 1. Pull real API signatures via Context7
Before you describe any function, class, hook, or CLI flag, look it
up with the context7 MCP server. Use the resolved signature verbatim.
Never reconstruct a signature from memory.
## 2. No claims without a source
Every factual statement gets a source: a file path in this repo, a
Context7 doc id, or a URL. If you cannot point to one, do not write
the sentence. Mark anything uncertain with `<!-- VERIFY -->` so the
fact-checker can find it.
## 3. Plain language, no fluff
Short sentences. Active voice. Cut "simply", "just", "easily",
"powerful", "seamless". If a beginner would not understand it,
rewrite it, do not footnote it.Context7 is the load-bearing wall
If you take one thing from this article, take this. The single biggest source of bad docs is a model describing an API from its training data, which may be months or years stale. Context7 fixes that by pulling current, version-correct signatures into context on demand. Without it, my drafts were polished and occasionally fictional. With it, they cite the real thing.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./docs", "./src"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}filesystem is scoped to docs and src only, so the writer can read the code it documents but cannot wander off into my .env. github lets it cross-reference issues and open PRs against the docs site. context7 is the one that earns its keep every single turn.
Two subagents: one writes, one doubts
The orchestrator does not write docs itself. It delegates. doc-author produces the draft, then fact-checker tears into it with a separate context window and a different brief. Splitting them matters: the same model that wrote a sentence is biased toward believing it. A fresh agent whose only job is suspicion catches things the author glides past.
---
name: fact-checker
description: >
Verifies every factual claim in a doc draft against a real source.
Use after doc-author produces a draft, before anything is published.
tools: Read, Grep, mcp__context7__get-library-docs
model: sonnet
---
You are a hostile fact-checker. You assume every claim is wrong until
a source proves it right.
For each draft you receive:
1. Find every factual statement (signatures, defaults, behaviors, counts).
2. For each one, locate a source: a repo file, a Context7 doc, or a URL.
3. If the source confirms it, leave it.
4. If the source contradicts it, fix it and note the correction.
5. If no source exists, DELETE the sentence. Do not soften it. Delete it.
Pay special attention to `<!-- VERIFY -->` markers from the author.
Output the corrected draft plus a short list of what you changed and why.Hooks do the chores so the model does not have to remember
Two things go wrong on every docs site that have nothing to do with the writing: typos and dead links. I do not want to rely on the model noticing them, because it will not, reliably. So I bolted them onto the lifecycle. A PostToolUse hook spell-checks and link-checks every file the writer touches, and a Stop hook rebuilds the docs site when the turn ends so I am always looking at the real thing.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "scripts/check-doc.sh \"$CLAUDE_FILE_PATH\""
}
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "npm run docs:build" }
]
}
]
}
}The check-doc.sh script runs a spell-checker over the changed file and a link checker that follows every URL and internal anchor. If a link 404s, it exits non-zero, the hook surfaces the error, and the writer gets told before I ever see the page. Cheap insurance.
5:42If hooks are new to you, the video above is the fastest way in. The lifecycle events are simple once you see them once, and they are the difference between a setup that hopes the model behaves and one that does not need it to.
What each piece is actually for
| Piece | Job | What breaks without it |
|---|---|---|
| Sonnet 4.6 | Draft prose, fast and cheap | Regenerating costs real money, you draft less |
| context7 | Real, current API signatures | Confident, fictional signatures |
| doc-author | First draft from sources | You write everything from a blank page |
| fact-checker | Delete anything unsourced | Plausible nonsense ships |
| PostToolUse hook | Spell + link check on save | Typos and dead links reach readers |
| Stop hook | Rebuild the site | You review stale output |
A note on AEO, since it is in the name
Half of why this setup exists is answer-engine optimization. People do not just Google things anymore, they ask an assistant, and the assistant reads your docs and paraphrases them back. Which means your docs are now training data and a retrieval target, not just a webpage. Plain, sourced, declarative sentences are exactly what an answer engine can quote cleanly. The same rules that make docs good for a tired human at 2am make them good for a model summarizing on someone's behalf. That is a nice coincidence, and I lean into it hard.
- One clear claim per sentence, so it can be lifted out of context and still be true.
- Answer the question in the first line, then explain, never the other way around.
- Real headings that match how people phrase the question, not clever ones.
- Every fact sourced, because an answer engine repeating your mistake scales the mistake.
I will be honest about the limits. This will not write a docs site from nothing while you sleep. It is a drafting and verification rig, not an author. I still read every page, still rewrite the sentences that matter, still make the calls about what to cover. What it bought me back is the boring 80%, the signature tables and the install steps and the glue prose, with a guarantee that the facts are checked. That is the trade I wanted: keep the judgment, lose the typing.
If you want to try the build, it is on Setuproll as Claude Code Docs & AEO Writer. Grab it with npx setuproll add claude-code-docs-aeo, drop your own three rules in CLAUDE.md, wire up Context7, and let the intern loose. Just keep it on the leash.