Skip to main content

Open-source PR reviewer that detects design-intent regressions

Project description

Adrift

Adrift

Catches the regressions other PR reviewers miss.

An open-source AI code reviewer for design-intent regressions — pull requests that quietly reverse a decision the team already made.

Complementary to CodeRabbit / Cubic / Greptile. Where they grade the diff for bugs, quality, and security, Adrift grades it against the reasoning that produced the codebase.

PyPI Python versions MIT License CI Buy Me A Coffee

Quick start · How it works · Examples · vs. other reviewers · Sponsor


The problem

Teams that ship with AI move fast. Cursor, Claude Code, Cline, Devin — a senior engineer routinely outputs what used to be a week of features in a day.

But velocity is a drift accelerator. Each PR is judged against the current codebase, not against the reasoning that produced the current codebase. A pattern someone deliberately rejected six months ago — written up in an ADR, defended in a PR description, encoded in a @decision: annotation — gets re-introduced silently by a teammate (or an AI agent) who never saw the original argument.

Six months later, the codebase has drifted from its own intent. Specific decisions exist, half-revised, with rationale buried in PR threads nobody re-reads. The diff looks fine. The bot says LGTM. The architectural commitment is gone.

Existing AI reviewers don't catch this. They see the diff; they don't see what the team already decided. That's the gap Adrift fills.

What Adrift does

On every PR, Adrift:

  1. Reads the diff.
  2. Searches its decision store — auto-mined from your repo's ADRs, @decision: annotations, CLAUDE.md, README, CHANGELOG, prior PR descriptions and review threads.
  3. Flags contradictions with quoted evidence and a link back to the source decision.
  4. Learns from reviewer feedback: /adrift accept, /adrift override <why>, /adrift dismiss, /adrift flag <missed>.

Conservative by default — biased to NO, because false positives erode reviewer trust faster than missed regressions.

Quick start

Two files, one secret, every PR.

# 1. Drop the review workflow into your repo
mkdir -p .github/workflows
curl -L https://raw.githubusercontent.com/MOHAMAD-ZUBI/Adrift/main/examples/workflows/adrift-review.yml \
  -o .github/workflows/adrift-review.yml

# 2. (Optional) drop the feedback-loop workflow
curl -L https://raw.githubusercontent.com/MOHAMAD-ZUBI/Adrift/main/examples/workflows/adrift-learn.yml \
  -o .github/workflows/adrift-learn.yml

# 3. In repo Settings → Secrets and variables → Actions, add ONE of:
#    ANTHROPIC_API_KEY      (simplest, direct Anthropic)
#    OPENROUTER_API_KEY     (multi-provider, lower cost)
#    OPENAI_API_KEY
#
#    GITHUB_TOKEN is auto-injected — you don't add it manually.

# 4. Open a PR. Adrift bootstraps a decision store from your repo's
#    ADRs + markdown + last 100 PRs (~30–60s, one-time), then posts a
#    review comment.

No adrift index call. No store to seed. The first PR after install does it automatically; every subsequent PR reuses the cached store and incrementally adds itself to it.

Want it locally?

pip install adrift-pr            # or: uv pip install adrift-pr
adrift review https://github.com/your-org/your-repo/pull/42 --dry-run

Want it with no Python / Node / GitNexus setup? Use the Docker image — everything's baked in:

docker run --rm \
  -e GITHUB_TOKEN \
  -e ANTHROPIC_API_KEY \
  -v "$(pwd):/workspace" \
  ghcr.io/mohamad-zubi/adrift:latest \
  review https://github.com/your-org/your-repo/pull/42 --dry-run

The image is tagged with each release (:0.1.0, :0.1, :latest). Pin to a specific version in CI; use :latest for one-off runs.

How it works

Adrift architecture — how a PR gets reviewed: indexing, reviewing, learning

Three stages:

