ADR Drift
Deterministic architecture tests for any language. ADRs rot the moment they
merge — this keeps them mechanically true. Define layers and constraints once,
then adr-drift check fails CI when code drifts from the decisions.
The problem
"The moment an ADR gets merged, it decays into a passive historical record unless something mechanical forces it to stay alive."
Developers — and now AI coding agents — merge boundary violations because nothing checks decisions against code. ArchUnit is Java-only, deptrac is PHP-only. ADR Drift is language-agnostic, repo-local, and CI-friendly.
Install
git clone <repo> && cd adr-drift
pip install . # provides the `adr-drift` console script
Or run without installing: python3 -m adr_drift from the repo root.
Quick start
cd your-repo
python3 -m adr_drift init # scaffold arch.yaml + sample ADR from folder layout
# tighten the rules in arch.yaml, add ## Constraints to your ADRs
python3 -m adr_drift check # verify; exit 1 on violations (CI-ready)
# repo already has legacy violations? grandfather them, then enforce:
python3 -m adr_drift baseline # record current violations
python3 -m adr_drift check --baseline .adr-baseline.json
Commands
| Command | What it does |
|---|---|
init |
Infer layers from folder layout, write arch.yaml + sample ADR + index |
check |
Verify the repo against arch.yaml + every ADR's ## Constraints (--baseline FILE grandfathers recorded violations) |
baseline |
Write current violations to .adr-baseline.json (default) for check --baseline |
drift --since HEAD~5 |
Report violations introduced since a git ref, plus violations fixed since then (pre-existing ones ignored) |
new "title" |
Create the next-numbered ADR from a template |
index |
Regenerate the one-sentence-per-ADR index (docs/adr/README.md) |
The constraint DSL
Rules live in arch.yaml and in any ADR's ## Constraints section:
forbid-import: handlers -> db.drivers # layering: handlers may not import drivers
forbid-pattern: src/**.py !~ \beval\( # no eval() anywhere in src/
require-pattern: src/handlers/**.py =~ authenticate\( # handlers must authenticate
from/to match layer names or module prefixes (db.drivers matches
db.drivers.pg); * wildcards supported. Comments start with #.
To-side patterns resolve against layer paths too: in a src/-layout repo
both db.drivers and src.db.drivers spellings match a db layer mapped
to src/db/**.
How it checks
- Python is parsed with the real
ast— no regex false positives from comments or strings. - JS/TS/Go/Rust/Java/Ruby/PHP use a heuristic regex import extractor
(documented as such;
--strict-py-onlydisables it). driftcompares violations at HEAD against the base ref viagit show— it never touches your working tree, andgit blametags each new violation with its introducing commit.
CI
- run: python3 -m adr_drift check --format json --fail-on violations
--fail-on none reports without failing. JSON output is stable for tooling.
Honest limits
- The regex fallback for non-Python languages is heuristic — it can miss exotic import forms. Python is precise; everything else is best-effort.
driftneeds a git repo;checkworks anywhere.- Rules are only as good as the constraints you write —
initscaffolds, you tighten.
Tests
55/55 pass: python3 -m unittest discover -s tests
Metadata
Release files for adr-drift 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| adr_drift-0.1.0.tar.gz | 23.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| adr_drift-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.6 kB
Release files / adr_drift-0.1.0.tar.gz
| Download URL | adr_drift-0.1.0.tar.gz |
|---|---|
| Size | 23.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4cfd86c2d6cf163f30a447533e799c1ccc57deadd27b455bf3d7306efd954906
|
|
BLAKE2b-256 checksum How to use checksums |
a07f3c3547386b22bcd11ef7687941019cde974d002305b5ce6cc5747ab6e449
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / adr_drift-0.1.0-py3-none-any.whl
| Download URL | adr_drift-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
135d814dba9a87cfe5c2235b1402d6df474da6e70eb823450c73c9245a422991
|
|
BLAKE2b-256 checksum How to use checksums |
c2e24c16229ee93e3b793fcc3f133bdc316c0c99be0405284c565b58f2a42231
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for adr-drift 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.