Skip to content
Back to skills

Docs Kit

ASecurity

Unified documentation toolkit — generate structured technical docs (API, architecture, guides, READMEs), discover and synthesize docs across local files and external sources, write conventional changelogs from git history, explain code at configurable depths, produce OpenAPI specs with code generation, design Swagger/Redoc-style API reference. Single entry point for any docs task.

  • 2 stars
  • 0 votes
  • 3 copies
  • 14 views
  • Added October 26, 2026
documentationjavascripttypescriptpythonrustgojavabashsqlreactnextjs

Works with

  • cursor
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 26, 2026

npx -y skills add nhan03bit/ultrathink --skill docs-kit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Docs Kit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Docs Kit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nhan03bit-docs-kit/badge)](https://www.skillsdirectory.com/skills/nhan03bit-docs-kit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: docs-kit
description: "Unified documentation toolkit — generate structured technical docs (API, architecture, guides, READMEs), discover and synthesize docs across local files and external sources, write conventional changelogs from git history, explain code at configurable depths, produce OpenAPI specs with code generation, design Swagger/Redoc-style API reference. Single entry point for any docs task."
layer: utility
category: documentation
triggers: ["API documentation", "ELI5 this code", "JSDoc", "README", "api docs", "api documentation", "api reference", "api spec", "architecture doc", "code walkthrough", "deep dive", "docstring", "document this", "explain like I'm", "explain this code", "find documentation", "find examples for", "generate changelog", "how does this library work", "how does this work", "interactive api docs", "look up the docs", "openapi", "openapi spec", "redoc", "reference for", "release notes", "runbook", "swagger", "swagger ui", "update CHANGELOG", "walk me through", "what changed", "what does this API do", "what does this do", "write a guide", "write changelog", "write docs"]
---

# docs-kit

Unified documentation toolkit — generate structured technical docs (API, architecture, guides, READMEs), discover and synthesize docs across local files and external sources, write conventional changelogs from git history, explain code at configurable depths, produce OpenAPI specs with code generation, design Swagger/Redoc-style API reference. Single entry point for any docs task.


## Absorbs

- `docs-writer`
- `docs-seeker`
- `changelog-writer`
- `code-explainer`
- `api-documentation`
- `openapi`


---

## From `docs-writer`

> Generate clear, structured technical documentation — API docs, architecture docs, guides, runbooks, and inline code documentation

# Docs Writer Skill

## Purpose

Write documentation that developers actually read. This skill generates documentation at the right level of detail for the target audience, follows consistent structure, and stays maintainable by staying close to the code it describes.

## Key Concepts

### Documentation Types

| Type | Audience | Purpose | Format |
|------|----------|---------|--------|
| **API Reference** | Developers integrating | Exhaustive endpoint/function docs | OpenAPI, JSDoc, markdown |
| **Architecture Doc** | Team, new hires | System design and decisions | Markdown + diagrams |
| **Tutorial** | New users | Learn by doing | Step-by-step markdown |
| **How-To Guide** | Experienced users | Solve specific problems | Task-oriented markdown |
| **Runbook** | Ops / on-call | Incident response procedures | Checklist markdown |
| **ADR** | Team, future maintainers | Record architecture decisions | Markdown template |
| **README** | Everyone | First impression, quick start | Markdown |
| **Inline Docs** | Code readers | Explain why, not what | JSDoc, docstrings, comments |

### The Four Quadrants (Diataxis Framework)

```
              LEARNING                    WORKING
          ┌─────────────────┬─────────────────┐
PRACTICAL │   Tutorials     │  How-To Guides   │
          │   (learning-    │  (problem-       │
          │    oriented)    │   oriented)      │
          ├─────────────────┼─────────────────┤
THEORY    │  Explanation    │   Reference      │
          │  (understanding-│  (information-   │
          │   oriented)     │   oriented)      │
          └─────────────────┴─────────────────┘
```

Each quadrant serves a different need. Do not mix them in a single document.

## Workflow

### Step 1: Identify Audience and Type

Ask:
1. **Who** will read this? (junior dev, senior dev, ops, end user)
2. **When** will they read it? (onboarding, debugging, integrating, incident)
3. **What** do they need to accomplish? (understand, implement, fix, decide)

### Step 2: Choose Structure

#### API Reference Structure

```markdown
# API Reference: [Service Name]

## Base URL
`https://api.example.com/v1`

## Authentication
All requests require a Bearer token in the Authorization header.

## Endpoints

### Create User
`POST /users`

Creates a new user account.

**Request Body:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | Yes | Valid email address |
| `name` | string | Yes | Display name (1-100 chars) |
| `role` | string | No | One of: `user`, `admin`. Default: `user` |

**Example Request:**
```json
{
  "email": "alice@example.com",
  "name": "Alice Chen",
  "role": "admin"
}
```

**Response: 201 Created**
```json
{
  "id": "usr_abc123",
  "email": "alice@example.com",
  "name": "Alice Chen",
  "role": "admin",
  "createdAt": "2025-06-15T10:30:00Z"
}
```

**Error Responses:**
| Status | Code | Description |
|--------|------|-------------|
| 400 | `INVALID_EMAIL` | Email format is invalid |
| 409 | `EMAIL_EXISTS` | Email already registered |
| 422 | `VALIDATION_ERROR` | Request body validation failed |
```

#### Architecture Document Structure

```markdown
# [System Name] Architecture

## Overview
One paragraph explaining what this system does and why it exists.

## Context Diagram
[Mermaid C4 diagram showing system in its environment]

## Key Design Decisions
1. **Decision**: Why we chose X over Y
   - Context: What problem we faced
   - Decision: What we chose
   - Consequences: Trade-offs accepted

## Components
### [Component A]
- **Responsibility**: What it does
- **Technology**: What it uses
- **Interfaces**: How other components interact with it

## Data Flow
[Mermaid sequence diagram for critical flows]

## Non-Functional Requirements
- Performance targets
- Availability requirements
- Security constraints

## Deployment
How and where this runs in production.
```

#### ADR (Architecture Decision Record)

```markdown
# ADR-NNN: [Title of Decision]

## Status
[Proposed | Accepted | Deprecated | Superseded by ADR-XXX]

## Context
What is the issue that we are seeing that motivates this decision?

## Decision
What is the change that we are proposing and/or doing?

## Consequences
What becomes easier or harder because of this change?

### Positive
- Benefit 1
- Benefit 2

### Negative
- Trade-off 1
- Trade-off 2

### Risks
- Risk 1 and mitigation
```

#### Runbook Structure

```markdown
# Runbook: [Incident Type]

## Severity
P1 / P2 / P3

## Symptoms
- What alerts fire
- What users report
- What metrics look abnormal

## Diagnosis Steps
1. Check [specific dashboard/metric]
2. Run `command to check logs`
3. Verify [specific system state]

## Resolution Steps
1. [ ] Step 1 with exact command
2. [ ] Step 2 with exact command
3. [ ] Verify fix: [how to confirm]

## Escalation
If unresolved after 15 minutes:
- Contact: [team/person]
- Slack: [channel]

## Post-Incident
- [ ] Write incident report
- [ ] Update this runbook if procedures changed
- [ ] Create tickets for preventive measures
```

### Step 3: Write Inline Documentation

#### JSDoc / TypeScript

```typescript
/**
 * Calculates the compound interest on a principal amount.
 *
 * Uses the formula: A = P(1 + r/n)^(nt)
 *
 * @param principal - Initial investment amount in dollars
 * @param annualRate - Annual interest rate as a decimal (e.g., 0.05 for 5%)
 * @param compoundsPerYear - Number of times interest is compounded per year
 * @param years - Number of years to calculate
 * @returns The total amount after compound interest
 *
 * @example
 * ```ts
 * // $1000 at 5% compounded monthly for 10 years
 * calculateCompoundInterest(1000, 0.05, 12, 10);
 * // => 1647.01
 * ```
 *
 * @throws {RangeError} If principal is negative
 * @throws {RangeError} If annualRate is negative
 *
 * @see https://en.wikipedia.org/wiki/Compound_interest
 */
function calculateCompoundInterest(
  principal: number,
  annualRate: number,
  compoundsPerYear: number,
  years: number
): number {
  if (principal < 0) throw new RangeError('Principal cannot be negative');
  if (annualRate < 0) throw new RangeError('Annual rate cannot be negative');

  return Math.round(
    principal * Math.pow(1 + annualRate / compoundsPerYear, compoundsPerYear * years) * 100
  ) / 100;
}
```

#### Python Docstrings

```python
def calculate_compound_interest(
    principal: float,
    annual_rate: float,
    compounds_per_year: int,
    years: int,
) -> float:
    """Calculate compound interest on a principal amount.

    Uses the formula: A = P(1 + r/n)^(nt)

    Args:
        principal: Initial investment amount in dollars. Must be non-negative.
        annual_rate: Annual interest rate as decimal (e.g., 0.05 for 5%).
        compounds_per_year: Times interest compounds per year (e.g., 12 for monthly).
        years: Duration in years.

    Returns:
        Total amount after compound interest, rounded to 2 decimal places.

    Raises:
        ValueError: If principal or annual_rate is negative.

    Example:
        >>> calculate_compound_interest(1000, 0.05, 12, 10)
        1647.01
    """
```

### Step 4: Comments — When and How

**Comment the WHY, not the WHAT:**

```typescript
// BAD: Comments that describe what the code does (the code already says that)
// Increment counter by 1
counter++;
// Check if user is admin
if (user.role === 'admin') { ... }

// GOOD: Comments that explain WHY
// We retry 3 times because the payment gateway has transient 503s during deployments
const MAX_RETRIES = 3;

// Sorted descending so the most recent item appears first in the feed
// without needing a separate query
items.sort((a, b) => b.createdAt - a.createdAt);

// HACK: The OAuth library doesn't expose token expiry, so we decode the JWT
// ourselves. Remove this when they merge PR #847.
const expiry = decodeJwtExpiry(token);
```

## Documentation Quality Checklist

- [ ] **Accurate** — Matches actual current behavior
- [ ] **Complete** — All parameters, return values, errors documented
- [ ] **Examples** — At least one working example per function/endpoint
- [ ] **Scannable** — Headers, tables, and code blocks for quick navigation
- [ ] **Maintained** — Updated when code changes (or auto-generated where possible)
- [ ] **No jargon** — Or jargon is defined on first use
- [ ] **Error cases** — Documents what happens when things go wrong

## Auto-Generation Tips

- Use TypeScript types to generate API docs (TypeDoc, tsdoc)
- Use OpenAPI spec to generate REST API docs (Swagger UI, Redoc)
- Use Storybook for component documentation
- Use `jest --coverage` output to identify undocumented code
- Prefer co-located docs (JSDoc in code) over separate files — they stay in sync better


---

## From `docs-seeker`

> Documentation discovery, retrieval, and synthesis across local files, APIs, and external sources

# Docs Seeker

## Purpose

This skill systematically locates, retrieves, and synthesizes documentation from multiple sources. It prevents hallucination by grounding responses in actual documentation rather than training data, and it ensures version-accurate information by checking the correct version of library docs.

## Key Concepts

### Documentation Source Hierarchy

Always prefer higher-reliability sources:

| Priority | Source | Reliability | Tool |
|----------|--------|-------------|------|
| 1 | Official docs (current version) | Highest | Context7, WebFetch |
| 2 | Source code / type definitions | High | Read, Grep, Glob |
| 3 | Official examples / tutorials | High | Context7, WebFetch |
| 4 | README / CHANGELOG in repo | High | Read |
| 5 | GitHub Issues / Discussions | Medium | WebSearch, gh CLI |
| 6 | Stack Overflow (high-vote) | Medium | WebSearch |
| 7 | Blog posts / tutorials | Low-Medium | WebSearch |
| 8 | Training data (model memory) | Lowest | Internal (last resort) |

### Version Awareness

Documentation is only useful if it matches the version in use:

```
STEP 1: Determine the version in use
  - Check package.json / requirements.txt / go.mod / Cargo.toml
  - Check lock files for exact resolved version
  - Check runtime: node -e "require('pkg/package.json').version"

STEP 2: Retrieve docs for THAT version
  - Context7: Specify version if available
  - Official docs: Use versioned URL (e.g., docs.example.com/v3/)
  - GitHub: Use tagged release (e.g., github.com/org/repo/tree/v3.2.1)

STEP 3: Flag version mismatches
  - If docs are for a different version, explicitly note the discrepancy
  - Highlight breaking changes between versions
```

## Workflow

### Phase 1: Query Analysis

Before searching, understand what is actually needed:

```
QUERY: "How do I use middleware in Express?"

ANALYSIS:
  - Library: Express.js
  - Topic: Middleware registration and execution
  - Scope: Usage patterns (not internal implementation)
  - Version needed: Check project's package.json
  - Depth: Practical examples, not theoretical overview
```

### Phase 2: Local Documentation Search

Check the project itself first:

```
SEARCH ORDER:
  1. README.md, CONTRIBUTING.md in project root
  2. /docs directory (if exists)
  3. Inline code comments and JSDoc/docstrings
  4. Type definitions (.d.ts, type hints)
  5. Test files (often serve as usage documentation)
  6. Configuration files (reveal available options)
  7. CHANGELOG.md (version-specific behavior changes)
```

Tools to use:
- `Glob` to find documentation files: `**/*.md`, `**/docs/**`
- `Grep` to search for specific terms across the codebase
- `Read` to examine specific files

### Phase 3: External Documentation Retrieval

```
STRATEGY A — Context7 (preferred for libraries):
  1. Resolve library ID: context7.resolve-library-id
  2. Query specific topic: context7.query-docs
  3. Extract code examples and API signatures

STRATEGY B — Official Documentation Sites:
  1. WebFetch the documentation URL
  2. Process with specific extraction prompt
  3. Verify version matches

STRATEGY C — Web Search (for niche topics):
  1. WebSearch with specific query including version
  2. Filter results by source reliability
  3. Cross-reference multiple sources

STRATEGY D — Source Code (when docs are insufficient):
  1. Find the library in node_modules / site-packages / vendor
  2. Read the source code directly
  3. Extract behavior from implementation
```

### Phase 4: Synthesis

Combine findings into actionable documentation:

```
TOPIC: [What was asked about]

VERSION: [Library/tool version in use]

SUMMARY: [1-3 sentence overview]

API REFERENCE:
  [Function/method signatures with parameter descriptions]

USAGE PATTERN:
  [Code example showing the standard usage]

COMMON PITFALLS:
  [Known gotchas, edge cases, or mistakes]

RELATED:
  [Links to related APIs or concepts]

SOURCES:
  - [Source 1 — reliability rating]
  - [Source 2 — reliability rating]
```

## Search Patterns

### Pattern: API Signature Discovery

When you need to know what a function accepts and returns:

```
1. Check TypeScript definitions:
   Glob: node_modules/@types/{package}/**/*.d.ts
   Glob: node_modules/{package}/dist/**/*.d.ts

2. Check source type annotations:
   Grep: "export function {functionName}" in node_modules/{package}

3. Check official docs:
   Context7: query for "{functionName} parameters return type"

4. Check tests for usage examples:
   Grep: "{functionName}" in node_modules/{package}/**/*.test.*
```

### Pattern: Configuration Options

When you need to know all available configuration options:

```
1. Check TypeScript config types:
   Grep: "interface.*Config" or "type.*Options" in package source

2. Check default configuration:
   Grep: "defaultConfig" or "defaults" in package source

3. Check JSON schemas:
   Glob: node_modules/{package}/**/*.schema.json

4. Check documentation:
   Context7: query for "configuration options"
```

### Pattern: Migration Guide

When upgrading a dependency:

```
1. Check CHANGELOG:
   Read: node_modules/{package}/CHANGELOG.md

2. Check migration docs:
   WebSearch: "{package} migration guide v{old} to v{new}"

3. Check breaking changes:
   WebSearch: "{package} v{new} breaking changes"
   GitHub: gh release view v{new} --repo {org}/{repo}

4. Check codemods:
   WebSearch: "{package} codemod v{new}"
```

### Pattern: Error Message Resolution

When encountering an unknown error from a library:

```
1. Search error message in source:
   Grep: "exact error message text" in node_modules/{package}

2. Find the throwing code to understand the cause:
   Read the file found above, examine the condition that triggers the error

3. Search GitHub issues:
   WebSearch: "{package} {error message}" site:github.com

4. Search community:
   WebSearch: "{package} {error message}"
```

## Documentation Quality Assessment

Rate retrieved documentation before presenting it:

```
QUALITY CRITERIA:
  ✓ Version-matched: Docs match the version in use
  ✓ Complete: Covers the specific question asked
  ✓ Accurate: Verified against source code or multiple sources
  ✓ Current: Not deprecated or outdated
  ✓ Actionable: Includes usable code examples

CONFIDENCE LEVELS:
  HIGH: Official docs, version-matched, with code examples
  MEDIUM: Official docs but different version, or community source with high engagement
  LOW: Single blog post, old Stack Overflow answer, or training data only
  UNCERTAIN: Conflicting sources or no documentation found
```

## Usage Examples

### Example: Finding React Server Component Docs

```
QUERY: "How to use async server components in Next.js App Router?"

STEP 1: Check project version
  → package.json shows next@14.2.0

STEP 2: Context7
  → Resolve: next.js → /vercel/next.js
  → Query: "async server components app router"
  → Result: Documentation with code examples

STEP 3: Synthesize
  TOPIC: Async Server Components (Next.js 14 App Router)
  VERSION: Next.js 14.2.0 ✓ (supported since 13.4)

  SUMMARY: Server Components in App Router are async by default.
  You can use await directly in the component body.

  USAGE:
    export default async function Page() {
      const data = await fetchData();
      return <div>{data.title}</div>;
    }

  PITFALLS:
    - Cannot use hooks (useState, useEffect) in server components
    - Cannot pass functions as props to client components
    - Use 'use client' directive for interactive components

  SOURCES:
    - Context7: /vercel/next.js (HIGH reliability)
```

## Anti-Patterns

1. **Trusting training data over docs**: Always verify with actual documentation. Training data may be outdated or contain errors from unreliable sources.
2. **Version-blind searching**: Retrieving docs without checking the project's actual version leads to incorrect API usage.
3. **Single source reliance**: Cross-reference at least two sources for critical information, especially for complex configurations.
4. **Ignoring local docs**: The project's own documentation, tests, and type definitions are often the most accurate and relevant source.
5. **Over-fetching**: Retrieving entire documentation pages when only a specific section is needed wastes context budget.

## Integration Notes

- Always check version before querying external sources — use `Read` on package manifests first.
- Feed results through **context-engineering** to compress documentation for context-constrained situations.
- When documentation reveals a complex setup, hand off to **sequential-thinking** for step-by-step implementation.
- Use **repomix** when the documentation question requires understanding the full repository structure.


---

## From `changelog-writer`

> Generate structured changelogs from git commits, PRs, and tags following Keep a Changelog conventions

# Changelog Writer Skill

## Purpose

Generate human-readable changelogs that communicate what changed, why it matters, and whether users need to take action. A good changelog bridges the gap between git history (for developers) and release notes (for users).

## Key Concepts

### Keep a Changelog Format (Recommended)

The standard format from [keepachangelog.com](https://keepachangelog.com):

```markdown
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- New feature description

### Changed
- Existing feature modification

### Deprecated
- Feature that will be removed in future versions

### Removed
- Feature that was removed

### Fixed
- Bug fix description

### Security
- Vulnerability fix description

## [1.2.0] - 2025-06-15

### Added
- User export functionality (#123)
- Dark mode support for dashboard (#145)

### Fixed
- Login timeout on slow connections (#156)
- Incorrect date formatting in UTC-negative timezones (#160)

## [1.1.0] - 2025-05-01
...

[Unreleased]: https://github.com/user/repo/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/user/repo/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/user/repo/compare/v1.0.0...v1.1.0
```

### Change Categories

| Category | When to Use | Triggers Version Bump |
|----------|-------------|----------------------|
| **Added** | New features | Minor |
| **Changed** | Modifications to existing features | Minor (or Major if breaking) |
| **Deprecated** | Features that will be removed | Minor |
| **Removed** | Features that were removed | Major |
| **Fixed** | Bug fixes | Patch |
| **Security** | Vulnerability patches | Patch (or Minor/Major) |

### Semantic Versioning Mapping

```
MAJOR (X.0.0) — Breaking changes
  - Removed features
  - Changed API contracts
  - Renamed public interfaces
  - Changed default behavior

MINOR (0.X.0) — New features, non-breaking
  - Added features
  - Added endpoints/parameters
  - Deprecated features (still work)

PATCH (0.0.X) — Bug fixes, non-breaking
  - Fixed bugs
  - Security patches
  - Performance improvements (no API change)
```

## Workflow

### Step 1: Collect Raw Changes

```bash
# Get commits since last tag
git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges

# Get commits between two tags
git log v1.1.0..v1.2.0 --oneline --no-merges

# Get commits with conventional commit parsing
git log v1.1.0..HEAD --format="%s|%h|%an" --no-merges

# Get merged PRs (GitHub)
gh pr list --state merged --base main --search "merged:>2025-05-01" --json number,title,labels,author
```

### Step 2: Categorize Changes

Map conventional commit types to changelog categories:

```
Commit Type → Changelog Category
  feat:     → Added
  fix:      → Fixed
  docs:     → (usually omit from changelog)
  style:    → (omit)
  refactor: → Changed (only if user-facing behavior changes)
  perf:     → Changed (or Fixed, depending on context)
  test:     → (omit)
  chore:    → (omit)
  ci:       → (omit)
  build:    → (omit)
  revert:   → Removed or Fixed (depending on what was reverted)

  BREAKING CHANGE: → always include, note prominently
  security:        → Security
  deprecate:       → Deprecated
```

### Step 3: Write Human-Readable Entries

**Transformation rules:**

```
Git commit (developer language):
  "fix(auth): handle race condition in token refresh when multiple tabs open simultaneously"

Changelog entry (user language):
  "Fixed login session being lost when using the app in multiple browser tabs"

---

Git commit:
  "feat(api): add GET /users/:id/export endpoint with CSV and JSON format support"

Changelog entry:
  "Added user data export in CSV and JSON formats via the API"

---

Git commit:
  "refactor(db): migrate from raw SQL to Prisma ORM"

Changelog entry:
  (omit — internal change, no user-facing impact)

---

Git commit:
  "feat(auth)!: require email verification for new accounts"

Changelog entry (Breaking):
  "**BREAKING**: New user accounts now require email verification before first login.
   Existing accounts are not affected."
```

### Step 4: Determine Version Bump

```
Analyze all changes since last release:

Has any commit:
  - Removed a public API/feature?        → MAJOR
  - Changed behavior in a breaking way?   → MAJOR
  - Has `BREAKING CHANGE:` footer?        → MAJOR
  - Has `!` after type (e.g., `feat!:`)?  → MAJOR

If no breaking changes, has any commit:
  - Added a new feature (`feat:`)?        → MINOR
  - Added new API endpoint/parameter?     → MINOR
  - Deprecated a feature?                 → MINOR

If only fixes:
  - Bug fixes (`fix:`)?                   → PATCH
  - Security patches?                     → PATCH
  - Performance improvements?             → PATCH
  - Documentation updates?                → PATCH (or no bump)
```

### Step 5: Format and Write

```bash
# Template for a release entry
cat <<'ENTRY'
## [1.3.0] - 2025-06-20

### Added
- User data export in CSV and JSON formats ([#178](link))
- Dark mode toggle in user preferences ([#182](link))
- Keyboard shortcuts for common actions ([#185](link))

### Changed
- Improved search performance for large datasets — results now load 3x faster ([#180](link))
- Updated password requirements to minimum 12 characters ([#183](link))

### Fixed
- Session loss when switching between browser tabs ([#176](link))
- Incorrect timezone display for users in UTC-negative zones ([#179](link))
- PDF export failing for reports with special characters in titles ([#181](link))

### Security
- Patched XSS vulnerability in comment rendering ([#184](link))
ENTRY
```

## Audience-Specific Formats

### Developer Changelog (Technical)

```markdown
## [1.3.0] - 2025-06-20

### Added
- `GET /api/users/:id/export` endpoint with `format` query param (csv|json) (#178)
- `prefers-color-scheme` media query support in theme system (#182)

### Changed
- **BREAKING**: `SearchService.query()` now returns `Promise<PaginatedResult>` instead of `Promise<Result[]>` (#180)
- Minimum password length increased from 8 to 12 in `PasswordValidator` (#183)

### Fixed
- Race condition in `TokenRefreshService` when multiple instances race (#176)
- `DateFormatter.toLocal()` returns wrong offset for negative UTC zones (#179)
```

### End-User Release Notes (Non-Technical)

```markdown
## What's New in Version 1.3

### New Features
- **Export your data** — Download your account data as a spreadsheet (CSV) or data file (JSON) from Settings > Privacy.
- **Dark mode** — Switch to dark mode in Settings > Appearance, or let it follow your system preference.
- **Keyboard shortcuts** — Press `?` anywhere to see available shortcuts.

### Improvements
- **Faster search** — Search results now load up to 3x faster for large workspaces.

### Bug Fixes
- Fixed an issue where you could be logged out when switching browser tabs.
- Fixed timezone display being incorrect for some regions.
- Fixed PDF exports failing when report titles contained special characters.

### Important
- Passwords now require at least 12 characters. Existing passwords are not affected until your next password change.
```

## Automation Integration

```bash
# Script to generate changelog from conventional commits
generate_changelog() {
  local from_tag="$1"
  local to_ref="${2:-HEAD}"

  echo "## Changes since ${from_tag}"
  echo ""

  # Added
  local added=$(git log "${from_tag}..${to_ref}" --oneline --no-merges --grep="^feat" --format="- %s (%h)")
  if [[ -n "$added" ]]; then
    echo "### Added"
    echo "$added" | sed 's/^- feat[^:]*: /- /'
    echo ""
  fi

  # Fixed
  local fixed=$(git log "${from_tag}..${to_ref}" --oneline --no-merges --grep="^fix" --format="- %s (%h)")
  if [[ -n "$fixed" ]]; then
    echo "### Fixed"
    echo "$fixed" | sed 's/^- fix[^:]*: /- /'
    echo ""
  fi

  # Breaking
  local breaking=$(git log "${from_tag}..${to_ref}" --oneline --no-merges --grep="BREAKING" --format="- %s (%h)")
  if [[ -n "$breaking" ]]; then
    echo "### BREAKING CHANGES"
    echo "$breaking"
    echo ""
  fi
}
```

## Quality Checklist

- [ ] Every user-facing change is documented
- [ ] Entries describe impact, not implementation
- [ ] Breaking changes are prominently marked
- [ ] PR/issue numbers are linked
- [ ] Date follows ISO 8601 (YYYY-MM-DD)
- [ ] Version follows semver
- [ ] Comparison links at bottom of file are updated
- [ ] Internal/developer-only changes are omitted (or in separate section)


---

## From `code-explainer`

> Explain code at configurable depth levels — from high-level overview to line-by-line analysis with algorithm complexity and design pattern identification

# Code Explainer Skill

## Purpose

Make code understandable to the reader at their level. This skill explains code at configurable depths — from a one-sentence summary to a line-by-line breakdown — while identifying patterns, complexity, and potential issues. The goal is not just to describe what code does, but to build understanding of why it works.

## Key Concepts

### Depth Levels

| Level | When to Use | Output |
|-------|-------------|--------|
| **L1: Overview** | Quick understanding, PR review | 1-3 sentence summary of purpose |
| **L2: Standard** | Code review, onboarding | Purpose, flow, key decisions, complexity |
| **L3: Detailed** | Learning, debugging | Step-by-step walkthrough with reasoning |
| **L4: Line-by-Line** | Deep debugging, teaching | Every line explained with context |

### Audience Calibration

| Audience | Assumes Knowledge Of | Explains |
|----------|---------------------|----------|
| **Beginner** | Basic syntax, variables, functions | Data structures, patterns, language idioms |
| **Intermediate** | Common patterns, standard library | Architecture decisions, trade-offs, edge cases |
| **Advanced** | Design patterns, complexity theory | Non-obvious optimizations, subtle bugs, alternatives |

## Workflow

### Step 1: Read and Classify

Before explaining, identify:
1. **Language and framework** — Determines idioms and conventions
2. **Purpose** — What problem does this code solve?
3. **Patterns used** — Design patterns, architectural patterns
4. **Complexity** — Time and space complexity
5. **Dependencies** — What external code does this rely on?
6. **Edge cases** — What inputs could cause unexpected behavior?

### Step 2: Explain at Requested Depth

#### L1: Overview

```typescript
// Code to explain:
export function debounce<T extends (...args: unknown[]) => unknown>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>;
  return (...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}
```

**L1 Explanation:**
> This is a `debounce` utility that delays function execution until a pause in calls. If the function is called repeatedly within the delay window, only the last call executes. Common use: preventing excessive API calls during user typing.

#### L2: Standard

**L2 Explanation:**

> **Purpose:** Creates a debounced version of any function. The debounced function waits for `delay` milliseconds of inactivity before executing.
>
> **How it works:**
> 1. Returns a wrapper function that captures the original function in a closure
> 2. Each call clears any pending timeout and sets a new one
> 3. The original function only executes when no new calls arrive within the delay period
>
> **Pattern:** Closure + Timer control (common in event handling)
>
> **Complexity:** O(1) time per call, O(1) space (one timeout reference)
>
> **TypeScript notes:** Generic type `T` preserves the original function's parameter types via `Parameters<T>`. Return type is `void` because the debounced function does not return the original's result (it executes asynchronously).
>
> **Common usage:** Search input handlers, window resize listeners, form auto-save

#### L3: Detailed

**L3 Explanation:**

> **Context:** Debouncing is a rate-limiting technique. Imagine a user typing in a search box. Without debouncing, every keystroke triggers an API call. With debouncing, the API call only fires after the user stops typing for a specified duration.
>
> **Step-by-step flow:**
>
> ```
> Call 1 at t=0ms:   Clear timer → Set timer for t=300ms
> Call 2 at t=100ms: Clear timer (cancels t=300ms) → Set timer for t=400ms
> Call 3 at t=200ms: Clear timer (cancels t=400ms) → Set timer for t=500ms
> [user stops typing]
> t=500ms: Timer fires → Original function executes with Call 3's arguments
> ```
>
> **Closure analysis:**
> - `timeoutId` lives in the closure created by `debounce()`, shared across all calls to the returned function
> - `fn` and `delay` are also captured in this closure
> - Each new call to the returned function resets the shared `timeoutId`
>
> **Type system breakdown:**
> - `T extends (...args: unknown[]) => unknown` — T is any function type
> - `Parameters<T>` — Extracts T's parameter types as a tuple
> - `ReturnType<typeof setTimeout>` — Platform-agnostic timeout ID type (number in browser, NodeJS.Timeout in Node)
>
> **Potential issues:**
> - The return value of `fn` is discarded (cannot `await` a debounced async function)
> - No `cancel()` method — once queued, the final call always executes
> - No `flush()` method — cannot force immediate execution
> - Memory: the closure holds a reference to `fn` and its bound context
>
> **Improved version would add:**
> ```typescript
> const debounced = (...args) => { ... };
> debounced.cancel = () => clearTimeout(timeoutId);
> debounced.flush = () => { clearTimeout(timeoutId); fn(...lastArgs); };
> return debounced;
> ```

#### L4: Line-by-Line

```
Line 1:  export function debounce<T extends (...args: unknown[]) => unknown>(
         │ export    — Makes this function importable from other modules
         │ function  — Named function declaration (hoisted, better stack traces than arrow)
         │ <T extends (...args: unknown[]) => unknown>
         │            — Generic type parameter T, constrained to be any function
         │            — This lets TypeScript infer the exact parameter types of the input function

Line 3:    fn: T,
         │ The original function to debounce, typed as T

Line 4:    delay: number
         │ Milliseconds to wait after last call before executing

Line 5:  ): (...args: Parameters<T>) => void {
         │ Return type: a function with same parameters as T, but returning void
         │ (because execution is deferred, we can't return T's return value synchronously)

Line 6:    let timeoutId: ReturnType<typeof setTimeout>;
         │ Declared in outer scope — shared across all invocations of the returned function
         │ ReturnType<typeof setTimeout> handles both browser (number) and Node (Timeout)
         │ Using `let` because it will be reassigned on each call

Line 7:    return (...args: Parameters<T>) => {
         │ Return a new function that captures fn, delay, and timeoutId in its closure

Line 8:      clearTimeout(timeoutId);
         │ Cancel any previously scheduled execution
         │ Safe to call even if timeoutId is undefined (clearTimeout handles it)

Line 9:      timeoutId = setTimeout(() => fn(...args), delay);
         │ Schedule fn to execute after `delay` ms
         │ Arrow function captures `args` from this specific call
         │ Assigns new timeout ID so next call can cancel it

Line 10:   };
Line 11: }
```

### Step 3: Identify Patterns and Concepts

When explaining code, call out recognized patterns:

```
Design Patterns:
  - Singleton          - Factory           - Observer
  - Strategy           - Decorator         - Adapter
  - Command            - State Machine     - Builder
  - Repository         - Middleware chain   - Pub/Sub

Algorithmic Patterns:
  - Two pointers       - Sliding window    - Divide and conquer
  - Dynamic programming - Greedy           - Backtracking
  - BFS/DFS            - Binary search     - Memoization

Architectural Patterns:
  - MVC / MVVM         - Event-driven      - CQRS
  - Microservices      - Hexagonal         - Layered
  - Saga               - Circuit breaker   - Bulkhead
```

### Step 4: Complexity Analysis

Always include when relevant:

```
Time Complexity:
  Best case:    O(...)
  Average case: O(...)
  Worst case:   O(...)

Space Complexity: O(...)

Explanation: [Why this complexity, what dominates]
```

Example:
```
This function uses a hash map for O(1) lookups inside a loop that
iterates n items, giving O(n) total time. The hash map stores at
most n entries, so space is also O(n).

Compared to the naive O(n^2) nested loop approach, this trades
O(n) extra memory for O(n) time improvement.
```

## Explanation Formatting

Structure explanations consistently:

```markdown
## [Function/Module Name]

**Purpose:** One sentence.

**Pattern:** [Design pattern if applicable]

**Flow:**
1. First thing that happens
2. Second thing
3. Third thing

**Complexity:** Time O(n), Space O(1)

**Key Insight:** [The non-obvious thing that makes this work]

**Watch Out For:** [Edge cases, gotchas, limitations]
```

## Visual Aids

For complex flows, include Mermaid diagrams:

```mermaid
flowchart TD
    A[Function called] --> B{Timer active?}
    B -->|Yes| C[Clear existing timer]
    B -->|No| D[Skip clear]
    C --> E[Set new timer]
    D --> E
    E --> F{Timer expires?}
    F -->|New call arrives| B
    F -->|No new calls| G[Execute original function]
```

For state machines:

```mermaid
stateDiagram-v2
    [*] --> Idle
    Idle --> Loading: fetch()
    Loading --> Success: data received
    Loading --> Error: request failed
    Error --> Loading: retry()
    Success --> Loading: refresh()
```

## Anti-Patterns in Explanation

1. **Narrating syntax** — "The `const` keyword declares a constant" (reader knows this)
2. **Ignoring context** — Explaining a function without explaining its role in the system
3. **Skipping the WHY** — Describing what each line does without explaining why this approach was chosen
4. **Assuming knowledge** — Using jargon without checking if the audience knows it
5. **Over-explaining** — Giving L4 depth when L2 was requested


---

## From `api-documentation`

> API documentation best practices — Swagger UI, Redoc, interactive examples, and auto-generation from code.

# API Documentation

## Purpose

Produce clear, interactive, and maintainable API documentation. Covers OpenAPI spec authoring, Swagger UI and Redoc hosting, auto-generation from TypeScript/Python code, and documentation-as-code workflows.

## Key Patterns

### OpenAPI 3.1 Spec Structure

```yaml
openapi: 3.1.0
info:
  title: My API
  version: 1.0.0
  description: |
    Production API for managing users and orders.

    ## Authentication
    All endpoints require a Bearer token in the `Authorization` header.

    ## Rate Limits
    - 100 requests/minute per API key
    - 429 response when exceeded
  contact:
    email: api-support@example.com

servers:
  - url: https://api.example.com/v1
    description: Production
  - url: https://staging-api.example.com/v1
    description: Staging

paths:
  /users:
    get:
      operationId: listUsers
      summary: List all users
      description: Returns a paginated list of users. Supports cursor-based pagination.
      tags:
        - Users
      parameters:
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
              example:
                data:
                  - id: "usr_abc123"
                    name: "Jane Doe"
                    email: "jane@example.com"
                next_cursor: "eyJpZCI6MTAwfQ=="
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  schemas:
    User:
      type: object
      required: [id, name, email]
      properties:
        id:
          type: string
          example: "usr_abc123"
        name:
          type: string
          example: "Jane Doe"
        email:
          type: string
          format: email

    UserListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        next_cursor:
          type: string
          nullable: true

  responses:
    Unauthorized:
      description: Missing or invalid authentication token
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                example: "Invalid or expired token"

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []
```

### Swagger UI Setup (Next.js)

```typescript
// app/api/docs/route.ts — Serve the OpenAPI spec
import { NextResponse } from 'next/server';
import spec from '@/openapi.json';

export async function GET() {
  return NextResponse.json(spec);
}
```

```tsx
// app/docs/page.tsx — Swagger UI page
'use client';

import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';

export default function ApiDocs() {
  return (
    <div className="py-16">
      <SwaggerUI url="/api/docs" />
    </div>
  );
}
```

### Redoc Setup

```html
<!-- Static HTML approach -->
<!doctype html>
<html>
  <head>
    <title>API Reference</title>
    <meta charset="utf-8" />
    <link
      href="https://fonts.googleapis.com/css?family=Inter:300,400,600"
      rel="stylesheet"
    />
  </head>
  <body>
    <redoc spec-url="/api/docs"></redoc>
    <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
  </body>
</html>
```

```tsx
// React component approach
'use client';

import { useEffect, useRef } from 'react';

export default function RedocDocs() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // @ts-expect-error — Redoc loaded via script tag
    if (window.Redoc && containerRef.current) {
      // @ts-expect-error
      window.Redoc.init('/api/docs', {
        theme: {
          colors: { primary: { main: '#3b82f6' } },
          typography: { fontSize: '1rem', fontFamily: 'Inter, sans-serif' },
        },
      }, containerRef.current);
    }
  }, []);

  return <div ref={containerRef} />;
}
```

### Auto-Generation from TypeScript (Zod + OpenAPI)

```typescript
import { z } from 'zod';
import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';

extendZodWithOpenApi(z);

const registry = new OpenAPIRegistry();

// Register schemas
const UserSchema = registry.register(
  'User',
  z.object({
    id: z.string().openapi({ example: 'usr_abc123' }),
    name: z.string().openapi({ example: 'Jane Doe' }),
    email: z.string().email().openapi({ example: 'jane@example.com' }),
  })
);

// Register endpoints
registry.registerPath({
  method: 'get',
  path: '/users',
  summary: 'List all users',
  tags: ['Users'],
  request: {
    query: z.object({
      cursor: z.string().optional(),
      limit: z.number().int().min(1).max(100).default(20),
    }),
  },
  responses: {
    200: {
      description: 'Successful response',
      content: {
        'application/json': {
          schema: z.object({
            data: z.array(UserSchema),
            next_cursor: z.string().nullable(),
          }),
        },
      },
    },
  },
});

// Generate the spec
import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';

const generator = new OpenApiGeneratorV31(registry.definitions);
const spec = generator.generateDocument({
  openapi: '3.1.0',
  info: { title: 'My API', version: '1.0.0' },
  servers: [{ url: 'https://api.example.com/v1' }],
});
```

### Auto-Generation from Python (FastAPI)

```python
from fastapi import FastAPI, Query
from pydantic import BaseModel, EmailStr

app = FastAPI(
    title="My API",
    version="1.0.0",
    docs_url="/docs",       # Swagger UI at /docs
    redoc_url="/redoc",     # Redoc at /redoc
    openapi_url="/openapi.json",
)

class User(BaseModel):
    id: str
    name: str
    email: EmailStr

    model_config = {
        "json_schema_extra": {
            "examples": [{"id": "usr_abc123", "name": "Jane Doe", "email": "jane@example.com"}]
        }
    }

class UserListResponse(BaseModel):
    data: list[User]
    next_cursor: str | None = None

@app.get("/users", response_model=UserListResponse, tags=["Users"])
async def list_users(
    cursor: str | None = Query(None, description="Pagination cursor"),
    limit: int = Query(20, ge=1, le=100, description="Items per page"),
):
    """Returns a paginated list of users. Supports cursor-based pagination."""
    ...
```

### Documentation-as-Code CI Pipeline

```yaml
# .github/workflows/api-docs.yml
name: API Docs
on:
  push:
    paths:
      - 'src/api/**'
      - 'openapi/**'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Generate OpenAPI spec
        run: npx tsx scripts/generate-openapi.ts > openapi.json

      - name: Validate spec
        run: npx @redocly/cli lint openapi.json

      - name: Check for breaking changes
        run: npx oasdiff breaking openapi-previous.json openapi.json

      - name: Build Redoc static site
        run: npx @redocly/cli build-docs openapi.json -o docs/index.html

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs
```

### Inline Code Examples

Always include request/response examples in docs:

```yaml
# Good: Examples show real data shapes
paths:
  /users:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUser'
            examples:
              basic:
                summary: Minimal user creation
                value:
                  name: "Jane Doe"
                  email: "jane@example.com"
              full:
                summary: User with all optional fields
                value:
                  name: "Jane Doe"
                  email: "jane@example.com"
                  avatar_url: "https://example.com/avatar.jpg"
                  metadata:
                    source: "signup_form"
```

## Best Practices

1. **Single source of truth** — Generate docs from code (Zod schemas, Pydantic models, JSDoc) rather than maintaining a separate spec file.
2. **Include realistic examples** — Every schema and endpoint should have concrete example values, not placeholder text.
3. **Document error responses** — Show all possible error codes, not just the happy path.
4. **Use tags to group endpoints** — Organize by resource (Users, Orders) not by HTTP method.
5. **Version your API docs** — Docs should match the deployed API version; use CI to enforce spec validity.
6. **Add authentication instructions** — Document how to obtain and use tokens at the top of the spec.
7. **Detect breaking changes in CI** — Use `oasdiff` or `openapi-diff` to catch breaking changes before merge.
8. **Provide SDKs or code snippets** — Show curl, JavaScript, and Python examples for each endpoint.

## Common Pitfalls

| Pitfall | Problem | Fix |
|---------|---------|-----|
| Manually maintained spec | Drift between docs and implementation | Generate spec from code with CI validation |
| No examples | Users cannot understand expected shapes | Add `example` to every schema property |
| Missing error documentation | Users surprised by error formats | Document all error codes and response bodies |
| Stale docs after refactor | 404s and wrong schemas frustrate consumers | CI pipeline regenerates + validates on every push |
| Too many nested $refs | Swagger UI becomes hard to navigate | Inline small schemas; use $ref only for reusable types |
| No authentication docs | Users cannot make their first API call | Add auth section to spec `info.description` |


---

## From `openapi`

> OpenAPI 3.1 specification authoring, Swagger UI integration, client SDK generation, schema validation, and API-first design workflow

# OpenAPI Specification Skill

## Purpose

OpenAPI is the industry standard for describing REST APIs. This skill covers writing OpenAPI 3.1 specs, integrating interactive documentation (Swagger UI, Scalar), generating type-safe client SDKs, validating requests/responses against the spec, and embedding spec linting into CI. An API-first approach catches contract mismatches before code is written.

## Key Concepts

### OpenAPI 3.1 Structure

```
openapi: 3.1.0
info:                  → API metadata (title, version, description)
servers:               → Base URLs for environments
paths:                 → Endpoints and their operations
  /resource:
    get:               → Operation (method + path)
      parameters:      → Query, path, header, cookie params
      requestBody:     → Request payload schema
      responses:       → Response schemas by status code
components:
  schemas:             → Reusable data models (JSON Schema)
  securitySchemes:     → Auth definitions
  parameters:          → Reusable parameters
  responses:           → Reusable response objects
security:              → Global security requirements
tags:                  → Logical grouping of operations
```

### OpenAPI 3.1 vs 3.0

| Feature | 3.0 | 3.1 |
|---------|-----|-----|
| JSON Schema compatibility | Subset (divergent) | Full JSON Schema 2020-12 |
| `type` as array | Not supported | `type: ["string", "null"]` |
| `$ref` siblings | Ignored | Allowed alongside `$ref` |
| Webhooks | Not supported | `webhooks` top-level key |
| `const` keyword | Not supported | Supported |

## Workflow

### Step 1: Write the Spec (API-First)

```yaml
# openapi.yaml
openapi: 3.1.0
info:
  title: Acme API
  version: 1.0.0
  description: |
    The Acme API provides programmatic access to manage products,
    orders, and customers.
  contact:
    name: API Support
    email: api@acme.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.acme.com/v1
    description: Production
  - url: https://staging-api.acme.com/v1
    description: Staging
  - url: http://localhost:3000/api/v1
    description: Local development

security:
  - bearerAuth: []

tags:
  - name: Products
    description: Product catalog management
  - name: Orders
    description: Order lifecycle management

paths:
  /products:
    get:
      operationId: listProducts
      tags: [Products]
      summary: List all products
      description: Returns a paginated list of products with optional filtering.
      parameters:
        - $ref: '#/components/parameters/PageParam'
        - $ref: '#/components/parameters/LimitParam'
        - name: category
          in: query
          schema:
            type: string
            enum: [electronics, clothing, books]
          description: Filter by product category
        - name: search
          in: query
          schema:
            type: string
            minLength: 2
            maxLength: 100
          description: Full-text search across name and description
      responses:
        '200':
          description: Paginated list of products
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

    post:
      operationId: createProduct
      tags: [Products]
      summary: Create a new product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProductRequest'
            example:
              name: "Wireless Headphones"
              price: 79.99
              category: electronics
              description: "Noise-cancelling Bluetooth headphones"
      responses:
        '201':
          description: Product created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Product with this SKU already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          $ref: '#/components/responses/ValidationError'

  /products/{productId}:
    get:
      operationId: getProduct
      tags: [Products]
      summary: Get a product by ID
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Product details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    PageParam:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

  schemas:
    Product:
      type: object
      required: [id, name, price, category, createdAt]
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          minLength: 1
          maxLength: 200
        price:
          type: number
          format: float
          minimum: 0
          exclusiveMinimum: true
        category:
          type: string
          enum: [electronics, clothing, books]
        description:
          type: ["string", "null"]
          maxLength: 2000
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: ["string", "null"]
          format: date-time

    CreateProductRequest:
      type: object
      required: [name, price, category]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
        price:
          type: number
          minimum: 0
          exclusiveMinimum: true
        category:
          type: string
          enum: [electronics, clothing, books]
        description:
          type: string
          maxLength: 2000

    ProductListResponse:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Product'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required: [page, limit, total, totalPages]
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string

  responses:
    Unauthorized:
      description: Missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: UNAUTHORIZED
              message: Invalid or expired token
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ValidationError:
      description: Request validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
```

### Step 2: Serve Interactive Documentation

#### Option A: Scalar (Modern, Recommended)

```typescript
// app/api/docs/route.ts (Next.js App Router)
import { ApiReference } from '@scalar/nextjs-api-reference';

const config = {
  spec: {
    url: '/openapi.yaml', // Serve from /public/openapi.yaml
  },
  theme: 'kepler',
  layout: 'modern',
  darkMode: true,
  hideModels: false,
  searchHotKey: 'k',
};

export const GET = ApiReference(config);
```

#### Option B: Swagger UI (Classic)

```typescript
// app/api/docs/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const html = `
<!DOCTYPE html>
<html>
<head>
  <title>API Docs</title>
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
  <script>
    SwaggerUIBundle({
      url: '/openapi.yaml',
      dom_id: '#swagger-ui',
      deepLinking: true,
      presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
    });
  </script>
</body>
</html>`;
  return new NextResponse(html, { headers: { 'Content-Type': 'text/html' } });
}
```

### Step 3: Generate Type-Safe Client SDKs

```bash
# Install openapi-typescript for TypeScript types
npm install -D openapi-typescript openapi-fetch

# Generate types from spec
npx openapi-typescript ./public/openapi.yaml -o ./src/lib/api/schema.d.ts
```

#### Type-Safe API Client

```typescript
// src/lib/api/client.ts
import createClient from 'openapi-fetch';
import type { paths } from './schema';

export const api = createClient<paths>({
  baseUrl: process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000/api/v1',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Middleware: attach auth token
api.use({
  async onRequest({ request }) {
    const token = getAuthToken();
    if (token) {
      request.headers.set('Authorization', `Bearer ${token}`);
    }
    return request;
  },
});

// Usage — fully typed, autocompleted
const { data, error } = await api.GET('/products', {
  params: {
    query: { category: 'electronics', page: 1, limit: 20 },
  },
});
// data is typed as ProductListResponse
// error is typed as ErrorResponse

const { data: product } = await api.POST('/products', {
  body: {
    name: 'Wireless Mouse',
    price: 29.99,
    category: 'electronics',
  },
});
// body is validated against CreateProductRequest at compile time
```

### Step 4: Request Validation Middleware

```typescript
// middleware/validate-openapi.ts
import { OpenAPIValidator } from 'openapi-backend';
import spec from '../../public/openapi.json';

const validator = new OpenAPIValidator({ definition: spec });

export async function validateRequest(
  req: Request,
  operationId: string
): Promise<{ valid: boolean; errors?: string[] }> {
  const body = req.method !== 'GET' ? await req.json() : undefined;
  const url = new URL(req.url);
  const query = Object.fromEntries(url.searchParams);

  const result = validator.validateRequest(
    {
      method: req.method.toLowerCase(),
      path: url.pathname,
      body,
      query,
      headers: Object.fromEntries(req.headers),
    },
    operationId
  );

  if (result.errors) {
    return {
      valid: false,
      errors: result.errors.map((e) => `${e.instancePath}: ${e.message}`),
    };
  }

  return { valid: true };
}
```

### Step 5: Lint the Spec in CI

```yaml
# .github/workflows/lint-api.yml
name: Lint OpenAPI Spec
on:
  pull_request:
    paths:
      - 'public/openapi.yaml'
      - 'openapi/**'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Lint OpenAPI spec
        uses: stoplightio/spectral-action@v0.8.11
        with:
          file_glob: 'public/openapi.yaml'
          spectral_ruleset: .spectral.yaml

      - name: Validate spec
        run: npx @redocly/cli lint public/openapi.yaml
```

#### .spectral.yaml (Linting Rules)

```yaml
extends:
  - spectral:oas

rules:
  operation-operationId: error
  operation-description: warn
  operation-tags: error
  oas3-schema: error
  info-contact: warn
  no-$ref-siblings: off  # OpenAPI 3.1 allows $ref siblings

  # Custom rules
  require-pagination:
    description: List endpoints must include pagination parameters
    given: "$.paths[*].get.parameters"
    severity: warn
    then:
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            properties:
              name:
                enum: [page, cursor, offset]
```

## Best Practices

1. **API-first design** — Write the spec before the implementation. The spec is the contract; code is the implementation detail.
2. **Use `$ref` aggressively** — Extract every schema, parameter, and response into `components/`. Duplication in specs causes drift.
3. **Always include `operationId`** — Client generators use this as the method name. Choose clear, unique names (e.g., `listProducts`, `createOrder`).
4. **Document error responses** — Every endpoint should document 401, 404, 422, and 500 responses with example payloads.
5. **Use `openapi-fetch` over hand-rolled clients** — It provides compile-time type safety derived directly from the spec, eliminating type drift.
6. **Version via URL path** — `/v1/products` is clearer than header-based versioning for most APIs.
7. **Add examples to schemas** — Swagger UI and Scalar render examples in the docs, making them immediately useful for consumers.
8. **Lint in CI** — Use Spectral or Redocly CLI to catch spec issues before merge.

## Common Pitfalls

| Pitfall | Symptom | Fix |
|---------|---------|-----|
| **Spec diverges from implementation** | Clients get unexpected responses | Generate types from spec and validate requests against it at runtime |
| **Missing `operationId`** | Generated client methods named `getPathProducts` instead of `listProducts` | Add `operationId` to every operation |
| **Nullable fields using `nullable: true`** | Validation errors in OpenAPI 3.1 | Use `type: ["string", "null"]` instead of `nullable: true` (3.0 syntax) |
| **Circular `$ref` breaking generators** | Code generation crashes or produces infinite types | Break cycles with intermediate schemas or use generators that handle cycles (openapi-typescript does) |
| **No pagination on list endpoints** | Clients fetch unbounded datasets | Always include `page`/`limit` or cursor-based pagination parameters |
| **Hardcoded server URLs** | Spec unusable across environments | Use `servers` array with dev/staging/prod URLs; support env variable override in clients |
| **Giant monolithic spec file** | Unreadable, merge conflicts | Split into multiple files using `$ref: './schemas/product.yaml'` and bundle at build time with `@redocly/cli bundle` |
| **Missing `required` arrays** | Clients treat all fields as optional | Explicitly list required fields on every object schema |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.