Skip to content

Docs drift check

A prek hook + GitHub Action that fails when public-surface code changes without the relevant docs page also changing. The intent is to keep the user-facing and developer-facing docs honest as the app grows.

What it checks

A code change is "drift-relevant" if it touches a path mapped in scripts/docs-drift-mapping.toml (sits at the repo root, not inside lifefile/). All paths in the mapping are relative to the repo root. For example:

[areas.classifier]
code_globs = ["lifefile/apps/classifier/**/*.py"]
docs_paths = ["lifefile/docs-site/docs/developers/architecture.md"]

[areas.dashboard]
code_globs = ["lifefile/templates/dashboard.html", "lifefile/apps/households/views.py"]
docs_paths = [
  "lifefile/docs-site/docs/users/getting-started.md",
  "lifefile/docs-site/docs/users/index.md",
]

[areas.vehicle_lookup]
code_globs = ["lifefile/apps/profiles/vehicle_lookup.py"]
docs_paths = [
  "lifefile/docs-site/docs/users/vehicles.md",
  "lifefile/docs-site/docs/developers/vehicle-lookup-setup.md",
]

When any file matching code_globs changes in the diff range, at least one file in docs_paths must also change. Otherwise the check fails with a report listing the offending area and the expected docs.

How to run it

# Manual one-shot, default range (@{upstream}..HEAD).
python scripts/check_docs_drift.py

# Against a specific revision range.
python scripts/check_docs_drift.py --range main..HEAD
DOCS_DRIFT_RANGE=main..HEAD python scripts/check_docs_drift.py

# Just the staged changes (used by the prek pre-commit/pre-push hook).
python scripts/check_docs_drift.py --staged

Installing the prek hook

prek is a fast, modern alternative to pre-commit, written in Rust. We use it because the docs-drift hook should run before push, not before every commit (commits in flight may legitimately not have the docs change yet).

uvx prek install --hook-type pre-push

This sets up .git/hooks/pre-push to invoke prek; prek then reads .pre-commit-config.yaml and runs the hooks staged for pre-push.

To run all hooks manually:

uvx prek run --all-files

To run just the docs-drift hook:

uvx prek run --hook-stage manual docs-drift

Escape hatches

Two ways to bypass when you genuinely don't need a docs change (e.g. a refactor that doesn't change behaviour):

  1. Per-commit: include the literal token [skip-docs-check] somewhere in any commit message in the range. The checker scans git log for the range and skips if any commit message contains the token.
  2. Per-run: set DOCS_DRIFT_SKIP=1 in the environment.

Use these sparingly — the point is that you actively decide "this doesn't need docs" rather than letting drift accumulate silently.

Updating the mapping

scripts/docs-drift-mapping.toml is the source of truth. Edit it whenever:

  • You add a new app under apps/ that has user- or developer-visible behaviour.
  • You add a new docs page that should track a specific code area.
  • You move or rename a code path.

The mapping uses fnmatch-style globs (**/*.py works). One area can map to multiple code globs and multiple docs pages. A single file can be in multiple areas — that just means a change requires some matching docs touch (not all).

How it runs in CI

.github/workflows/docs-drift.yml runs on every push and PR to main. It checks out the full git history, sets up Python, installs prek via uvx, and runs uvx prek run --hook-stage manual docs-drift with DOCS_DRIFT_RANGE=origin/main..HEAD. Failure fails the PR check.

When to update the mapping vs. the code

If the check fires on a change that you're sure shouldn't require a docs touch, the mapping is probably too aggressive — narrow the code_globs or remove the area. If it doesn't fire on something it should, the mapping needs widening or a new area.

A good test: every page in docs-site/docs/ should be reachable from at least one mapping area. Otherwise it's probably stale.