Detect when source code referenced from markdown has drifted from a stored hash
Project description
snippetdrift
snippetdrift keeps code snippets in your markdown documentation honest. It syncs source code directly into your docs and detects when those regions change — so you always know when documentation may have gone stale.
What it does
Add a sentinel comment above any fenced code block pointing to a source file and line range. snippetdrift will:
- Sync — copy those exact lines from the source file into the code block
- Hash — record a SHA-256 fingerprint of the region
- Check — on every subsequent run, recompute the hash and fail if the source has changed
The result: your docs always show real, current code, and CI tells you the moment a referenced region drifts.
Installation
# Recommended
uv tool install snippetdrift
# Or with pip
pip install snippetdrift
Quickstart
1. Add a sentinel comment above an empty code block
<!-- snippetdrift: src/api/models.py#L27-34 -->
```python
The path is relative to the repo root. The code block can be empty — `init` will fill it in.
### 2. Initialize: sync content + write hashes
```bash
snippetdrift init docs/
This copies the referenced source lines into the code block and writes the hash and today's date into the sentinel comment, in-place:
<!-- snippetdrift: src/api/models.py#L27-34 hash:a3f9b2c1 reviewed:2025-04-09 -->
```python
class IngestRequest(BaseModel):
document_id: str
content: str
...
Commit the updated markdown files.
### 3. Check for drift in CI
```bash
snippetdrift check docs/
Exit code 0 = all snippets match. Exit code 1 = drift detected.
4. When drift is detected
Review the diff that snippetdrift check prints, update any surrounding prose, then sync the new content and accept:
snippetdrift sync docs/ # pull current source lines into code blocks
snippetdrift accept docs/ # reset hashes, mark as reviewed today
Or, if you're happy to accept the source as-is without manual editing:
snippetdrift accept --sync docs/
Comment syntax reference
| Field | Description |
|---|---|
src/api/models.py#L27-34 |
Path relative to repo root, with line range |
hash:a3f9b2c1 |
First 8 hex chars of SHA-256 of the source lines (written by init/accept) |
reviewed:2025-04-09T14:32:00 |
Datetime when the hash was last confirmed correct (written by init/accept) |
CLI commands
| Command | Description |
|---|---|
snippetdrift init [PATH] |
Sync source into code blocks + write hashes for uninitialized snippets. Use --no-sync to skip content sync. |
snippetdrift sync [PATH] |
Copy current source lines into code blocks. Does not change hashes. |
snippetdrift check [PATH] |
Detect drift. Exit 0 = ok, 1 = drift. |
snippetdrift accept [PATH] |
Reset hashes after reviewing drift. Add --sync to also update code block content. |
snippetdrift status [PATH] |
Show a summary table of all tracked snippets and their status. |
All commands accept a path to a markdown file or directory (default: current directory, recursive).
snippetdrift accept also accepts --snippet <source-path> to scope acceptance to a single source file.
What each command touches
| Code block content | hash: in sentinel |
reviewed: in sentinel |
Cache | |
|---|---|---|---|---|
init |
✅ (default, skip with --no-sync) |
✅ written | ✅ set to now | ✅ written |
sync |
✅ updated | ❌ | ❌ | ❌ |
check |
❌ read-only | ❌ read-only | ❌ read-only | ❌ |
accept |
❌ (opt-in with --sync) |
✅ recomputed | ✅ set to now | ✅ updated |
status |
❌ read-only | ❌ read-only | ❌ read-only | ❌ |
The reviewed: timestamp is the human sign-off marker — only init and accept update it, because those are the points where someone confirms the documentation is correct.
Typical workflows
First time setup:
snippetdrift init docs/ # sync source into code blocks + write hashes
git add docs/
git commit -m "docs: initialize snippet hashes"
Day to day — source code changes:
# change source code
snippetdrift check docs/ # drift detected → exit 1
snippetdrift sync docs/ # pull new source lines into code blocks
# review the diff, edit surrounding prose if needed
snippetdrift accept docs/ # reset hashes, mark as reviewed
git add docs/
git commit -m "docs: accept drift in api_guide"
CI integration
name: Snippet Drift Check
on:
push:
branches: [main]
pull_request:
jobs:
snippetdrift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv tool install snippetdrift
- run: snippetdrift check docs/
Pre-commit with lefthook
A suggested lefthook.yml is included in this repo:
pre-commit:
commands:
snippetdrift:
run: snippetdrift check {staged_files}
glob: "*.md"
This is a suggestion — snippetdrift check on staged files only checks committed files. You may prefer to always run snippetdrift check docs/ unconditionally.
Cache
snippetdrift writes a .snippetdrift_cache/ directory at the repo root containing:
index.json— maps each sentinel to its full hash and metadatasnippets/<hash>.txt— the accepted source lines at the time of last accept, used to render diffs
The markdown file itself is the source of truth for the short hash. The cache is supplementary.
To commit or not: Committing the cache lets CI show rich diffs even on the first run after a drift. Excluding it means the cache is rebuilt locally on the next accept. Either works — add to .gitignore if you prefer not to commit it:
.snippetdrift_cache/
Contributing
uv sync --dev
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run ty check src/
uv run pytest
Project details
Release history Release notifications | RSS feed
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 snippetdrift-0.1.0.tar.gz.
File metadata
- Download URL: snippetdrift-0.1.0.tar.gz
- Upload date:
- Size: 50.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c121de83c98a0f7f927b92dff46ccd974c804f511baf8eecd4fce2918dec64c
|
|
| MD5 |
981aa456355a5bccaefdf5c8ffb31eb6
|
|
| BLAKE2b-256 |
e026c9b568d4444e251448bc238a09c8db6de1ad72359124e291eea1510eef41
|
File details
Details for the file snippetdrift-0.1.0-py3-none-any.whl.
File metadata
- Download URL: snippetdrift-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21072ceda9edded4433bb7777403102710e8a7ac984e63e09ee7d366e7468f62
|
|
| MD5 |
f42d3b9d0106a7a6a127ae55f16ca78b
|
|
| BLAKE2b-256 |
eb04c06a2f9b6eb023d23ebbffc104ab0ea82d677f5f8766052553a9d07d1e70
|