README Writer skill for Claude Code
From landing on the repo to a working result. Claude reads your manifest, CLI help and config schema before writing so every flag and version in the README is real. It leads with a plain one-sentence pitch, keeps badges to the few users act on, writes a quickstart that reaches visible output in three commands or fewer, and runs a bundled checker that catches broken relative links, untagged code blocks and leftover TODOs.
- name
readme-writer - size180 lines · ≈1,600 tokens
- files2
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Writes and rewrites project READMEs that get a new user from "what is this?" to a working result in minutes — one-sentence pitch, verified install and quickstart, a real usage example with output, configuration table, and pointers to docs and contributing — with badges used sparingly. Includes a stdlib checker for missing sections, broken relative links, untagged code blocks, and leftover placeholders. Use when creating a README, improving an open-source project page, or when users open issues asking how to install or use the project. Try asking
- Write a README for this CLI tool based on the code
- People keep opening issues asking how to install this. Fix the README
- Trim our README down. It's 900 lines and nobody reads past the badges
Use it when
- Creating a README for a new project or package
- Users cannot get started from the current README
- Preparing a repo for open-source release
- Documenting an internal service for new teammates
Skip it when
- You need a full documentation website, use docs-site instead
- You are writing endpoint reference for an HTTP API, use api-documentation instead
How to check the result
scripts/readme_check.py exits 0, and the quickstart runs exactly as written in a fresh container and prints the documented output.
The common mistake it prevents
Shipping a quickstart that was never run on a clean machine, so it silently depends on a global tool, env var or login the author already had.
A README is done when a stranger who lands on the repo can answer “what is this, is it for me, how do I try it” from the first screen, and can copy the quickstart into a clean machine and see the promised result. Everything else is secondary and can be linked.
Ground rules
- Order by the visitor’s questions: what → why/for whom → install → first result → more usage → configuration → help/contributing → license.
- The quickstart is tested in a clean environment (fresh container or new clone). An untested quickstart is the most common reason projects get “doesn’t work” issues.
- Show, then tell: a code sample with real output beats a paragraph of features.
- Keep it scannable: the README is a landing page, not the manual. Past ~400 lines,
move material to
docs/and link. - Write for the user of the project, not its maintainers. Maintainer notes go in
CONTRIBUTING.md.
Workflow
- [ ] 1. Identify project type and primary user
- [ ] 2. Collect facts from the repo (package manifest, CLI help, config schema, CI)
- [ ] 3. Write the top: name, one-line pitch, optional visual
- [ ] 4. Write install + quickstart and run them in a clean environment
- [ ] 5. Add usage, configuration, and links
- [ ] 6. Run scripts/readme_check.py; fix errors
2. Collect facts, do not invent them
cat package.json | head -40 # name, bin, engines, scripts
cat pyproject.toml 2>/dev/null # requires-python, scripts, extras
ls .github/workflows/ 2>/dev/null # which badges are real
<cli> --help # actual commands and flags
git describe --tags --abbrev=0 # current version
Take supported runtime versions from engines / requires-python, commands from the
real --help, and config keys from the schema or source. Never document a flag that is
not in the code.
3. The top of the README
# fotosort
Rename and file photos by the date they were taken, using EXIF data. Works offline on
macOS, Linux, and Windows.

