Skip to content

fiberplane/drift

Repository files navigation

Drift Logo

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.

Install

Homebrew

brew install fiberplane/tap/drift

Shell installer

curl -fsSL https://drift.fp.dev/install.sh | sh

To install a specific version:

curl -fsSL https://drift.fp.dev/install.sh | sh -s -- --version vX.Y.Z

Or build from source:

zig build -Doptimize=ReleaseSafe --prefix ~/.local

Coding agent skill (Claude Code, Codex)

curl -fsSL https://drift.fp.dev/install.sh | sh
npx skills add fiberplane/drift

The 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.

Usage

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

What a spec anchor looks like

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 #Name suffix 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
-->

Cross-repo specs (origin)

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:a1b2c3d4e5f67890

When 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.

Commands

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.

How staleness works

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

CI

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 check

fetch-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.

Development

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 binary

Enable the pre-push hook to run build, lint, and tests before every push:

git config core.hooksPath hooks

About

Bind specs to code and check for drift.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors