Skip to main content

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:

  1. Sync — copy those exact lines from the source file into the code block
  2. Hash — record a SHA-256 fingerprint of the region
  3. 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 metadata
  • snippets/<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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

snippetdrift-0.1.0.tar.gz (50.9 kB view details)

Uploaded Source

Built Distribution

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

snippetdrift-0.1.0-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

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

Hashes for snippetdrift-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1c121de83c98a0f7f927b92dff46ccd974c804f511baf8eecd4fce2918dec64c
MD5 981aa456355a5bccaefdf5c8ffb31eb6
BLAKE2b-256 e026c9b568d4444e251448bc238a09c8db6de1ad72359124e291eea1510eef41

See more details on using hashes here.

File details

Details for the file snippetdrift-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for snippetdrift-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 21072ceda9edded4433bb7777403102710e8a7ac984e63e09ee7d366e7468f62
MD5 f42d3b9d0106a7a6a127ae55f16ca78b
BLAKE2b-256 eb04c06a2f9b6eb023d23ebbffc104ab0ea82d677f5f8766052553a9d07d1e70

See more details on using hashes here.

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