1. Indexing — harvest every plausible decision signal. ADRs (docs/adr/*.md), @decision: annotations in code, and CLAUDE.md become EXPLICIT decisions with full trust. Every other markdown file is section-split by H2 and run through a strict-precision classifier (biased to NO). PR bundles — description + commit messages + review-line comments + issue comments — are aggregated and classified per PR. Accepted candidates become MINED decisions weighted by the classifier's confidence.

2. Reviewing — an LLM agent decides what to investigate. The agent has three tools: search_decisions (semantic search over the store), query_impact_zone (callers/callees via GitNexus MCP), report_finding (record a confirmed contradiction). It picks its own queries, calibrates severity by structural reach, and stops when nothing material remains.

3. Learning — reviewer slash commands teach the store. /adrift override 1 <why> supersedes the original decision with new EXPLICIT-tier reasoning. /adrift flag <missed> adds a new MINED decision. Adrift gets smarter over time without anyone curating manually.

The store lives in actions/cache — branch-scoped, ~100 KB, monotonically grows as PRs flow through CI.

Examples

A real review comment from this repo's own dogfood run:

🛡️ Adrift

Found 1 potential design-intent regression.

1. Agentic mode and GitNexus enabled by default Source: github://MOHAMAD-ZUBI/Adrift/pull/4 · Confidence: 85% · Severity: high

Decision PR #4 explicitly documents that both workflow files must include a Set up Node.js (for GitNexus) step. This diff adds a gitnexus analyze step but omits the Node.js setup, which means…

Reply with /adrift accept 1, /adrift override 1 <reason>, /adrift dismiss 1, or /adrift flag <missed> to teach future reviews.

Findings include:

  • The decision's source — clickable to the ADR / PR / file:line.
  • A confidence score (0–1) — Adrift won't fire below 0.7 by default.
  • A severity rating informed by structural impact.
  • Quoted evidence from both the diff and the decision.

How Adrift is different

Adrift CodeRabbit / Cubic / Greptile
Asks "Does this diff contradict something we already decided?" "Does this diff have bugs?"
Reads ADRs, @decision: annotations, markdown docs, PR history The diff + surrounding code
Improves Reviewer feedback updates the decision store; the system adapts to your team's rules Static prompt; same response everywhere
Surface area Architectural regressions, contracts, conventions Bugs, style, security, performance
Stance Complementary Complementary
Cost ~$0.05–$0.30 per PR typically $20–$30/mo per dev

Run both. They're answering different questions.

@decision: annotations

Document a load-bearing design choice right next to the code that implements it:

# @decision: All DB access goes through the repository pattern.
#   Reason: lets us swap persistence later without touching business logic.
#   Constraint: no module outside `repositories/` imports from the ORM directly.
def get_user(user_id: str) -> User:
    return user_repository.find(user_id)

Works in any language with #, //, or -- line comments. Picked up automatically during indexing as EXPLICIT-tier decisions. The source citation in any finding is path/to/file.py:42 — one click and the reviewer is on the rule.

CLI reference

adrift --version
adrift --help

adrift index    <repo-url>                          [--config PATH] [--verbose]
adrift index-pr <pr-url>                            [--config PATH] [--verbose]
adrift review   <pr-url>  [--dry-run] [--mode MODE] [--config PATH] [--verbose]
adrift learn    <pr-url>                            [--config PATH] [--verbose]
Command Purpose
index Full bootstrap — walks ADRs + @decision: + markdown + the last N PRs. Run once per repo.
index-pr Incremental — mines one PR's bundle and adds it if decision-bearing. Used as a post-review step in CI.
review Run the reviewer on a PR. --mode agentic (default) uses the LLM tool loop; --mode pipeline is a faster retrieve-then-judge fallback.
learn Process reviewer /adrift slash commands from a PR's comments.

--dry-run on review prints the comment without posting.

Configuration

Drop a .adrift.yml at your repo root to override defaults. Every field has a sensible default; you only override what you want.

Minimal config for OpenRouter:

judge_model: openrouter/anthropic/claude-sonnet-4.6
classify_model: openrouter/anthropic/claude-haiku-4.5

Full schema with comments: examples/.adrift.yml.

Reviewer feedback

Adrift's comments are numbered. Reviewers train the system by replying with slash commands:

/adrift accept 1
/adrift override 2 The Windows packaging change is intentional — we're moving away from the old format.
/adrift dismiss 3
/adrift flag Service A must not call Service B directly over HTTP; everything goes through the queue.
Command Effect on the decision store
accept N Reinforce the decision behind finding N (weight +).
override N <reason> Create a new EXPLICIT decision with the reviewer's reasoning. Original is marked superseded_by and silently excluded from future retrieval.
dismiss N Demote the decision (weight −). No replacement.
flag <description> Create a new MINED decision from the description. Use when Adrift missed something real.

Decisions also undergo recency decay — weights decline exponentially with age (configurable half_life_days, default 365). Unreinforced decisions age out over years; reinforced ones stay sharp.

GitNexus integration

Adrift uses GitNexus as an optional code-graph backend for impact-zone queries (callers, callees, structural reach). The bundled workflow installs it in CI automatically. Disable in .adrift.yml:

gitnexus:
  enabled: false

When unavailable, Adrift logs a one-line warning and runs without the structural signal. Reviews still work; they just lose graph-aware severity calibration.

Architecture

adrift/
├── cli.py             # Typer entry point
├── reviewer.py        # Pipeline reviewer (retrieve → judge)
├── agentic/           # Agentic reviewer (tool_use loop)
├── tools/             # search_decisions, query_impact_zone, report_finding
├── sources/           # ADRSource, AnnotationSource, MarkdownSource
├── mining/            # MarkdownMiner, PRBundleMiner, DecisionClassifier
├── store/             # SQLite + sqlite-vec
├── embedding/         # fastembed (BAAI/bge-small-en-v1.5, local, no API)
├── retrieval/         # Hybrid (structural × embedding × tier × recency)
├── graph/             # GitNexus MCP client (sync facade over async MCP)
├── feedback/          # /adrift slash-command parser + processor
└── reporting.py       # Markdown rendering, hidden markers for `learn`

Built on Python 3.11+, Typer, litellm (multi-provider LLM), PyGithub, Pydantic, sqlite-vec, fastembed. 236 tests, fast (~2s), offline.

See docs/adr/ for our own design-decision records.

Contributing

Issues and PRs welcome. See CONTRIBUTING.md for setup and conventions. We keep the dependency surface small, the test suite fast, and the abstractions honest.

The repo itself is dogfooded — every PR is reviewed by Adrift before it lands. That's how the GitNexus integration bug got caught (silent for a phase), the learn-workflow bug got caught (silent across three PRs), and several false-positives got the system trained on what not to flag.

Support the project

Adrift is built and maintained by one person, in the open, without a business model behind it. If it saves you from a bad merge — or if you just like that it exists — buying me a coffee keeps the work going.

Buy Me A Coffee

Other ways to help:

  • ⭐ Star the repo — visibility helps a lot
  • 🐛 Open an issue if Adrift reviews something on your repo and gets it wrong, weirdly, or interestingly right
  • 💬 Tell one teammate about it
  • 🤝 Contribute — even a one-line README fix is appreciated

License

MIT. Use it, fork it, ship it.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

adrift_pr-0.1.0.tar.gz (73.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

adrift_pr-0.1.0-py3-none-any.whl (82.1 kB view details)

Uploaded Python 3

File details

Details for the file adrift_pr-0.1.0.tar.gz.

File metadata

  • Download URL: adrift_pr-0.1.0.tar.gz
  • Upload date:
  • Size: 73.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for adrift_pr-0.1.0.tar.gz
Algorithm Hash digest
SHA256 53d32413f7b8ec8861d7d05155cd3a48ab14d423c6486ccd8a2a8e93c60b8ed4
MD5 c00cad33b854acdf1cf0159573748890
BLAKE2b-256 0de9703b835848a5ae83f0fe3fccf384fb7339c22c4feb80c53702b522f14922

See more details on using hashes here.

Provenance

The following attestation bundles were made for adrift_pr-0.1.0.tar.gz:

Publisher: release.yml on MOHAMAD-ZUBI/Adrift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adrift_pr-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: adrift_pr-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 82.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for adrift_pr-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 90962b7bcc2235399a9b6fb7675b7d3ffa2ff09fe1cc5d5e66d9d0665e05a5c8
MD5 ac885dd24831bdd7a92085be7a007542
BLAKE2b-256 14c155a2575daa36df19db8eb9c36d6711fc04f3cf1307e10f7d61536f7f9986

See more details on using hashes here.

Provenance

The following attestation bundles were made for adrift_pr-0.1.0-py3-none-any.whl:

Publisher: release.yml on MOHAMAD-ZUBI/Adrift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page