code-documentation
Code Documentation Skill
Why this matters
In AI-assisted development, code is written fast but read many times — by humans doing code review, by other AI agents iterating on the codebase, and by documentation generators building system docs. Poorly documented code forces every future reader to reverse-engineer intent from implementation, which is slow, error-prone, and expensive.
Well-documented code serves three audiences simultaneously:
- Human developers — understand what the code does and why, without reading every line
- AI agents — get the context they need to make safe, accurate modifications
- Documentation systems — extract structured metadata to auto-generate API docs, architecture diagrams, and onboarding guides
The cost of documenting at write-time is near zero (especially with AI assistance). The cost of not documenting is paid every single time someone touches that code again.
Core Principles
1. Document Intent, Not Mechanics
Comments should answer why, not what. The code already says what it does. The comment explains the reasoning, the constraints, the business rule, or the non-obvious decision.
// BAD — restates the code
// Set timeout to 5000ms
const TIMEOUT = 5000;
// GOOD — explains the reasoning
// Payment gateway requires responses within 5s or it drops the connection.
// We set our timeout slightly below to have room for cleanup.
const TIMEOUT = 5000;
2. Every File Has a Header
Every source file starts with a block comment that answers: what is this file, why does it exist, and who/what consumes it. This is the single most important documentation a file can have — it's the first thing a human or AI reads, and it sets the context for everything below.
/**
* @file PaymentProcessor
* @description Handles payment lifecycle: authorization, capture, and refund.
* Wraps the Stripe SDK with retry logic and idempotency keys to survive
* transient failures. Used by the checkout flow and the admin refund panel.
*
* @module payments
* @dependencies stripe, @lib/retry, @lib/logger
*
* @architecture
* - Exposes a stateless service class (no internal state between calls)
* - All methods are idempotent — safe to retry on failure
* - Errors are wrapped in PaymentError for uniform handling upstream
*
* @seeAlso
* - docs/architecture/payment-flow.md — sequence diagram
* - CheckoutController — primary consumer
*/
3. Functions Are Self-Documenting Units
Every exported function or method gets a JSDoc/TSDoc block. Internal helper functions get one too if their purpose isn't immediately obvious from name + params.
/**
* Attempts to authorize a payment, retrying on transient Stripe errors.
*
* Returns the authorized payment intent on success. Throws PaymentError
* with code 'CARD_DECLINED' or 'GATEWAY_TIMEOUT' on permanent failures.
*
* @param amount - Charge amount in cents (integer, no decimals)
* @param currency - ISO 4217 currency code (e.g., 'usd', 'ars')
* @param customerId - Stripe customer ID, must start with 'cus_'
* @param idempotencyKey - Client-generated UUID to prevent double charges
*
* @returns The Stripe PaymentIntent object with status 'requires_capture'
* @throws {PaymentError} When the card is declined or gateway times out
*
* @example
* const intent = await authorize(5000, 'usd', 'cus_abc123', uuidv4());
* // intent.status === 'requires_capture'
*/
async function authorize(
amount: number,
currency: string,
customerId: string,
idempotencyKey: string
): Promise<Stripe.PaymentIntent> {
4. Types Are Documentation
In TypeScript, interfaces and type aliases are first-class documentation. Document them with the same care as functions — they're the contract other code relies on.
/**
* Represents a user's subscription state.
*
* The subscription lifecycle goes: trial → active → (past_due → active | canceled).
* A subscription can also go directly from trial to canceled if the user never
* adds a payment method.
*
* @see SubscriptionService.transition() for the state machine implementation
*/
interface Subscription {
/** Unique subscription ID (prefixed 'sub_') */
id: string;
/** Current lifecycle state — see type comment for valid transitions */
status: 'trial' | 'active' | 'past_due' | 'canceled';
/** ISO 8601 timestamp. Null only for trial subscriptions. */
currentPeriodEnd: string | null;
/**
* Stripe price ID for the current plan.
* Changes on upgrade/downgrade take effect at currentPeriodEnd.
*/
priceId: string;
}
5. Comments Age — Keep Them Honest
A wrong comment is worse than no comment. When modifying code, always check if nearby comments are still accurate. If a comment describes behavior that the code no longer implements, update or remove it immediately.
Documentation Checklist
Apply this checklist to every file, whether written by a human or generated by AI:
File Level
- File header with
@file,@description,@module - Dependencies listed (not just imports — explain why each external dep is needed if non-obvious)
- Architecture notes if the file has non-trivial structure (state machines, event flows, caching)
-
@seeAlsolinks to related files, docs, or specs when they exist
Function / Method Level
- JSDoc/TSDoc block on every exported function
-
@paramfor each parameter with type and constraints (ranges, formats, valid values) -
@returnswith description of success case -
@throwsfor each error type with when/why it's thrown -
@examplefor functions with non-trivial usage patterns - For private helpers: at minimum a one-line comment explaining purpose
Type / Interface Level
- Doc block on the type/interface explaining its role and lifecycle
- Individual field comments for anything that isn't self-explanatory from the name
- Enum values documented if their meaning isn't obvious from the name
Inline Comments
- Business rules explained where they're enforced (
// HIPAA requires we mask the last 4 digits) - Non-obvious algorithms or performance choices annotated
- Workarounds or hacks flagged with
// HACK:or// WORKAROUND:and a brief explanation - TODOs formatted as
// TODO(owner): description — ticket/issue link - Edge cases called out (
// Empty array is valid here — means "all categories")
React Components
- Component-level JSDoc explaining what it renders and when to use it
- Props interface fully documented (see Types section above)
- Complex hooks explained: why this hook, what triggers re-renders, cleanup behavior
- Conditional rendering logic commented if it's based on business rules
Patterns by File Type
For detailed examples and templates for each file type in a JS/TS stack, read the reference file:
→ references/patterns.md — Complete templates for:
- React components (functional, with hooks)
- Express/Node API routes
- Service classes
- Utility / helper modules
- Database models and migrations
- Configuration files
- Test files
- Scripts and CLI tools
Anti-Patterns
These are common in AI-generated code and must be caught in review:
Parrot comments — Restating the code in English adds zero value.
// BAD
// Increment counter by one
counter++;
// GOOD — no comment needed, the code is self-explanatory
counter++;
Missing "why" on magic values — Every literal that isn't 0, 1, true, false, or an empty string needs either a named constant or an inline explanation.
// BAD
if (retries > 3) { ... }
// GOOD
const MAX_RETRIES = 3; // Stripe recommends max 3 retries for idempotent requests
if (retries > MAX_RETRIES) { ... }
Orphan TODOs — A TODO without an owner or link is just noise that never gets resolved.
// BAD
// TODO: fix this later
// GOOD
// TODO(fran): Handle currency conversion for ARS — see JIRA-1234
Stale comments — Comments that describe behavior the code no longer exhibits. If you change logic, update or remove the comment in the same commit.
Over-documentation — Documenting every single line is noise. Self-explanatory code (well-named variables, small functions, clear types) doesn't need inline comments. The goal is to document what can't be expressed through code alone.
Comment Tags Reference
Use these consistently across the codebase. AI agents and documentation tools can parse them:
| Tag | Meaning | Example |
|---|---|---|
TODO(owner) |
Planned work, not urgent | // TODO(fran): Add pagination — JIRA-456 |
FIXME(owner) |
Known bug, needs attention | // FIXME(leo): Race condition on concurrent saves |
HACK |
Intentional shortcut, explain why | // HACK: Stripe SDK v12 has a bug with ARS, hardcoding rate |
WORKAROUND |
External constraint forced this | // WORKAROUND: Chrome 120 broke flexbox in this layout |
PERF |
Performance-related decision | // PERF: Memoized because this runs on every keystroke |
SECURITY |
Security-sensitive code | // SECURITY: Sanitize before DB query to prevent injection |
DEPRECATED |
Scheduled for removal | // DEPRECATED: Use v2/checkout instead — removing in Q2 |
AI-CONTEXT |
Extra context specifically for AI agents | // AI-CONTEXT: This ordering matters — events must process sequentially |
The AI-CONTEXT tag is new and specific to AI-assisted workflows. Use it to leave breadcrumbs
that help an AI agent understand constraints that aren't obvious from the code structure alone.
For AI Code Generation
When an AI generates code (via Copilot, Cursor, Claude, or any agent), the output MUST meet these documentation standards before it's considered "done." This means:
-
The prompt or spec should request documented output. Don't assume the AI will document by default — be explicit. Example: "Generate with full JSDoc, file header, and inline comments explaining business logic."
-
Review AI output for parrot comments. AI models tend to generate comments that restate the code. These should be caught and either upgraded to explain "why" or removed.
-
Verify type documentation. AI sometimes generates correct types but skips the doc comments on interfaces. Every field in a shared type needs a comment.
-
Check for missing edge-case comments. AI-generated code often handles edge cases correctly but doesn't explain why the edge case exists or matters.
-
Add architecture context. AI doesn't know your system's architecture. After generating, add the
@seeAlso,@module, and@architecturenotes that connect this code to the broader system.
Documentation as System Input
Well-documented code is a data source for generating system documentation:
- API documentation — JSDoc
@param,@returns,@throwsmap directly to API reference docs - Architecture docs — File headers with
@moduleand@architecturefeed into system diagrams - Onboarding guides —
@exampleblocks become the basis for getting-started tutorials - Change logs —
@deprecatedand@seeAlsotrack migration paths - Agent context —
AI-CONTEXTtags feed directly into the context an orchestrator provides to sub-agents
When documentation lives in the code (not in a separate wiki that gets stale), it stays accurate because it's updated in the same PR that changes the behavior.
Enforcement
Documentation quality should be enforced at three levels:
- At generation time — Specs and prompts explicitly require documentation standards
- At review time — PR checklist includes documentation coverage
- At CI time — Lint rules catch missing JSDoc on exports (e.g.,
eslint-plugin-jsdoc)
See references/eslint-config.md for the recommended ESLint configuration.