# Doc Craft

> Write documentation that engineers actually read — READMEs that get you running in under 5 minutes, API docs with executable examples, inline JSDoc/TSDoc/docstrings that explain the why. No corporate filler, no "this function does what its name says", no AI-generated cadence. Use when the user says "write a README", "document this", "add docstrings", "write the API docs", "write a CHANGELOG", or hands over a module and asks for docs.

- Skill: `ak-ship/doc-craft` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ak-ship/doc-craft`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ak-ship/doc-craft/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: ak-ship (https://skillmd.com/u/ak-ship)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ak-ship/doc-craft

---


# 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:

1. **Get someone from zero to running in under 5 minutes** — for READMEs.
2. **Explain the *why*, not the *what*** — for inline docs.
3. **Have examples that copy-paste and actually work** — verified by running them.
4. **Are skimmable** — headings + short paragraphs + code blocks; never a wall of text.
5. **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:

```markdown
# <name>

> One-sentence value proposition. What this gives you that you didn't have.

[![CI](badge)](link) [![npm](badge)](link) [![License](badge)](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):

```python
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): User` with "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

```ts
// 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`.

```markdown
## [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."

1. Read the existing tests + source to learn the public API and the value over alternatives.
2. 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`).
3. Verify: copy quickstart into a fresh project, runs as advertised. Quickstart example output matches the comment.
4. Polish: read aloud, trim two filler sentences, ship.

## See also

- `api-architect` — the spec the API docs document
- `git-flow-pro` — the CHANGELOG that ships with the next release
- `code-auditor` — find the public functions still missing docstrings

