Bind specs to code and check for drift.
Any markdown file in your repo can declare anchors to code — specific files or AST symbols. When bound code changes, drift check flags the spec as stale. Agents that change code must update the specs they affect.
brew install fiberplane/tap/driftcurl -fsSL https://drift.fp.dev/install.sh | shTo install a specific version:
curl -fsSL https://drift.fp.dev/install.sh | sh -s -- --version vX.Y.ZOr build from source:
zig build -Doptimize=ReleaseSafe --prefix ~/.localcurl -fsSL https://drift.fp.dev/install.sh | sh
npx skills add fiberplane/driftThe skill teaches coding agents how to maintain drift anchors. Once installed, the agent will run drift link to stamp provenance and keep specs in sync as it makes code changes. When drift check is in CI, stale specs block merges — so the agent can't silently break documentation.
Write a markdown spec, then bind it to code:
drift link docs/auth.md src/auth/login.ts
drift link docs/auth.md src/auth/provider.ts#AuthConfig
drift link adds the anchor to the spec's YAML frontmatter and stamps a content signature — an AST fingerprint of the target file or symbol. No git commit needed; it hashes what's on disk. You can also reference code inline — @./src/auth/provider.ts#AuthConfig in the spec body — and drift link will stamp those too.
Check if specs are fresh:
drift check
Refresh all anchors in a spec after updating it:
drift link docs/auth.md
After linking, your spec has frontmatter anchors and (optionally) inline references:
---
drift:
files:
- src/auth/login.ts@sig:e4f8a2c10b3d7890
- src/auth/provider.ts#AuthConfig@sig:1a2b3c4d5e6f7890
---
# Auth Architecture
Users authenticate via OAuth2. The validation flow uses @./src/auth/provider.ts#AuthConfig@sig:1a2b3c4d5e6f7890 ...Every anchor has three parts:
src/auth/provider.ts #AuthConfig @sig:1a2b3c4d5e6f7890
└── file path ──────┘ └─ symbol ─┘ └──── signature ─────┘
- Path — the file you're binding to, relative to the repo root.
- Symbol — optional
#Namesuffix that narrows the anchor to a specific declaration (function, class, type). Only changes to that symbol trigger staleness. - Signature — content fingerprint stamped by
drift link. An XxHash3 hash of the file or symbol's normalized AST, so staleness detection doesn't depend on VCS history. Rebasing, amending, or linking uncommitted files all work. Per-anchor, so different files track independently.
If you don't want frontmatter visible on GitHub, use an HTML comment instead:
<!-- drift:
files:
- src/auth/login.ts@sig:e4f8a2c10b3d7890
- src/auth/provider.ts#AuthConfig@sig:1a2b3c4d5e6f7890
-->Specs that travel across repo boundaries — installed skills, vendored docs, shared templates — can declare where their anchors belong:
drift:
origin: github:fiberplane/drift
files:
- src/main.zig@sig:a1b2c3d4e5f67890When origin is set and doesn't match the current repo, drift check skips those anchors instead of reporting false "file not found" errors. Specs without origin are always checked.
drift check Check all specs for staleness (exits 1 if stale)
drift status Show all spec anchors, including inline @./ refs
drift link Add an anchor to a spec (auto-appends provenance)
drift unlink Remove an anchor from frontmatter or drift comments
drift lint is an alias for drift check.
Each anchor's @sig: records a fingerprint of the code at the time it was linked. drift check recomputes the fingerprint from the current file and compares. For supported languages (TypeScript, Python, Rust, Go, Zig, Java), comparison is syntax-aware — drift parses with tree-sitter and hashes a normalized AST fingerprint (node kinds + token text, no whitespace or position data). Reformatting won't trigger false positives. Symbol-level anchors (#AuthConfig) narrow this to just that declaration's subtree. Unsupported languages fall back to raw content comparison.
No VCS history is needed for staleness detection — drift check works entirely from the stored signature and current file content.
$ drift check
docs/auth.md
STALE src/auth/provider.ts#AuthConfig (changed after spec)
changed by mike in e4f8a2c (Mar 15)
"refactor: split auth config into separate concerns"
STALE src/core/old-module.ts (file not found)
ok src/auth/login.ts
docs/payments.md
ok
1 spec stale, 1 ok
drift check exits 1 when any spec is stale, so it works as a CI gate:
# .github/workflows/drift.yml
name: Drift
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: fiberplane/drift@main
- run: drift checkfetch-depth: 0 is recommended — drift uses VCS history for blame info on stale anchors. With @sig: provenance (the default), staleness detection itself doesn't need history. The setup action auto-detects platform, downloads the right binary from GitHub releases, and verifies its checksum before installing.
Requires Zig 0.15.2. The repo includes a .tool-versions file for mise (or asdf). If you haven't already, activate mise in your shell, then:
mise install # installs zig 0.15.2
zig build test # run tests
zig build -Doptimize=ReleaseSafe # build release binaryEnable the pre-push hook to run build, lint, and tests before every push:
git config core.hooksPath hooks