Documentation Standards
Documentation standards define the required content, format, and maintenance expectations for all documentation artifacts that accompany code. This reference covers project documentation (READMEs, changelogs), technical documentation (API specs, architecture records), operational documentation (runbooks), and inline documentation (comments). Each section specifies mandatory elements, format requirements, and quality criteria.
README requirements
Every repository requires a README.md file in the root directory. The README serves as the entry point for anyone encountering the codebase, whether they are new team members, future maintainers, or integration partners. README content follows a fixed structure that ensures consistency across projects while accommodating variation in project complexity.
Mandatory sections
| Section | Purpose | Required content |
|---|---|---|
| Title and description | Identify the project | Project name as H1, single paragraph explaining what the software does and who uses it |
| Status | Communicate maintenance state | Badge or text indicating: active, maintenance-only, deprecated, or archived |
| Prerequisites | Enable environment setup | Runtime versions, system dependencies, required accounts or access |
| Installation | Enable local setup | Step-by-step commands to install dependencies and configure the application |
| Configuration | Enable customisation | Environment variables with descriptions, example values, and defaults |
| Usage | Demonstrate basic operation | Commands or code examples showing primary use cases |
| Contributing | Guide contributors | Link to CONTRIBUTING.md or inline contribution process |
| Licence | Declare terms | Licence name with link to LICENCE file |
Conditional sections
Include these sections when the specified conditions apply:
| Section | Include when | Content |
|---|---|---|
| Architecture | Codebase exceeds 5,000 lines or has 3+ major components | Component diagram, data flow description, key design decisions |
| API reference | Project exposes an API | Link to generated documentation or inline endpoint summary |
| Deployment | Project runs as a service | Environment requirements, deployment commands, health check endpoints |
| Testing | Test suite exists | Commands to run tests, coverage requirements, test data setup |
| Troubleshooting | Support requests reveal common issues | Problem/solution pairs for 5+ frequent issues |
| Changelog | Project has external consumers | Link to CHANGELOG.md |
| Security | Project handles sensitive data | Security contact, vulnerability reporting process, link to SECURITY.md |
README template
# Project Name
Brief description of what this project does and who it serves.

## Prerequisites
- Node.js 18.0 or higher- PostgreSQL 14 or higher- Access to organisation's npm registry
## Installation
Clone the repository and install dependencies:
git clone https://github.com/org/project.gitcd projectnpm install
Copy the example environment file and configure:
cp .env.example .env
## Configuration
| Variable | Description | Default | Required ||----------|-------------|---------|----------|| `DATABASE_URL` | PostgreSQL connection string | None | Yes || `API_KEY` | External service API key | None | Yes || `LOG_LEVEL` | Logging verbosity | `info` | No || `PORT` | HTTP server port | `3000` | No |
## Usage
Start the development server:
npm run dev
Run database migrations:
npm run migrate
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setupand contribution guidelines.
## Licence
[MIT](LICENCE)API documentation
API documentation uses the OpenAPI Specification (formerly Swagger) version 3.0 or higher. Every HTTP API requires an OpenAPI document maintained alongside the code. The specification file lives in the repository at docs/openapi.yaml or openapi.yaml in the root directory.
OpenAPI requirements
The OpenAPI document includes these mandatory elements:
| Element | Path | Requirement |
|---|---|---|
| Info | info.title | Human-readable API name |
| Info | info.version | Semantic version matching release |
| Info | info.description | Purpose, authentication overview, rate limits |
| Servers | servers | At least production URL; staging if applicable |
| Paths | paths.{path}.{method}.summary | One-line operation description |
| Paths | paths.{path}.{method}.description | Detailed behaviour explanation |
| Paths | paths.{path}.{method}.operationId | Unique identifier in camelCase |
| Paths | paths.{path}.{method}.responses | All possible response codes with schemas |
| Components | components.schemas | Reusable schema definitions for all request/response bodies |
| Components | components.securitySchemes | Authentication mechanisms |
| Security | security | Default security requirements |
Response documentation
Document all response codes the endpoint can return. For each response, include a schema definition and at least one example.
paths: /beneficiaries/{id}: get: summary: Retrieve a beneficiary record description: | Returns the complete beneficiary record including demographic data and programme enrolments. Requires read access to the beneficiary's programme. operationId: getBeneficiary parameters: - name: id in: path required: true schema: type: string format: uuid description: Beneficiary unique identifier responses: '200': description: Beneficiary record retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Beneficiary' example: id: "550e8400-e29b-41d4-a716-446655440000" givenName: "Maria" familyName: "Santos" dateOfBirth: "1985-03-15" programmes: - id: "prog-001" name: "Cash Assistance" enrolledAt: "2024-01-15T10:30:00Z" '401': description: Authentication required content: application/json: schema: $ref: '#/components/schemas/Error' '403': description: Insufficient permissions for this beneficiary content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Beneficiary not found content: application/json: schema: $ref: '#/components/schemas/Error'Schema documentation
Every schema includes a description at the schema level and for each property. Mark required fields explicitly. Provide format hints for strings that follow patterns.
components: schemas: Beneficiary: type: object description: | A beneficiary represents an individual registered to receive assistance through one or more programmes. required: - id - givenName - familyName properties: id: type: string format: uuid description: System-generated unique identifier readOnly: true givenName: type: string minLength: 1 maxLength: 100 description: First or given name as provided by the beneficiary familyName: type: string minLength: 1 maxLength: 100 description: Family name or surname dateOfBirth: type: string format: date description: Birth date in ISO 8601 format (YYYY-MM-DD) programmes: type: array description: Programmes in which the beneficiary is enrolled items: $ref: '#/components/schemas/ProgrammeEnrolment'Code comments
Comments explain intent, constraints, and non-obvious behaviour. They do not restate what code does when the code is self-explanatory. The purpose of a comment is to provide information that cannot be expressed in the code itself.
Comment categories
Intent comments explain why code exists or why a particular approach was chosen. These comments survive refactoring because they document decisions rather than implementation.
# Use insertion sort for small arrays because the overhead of# quicksort exceeds its benefits below 10 elements. Benchmarked# on target hardware: insertion wins below n=12.if len(items) < 10: return insertion_sort(items)Constraint comments document requirements imposed by external systems, regulations, or integration partners that are not evident from the code.
# USAID requires beneficiary exports to exclude PII fields when# destination is outside the implementing country. See compliance# requirement DR-2024-0042.if export_destination != programme.country: exclude_fields.extend(PII_FIELDS)Warning comments alert future developers to non-obvious risks or dependencies.
# WARNING: This timeout must exceed the payment provider's retry# window (currently 180 seconds) to prevent duplicate transactions.# Do not reduce without coordinating with finance team.TRANSACTION_TIMEOUT = 200TODO comments mark incomplete work with enough context to action later. Include the author identifier and date.
# TODO(jsmith 2024-11-15): Replace with batch query once# PostgreSQL 16 upgrade completes. Current approach makes# N+1 queries. Issue #1234.Language-specific standards
| Language | Single-line | Multi-line | Documentation |
|---|---|---|---|
| Python | # | """...""" | Docstrings per PEP 257 |
| JavaScript/TypeScript | // | /* ... */ | JSDoc for exports |
| Java | // | /* ... */ | Javadoc for public APIs |
| Go | // | /* ... */ | Package and exported identifiers |
| SQL | -- | /* ... */ | Complex queries only |
| Shell | # | None | Script header and complex logic |
Function and method documentation
Public functions, methods, and classes require documentation comments. Documentation specifies parameters, return values, exceptions, and usage examples.
def calculate_distribution_amount( beneficiary_id: str, programme_id: str, distribution_date: date) -> Decimal: """Calculate the distribution amount for a beneficiary.
Determines the amount based on household size, programme rules, and any adjustments from previous distributions. The calculation accounts for partial periods when beneficiaries enrolled mid-cycle.
Args: beneficiary_id: UUID of the registered beneficiary. programme_id: UUID of the distribution programme. distribution_date: Date of the planned distribution.
Returns: Distribution amount in programme currency. Returns Decimal('0') if the beneficiary is ineligible for this distribution.
Raises: BeneficiaryNotFoundError: If beneficiary_id does not exist. ProgrammeNotFoundError: If programme_id does not exist. IneligibleError: If beneficiary is suspended or exited.
Example: >>> amount = calculate_distribution_amount( ... beneficiary_id="550e8400-e29b-41d4-a716-446655440000", ... programme_id="prog-001", ... distribution_date=date(2024, 12, 1) ... ) >>> print(amount) Decimal('150.00') """/ * Validates beneficiary registration data against programme requirements. * * Checks required fields, data formats, and programme-specific eligibility * rules. Returns validation errors grouped by field for display in forms. * * @param data - Registration form data from client * @param programmeId - Target programme for enrolment * @returns Validation result with any errors grouped by field name * * @example * const result = validateRegistration(formData, 'prog-001'); * if (!result.valid) { * console.log(result.errors); * // { dateOfBirth: ['Must be 18 or older'], phone: ['Invalid format'] } * } */export function validateRegistration( data: RegistrationData, programmeId: string): ValidationResult {Architecture decision records
An architecture decision record (ADR) documents a significant technical decision along with its context and consequences. ADRs create a decision log that explains why the system has its current shape, which prevents relitigating settled decisions and helps new team members understand constraints.
When to write an ADR
Write an ADR when the decision:
- Affects multiple components or services
- Is difficult or expensive to reverse
- Involves significant trade-offs
- Departs from established patterns
- Has security or compliance implications
- Results from evaluating multiple alternatives
Do not write an ADR for routine choices like variable naming, minor refactoring, or bug fixes.
ADR format
ADRs use a fixed format stored as Markdown files in docs/adr/ with filenames following the pattern NNNN-title-with-dashes.md where NNNN is a zero-padded sequence number.
docs/adr/├── 0001-use-postgresql-for-primary-database.md├── 0002-adopt-event-sourcing-for-audit-log.md├── 0003-select-keycloak-for-identity-provider.md└── README.mdADR template
# NNNN. Title of Decision
Date: YYYY-MM-DD
## Status
Proposed | Accepted | Deprecated | Superseded by [NNNN](link)
## Context
Describe the situation that requires a decision. Include:- The problem or need driving the decision- Constraints (technical, organisational, regulatory)- Forces in tension that make this decision non-trivial
## Decision
State the decision in active voice. "We will use X" not"X was chosen". Be specific about scope and boundaries.
## Consequences
Describe the results of the decision:- Positive outcomes and benefits- Negative outcomes and risks accepted- New constraints this creates- Follow-up actions required
## Alternatives Considered
For each rejected alternative:- Brief description- Why it was not selectedADR example
# 0003. Select Keycloak for Identity Provider
Date: 2024-09-15
## Status
Accepted
## Context
The organisation requires centralised identity management tosupport single sign-on across 12 applications, multi-factorauthentication, and federation with partner organisations.Current state has each application managing its own userdatabase, creating credential sprawl and inconsistentsecurity controls.
Requirements:- Support SAML 2.0 and OIDC protocols- Self-hosted option for data sovereignty (field offices operate in countries with data localisation requirements)- Integration with existing Active Directory for HQ staff- Support for social login for external stakeholders- Budget constraint: no per-user licensing fees
## Decision
We will deploy Keycloak as the organisation's identityprovider. Keycloak will run on the existing Kubernetescluster in the EU region with a replica deployment inthe East Africa regional data centre.
## Consequences
Positive:- No licensing costs regardless of user count- Full control over data location- Active community and extensive documentation- Native Kubernetes operator simplifies deployment
Negative:- Requires internal expertise for maintenance- Upgrades require manual intervention and testing- No vendor support SLA (mitigated by commercial support option if needed)
New constraints:- All new applications must integrate via OIDC- Identity team must maintain Keycloak expertise- Quarterly security patching required
## Alternatives Considered
Microsoft Entra ID: Strong Active Directory integrationbut per-user pricing exceeds budget at scale. Data residencyoptions do not cover all required countries.
Okta: Excellent developer experience but licensing costsprohibitive. Nonprofit discount still exceeded budget by 3x.
Authentik: Promising open-source alternative but smallercommunity and less mature federation support. Considered toorisky for production identity infrastructure.Changelog
A changelog documents all notable changes to a project in reverse chronological order. Every project with external consumers (other teams, partners, end users) maintains a CHANGELOG.md file in the repository root.
Changelog format
Changelogs follow the Keep a Changelog format (keepachangelog.com) with modifications for semantic versioning.
# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added- Beneficiary export to CSV format
### Changed- Increased API rate limit from 100 to 500 requests per minute
## [2.1.0] - 2024-11-01
### Added- Multi-factor authentication support for API access- Bulk beneficiary import from Excel files- Arabic language support in beneficiary portal
### Changed- Upgraded PostgreSQL client to version 3.4.0- Distribution calculations now round to 2 decimal places
### Fixed- Corrected timezone handling in distribution scheduling- Resolved memory leak in long-running export jobs
### Security- Patched CVE-2024-1234 in authentication library
## [2.0.0] - 2024-08-15
### Changed- BREAKING: Renamed `participant` to `beneficiary` in all APIs- BREAKING: Date fields now use ISO 8601 format exclusively
### Removed- BREAKING: Deprecated v1 API endpoints removed
### Migration- Run `npm run migrate:v2` before deploying- Update API clients to use new field names- See [Migration Guide](docs/migration-v2.md) for detailsChange categories
| Category | Use for |
|---|---|
| Added | New features and capabilities |
| Changed | Changes to existing functionality |
| Deprecated | Features marked for future removal |
| Removed | Features removed in this release |
| Fixed | Bug fixes |
| Security | Vulnerability patches and security improvements |
| Migration | Required steps when upgrading (for breaking changes) |
Writing changelog entries
Each entry describes the change from the user’s perspective. Focus on what changed and why it matters, not how it was implemented.
| Poor | Better |
|---|---|
| ”Refactored database queries" | "Reduced report generation time from 45 seconds to 8 seconds" |
| "Fixed bug in validation" | "Fixed: Phone numbers with country codes now validate correctly" |
| "Updated dependencies" | "Upgraded authentication library to patch CVE-2024-1234" |
| "Added new endpoint" | "Added: Export beneficiary data to CSV for offline analysis” |
Runbook documentation
A runbook documents procedures for operating, maintaining, and troubleshooting a system in production. Runbooks enable operations staff to handle routine tasks and common incidents without requiring deep system knowledge. Every service deployed to production requires a runbook in docs/runbook.md or docs/runbook/ directory for complex services.
Runbook sections
| Section | Content |
|---|---|
| Service overview | Purpose, dependencies, SLA targets, owning team |
| Architecture | Component diagram, data flows, external integrations |
| Health checks | Endpoints, expected responses, check frequency |
| Metrics and alerts | Key metrics, threshold values, alert configurations |
| Common procedures | Start, stop, restart, scale, backup, restore |
| Troubleshooting | Symptoms, diagnosis steps, resolution procedures |
| Escalation | Contact information, escalation criteria, on-call rotation |
| Disaster recovery | RPO/RTO targets, recovery procedures, test schedule |
Health check documentation
## Health Checks
### Application Health
Endpoint: `GET /health`
Expected response:```json{ "status": "healthy", "version": "2.1.0", "checks": { "database": "ok", "cache": "ok", "queue": "ok" }}```
Check frequency: Every 30 seconds
Alert threshold: 3 consecutive failures
### Database Connectivity
Command:```bashpsql $DATABASE_URL -c "SELECT 1"```
Expected: Returns `1` within 5 seconds
Alert threshold: Response time exceeds 5 seconds or connection failsTroubleshooting format
Troubleshooting sections follow a consistent structure: symptom, possible causes with likelihood, diagnosis steps, and resolution.
## Troubleshooting
### High Memory Usage
Symptom: Container memory exceeds 80% of limit; OOMKilled eventsin logs.
Possible causes:1. Memory leak in export processing (common)2. Excessive concurrent connections (occasional)3. Large payload processing (rare)
Diagnosis:
Check current memory consumers:```bashkubectl top pods -n productionkubectl exec -it $POD -- ps aux --sort=-%mem | head -20```
Check for export jobs:```bashkubectl logs $POD | grep "export" | tail -50```
Resolution:
For export-related memory growth:```bash# Cancel running exportscurl -X POST http://$POD:8080/admin/exports/cancel-all
# Restart pod to reclaim memorykubectl rollout restart deployment/beneficiary-service -n production```
For connection pool exhaustion:```bash# Check connection countkubectl exec -it $POD -- psql $DATABASE_URL -c \ "SELECT count(*) FROM pg_stat_activity WHERE application_name = 'beneficiary-service'"
# If above 80, restart to reset connectionskubectl rollout restart deployment/beneficiary-service -n production```
Prevention: Configure export jobs to process in batches of 1,000records. Set connection pool maximum to 20 (see config `DB_POOL_MAX`).Documentation review
Documentation undergoes review before merge, following the same process as code. Reviews verify accuracy, completeness, and adherence to standards.
Review checklist
| Category | Verification |
|---|---|
| Accuracy | Technical content is correct; commands and examples work |
| Completeness | All required sections present; no placeholder text |
| Currency | Version numbers and dependencies are current |
| Clarity | Explanations are understandable to target audience |
| Consistency | Terminology matches project conventions |
| Links | Internal and external links resolve correctly |
| Formatting | Markdown renders correctly; code blocks have language tags |
| Accessibility | Images have alt text; content structure uses headings correctly |
Review assignment
| Documentation type | Required reviewer |
|---|---|
| README | Any team member |
| API documentation | Developer familiar with the API |
| Architecture decision record | Technical lead or architect |
| Runbook | Operations team member |
| Security documentation | Security team member |
| User documentation | Someone unfamiliar with the feature |
Documentation tooling
Documentation tooling follows docs-as-code principles: documentation source lives in version control alongside code, undergoes the same review process, and builds through automated pipelines.
Tooling stack
+--------------------------------------------------------------------+| DOCUMENTATION PIPELINE |+--------------------------------------------------------------------+| || SOURCE BUILD PUBLISH || +-------------+ +----------------+ +-------------+ || | | | | | | || | Markdown +------>| Static Site +------>| Web Server | || | in Git | | Generator | | or CDN | || | | | | | | || +------+------+ +-------+--------+ +-------------+ || | | || | +-------+--------+ || | | | || +------------->| Linting and | || | Validation | || | | || +----------------+ || |+--------------------------------------------------------------------+Recommended tools
| Purpose | Tool | Notes |
|---|---|---|
| Markdown linting | markdownlint | Enforces consistent formatting |
| Link checking | markdown-link-check | Validates internal and external links |
| Spell checking | cspell | Configurable with technical dictionaries |
| API documentation | Redoc, Swagger UI | Renders OpenAPI specifications |
| Static site | Astro, MkDocs, Docusaurus | Generates documentation websites |
| Diagram generation | Mermaid, PlantUML | Creates diagrams from text |
CI pipeline integration
Documentation builds and validates in the CI pipeline alongside code. Failed documentation checks block merge.
name: Documentation
on: pull_request: paths: - 'docs/' - '*.md' - 'openapi.yaml'
jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Lint Markdown uses: DavidAnson/markdownlint-cli2-action@v14 with: globs: '/*.md'
- name: Check links uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-quiet-mode: 'yes' config-file: '.markdown-link-check.json'
- name: Validate OpenAPI run: npx @redocly/cli lint openapi.yaml
- name: Spell check run: npx cspell "/*.md" --no-progressAccessibility in documentation
Documentation accessibility ensures content is usable by people with disabilities, including those using screen readers, keyboard navigation, or other assistive technologies.
Requirements
| Element | Requirement |
|---|---|
| Headings | Use hierarchical structure (H1 → H2 → H3); do not skip levels |
| Images | Provide alt text describing content and purpose |
| Links | Use descriptive text; avoid “click here” or “read more” |
| Code blocks | Include language identifier; provide text description for complex examples |
| Tables | Include header row; avoid merged cells; keep tables simple |
| Colour | Do not use colour alone to convey meaning |
| Language | Specify document language; mark language changes in multilingual content |
Alt text guidelines
Alt text describes what an image shows and why it matters in context. For diagrams, describe the relationships and key information rather than visual appearance.
| Image type | Alt text approach |
|---|---|
| Screenshot | Describe the relevant UI state and highlighted elements |
| Diagram | Describe the components and their relationships |
| Chart | State the trend or key data points |
| Decorative | Use empty alt (alt="") or CSS background |
<!-- Poor -->
<!-- Better -->Versioning documentation
Documentation versions track with the software it describes. When code changes affect behaviour, documentation updates in the same commit or pull request.
Version synchronisation
| Change type | Documentation action |
|---|---|
| New feature | Add documentation before or with feature merge |
| API change | Update OpenAPI spec in same pull request |
| Configuration change | Update README and runbook |
| Breaking change | Update changelog, migration guide, and affected docs |
| Bug fix | Update if fix changes documented behaviour |
| Deprecation | Add deprecation notice with removal timeline |
Multi-version documentation
Projects supporting multiple versions simultaneously maintain documentation branches or version selectors.
docs/├── v1/ # Legacy version (maintenance only)│ ├── api.md│ └── guide.md├── v2/ # Current stable version│ ├── api.md│ ├── guide.md│ └── migration-from-v1.md└── next/ # Upcoming version (pre-release) ├── api.md └── guide.mdDocumentation for deprecated versions includes a notice directing users to current documentation:
<Aside variant="caution" title="Deprecated version"> This documentation covers API v1, which is deprecated and will be removed on 2025-06-01. See the [v2 documentation](/docs/v2/) for the current version and [migration guide](/docs/v2/migration-from-v1/) for upgrade instructions.</Aside>