The doc and the code stopped agreeing.
Nothing in your pipeline noticed.

Missing documentation announces itself. Drifted documentation does the opposite — it was true when somebody wrote it, the code moved underneath, and the file still reads as authoritative. It still carries a date. It still passes review.

$ adda audit .
docs/modules/billing/limiter.md last verified 2024-11-03
Fixed-window limiter.
100 requests per calendar minute
counter resets at the top of the minute
## Known gotcha
a client can send 200 in two seconds
src/billing/limiter.py changed 41 days ago
Token bucket limiter.
CAPACITY = 60
REFILL_PER_SECOND = 1
def allow(client_id):
refill = elapsed * REFILL_PER_SECOND

[high] doc stale   docs/modules/billing/limiter.md  ·  the code's commit is a descendant of the doc's

$ pip install adda Read the README →
MITPython 3.10+84 tests no API keydeterministic · offline
The part nobody else has

Every check has two answers.
This one has three.

Staleness is decided by git commit ancestry, not a hand-written date. Sometimes ancestry cannot decide — a rebase, divergent branches, a shallow clone. That is a third answer, and it gets reported as one.

01 · CURRENT
Fine.

The doc's commit follows the code's, or they landed together. It was actually checked.

02 · STALE
Wrong.

The code's commit is a descendant of the doc's. The doc got left behind, and the gate refuses the next commit.

03 · CANNOT TELL
I don't know — and I'm saying so.

Reported under [skipped], never counted as a pass. A tool that cannot tell and answers "fine" is worse than no tool at all, because you believe it. Every amber mark on this page is that third answer.

This is not a caveat, it is the design. A green check returned over evidence the tool never had is the exact failure this exists to prevent — so the honest answer had to be sayable before the other two were worth anything.
How it decides

Two histories. One stopped moving.

No timestamps, no hand-written "last reviewed" line to forget. If the code's commit is a descendant of the doc's, the doc was left behind — and git already knows.

src/billing/limiter.py HEAD last touched docs/modules/billing/limiter.md 4 commits behind
Current — the doc follows the code
Stale — the code is a descendant
Cannot tell — unordered, reported
What it does

Route, sweep, refuse.

adda sync --map

Route

An explicit, greppable map from every code path to the document that must change with it. Derived from the repo, not hand-kept.

adda audit

Sweep

Missing, stale, orphaned and unmapped docs across the whole repository. Exits non-zero, so it drops straight into CI.

adda hook install

Refuse

A commit gate. Staged code without its staged document does not get committed — not a warning you learn to scroll past.

git commit — with the gate installed
$ git commit -m "switch limiter to token bucket"Commit blocked: 1 code change(s) without their doc.  src/billing/limiter.py  ->  update and stage docs/modules/billing/limiter.md Update the doc (bump `Last verified`, append to its Change Log), then stage it.To bypass deliberately: `git commit --no-verify`, or set ADDA_SKIP=1.
Measured, not asserted

Run against codebases it did not design.

Commit SHAs are recorded, because otherwise the numbers are reproducible mechanically but not in time. The amber column is the point: it is what ADDA declined to map, said out loud.

repocommitfiles mapped reported as skippedcollisionstime
django0b4021071902.18s
date-fnsa0a3922125601.33s
fastapi9a8a13f41458 · docs_src/00.26s
flaskd318b682100.01s
requests5460f461800.01s
On fastapi it could have shown 41 files and looked tidy. Instead it names the 458 it skipped and why — a directory of tutorial snippets with no __init__.py — so the call is yours rather than the tool's. Zero collisions across 2,066 mapped files: a doc path shared by two code paths is a module reported as documented while having no documentation of its own, and the mapping is derived so that cannot happen.
Honest limits

What it cannot see.

ADDA detects the document nobody touched. It does not detect the document somebody touched carelessly — if the code and the doc are committed together but the doc was updated wrongly, ancestry says they moved together and every check passes.

Catching that needs a model reading both and forming a judgement. ADDA is deterministic and offline by design, so it does not cross that line. The limit is published rather than left for you to find.

Two minutes

On a repo you already have.

bash
$ pip install adda$ cd /your/project$ adda sync . --map --out adda/MODULE_MAP.json$ adda audit . Doc drift detected: 2 finding(s)  [high  ] doc missing    docs/modules/auth/session.md  [high  ] doc missing    docs/modules/billing/limiter.md