| name | documentation-writer |
| description | Use this skill when writing or improving technical documentation including READMEs, docstrings, API docs, changelogs, or inline code comments. Trigger phrases: 'write a README', 'document this function', 'add docstrings', 'improve the docs'. Not for writing blog posts, marketing copy, or user-facing help articles. |
| version | 1.0.0 |
| author | community |
| tags | ["coding","documentation","readme","docstrings","comments"] |
| license | MIT |
Documentation Writer
Overview
The Documentation Writer skill produces clear, accurate, and maintainable technical documentation across all layers: project-level READMEs, module-level docstrings, function-level API docs, and inline code comments. It covers the standard formats for multiple languages (JSDoc, Python docstrings in Google/NumPy/Sphinx style, GoDoc), README structure best practices, CHANGELOG format (Keep a Changelog), and principles for writing comments that add value rather than noise. Good documentation is part of the code — it reduces onboarding time, prevents misuse, and serves as a contract with consumers.
When to Use
- Writing a README for a new library, tool, or service
- Adding docstrings to functions, classes, or modules
- Documenting a public API for external or internal consumers
- Writing or updating a CHANGELOG when releasing a new version
- Adding inline comments to explain non-obvious logic
When NOT to Use
- Writing user-facing help docs or tutorials (different audience and tone)
- Writing blog posts or marketing copy
- Generating API reference docs from non-annotated code (annotate first, then generate)
- Writing architectural decisions (use the architecture-designer skill for ADRs)
Quick Reference
| Doc Type | Purpose | Format |
|---|
| README | Project overview, quickstart, usage | Markdown with badges, code blocks, TOC |
| Docstring (Python) | Function/class contract | Google, NumPy, or Sphinx style |
| JSDoc | JS/TS function/class docs | /** @param @returns @throws */ |
| GoDoc | Go package/function docs | Plain comment above declaration |
| Inline comment | Explain why, not what | Sparingly; 1-2 lines |
| CHANGELOG | User-facing change history | Keep a Changelog format |
| OpenAPI | REST API reference | YAML/JSON, generated from annotations |
Instructions
-
Know your audience
- README: New users who need to evaluate, install, and get started quickly.
- Docstrings: Developers who will call your function — they need to know the contract (parameters, return values, exceptions, side effects), not the implementation.
- Inline comments: The developer maintaining this code 6 months from now (possibly you).
- CHANGELOG: Users upgrading — they need to know what changed and if they need to do anything.
-
Write the README with this structure
1. Project name + one-line tagline
2. Badges (CI, coverage, version, license)
3. Short description (2-3 sentences, why does this exist?)
4. Quick Start / Installation (fewest steps to get running)
5. Usage with code examples
6. Configuration reference
7. API reference (or link to generated docs)
8. Contributing guide
9. License
-
Write docstrings for every public function, class, and module
- Describe what it does, not how (don't repeat the code).
- Document every parameter with type and description.
- Document the return value with type and description.
- Document every exception that can be raised and when.
- Include a usage example for non-trivial functions.
-
Write inline comments that explain why, not what
- Bad:
i += 1 # increment i (the code already says this)
- Good:
# Skip the header row — it's not data
- Good:
# Retry up to 3 times to handle transient network errors
- Comment non-obvious algorithms, workarounds, and performance-critical decisions.
-
Maintain the CHANGELOG
- Follow Keep a Changelog:
Added, Changed, Deprecated, Removed, Fixed, Security.
- Each release has a version number and date.
- Write from the user's perspective: "what changed for me?" not "what did we do?"
-
Review and update docs with code changes
- Treat outdated documentation as a bug.
- Update docstrings when function signatures, behavior, or exceptions change.
Examples
Example 1: Write a README for an open-source Python library
Input: "Write a README for quickcache, a Python library that provides a simple decorator for caching function results with TTL support."
Output:
# quickcache
[](https://github.com/example/quickcache/actions)
[](https://pypi.org/project/quickcache/)
[](https://codecov.io/gh/example/quickcache)
[](LICENSE)
A simple, zero-dependency Python decorator for caching function results with optional time-to-live (TTL) expiration. Stop re-computing expensive results on every call.
## Quick Start
```bash
pip install quickcache
```
```python
from quickcache import cache
@cache(ttl=60) # Cache for 60 seconds
def get_user(user_id: int) -> dict:
return database.fetch_user(user_id)
user = get_user(42) # Hits the database
user = get_user(42) # Returns cached result instantly
```
## Features
- ✅ Simple decorator API
- ✅ Configurable TTL (time-to-live) per function
- ✅ Supports sync and async functions
- ✅ Thread-safe in-memory cache
- ✅ Optional Redis backend for distributed caching
- ✅ Zero dependencies (in-memory mode)
## Installation
```bash
pip install quickcache # In-memory caching (zero dependencies)
pip install quickcache[redis] # Redis backend
```
## Usage
### Basic caching
```python
from quickcache import cache
@cache()
def expensive_computation(x: int, y: int) -> int:
"""Cached with no TTL — cached until process restarts."""
return x ** y
```
### With TTL
```python
@cache(ttl=300) # Cache for 5 minutes
def fetch_exchange_rate(currency: str) -> float:
return api.get_rate(currency)
```
### Async support
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| | | | Seconds before cache entry expires. = never expires. |
| | | | Cache backend instance. |
| | | Auto | Cache key template. Use for dynamic keys. |
| | | | Max entries for in-memory backend (LRU eviction). |
Contributions are welcome! Please read []() for the development setup, coding standards, and pull request process.
MIT — see []().
Example 2: Document a Python module with docstrings
Input: A Python module to document:
import re
from typing import Optional
def validate_email(email, strict=False):
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
if strict:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email, re.IGNORECASE))
def normalize_email(email):
local, domain = email.rsplit('@', 1)
local = local.split('+')[0]
return f"{local.lower()}@{domain.lower()}"
class EmailValidator:
def __init__(self, allowed_domains=None):
self.allowed_domains = set(allowed_domains or [])
def is_valid(self, email):
if not validate_email(email):
return False
if self.allowed_domains:
domain = email.rsplit('@', 1)[1].lower()
return domain in self.allowed_domains
Output (with Google-style docstrings):
"""Email validation and normalization utilities.
This module provides functions and a class for validating and normalizing
email addresses. It supports basic RFC-compliant validation, strict mode
for stricter character rules, and domain allowlisting.
Typical usage::
from email_utils import validate_email, normalize_email, EmailValidator
if validate_email("alice@example.com"):
canonical = normalize_email("Alice+tag@Example.COM")
# canonical == "alice@example.com"
validator = EmailValidator(allowed_domains=["company.com"])
validator.is_valid("alice@company.com") # True
validator.is_valid("alice@gmail.com") # False
"""
import re
from typing import Optional
def validate_email(email: str, strict: bool = False) -> bool:
"""Validate whether a string is a well-formed email address.
Uses a regex pattern to check that the email has a valid local part,
an @ symbol, a domain, and a TLD of at least 2 characters. Strict mode
disallows some special characters (e.g., `!`, `#`) in the local part.
Args:
email: The email address string to validate.
strict: If True, applies stricter character rules in the local part
(only alphanumeric, dots, underscores, percent, plus, hyphen).
Defaults to False.
Returns:
True if the email matches the pattern; False otherwise.
Examples:
>>> validate_email("alice@example.com")
True
>>> validate_email("not-an-email")
False
>>> validate_email("alice!@example.com", strict=True)
False
"""
pattern = r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$'
if strict:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email, re.IGNORECASE))
def normalize_email(email: str) -> str:
"""Normalize an email address to its canonical form.
Strips the plus-addressing suffix from the local part and lowercases
both the local part and the domain. This enables deduplication of
addresses like ``Alice+newsletter@Example.COM`` and ``alice@example.com``.
Note:
This function does not validate the email. Call ``validate_email``
first if you need to ensure the input is well-formed.
Args:
email: A well-formed email address string.
Returns:
The normalized email address in lowercase with plus-addressing removed.
Raises:
ValueError: If the email does not contain an ``@`` symbol.
Examples:
>>> normalize_email("Alice+tag@Example.COM")
'alice@example.com'
>>> normalize_email("BOB@COMPANY.ORG")
'bob@company.org'
"""
email:
ValueError()
local, domain = email.rsplit(, )
local = local.split()[]
:
() -> :
.allowed_domains = {d.lower() d (allowed_domains [])}
() -> :
validate_email(email):
.allowed_domains:
domain = email.rsplit(, )[].lower()
domain .allowed_domains
JSDoc equivalent (TypeScript):
export function validateEmail(email: string, strict = false): boolean {
const pattern = strict
? /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/i
: /^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/i;
return pattern.test(email);
}
GoDoc equivalent:
package email
func IsValid(s string) bool { ... }
func Normalize(s string) (string, error) { ... }
Best Practices
- Write docs before or alongside code, not as an afterthought
- Keep docs close to the code they describe — co-located docstrings beat separate wiki pages
- Treat documentation as code: review it in PRs, update it with behavior changes
- Use examples liberally — a usage example is worth more than three paragraphs of prose
- Make the first sentence of a docstring a complete, standalone description (it appears in IDE tooltips and auto-generated indexes)
Common Mistakes
- Restating what the code does ("This function increments x by 1") instead of explaining purpose and contract
- Documenting parameters without types or units (is
timeout in ms or seconds?)
- Outdated docs that contradict the current behavior — worse than no docs
- Missing exception documentation — callers can't handle errors they don't know about
- Documenting private/internal implementation details that may change vs. the stable public API
Tips & Tricks
pydoc, sphinx, typedoc, and godoc generate HTML API references from docstrings automatically
- Keep a Changelog (keepachangelog.com) is the standard CHANGELOG format — follow it consistently
- Use
# TODO(username): reason format for deferred work so it's trackable
- Add examples to docstrings that are also run as doctests in Python:
pytest --doctest-modules
- Badge generators: shields.io creates badges for README (CI status, version, license, coverage)
Related Skills