Documentation That Stays Current: Habits and Tooling That Work

Docs go stale because nothing forces them to change when code changes. Here is a system that does: docs near code, generated references, executable examples and review habits.

By The OpenVisio Team · · 11 min read

Documentation does not go stale because engineers are lazy. It goes stale because nothing forces it to change when the code changes. The compiler checks your function calls, the tests check your behavior, the linter checks your style. Nothing checks that the README still describes how to run the service, or that the setup guide still mentions the right environment variable. So the docs drift, one small change at a time, until a new hire follows them exactly and nothing works.

The fix is not a documentation sprint. It is a system where the cheapest path keeps docs current: docs live next to the code, references are generated, examples are executed, links are checked, and the review process asks about docs at the moment the code changes. This post walks through each part with concrete tooling.

Key takeaways

  • Docs stay current when they are close to the code, generated where possible, tested where they contain code, and reviewed in the same pull request as the change.
  • Divide docs by how fast they change. Reference material changes with every release and should be generated. Concepts and decisions change rarely and should be written.
  • Execute code examples in CI: doctests, Rust doc tests, compiled snippets, or examples imported from real files.
  • Check links and prose automatically with tools like lychee, markdownlint and Vale.
  • Add a docs question to the pull request template and give docs owners through CODEOWNERS.
  • Delete docs nobody maintains. Wrong docs are worse than missing docs.

Why docs drift

It helps to name the forces precisely, because each one has a specific countermeasure.

  1. Distance. Docs in a wiki or a separate repository are not in the diff when code changes. The person changing the code never sees them.
  2. No failure signal. A wrong sentence in a README fails nothing. Errors accumulate silently.
  3. Duplication. The same fact (a port number, a command, an API field) is written in three places. One gets updated.
  4. Unclear ownership. Docs owned by "everyone" are owned by no one.
  5. Wrong level of detail. Docs that describe implementation details change with every refactor. Docs that describe purpose and concepts rarely need to.

Every practice below targets one or more of these.

Sort docs by how fast they change

The Diátaxis framework is a useful lens: it divides documentation into tutorials, how-to guides, reference, and explanation. For keeping docs current, a simpler split works:

KindExamplesChangesStrategy
ReferenceAPI endpoints, CLI flags, config options, function signaturesWith every releaseGenerate from code
How-toRun locally, deploy, add a migration, rotate a keyWhen tooling changesWrite, keep next to code, test the commands
ExplanationArchitecture, domain concepts, why things are the way they areRarelyWrite, link to ADRs and code, review quarterly
TutorialsGetting started guidesWhen onboarding flow changesWrite, test end to end periodically

Most stale-docs pain lives in reference and how-to. Those are also the categories where automation helps most.

Put docs next to the code

Move documentation into the repository, as close as possible to the code it describes:

  • README.md at the root: what this is, how to run it, how to test it, where to find more.
  • A README.md in each significant module or service folder: what it does, its public API, its dependencies, its owners.
  • docs/ for cross-cutting explanation, how-to guides and architecture decision records.
  • Docstrings and doc comments for function- and type-level reference.

Docs in the repository show up in code search, in the pull request diff, and in the reviewer's view. That alone removes the distance problem. It also means the docs for version 2.3 are in the version 2.3 tag, which is exactly what someone debugging an old release needs.

Generate reference documentation

Anything that can be derived from the code should be. Hand-written reference docs are duplication, and duplication drifts.

# TypeScript API reference from TSDoc comments
npx typedoc src/index.ts --out docs/api

# Python: Sphinx with autodoc pulls docstrings into the docs
sphinx-apidoc -o docs/api src/mypackage
sphinx-build -b html docs docs/_build

# Rust: rustdoc, including private items for internal docs
cargo doc --no-deps --document-private-items

# Go: doc comments are rendered by pkg.go.dev and by the go doc command
go doc -all ./internal/orders

