doc-craft — docs that get used, not docs that look thorough
When to use this skill
Trigger when the user wants documentation. Strong signals:
- "write a README", "write the docs", "document this"
- "add docstrings / JSDoc / TSDoc to this"
- "write a CHANGELOG entry"
- A module pasted with "make this readable for new contributors"
Do not trigger for: API spec writing (use api-architect), code comments inside a complex algorithm (judgment call by the author), or for marketing copy.
The output contract
Docs that:
- Get someone from zero to running in under 5 minutes — for READMEs.
- Explain the why, not the what — for inline docs.
- Have examples that copy-paste and actually work — verified by running them.
- Are skimmable — headings + short paragraphs + code blocks; never a wall of text.
- Match the project's voice — read 2 existing docs first; mirror the register.
Workflow
1 — Pick the form
- README.md — for a repo, a package, a service. Has install + usage + a few examples.
- API reference — for libraries with > 10 public exports. One section per export, signature + example.
- Inline (JSDoc/TSDoc/docstring) — for public functions whose name doesn't fully explain them.
- CHANGELOG.md — for any package with consumers. Keep-a-Changelog format.
- Architecture doc / ADR — for non-obvious decisions future contributors will revisit.
Pick one. Don't write all five for a 200-line package.
2 — READMEs
Structure that works for ~95% of repos:
# <name>
> One-sentence value proposition. What this gives you that you didn't have.
[](link) [](link) [](link)
## Why
2–4 sentences. The problem this solves. The kind of project it fits.
## Install
```bash
npm install <name>
Quickstart
A working example in 8–15 lines. Must actually run.
Common tasks
- How do I do X? → 5-line snippet
- How do I do Y? → 5-line snippet
API
Brief signatures with one-line descriptions. Link to a dedicated docs/ if there's more.
Configuration
Env vars, options, defaults. As a table.
Troubleshooting
Top 3 things that go wrong + the fix.
Contributing
Link to CONTRIBUTING.md. Don't restate it here.
License
MIT (or whatever).
Rules:
- The value proposition at top must answer "why would I use this over the alternatives?"
- The quickstart must work when copy-pasted into a fresh project.
- Skip sections you don't have content for; don't leave "TBD" headings.
### 3 — Inline docs
For TypeScript/JavaScript (TSDoc):
```ts
/**
* Atomically renames a file. On Windows, falls back to copy+delete if
* the source and destination are on different volumes.
*
* @param src - The source path; must exist.
* @param dst - The destination path; will be overwritten if it exists.
* @throws {ENOENT} if `src` does not exist.
* @throws {EACCES} if the process lacks permission to write `dst`.
*
* @example
* await renameAtomic('/tmp/upload.tmp', '/data/file.txt')
*/
export async function renameAtomic(src: string, dst: string): Promise<void> { ... }
For Python (Google style or NumPy style — match the project):
def rename_atomic(src: str, dst: str) -> None:
"""Atomically rename a file across volumes when possible.
Falls back to copy-then-unlink on Windows for cross-volume moves.
Args:
src: The source path; must exist.
dst: The destination path; overwritten if present.
Raises:
FileNotFoundError: If `src` does not exist.
PermissionError: If the process can't write `dst`.
Example:
>>> rename_atomic('/tmp/upload.tmp', '/data/file.txt')
"""
Rules:
- Document every public function.
- Document non-obvious internal functions, especially ones with weird arg orders or side effects.
- Don't document
getUser(id: string): Userwith "Gets a user by id". The signature already says that. Document what kind of lookup (cached? throws on miss? returns soft-deleted?).
4 — Examples
Every example must:
- Be runnable as-is, with the imports shown
- Use realistic values (not
foo,bar,baz) - Show the expected output as a comment
// good
import { slugify } from '@my/utils'
slugify('Hello, world!') // → 'hello-world'
slugify('café', { ascii: false }) // → 'café'
slugify(' ', { fallback: 'untitled' }) // → 'untitled'
// bad — abstract, no expected output
slugify(input)
5 — CHANGELOG
Keep-a-Changelog format. One line per change. Group by Added, Changed, Fixed, Deprecated, Removed, Security.
## [1.4.0] - 2026-05-28
### Added
- `slugify(s, { fallback })` option to return a default for empty/whitespace inputs.
### Fixed
- `slugify` no longer returns `'-'` for whitespace-only input.
### Deprecated
- The `lowercase: false` option will be removed in 2.0. Use `preserveCase: true` instead.
6 — Verify
Before finishing:
- Copy every quickstart command into a fresh shell. They work?
- Copy every code example into a fresh file. It runs and matches the comment?
- Read the README out loud. Anywhere you stumble, the reader will too.
Patterns and anti-patterns
✅ Do:
- Lead with the value proposition. The first 2 sentences decide whether the reader keeps going.
- Use the second person ("you can...") for guides, third person ("the function returns...") for reference.
- Show errors as well as success cases. Most people read docs after something broke.
- Link to external docs for prerequisites instead of restating them.
❌ Don't:
- Don't write "Easy-to-use, blazing-fast, modern, robust". Show, don't claim.
- Don't include screenshots that go stale every minor version.
- Don't number lists where order doesn't matter. Bullets are friendlier.
- Don't use 🚀 emoji headings. The product should be the wow, not the typography.
- Don't write "TBD" or "(Coming soon)". Either write the section or omit it.
Example invocation
User: "Write a README for
@my-org/feature-flags, a TypeScript client for a feature-flag service."
- Read the existing tests + source to learn the public API and the value over alternatives.
- Draft:
- One-sentence value: "Type-safe feature flags with zero-config local overrides for development."
- Why: 3 sentences on the problem (typo-prone flag keys, dev needs to override flags without bothering ops).
- Install:
npm i @my-org/feature-flags - Quickstart: 10-line example fetching one flag and using it in an
if. - Common tasks: override a flag locally (one snippet), bulk-fetch (one snippet), wait for flags to load before render (one snippet).
- API: 4 functions, one-line each + link to
docs/api.md. - Configuration: env var table.
- Troubleshooting: 3 entries (flag returns false when expected true → caching, type error on flag name → run codegen, fetch fails locally → run with
OFFLINE=1).
- Verify: copy quickstart into a fresh project, runs as advertised. Quickstart example output matches the comment.
- Polish: read aloud, trim two filler sentences, ship.
See also
api-architect— the spec the API docs documentgit-flow-pro— the CHANGELOG that ships with the next releasecode-auditor— find the public functions still missing docstrings