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).
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:
To run just the docs-drift hook:
Escape hatches¶
Two ways to bypass when you genuinely don't need a docs change (e.g. a refactor that doesn't change behaviour):
- Per-commit: include the literal token
[skip-docs-check]somewhere in any commit message in the range. The checker scansgit logfor the range and skips if any commit message contains the token. - Per-run: set
DOCS_DRIFT_SKIP=1in 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.