- One sentence: what it does, for whom or in what context. No “blazingly fast”, “modern”, “powerful”.
- Optional second sentence: the key differentiator.
- Visual (screenshot or short GIF under ~2 MB) for anything with a UI or terminal output.
- Badges: 0–4 that users act on — CI status, latest version, license, maybe downloads/coverage. Not a wall of shields.
Before / after for the pitch:
| Before | After |
|---|---|
| “A powerful, flexible, and modern solution for managing your media.” | “Rename and file photos by the date they were taken, using EXIF data.” |
| “Next-gen state management.” | “A 2 KB React state library that stores state in the URL so views are shareable.” |
4. Install and quickstart
## Install
Requires Node.js 20 or later.
```bash
npm install -g fotosort
```
## Quickstart
```bash
fotosort ./camera-dump --into ./photos
```
```text
Scanned 214 files
Moved 208 → photos/2026/10/
Skipped 6 (no EXIF date; listed in fotosort.log)
```
- State prerequisites with versions before the install command.
- One recommended install path first; alternatives (Homebrew, Docker, from source) after, briefly.
- The quickstart reaches a visible result in 1–3 commands.
- Show expected output so users know it worked.
- Use placeholders consistently and say where values come from:
export FOTOSORT_TOKEN=<token from https://example.com/settings>.
5. Usage, configuration, links
- Usage: 2–4 common tasks, each a heading + command + one-line explanation. Link to full docs for the rest.
- Configuration as a table:
| Option | Env var | Default | Description |
|---|---|---|---|
| `--into <dir>` | `FOTOSORT_INTO` | `./sorted` | Destination root |
| `--pattern <fmt>` | `FOTOSORT_PATTERN` | `{yyyy}/{mm}` | Folder pattern |
- Help: where to ask questions (Discussions, chat), how to report bugs, security
contact (link
SECURITY.md). - Contributing: one line linking
CONTRIBUTING.mdplus the dev setup command if short. - License: name it and link the file.
Variants by project type
| Type | Must include | Usually skip |
|---|---|---|
| Library | Install, minimal import + call example, link to API reference, supported versions | Screenshots |
| CLI | Install, quickstart with output, command table or --help excerpt | Long API sections |
| Web app / service | What it does, screenshot, run locally (env vars, docker compose up), deploy link | Full API |
| Internal repo | Purpose, owners, how to run/test/deploy, links to runbooks and dashboards | Badges, marketing |
| Monorepo root | What lives where (table of packages with one-line purpose), common commands | Per-package detail |
Gotchas
- Copy-paste traps:
$prompts inside code blocks break copying; put commands without prompts and output in a separate block. - Relative links and images break on npm/PyPI pages; use absolute URLs for images that must render there.
- Stale versions in examples (“pip install tool==1.2”) drift; prefer unpinned installs in the README, pin in docs where needed.
- A table of contents is only worth it past ~6 sections; GitHub renders one automatically from headings.
- Emoji-laden headings and decorative badges make the page look generated and harder to scan.
- Do not paste the whole
--helpoutput; summarize the 5 commands people use and link.
Verification
python3 scripts/readme_check.py README.md
The script flags a missing H1, missing install/quickstart/usage section, broken relative
links and images, leftover placeholders (TODO, TBD, <your-...>), untagged code blocks,
images without alt text, and badge overload. Exit code 1 on errors.
Then:
- Run the quickstart in a clean environment (e.g.
docker run --rm -it node:20 bash) exactly as written; fix the README for every deviation. - Read only the first screen (about 25 lines): is “what, for whom, how to try” answered?
- Check that every documented flag exists in
--helpoutput.
Output contract
# <name>
<one-sentence pitch> <optional differentiator>
[visual] [≤ 4 badges]
## Install prerequisites + one command
## Quickstart 1–3 commands + expected output
## Usage 2–4 common tasks
## Configuration table
## Documentation links
## Contributing link to CONTRIBUTING.md
## License name + link Bundled files
Loaded only when a step in the skill needs them. Review them here before installing.
scripts/readme_check.py
#!/usr/bin/env python3
"""Lint a README for the problems that most often stop users from getting started.
Usage:
python3 readme_check.py [README.md] [--max-badges 6]
Errors (exit 1):
- no H1 title
- no install / quickstart / getting started / usage section
- relative links or images that point to files that do not exist
- placeholder text left in (TODO, TBD, lorem ipsum, <your-...>, FIXME)
Warnings:
- fenced code blocks without a language tag
- images without alt text
- more badges than --max-badges
- first paragraph missing or longer than 60 words
- no license mention
Standard library only.
"""
import argparse
import re
import sys
from pathlib import Path
SECTION_RE = re.compile(r"^#{2,3}\s+(install|installation|quick ?start|getting started|usage|setup)\b", re.I | re.M)
LINK_RE = re.compile(r"(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+\"[^\"]*\")?\)")
PLACEHOLDER_RE = re.compile(r"\b(TODO|TBD|FIXME|lorem ipsum)\b|<your[-_ ][^>]+>", re.I)
BADGE_RE = re.compile(r"!\[[^\]]*\]\(https?://(img\.shields\.io|badge\.fury\.io|github\.com/[^)]+/badge\.svg|codecov\.io|[^)]*badge)[^)]*\)", re.I)
def strip_code(text):
return re.sub(r"```.*?```", "", text, flags=re.S)
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("path", nargs="?", default="README.md")
p.add_argument("--max-badges", type=int, default=6)
a = p.parse_args()
path = Path(a.path)
if not path.is_file():
print(f"error: {path} not found", file=sys.stderr)
return 2
text = path.read_text(encoding="utf-8")
prose = strip_code(text)
errors, warnings = [], []
if not re.search(r"^#\s+\S", prose, re.M) and not re.search(r"<h1[\s>]", prose, re.I):
errors.append("no H1 title")
if not SECTION_RE.search(prose):
errors.append("no Install / Quickstart / Getting started / Usage section (## level)")
in_fence = False
for lineno, line in enumerate(text.splitlines(), 1):
if line.lstrip().startswith("```"):
in_fence = not in_fence
continue
if not in_fence:
for m in PLACEHOLDER_RE.finditer(line):
errors.append(f"placeholder '{m.group(0)}' on line {lineno}")
for bang, alt, target in LINK_RE.findall(prose):
if bang and not alt.strip():
warnings.append(f"image without alt text: {target}")
if re.match(r"^(https?:|mailto:|#|data:)", target):
continue
local = (path.parent / target.split("#", 1)[0]).resolve()
if target.split("#", 1)[0] and not local.exists():
errors.append(f"broken relative {'image' if bang else 'link'}: {target}")
fences = re.findall(r"^```(.*)$", text, re.M)
openers = fences[0::2]
untagged = sum(1 for f in openers if not f.strip())
if untagged:
warnings.append(f"{untagged} code block(s) without a language tag")
badges = len(BADGE_RE.findall(text))
if badges > a.max_badges:
warnings.append(f"{badges} badges (> {a.max_badges}); keep the ones users act on")
body = re.sub(r"^\s*(#.*|!\[.*|<.*>|\[!\[.*)\s*$", "", prose, flags=re.M)
paras = [x.strip() for x in re.split(r"\n\s*\n", body) if x.strip()]
if not paras:
warnings.append("no introductory paragraph")
else:
words = len(paras[0].split())
if words > 60:
warnings.append(f"first paragraph is {words} words; say what it is and who it is for in under 60")
if not re.search(r"licen[cs]e", text, re.I) and not (path.parent / "LICENSE").exists():
warnings.append("no license mention and no LICENSE file")
for e in errors:
print(f"ERROR {e}")
for w in warnings:
print(f"warn {w}")
print(f"\n{path}: {len(errors)} error(s), {len(warnings)} warning(s)")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main())
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.