Sheet 1 of 1 Revision 1.1.0 Node 24+ No config, no API key, no network MIT
81 of 96 real repositories produce zero false positives.
See the surveyAn out-of-date README confuses a person, who notices and asks. An out-of-date
CLAUDE.md hands the agent a false premise that it executes with
confidence: it runs a script that no longer exists, edits a file that moved,
follows an abandoned convention.
The cost is not confusion, it is incorrect work with no error signal.
Linters check your code. Nothing checks the document you wrote about your code.
Unlike code, these files have no compiler, no tests, no linter. They are the one part of the repo where lying has no mechanical consequence.
Struck in overprint: a claim the ground contradicts. Written beneath it: the correction, only when exactly one candidate clears 0.8 confidence.
npm/pnpm/yarn/bun run, deno task and
make, against the nearest package.json, Makefile or
deno.json. No manifest anywhere means silence.
fixable
SKILL.md names itself something other than its directory —
the folder was renamed and the frontmatter did not follow. Not a format
validator: a name in snake_case or a four-word description are real
problems and none of them are drift, so this does not report them.
fixable
--skip link/broken turns that check off everywhere--only path keeps only that family, and a prefix matches, so
--only path and --only path/missing both workchecks: in the config file makes either permanent, or demotes a check to a
warning instead of silencing itignore: in the config drops a whole document — a skill somebody else
wrote, a vendored file you do not maintain — which is the one thing
sources cannot doA selection that leaves no check enabled is refused.
--fix touches the mechanically verifiable fragment of a line
and nothing around itA linter that shouts too much gets uninstalled on first use. So driftwatch stays silent when it is unsure, and every discard rule in the extractor carries a comment naming the false positive it prevents. This is the measurement, over context files their authors wrote without knowing driftwatch exists.
1901 context files96 repositories pinned to a commitnine languages
nothing found findings, none false carries a false positive One mark is one repository. Point at one, or tab in and use the arrow keys.
A lit mark is a repository carrying at least one false positive — 15 of the 96, which is the figure below read the other way. An outlined mark produced findings and none of them false: 216 context files carry 337 findings between them, and 32 are true drift the maintainers had not noticed. 289 findings in the nine noisiest repositories are unread, and cannot move the figure: each of those repositories already carries a false positive, so it is already counted.
--fix would apply across all 96<name>,
{{path}}, $VAR, [id], EventNameHere
feat/linkedin.com/in/, or a Windows drive
letter, D:/Projects/… — both name somewhere that is not this repository
core/.../sql/, or a variable with its
sigil missing, SKILL_DIR/wiki/.git/, which no repository versionsThirty-three rounds of hand adjudication, including the four where a condition broke and the two where the corpus caught a regression that reading the diff would not have. A repository is settled by its first false positive, so not every finding is read — and a test refuses to count one clean on evidence nobody looked at.
Ten false-positive classes have been named, each with a fixture and a test naming the repository and line it came from. Not every class becomes a rule: of the five largest, three produced none — one because its vocabulary is the vocabulary of real directory names, one because its marker says nothing about absence, one because its signal is not on disk. Every rule that did ship was measured over both corpora before being kept, and not one has ever removed a true positive.
The corpus does not run in CI, because the verdict requires a person. CI can detect that a snapshot changed; it cannot rule on whether the change is an improvement, and that ruling is the measurement.
Sixty-seven of the ninety-six repositories in the corpus report nothing at all. A tool built to stay quiet when it is unsure is quiet most of the time, so a first run has three possible endings and only one of them reports drift.
✓ 14 files · no drift · 210ms
Fourteen context files, every claim in them checked against the repository. Exit code
0.
The file count is the proof that it looked at all. --json
carries summary.claims beside it: how many individual assertions were
verified. That is the only thing that tells nothing to find apart from
nothing found. In CI, where nobody reads the output of a green run, it is the
only number worth asserting on.
✓ 0 files · no drift · 12ms
Zero files does not mean your documents are clean. It means driftwatch found none to read, and it is the one result worth checking.
Discovery runs off git ls-files, so .gitignore is
respected for free, and anything it hides is invisible to driftwatch too. A repository
with no .git falls back to a glob walk.
AGENTS.md, at any depthCLAUDE.md, CLAUDE.local.md<root>/skills/**/SKILL.md, where <root> is
.claude, .agents, .cursor,
.codex, .github or .opencode. There is no single one, and .claude/ is not the
busiest: npx skills add writes to .agents/ by
default..claude/agents/*.md.claude/commands/**/*.md.cursorrules, .cursor/rules/**/*.mdc.github/copilot-instructions.mdAnything else, name it yourself: driftwatch docs/ AGENTS.md
audits only those paths, and a sources: key in the config makes it
permanent.
AGENTS.md
✗ 3 src/util/date.ts path does not exist → src/helpers/date.ts?
✗ 5 src/cli.ts path does not exist
1 file · 2 problems (2 errors) · 59ms
1 fixable with --fix
A claim in your own repository that an agent would have believed, on the line that makes
it. Exit code 1.
file:line is clickable in a modern terminal, so the fastest
next move is to open the line and read what it claims. The arrow is a suggestion, and
--fix --dry-run prints the exact rewrite before anything is written to
disk.
It will happen: six of the corpus's twenty-six findings are false. The recovery is a comment in the document itself, next to the claim it covers.
<!-- driftwatch-ignore-next-line -->
`src/planned/feature.ts` does not exist yet, it is the plan
<!-- driftwatch-ignore path/missing -->
<!-- driftwatch-ignore-file -->
With no id it silences every check on that line; with an id, only that one. Directives apply to findings rather than to claims, so an id you misspell silences nothing and the finding still shows up.
The first run tells you whether your documents are true today. Keeping them true is the part that has to happen on every change, which is what CI is for.
With --fix the exit code describes what is left, so a
repository whose only error was autofixable exits 0. Code 2 is
reserved for driftwatch itself failing (bad config, a path that does not exist, a crash) and
never means drift.
A wrong fix is not noise, it is a document that now points confidently at the wrong file, and the next agent will believe it.
So --fix applies a correction only when there is exactly one candidate above 0.8
confidence. It replaces the claim and nothing around it. And it refuses outright to rewrite
a relative path in a document that is not at the repo root.
Terminal
$ driftwatch --fix --dry-run # what it would change
$ driftwatch --fix # change it
Try both in the field book above. It prints the real output.
.github/workflows/ci.yml
- uses: actions/checkout@v7
- uses: abr4xas/driftwatch@v1.1.0
fail-on-drift: false makes it advisory. sarif: true
writes a file for Code Scanning.
paths Paths to audit, space separated. Empty means the whole
repository.only / skip Run only these checks, or everything except
them. A prefix works: path.format pretty | json | github | sarif. Annotations are
the reason to run this in CI.sarif Write a SARIF file and expose its path as an output.
fail-on-drift Whether drift fails the job, or only annotates
it.working-directory Where to run. Defaults to the workspace
root.pretty--jsonversion is what
you branch on, and it is 1.--github--sarifstdout is the report, stderr is everything else, and the exit code does not depend on the format.
on the command line
driftwatch # the whole repo
driftwatch docs/ AGENTS.md # only these paths
driftwatch --only path/missing # only this check
driftwatch --skip link/broken # everything but this one
driftwatch --strict # warnings fail the build too
driftwatch --fix --dry-run # what it would rewrite
driftwatch --json # the machine-readable contract
driftwatch --init # write a commented config
Exit 0 clean, 1 drift, 2 the tool
could not run — a bad config, a path that does not exist. A crash never looks like a
clean run.
driftwatch.config.yaml
# documents you did not write
ignore:
- '.claude/skills/vendored/**'
- 'third_party/**'
checks:
'link/broken': 'warning'
A repository carries documents it did not write: an installed skill, a vendored
AGENTS.md, a template's scaffold. They name paths in the project they came
from, and reporting those is the check being asked the wrong question.
For a document you do maintain, silence one line with a directive
instead. A directive for a document you own, ignore for one you do not —
editing somebody else's skill to quiet your linter loses the edit the next time they
ship it.