lockwire
OfficialREADME.md
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/wordmark-dark.png">
<source media="(prefers-color-scheme: light)" srcset="assets/wordmark-light.png">
<img alt="lockwire" src="assets/wordmark-light.png" width="420">
</picture>
</p>
# lockwire
**Catch stale `CLAUDE.md`, `AGENTS.md`, and docs before your coding agent acts on them.**
The mark is literal, not a metaphor: [lockwiring](https://en.wikipedia.org/wiki/Safety_wire) is the aviation-maintenance technique of threading wire through two fasteners so neither can work loose under vibration — the same idea applied to a doc claim and the code it depends on.
[](https://www.npmjs.com/package/lockwire)
[](https://socket.dev/npm/package/lockwire)
[](https://bundlephobia.com/package/lockwire)
[](LICENSE)
[](https://code.claude.com/docs/en/plugins)
[](docs/agents.md)
[](docs/agents.md)
[](https://github.com/Reality-Ventures/lockwire/actions/workflows/ci.yml)
A sentence in your docs is a claim about a specific state of your code. Nothing binds the two together, so the claim rots and nobody notices — until an autonomous agent reads the stale sentence and acts on it. lockwire fingerprints the code a claim depends on, checks that fingerprint at write time inside Claude Code and Codex, and keeps a tamper-evident history of every time the claim broke.
```
$ lockwire check
CLAUDE.md
DRIFTED src/auth/session.ts#createSession (sig)
2 anchors · 1 ok · 1 drifted · 0 orphaned
single-hash would flag 1 · lockwire flagged 1 · noise −0%
```
```
$ lockwire history k7q2m9xv
2026-09-25T10:00:11.579Z anchor.created
2026-09-25T14:12:03.656Z anchor.drifted sig
2026-09-25T14:15:40.726Z anchor.resolved re-stamped via link
```
Each event records which tier moved, the commit it happened on, and who ran the check that noticed (the agent and session for a hook, the person or CI job for the CLI) — see [docs/ledger.md](docs/ledger.md).
---
## The problem, specifically
1. **Coding agents change code faster than humans reconcile prose.** Drift that used to accumulate over quarters now accumulates over an afternoon.
2. **`CLAUDE.md` and `AGENTS.md` are read by an agent on every request.** A stale line doesn't just mislead a person reading it later — it silently redirects an autonomous process *right now*. Stale agent context is a correctness problem, not a hygiene problem.
3. **Wrong documentation is worse than no documentation.** Macke & Doyle (NAACL 2024) found that incorrect documentation "can greatly hinder" an LLM's ability to understand code, while missing documentation barely affects it at all — see [docs/research.md](docs/research.md). A confidently wrong `CLAUDE.md` is actively harmful in a way an empty one isn't.
And the tools built to fix this have a shared failure mode: **one hash per symbol.** Hash a whole function, and a one-line body tweak invalidates a doc that only ever claimed something about the *signature*. Teams get flooded with false staleness, the CI gate gets marked `continue-on-error`, and the tool gets uninstalled. This is the specific failure lockwire is built to not have.
## How it's different
lockwire fingerprints **four aspects of a symbol separately**, and a claim only binds the aspects it actually depends on:
```mermaid
flowchart LR
SYM["Symbol<br/><b>createSession()</b>"] --> P & S & B & D
P["<b>path</b><br/>file location"]
S["<b>sig</b><br/>name · params · types<br/>return · modifiers"]
B["<b>body</b><br/>normalized AST<br/>of the implementation"]
D["<b>deps</b><br/>sorted set of<br/>outbound calls"]
P --> C1["<i>structural claim</i><br/>'sessions are created in<br/>src/auth/session.ts'"]
S --> C2["<i>contractual claim</i><br/>'takes a UserId,<br/>returns a Session'"]
B --> C3["<i>behavioral claim</i><br/>'retries 3x with<br/>exponential backoff'"]
D --> C4["<i>architectural claim</i><br/>'delegates token minting<br/>to the KMS client'"]
```
*A contractual claim binds only `sig` — a body refactor never touches it. This is the whole mechanism.*
A doc claiming "takes a `UserId`, returns a `Session`" binds `sig` only, and stays silent through every internal refactor of `createSession`. The [P0 test suite](test/e2e.test.ts) proves this on a real example: a body-only edit that adds a logging call produces **zero** drifted anchors and a noise line reading `single-hash would flag 1 · lockwire flagged 0 · noise −100%`.
## System view
```mermaid
flowchart TB
subgraph SRC["Inputs"]
direction LR
CODE["Source tree<br/>TS · TSX · JS · Python"]
DOCS["CLAUDE.md · AGENTS.md<br/>docs/**/*.md"]
end
subgraph L1["Fingerprint"]
direction LR
TS["tree-sitter<br/>parse"] --> EX["Tier extractor<br/>path · sig · body · deps"] --> HASH["BLAKE3<br/>fingerprint"]
end
subgraph L2["State"]
direction LR
LF[("lockwire.lock<br/>anchors")]
LG[("ledger.jsonl<br/>history")]
end
subgraph L3["Three gates"]
direction LR
G1["write-time<br/>PreToolUse hook"]
G2["commit-time<br/>pre-commit"]
G3["merge-time<br/>GitHub Action"]
end
CODE --> L1
DOCS -->|"<!-- lockwire ... --><br/>markers"| L2
L1 --> L2
L2 --> L3
classDef store fill:#1f2937,stroke:#60a5fa,color:#e5e7eb
class LF,LG store
```
## Anchor lifecycle
```mermaid
stateDiagram-v2
[*] --> Fresh: lockwire link<br/><i>anchor.created</i>
Fresh --> Drifted: a bound tier changed, or the claim<br/>sentence was edited<br/><i>anchor.drifted</i>
Fresh --> Fresh: symbol renamed in the same file,<br/>exactly one match<br/><i>anchor.relocated</i>
Fresh --> Orphaned: symbol, file or doc marker gone<br/><i>anchor.orphaned</i>
Drifted --> Fresh: doc updated + link --reviewed,<br/>or ack<br/><i>anchor.resolved / anchor.acknowledged</i>
Drifted --> Waived: time-boxed waiver<br/><i>waiver.granted</i>
Drifted --> Superseded: claim no longer applies<br/><i>anchor.acknowledged</i>
Waived --> Fresh: waiver expires, nothing moved
Waived --> Drifted: waiver expires, something moved<br/><i>waiver.expired</i>
Orphaned --> Fresh: marker or symbol restored,<br/>then link (--reviewed if the code changed)
Superseded --> [*]
note right of Fresh
Relocation is an event, not a state: after a
rename the anchor is just fresh (or drifted,
if a bound tier also moved) under its new
name. Same-file renames only in P0; a symbol
moved to another file reports orphaned — see
docs/concepts.md limitations.
end note
```
## Write-time enforcement
```mermaid
sequenceDiagram
participant A as Coding agent
participant H as PreToolUse hook
participant L as lockwire core
participant D as Ledger
A->>H: Edit src/auth/session.ts
H->>L: which anchors cover this file?
L-->>H: 1 claim, bound to sig
alt advisory (default)
H-->>A: inject claim as context —<br/>no block, no prompt
else ask
H-->>A: prompt: acknowledge or proceed anyway
else deny
H-->>A: block until acknowledged
end
A->>A: edits the file
A->>L: PostToolUse — re-fingerprint
L->>D: anchor.drifted (if the bound tier moved)
D-->>A: additionalContext: which claim just broke
```
## Install
**Claude Code** — hook, MCP server, and skill in one plugin:
```bash
/plugin marketplace add Reality-Ventures/lockwire
/plugin install lockwire@lockwire
```
**Codex, Cursor, Gemini CLI, and other agents that read `SKILL.md`** — the skill only; wire up the hook and MCP server per your agent (see [docs/agents.md](docs/agents.md)):
```bash
npx skills add Reality-Ventures/lockwire
```
**CLI and CI** — no agent required:
```bash
npm install -g lockwire
lockwire check
```
Or without installing anything, via `npx`:
```bash
npx lockwire check
```
**GitHub Action** — merge-time gate (and [`lockwire/autofix`](docs/autofix.md) to fix what it finds):
```yaml
- uses: actions/checkout@v4
with:
fetch-depth: 0 # --changed needs history to find the merge base
- uses: Reality-Ventures/lockwire@v0
with:
args: --changed
```
**pre-commit** — commit-time gate, as a plain git hook (`.git/hooks/pre-commit`, made executable):
```bash
#!/bin/sh
npx --no-install lockwire check --staged --no-write
```
## Quickstart
```bash
$ lockwire init
lockwire initialized: lockwire.lock, .lockwire/config.json, .gitattributes (ledger merge=union)
```
Add a marker above a claim in `CLAUDE.md`:
```markdown
<!-- lockwire src/auth/session.ts#createSession sig -->
`createSession` takes a `UserId` and returns a `Session` valid for 24 hours.
```
```bash
$ lockwire link CLAUDE.md
CLAUDE.md: 1 created, 0 refreshed
```
The marker is now stamped with an id:
```markdown
<!-- lockwire src/auth/session.ts#createSession sig id=k7q2m9xv -->
```
Refactor the function's body without touching its signature, then check:
```bash
$ lockwire check
CLAUDE.md
ok
1 anchor · 1 ok · 0 drifted · 0 orphaned
single-hash would flag 1 · lockwire flagged 0 · noise −100%
```
Now change the actual signature (add a parameter) and check again:
```bash
$ lockwire check
CLAUDE.md
DRIFTED src/auth/session.ts#createSession (sig)
1 anchor · 0 ok · 1 drifted · 0 orphaned
single-hash would flag 1 · lockwire flagged 1 · noise −0%
```
Update the doc, then re-stamp — `--reviewed` is required because the code changed since the claim was stamped:
```bash
$ lockwire link CLAUDE.md --reviewed
CLAUDE.md: 0 created, 1 refreshed
```
And ask what happened, whenever, from anyone:
```bash
$ lockwire history k7q2m9xv
2026-09-25T10:00:11.579Z anchor.created
2026-09-25T14:12:03.656Z anchor.drifted sig
2026-09-25T14:15:40.726Z anchor.resolved re-stamped via link
```
Inside Claude Code, the hook does this automatically — before the edit, you'd see the claim injected as context; after, you'd see exactly which claim just drifted, with no `check` invocation needed. See [docs/agents.md](docs/agents.md) for the injected text.
## Let your agent fix it
lockwire doesn't write docs and has no model of its own. It gives the agent you already run — Claude Code, Codex, anything that speaks MCP — a work queue: every claim that's wrong, the code or value it was checked against and what it is now, and a resolve step that re-checks the fix and refuses one that's still wrong (a number that still disagrees with its data, a marker pointing at nothing). Ask your agent to run the `fix-docs` prompt, or do it by hand:
```bash
lockwire tasks # what needs attention
lockwire task k7q2m9xv # one task: the claim, the code as stamped and now
lockwire resolve k7q2m9xv --note "documented the new scope parameter"
```
Every resolution is recorded with who made it and why. See [docs/agents.md](docs/agents.md#the-work-queue-your-agent-fixes-lockwire-checks).
To run it unattended, `lockwire autofix` starts Claude Code or Codex on your own key — only when a claim is actually wrong — then re-checks every claim and refuses a run that touched anything but docs. In CI, the Action does the same and opens a pull request:
```yaml
- uses: Reality-Ventures/lockwire/autofix@v0
with:
agent: claude-code
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
```
See [docs/autofix.md](docs/autofix.md).
## What lockwire does not do
Hashes cannot catch semantic drift — a doc saying "we use Redux" when the code moved to Zustand, where every file path still resolves and no signature changed. Catching that requires reasoning about meaning, not fingerprints; [ClaudeDrift](https://github.com/marky291/claude-drift) does this well as an on-demand, LLM-reasoning layer, and pairs naturally with lockwire's continuous, deterministic one — see [docs/comparison.md](docs/comparison.md). Numbers are the exception: a sentence bound to the value it came from — a JSON key or a LaTeX macro a pipeline writes — is checked against that value at the precision it states, so "the lower bound is 0.18" is flagged when a rerun makes it 0.19, and not when it makes it 0.1791 ([docs/concepts.md](docs/concepts.md#numbers-and-other-values)). P0 supports TypeScript, TSX, JavaScript, and Python source, and values in JSON and LaTeX files; cross-file rename detection, a resolver agent, and semantic-claim extraction are roadmap, not shipped — see [docs/concepts.md](docs/concepts.md#limitations-read-before-relying-on-this-in-production).
## FAQ
### How is this different from fiberplane/drift?
[fiberplane/drift](https://github.com/fiberplane/drift) computes one AST hash per anchor — a body refactor invalidates a doc that only claimed something about the signature. lockwire fingerprints path, signature, body, and dependencies separately, so a claim only re-flags when the aspect it actually depends on changes. Neither fiberplane/drift nor any other tool we found keeps a history of *why* a claim broke, *who* broke it, or *how often* — lockwire's ledger does. See the full comparison in [docs/comparison.md](docs/comparison.md).
### Does it call an LLM?
No. Detection is deterministic: tree-sitter parsing, BLAKE3 hashing, set comparison. No network calls, no API keys, no per-check cost. The write-time hook and CLI both run entirely offline.
### Will it slow down Claude Code?
The hook has a 10-second timeout and only re-parses the one file that was just touched — in practice, well under a second for typical files. It fails open: if anything throws, the hook exits silently and the tool call proceeds untouched. See [docs/agents.md](docs/agents.md).
### What happens on merge conflicts in the ledger?
`.lockwire/ledger.jsonl` is append-only JSONL with `merge=union` in `.gitattributes` (written by `lockwire init`), so `git merge` unions the lines from both branches instead of conflicting. Event hashes don't chain to a previous event, deliberately — the ledger's tamper-evidence is order-independent (a BLAKE3 root over the *set* of event hashes), because ordering is already supplied by each event's timestamp and commit SHA. See [docs/ledger.md](docs/ledger.md).
### Can I use it without Claude Code?
Yes. The core is a standalone CLI (`lockwire check`, `lockwire link`, …) that runs anywhere Node runs — in a pre-commit hook, in any CI system, or by hand. Claude Code and Codex hooks are two of several ways to trigger it; the GitHub Action is a third.
### What languages are supported?
TypeScript, TSX, JavaScript, and Python in P0 (v0.1.0). More tree-sitter grammars are a small addition on top of the existing tier-extraction pipeline — see [docs/concepts.md](docs/concepts.md).
### Why not just use an LLM to check everything?
Because then every `check` costs money and time, becomes non-deterministic, and can't run inside a 10-second write-time hook. lockwire's deterministic core handles the mechanical 80% (does this claim's bound aspect still match); an LLM-reasoning layer for the harder semantic 20% is a natural P2 addition, not a P0 requirement — see [docs/research.md](docs/research.md) for why teams abandon LLM-only or hash-flood-prone tools.
## Comparison
| | lockwire | [fiberplane/drift](https://github.com/fiberplane/drift) | [ClaudeDrift](https://github.com/marky291/claude-drift) | [pallaprolus/drift](https://github.com/pallaprolus/drift) |
|---|---|---|---|---|
| Fingerprint granularity | 4 tiers per symbol | 1 hash per anchor | none (LLM judgment) | 1 score per doc-code pair |
| Write-time agent hook | ✅ Claude Code + Codex | ❌ | ❌ (on-demand) | ❌ |
| History / ledger | ✅ append-only, tamper-evident | ❌ | ❌ | ❌ |
| Semantic drift (meaning changed, refs still resolve) | ❌ (roadmap) | ❌ | ✅ | ✅ (opt-in, on-demand) |
| Runs with no LLM | ✅ | ✅ | ❌ | ✅ (core checks) |
| Languages (P0) | TS, TSX, JS, Python | TS, Python, Rust, Go, Zig, Java | any (LLM-read) | TS/JS, Python, Go, Rust, Java |
Full detail, including two smaller entrants and where each tool actually stops, in [docs/comparison.md](docs/comparison.md).
## Documentation
- [docs/concepts.md](docs/concepts.md) — tiers, anchors, claims, drift, and the current limitations
- [docs/cli.md](docs/cli.md) — full command reference
- [docs/agents.md](docs/agents.md) — Claude Code plugin, Codex, MCP tools, hook payloads, other agents via skills
- [docs/ledger.md](docs/ledger.md) — the append-only ledger, union merge, Agent Trace actors, verification
- [docs/comparison.md](docs/comparison.md) — the full competitive comparison
- [docs/research.md](docs/research.md) — the evidence base this design is built on
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues: see [SECURITY.md](SECURITY.md), not a public issue.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues