Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions .claude/skills/drift/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ drift binds markdown docs to code and lints for staleness.

When you change code without updating the docs that describe it, those docs become stale. Stale docs get loaded as context in future sessions and produce wrong code based on wrong descriptions. This compounds — each session that trusts a stale doc makes things worse. drift makes the anchor explicit and enforceable so this feedback loop breaks.

## CRITICAL: never relink without reviewing
## Relink gate

`drift link` refreshes provenance — it tells drift "I've reviewed this code and the doc is accurate." If you relink without actually updating the doc prose to match the code change, you are lying to every future session that loads that doc. Read the stale report, understand what changed, update the prose, THEN relink.
`drift link` refuses to restamp a stale anchor without explicit review. When a target's signature has drifted, `drift link` prints both sides — the doc section (spec) and the current code — then exits 1.

This means you cannot blindly relink. You must review the doc prose and confirm it is still accurate. Use:

```bash
drift link docs/auth.md --doc-is-still-accurate
```

## After you change code

Expand All @@ -30,10 +36,11 @@ drift check
```

If a doc is stale because of your change:
1. Read the blame info to understand what changed and why
2. Update the doc's prose to reflect what you changed
3. Refresh provenance: `drift link <doc-path> <file-you-changed>`
4. Verify: `drift check`
1. Run `drift link <doc-path>` — it will print the doc section and current code side by side, then refuse
2. Read both sides to understand what's out of sync
3. Update the doc's prose to reflect what you changed
4. Run `drift link <doc-path> --doc-is-still-accurate` — succeeds now that you've reviewed
5. Verify: `drift check`

Do not skip this. Leaving a doc stale is worse than leaving it unwritten.

Expand Down Expand Up @@ -100,6 +107,8 @@ Anchors can target code files, code symbols (`file#Symbol`), or doc headings (`d

`drift link` writes bindings to `drift.lock` with content signatures (`sig:<hex>`). Content signatures are AST fingerprints of the target, so staleness detection works without querying VCS history. This means `drift link` works on uncommitted files — no need to commit first.

When relinking a stale anchor, `drift link` refuses and prints both sides (doc section and current code) so you can review the change. Pass `--doc-is-still-accurate` to confirm the doc doesn't need updates.

`drift lint` also checks all markdown links (`[text](path.md)`) in drift-managed docs for existence — broken links are reported as `BROKEN` without needing a lockfile entry.

## Cross-repo docs (origin)
Expand Down
14 changes: 8 additions & 6 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## drift check / drift lint

Check all docs for staleness. The primary command. Exits 1 if any anchor is stale or any link is broken. `drift lint` is an alias.
Check all docs for staleness. The primary command. Exits 1 if any anchor is stale or any link is broken. `drift lint` is an alias. Markdown files under directories with their own `drift.lock` are skipped — they belong to a nested scope.

```
drift check [--format text|json] [--changed <path>]
Expand Down Expand Up @@ -69,12 +69,12 @@ docs/payments.md (1 anchor)

## drift link

Add or refresh bindings in `drift.lock`. `drift link` computes a content signature (`sig:`) from the target file's current syntax fingerprint and writes it to the lockfile. Creates `drift.lock` if it doesn't exist.
Add or refresh bindings in `drift.lock`. `drift link` computes a content signature (`sig:`) from the target file's current syntax fingerprint and writes it to the lockfile. Creates `drift.lock` if it doesn't exist. The lockfile is discovered by walking up from the doc's directory, not from cwd — if a nested `drift.lock` exists closer to the doc, that lockfile is used.

```
drift link <doc-path> <file>
drift link <doc-path> <file#Symbol>
drift link <doc-path>
drift link <doc-path> <file> [--doc-is-still-accurate]
drift link <doc-path> <file#Symbol> [--doc-is-still-accurate]
drift link <doc-path> [--doc-is-still-accurate]
```

**Targeted mode** — adds a single binding to `drift.lock`:
Expand All @@ -99,9 +99,11 @@ relinked all anchors in docs/auth.md

Each anchor gets its own content signature computed from the current file on disk.

**Relink gate** — when relinking a stale anchor (target signature changed), the relink is refused and both sides are printed (doc section and current code). This prevents blindly restamping without reviewing documentation. Pass `--doc-is-still-accurate` to confirm you've reviewed the doc and it doesn't need changes.

## drift unlink

Remove a binding from `drift.lock`.
Remove a binding from `drift.lock`. Like `drift link`, the lockfile is discovered from the doc's directory.

```
drift unlink <doc-path> <file>
Expand Down
14 changes: 7 additions & 7 deletions drift.lock
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
.claude/skills/drift/SKILL.md -> src/main.zig sig:a0933989333423fb origin:github:fiberplane/drift
.claude/skills/drift/SKILL.md -> src/main.zig sig:647a31274655a84d origin:github:fiberplane/drift
.claude/skills/drift/SKILL.md -> src/vcs.zig sig:2468937f00d5305a origin:github:fiberplane/drift
CLAUDE.md -> build.zig sig:7194b38f39dbadba
CLAUDE.md -> src/main.zig sig:a0933989333423fb
docs/CLI.md -> src/commands/link.zig sig:f4ab9576ebef2981
docs/CLI.md -> src/commands/lint.zig sig:a0237cda56055884
CLAUDE.md -> src/main.zig sig:647a31274655a84d
docs/CLI.md -> src/commands/link.zig sig:70e52c01fb9022a8
docs/CLI.md -> src/commands/lint.zig sig:b8ab0cd93909b888
docs/CLI.md -> src/commands/refs.zig sig:e3309a0d11c02bb0
docs/CLI.md -> src/commands/status.zig sig:ab9cee37b4b22644
docs/CLI.md -> src/commands/unlink.zig sig:590c53a3920551d3
docs/CLI.md -> src/commands/unlink.zig sig:6fc59e2a25f80fac
docs/DESIGN.md -> src/context.zig sig:70678dcc0872470d
docs/DESIGN.md -> src/lockfile.zig sig:f659df3e59325b71
docs/DESIGN.md -> src/main.zig sig:a0933989333423fb
docs/DESIGN.md -> src/lockfile.zig sig:23bc7256cff13942
docs/DESIGN.md -> src/main.zig sig:647a31274655a84d
docs/DESIGN.md -> src/symbols.zig sig:8e4a403c6f0130c3
docs/DESIGN.md -> src/vcs.zig sig:2468937f00d5305a
docs/RELEASING.md -> .github/workflows/ci.yml sig:e8440b1d7ee3e4ba
Expand Down
23 changes: 23 additions & 0 deletions examples/broken-links/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Broken Links Example

Demonstrates `drift check` detecting broken markdown links.

## Setup

No lockfile binding needed — broken link detection works on any markdown file
discovered by `drift check`.

## Run

```bash
drift check
```

Expected output: `doc.md` reports 3 `BROKEN` links:

- `./stripe-guide.md` — doesn't exist
- `../docs/payment-arch.md` — doesn't exist
- `./errors.md` — doesn't exist

The two links under "Working links" point to files in the relink-gate example
and should pass.
14 changes: 14 additions & 0 deletions examples/broken-links/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Payment Processing

See the [Stripe integration guide](./stripe-guide.md) for setup instructions.

The payment flow is documented in [the architecture doc](../docs/payment-arch.md).

For error codes, refer to [error reference](./errors.md).

## Working links

These should pass lint:

- [Auth example](../relink-gate/doc.md)
- [README](../relink-gate/README.md)
Empty file.
37 changes: 37 additions & 0 deletions examples/relink-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Relink Gate Example

Demonstrates `drift link` refusing to restamp when the doc hasn't been updated.

## Setup

```bash
drift link examples/relink-gate/doc.md examples/relink-gate/auth.ts#login
```

## Trigger the gate

Change the code without updating the doc:

```bash
# Add a rate-limit check to login — the doc now lies about what login does
sed -i '' 's/return createSession(username);/if (rateLimited(username)) throw new Error("rate limited");\n return createSession(username);/' examples/relink-gate/auth.ts

# Try to relink — refused because doc.md wasn't updated
drift link examples/relink-gate/doc.md
```

Expected output: drift prints the doc section and current code, then refuses.

## Fix it

Either update `doc.md` to mention rate limiting, then relink:

```bash
drift link examples/relink-gate/doc.md
```

Or confirm the doc is still accurate:

```bash
drift link examples/relink-gate/doc.md --doc-is-still-accurate
```
13 changes: 13 additions & 0 deletions examples/relink-gate/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function login(username: string, password: string): string {
if (password.length === 0) {
throw new Error("password required");
}
if (username.length < 3) {
throw new Error("username must be more than 3 chars");
}
return createSession(username);
}

function createSession(username: string): string {
return `session_${username}_${Date.now()}`;
}
10 changes: 10 additions & 0 deletions examples/relink-gate/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Authentication

The `login` function validates a username/password pair against the
database and returns a session token on success.

```
login(username, password) -> session_token
```

It rejects empty passwords and usernames shorter than 3 characters.
1 change: 1 addition & 0 deletions examples/relink-gate/drift.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
doc.md -> auth.ts#login sig:fde13635c2922d43
26 changes: 26 additions & 0 deletions examples/symbol-anchor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Symbol Anchor Example

Demonstrates symbol-level anchors where drift tracks a specific named symbol
rather than the whole file. The relink gate shows the symbol body when refusing.

## Setup

```bash
drift link examples/symbol-anchor/doc.md examples/symbol-anchor/config.ts#DatabaseConfig
drift link examples/symbol-anchor/doc.md examples/symbol-anchor/config.ts#createPool
```

## Trigger the gate

Change the `DatabaseConfig` interface without updating the doc:

```bash
# Add a new field — the doc now omits it
sed -i '' 's/maxConnections: number;/maxConnections: number;\n ssl: boolean;/' examples/symbol-anchor/config.ts

# Relink refused — prints the doc section AND the current DatabaseConfig body
drift link examples/symbol-anchor/doc.md
```

The output shows both sides so you can see what's out of sync: the doc lists
4 fields but the code now has 5.
15 changes: 15 additions & 0 deletions examples/symbol-anchor/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface DatabaseConfig {
host: string;
port: number;
database: string;
maxConnections: number;
}

export interface CacheConfig {
ttlSeconds: number;
maxEntries: number;
}

export function createPool(config: DatabaseConfig): void {
console.log(`Connecting to ${config.host}:${config.port}/${config.database}`);
}
9 changes: 9 additions & 0 deletions examples/symbol-anchor/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Database Configuration

`DatabaseConfig` defines the connection parameters:

- `host` / `port` — database server address
- `database` — target database name
- `maxConnections` — connection pool ceiling

Use `createPool` to initialize the pool from a config object.
2 changes: 2 additions & 0 deletions examples/symbol-anchor/drift.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
doc.md -> config.ts#DatabaseConfig sig:a643fafff54d00ed
doc.md -> config.ts#createPool sig:3294e048b6c144ae
Loading
Loading