For HTTP APIs, generate the OpenAPI document from the code (FastAPI, NestJS's Swagger module, springdoc-openapi for Spring Boot, and similar tools do this) or generate the code from the OpenAPI document. Either direction works; hand-maintaining both does not.

For CLIs, generate the flag reference from the parser. Most CLI frameworks (Cobra, Click, clap) can print complete help text or generate Markdown or man pages, and a CI step can fail if the committed reference differs from freshly generated output:

# Example for a Cobra-based CLI with a hidden "gen-docs" command
go run ./cmd/mycli gen-docs --dir docs/cli
git diff --exit-code docs/cli || (echo "CLI docs are out of date; run make docs" && exit 1)

That git diff --exit-code pattern works for any generated file you choose to commit: config schemas, environment variable lists, database schema docs.

Test the code in your docs

Code examples are the part of docs that rots fastest and hurts most. Execute them.

Python doctests run examples embedded in docstrings:

def slugify(title: str) -> str:
    """Convert a title to a URL slug.

    >>> slugify("Hello, World!")
    'hello-world'
    """
    ...
python -m doctest -v src/mypackage/text.py
pytest --doctest-modules src/

Rust doc tests compile and run every code block in doc comments by default with cargo test --doc.

Go example functions (func ExampleSlugify() with an // Output: comment) are run by go test and shown in the generated documentation.

Markdown code blocks in READMEs and guides can be checked too. Options include importing snippets from real, tested source files at build time (many docs frameworks support including a file or a line range), or extracting blocks and running them in CI. For shell instructions, a cheap check is to run the documented setup commands in a clean container as part of a nightly job. If "Run locally" breaks, you find out before a new hire does.

Broken links are the most visible sign of stale docs and the easiest to catch:

# lychee: fast link checker for Markdown, HTML and more
lychee --no-progress './**/*.md'

# markdownlint for consistent formatting
npx markdownlint-cli2 "**/*.md" "#node_modules"

Vale lints prose against style rules you define: banned terms, product names spelled correctly, no references to retired components. A rule that flags the name of a service you decommissioned last year will find every doc that still mentions it:

# .vale/styles/House/Retired.yml
extends: existence
message: "'%s' was retired. See docs/adr/0014-retire-legacy-billing.md"
level: error
ignorecase: true
tokens:
  - legacy-billing
  - LegacyBillingClient

Run these in CI on every pull request that touches Markdown, and on a schedule for external links, which break without anyone touching your repository.

Make docs part of code review

Automation catches mechanical drift. Conceptual drift, where the doc is technically valid but no longer describes how the system works, needs a human at the right moment. The right moment is the pull request.

Add a line to the PR template:

## Docs
- [ ] No docs change needed
- [ ] Updated README / docs in this PR
- [ ] Follow-up docs ticket: #

The checkbox costs nothing and forces a two-second decision instead of a default of "no".

Use CODEOWNERS for docs. Assign docs/ and each module's README.md to the owning team. Changes to docs then get a reviewer who knows whether they are right, and changes to code in that folder put the owner in a position to ask "does the README still hold?"

Review docs in the same PR as code. A separate "docs follow-up" PR is often never opened. If the change alters behavior a doc describes, update the doc in the same diff.

Ask reviewers to read the docs diff, not skim it. Reviewing the docs of an unfamiliar area is also a good way to learn it, a point covered in code review for unfamiliar code.

The most durable docs say less. Some habits:

  • State each fact once. Port numbers, env var names and commands live in one place; everything else links there.
  • Describe purpose, not implementation. "The pricing module computes totals, taxes and discounts for a cart" survives refactors. "PricingService calls TaxCalculatorImpl via the TaxStrategy interface" does not.
  • Link to code by path, not by line number. Line numbers drift with every edit; a path or a symbol name survives much longer.
  • Date explanatory docs. "Last reviewed 2026-09" tells the reader how much to trust it.
  • Delete aggressively. A wrong doc misleads with confidence. If nobody will own a page, archive it with a pointer to the code or a generated view.

A rollout plan

You do not need all of this at once. A sensible order:

  1. Week 1: Add the PR template docs checkbox and CODEOWNERS entries for docs. Add a link checker to CI.
  2. Week 2: Move the most-used wiki pages (setup, deploy, architecture overview) into the repository.
  3. Week 3: Generate one reference (API, CLI or config) and add a git diff --exit-code check.
  4. Week 4: Turn the most-copied code examples into doctests, example functions, or imported snippets.
  5. Ongoing: Quarterly, review explanation docs with a dated "last reviewed" line, and delete what nobody owns.

Docs that do not have to be written

Some documentation exists only because there was no other way to see how a codebase fits together: the architecture overview, the "where things are" guide, the onboarding walkthrough. Those are exactly the docs that drift fastest, because they describe structure, and structure changes every week. Generating them from the code avoids the problem entirely. OpenVisio's Tour builds a stop-by-stop walkthrough of a repository from the current code, with article, code, diagram and slide views, and Explore shows the module map as it stands today. Pair that with a short, hand-written explanation of intent and a set of ADRs, and you have documentation where the parts that change constantly are derived, and the parts that are written by hand rarely need to change. The architecture diagrams that stay accurate post applies the same split to diagrams.

documentationdocs as codedeveloper experiencecode review

See it on your own repo

Paste a GitHub URL and get the day-one walkthrough — free, in your browser.

Index a repo

Keep reading