Detect stale README references after code changes — for pre-commit and CI.
Project description
Detect stale README references after code changes — for pre-commit and CI.
When you rename a function, change a method signature, remove a class, or rename a key in a config file, readme-drift warns you if those names are still referenced in your README — before the commit lands.
How it works
flowchart LR
A["git diff"] --> B["Changed .py files\nAST diff"]
A --> C["Changed config files\nKey-path diff"]
B --> D["Scan README\nbacktick + word-boundary"]
C --> D
D --> E{"Match?"}
E -->|Yes| F["❌ Fail"]
E -->|No| G["✅ Pass"]
Installation
pip install readme-drift
Usage
As a pre-commit hook (recommended)
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/sachn1/readme-drift
rev: v1.0.1 # use the latest release
hooks:
- id: readme-drift
Then install the hook:
pre-commit install
No extra args needed — the hook automatically checks your staged changes (what you've git add-ed). Do not pass --staged yourself; it's already set internally and duplicating it will cause an error.
In CI (GitHub Actions)
- name: Check README staleness
run: readme-drift --base-ref origin/${{ github.base_ref }}
CI compares committed changes against a base branch. Do not pass --staged here.
As a CLI tool
readme-drift --staged # check staged changes
readme-drift --base-ref origin/main # check against a branch
readme-drift --base-ref origin/main --warn-only # warn but don't fail
Configuration
All CLI flags can be set permanently in pyproject.toml under [tool.readme-drift].
CLI flags always take precedence over the file. See the full configuration reference for all options.
[tool.readme-drift]
base-ref = "origin/main"
warn-only = false
include-private = false
plain-text-search = true
min-symbol-length = 4
exclude = ["generated/", "tests/"]
symbol-allowlist = ["MyPublicClass"]
symbol-denylist = ["_internal"]
noise-blocklist = ["run", "build"] # replaces built-in default; [] disables
noise-allowlist = ["run"] # remove words from built-in (use instead of noise-blocklist)
readme-paths = [] # explicit list; empty = auto-discover
readme-exclude-dirs = []
| Key | CLI flag | Default |
|---|---|---|
base-ref |
--base-ref |
"HEAD" |
warn-only |
--warn-only |
false |
include-private |
--include-private |
false |
plain-text-search |
--plain-text-search / --no-plain-text-search |
true |
min-symbol-length |
--min-symbol-length |
4 |
exclude |
--exclude (repeatable) |
[] |
symbol-allowlist |
--symbol-allowlist (repeatable) |
[] |
symbol-denylist |
--symbol-denylist (repeatable) |
[] |
noise-blocklist |
--noise-blocklist (repeatable) |
built-in default |
noise-allowlist |
--noise-allowlist (repeatable) |
[] |
readme-paths |
--readme-paths (repeatable) |
[] (auto-discover) |
readme-exclude-dirs |
--readme-exclude-dirs (repeatable) |
[] |
pyproject.toml is discovered by walking up from the current directory (or --repo-root if set). If no [tool.readme-drift] section is present, all defaults apply.
Developer reference
A fully annotated Jupyter notebook walks through each module in depth — AST parsing, signature extraction, config diffing, the README scanner, and the complete end-to-end pipeline without git. Useful for understanding the internals or experimenting with edge cases.
Example output
readme-drift: ❌ README.md may be stale:
• `Client.connect` signature changed: connect(host, port) → connect(url)
in src/client.py
referenced in README.md line 42: …call `Client.connect(host, port)` to connect…
• `build` was removed
in package.json
referenced in README.md line 18: …run `npm run build` to compile…
→ Please update the README or run with --no-verify to skip.
What it catches
Python files (.py)
| Change | Detected? |
|---|---|
| Function renamed | ✅ old name flagged as removed |
| Function removed | ✅ |
| Method signature changed | ✅ |
| Class removed | ✅ |
Private symbol changed (_name) |
➖ ignored by default (enable with --include-private) |
| README updated alongside code | ✅ passes silently |
| No Python files changed | ✅ skipped |
Config files (.yml, .yaml, .json, .toml)
| Change | Detected? |
|---|---|
Script key removed ("build" → gone) |
✅ |
Job name removed (build: → gone) |
✅ |
Tool section removed ([tool.black] → gone) |
✅ |
| Key renamed at same level | ✅ (reported as remove + add) |
| Value changed, key unchanged | ➖ not tracked |
What it doesn't catch
- Behavioral changes that don't affect the public API or config surface
- Symbols not mentioned in the README
Supported README formats
Any file named readme (case-insensitive) with the extension .md, .markdown, .rst, .txt, or no extension is scanned. All README files in the repository are discovered recursively, including per-package READMEs in monorepos.
The following directories are never searched:
.git · node_modules · venv · .venv · .tox · __pycache__ · .pytest_cache · dist · build · .mypy_cache
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file readme_drift-3.0.0.tar.gz.
File metadata
- Download URL: readme_drift-3.0.0.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc47652e962bc22ed098052357698a910910c8fb0289a22ec3ac0365095c8308
|
|
| MD5 |
dd86cb79ac546949179a86f3612f43d0
|
|
| BLAKE2b-256 |
e421c2e83dbb91435249cdd56b04b2ec2bb40faf2d6d7cb6a58085fbc5c4fc5c
|
Provenance
The following attestation bundles were made for readme_drift-3.0.0.tar.gz:
Publisher:
publish.yml on sachn1/readme-drift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
readme_drift-3.0.0.tar.gz -
Subject digest:
dc47652e962bc22ed098052357698a910910c8fb0289a22ec3ac0365095c8308 - Sigstore transparency entry: 1789314882
- Sigstore integration time:
-
Permalink:
sachn1/readme-drift@bb40a51802cf33b2ecc0b8ddd046807c3959dfb7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/sachn1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb40a51802cf33b2ecc0b8ddd046807c3959dfb7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file readme_drift-3.0.0-py3-none-any.whl.
File metadata
- Download URL: readme_drift-3.0.0-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb0557c91df77d0d7b8a0ced420564841cbf389354a05d9192360dacffee445c
|
|
| MD5 |
f4eb4901907c0c7843dfa386ae7cc108
|
|
| BLAKE2b-256 |
5061722499919f9916dad829cfdb9e4b6481aae2bf2cbfb792f93b57f0989f85
|
Provenance
The following attestation bundles were made for readme_drift-3.0.0-py3-none-any.whl:
Publisher:
publish.yml on sachn1/readme-drift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
readme_drift-3.0.0-py3-none-any.whl -
Subject digest:
eb0557c91df77d0d7b8a0ced420564841cbf389354a05d9192360dacffee445c - Sigstore transparency entry: 1789314894
- Sigstore integration time:
-
Permalink:
sachn1/readme-drift@bb40a51802cf33b2ecc0b8ddd046807c3959dfb7 -
Branch / Tag:
refs/tags/v3.0.0 - Owner: https://github.com/sachn1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb40a51802cf33b2ecc0b8ddd046807c3959dfb7 -
Trigger Event:
push
-
Statement type: