Skip to main content

Detect stale README references after code changes — for pre-commit and CI.

Project description

readme-drift

PyPI version Python versions License Docs

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: v3.0.0 # 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

readme_drift-3.1.0.tar.gz (19.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

readme_drift-3.1.0-py3-none-any.whl (21.6 kB view details)

Uploaded Python 3

File details

Details for the file readme_drift-3.1.0.tar.gz.

File metadata

  • Download URL: readme_drift-3.1.0.tar.gz
  • Upload date:
  • Size: 19.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for readme_drift-3.1.0.tar.gz
Algorithm Hash digest
SHA256 e77a48c6ff3dd26cc5d8e18a1b52e785f57a306f4e1dfca450c867c62995c1bf
MD5 3235b0d01f38c15dd9214ff72a450f4a
BLAKE2b-256 5cfcd6b13d501e3069fd52ee28009355e9d1b8f149833e716c88e48ae73ee502

See more details on using hashes here.

Provenance

The following attestation bundles were made for readme_drift-3.1.0.tar.gz:

Publisher: publish.yml on sachn1/readme-drift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file readme_drift-3.1.0-py3-none-any.whl.

File metadata

  • Download URL: readme_drift-3.1.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

Hashes for readme_drift-3.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c10285f51d4689a6cb10f5186cdeabdb5f62a9dc3d331bf81fef40a0fdcc7d9
MD5 6af39f1908456d62bfb6490d6a7a43f1
BLAKE2b-256 2203548d997e1a0c38b316e6934a2c771f8fd416bf02f57587de078665bece91

See more details on using hashes here.

Provenance

The following attestation bundles were made for readme_drift-3.1.0-py3-none-any.whl:

Publisher: publish.yml on sachn1/readme-drift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page