Metadata-Version: 2.4
Name: adrift-pr
Version: 0.1.0
Summary: Open-source PR reviewer that detects design-intent regressions
Project-URL: Homepage, https://github.com/MOHAMAD-ZUBI/Adrift
Project-URL: Repository, https://github.com/MOHAMAD-ZUBI/Adrift
Project-URL: Issues, https://github.com/MOHAMAD-ZUBI/Adrift/issues
Project-URL: Changelog, https://github.com/MOHAMAD-ZUBI/Adrift/blob/main/CHANGELOG.md
Author: Adrift contributors
License: MIT
License-File: LICENSE
Keywords: adrift,ai,code-review,github,llm,pull-request,regression
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Requires-Dist: fastembed>=0.4
Requires-Dist: httpx>=0.27
Requires-Dist: litellm>=1.40
Requires-Dist: mcp>=1.0
Requires-Dist: pydantic>=2.7
Requires-Dist: pygithub>=2.3
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7
Requires-Dist: sqlite-vec>=0.1.6
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

<img src="assets/logo-200.png" width="110" alt="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**.

<p>
  <a href="https://pypi.org/project/adrift-pr/"><img src="https://img.shields.io/pypi/v/adrift-pr.svg?color=e03131" alt="PyPI"></a>
  <a href="https://pypi.org/project/adrift-pr/"><img src="https://img.shields.io/pypi/pyversions/adrift-pr.svg" alt="Python versions"></a>
  <a href="https://github.com/MOHAMAD-ZUBI/Adrift/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
  <a href="https://github.com/MOHAMAD-ZUBI/Adrift/actions"><img src="https://img.shields.io/github/actions/workflow/status/MOHAMAD-ZUBI/Adrift/ci.yml?branch=main&label=CI" alt="CI"></a>
  <a href="https://www.buymeacoffee.com/mohamadzubi"><img src="https://img.shields.io/badge/Buy_me_a_coffee-FFDD00?logo=buymeacoffee&logoColor=black" alt="Buy Me A Coffee"></a>
</p>

<p>
  <a href="#quick-start"><strong>Quick start</strong></a>  ·
  <a href="#how-it-works">How it works</a>  ·
  <a href="#examples">Examples</a>  ·
  <a href="#how-adrift-is-different">vs. other reviewers</a>  ·
  <a href="#support-the-project">Sponsor</a>
</p>

</div>

---

## 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.

```bash
# 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?**

```bash
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:

```bash
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

<div align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="assets/diagram-dark.png" />
    <img src="assets/diagram-light.png" alt="Adrift architecture — how a PR gets reviewed: indexing, reviewing, learning" />
  </picture>
</div>

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](https://github.com/abhigyanpatwari/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:

```python
# @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

```bash
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:

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

Full schema with comments: [`examples/.adrift.yml`](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](https://github.com/abhigyanpatwari/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`:

```yaml
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](https://typer.tiangolo.com), [litellm](https://github.com/BerriAI/litellm) (multi-provider LLM), [PyGithub](https://github.com/PyGithub/PyGithub), [Pydantic](https://github.com/pydantic/pydantic), [sqlite-vec](https://github.com/asg017/sqlite-vec), [fastembed](https://github.com/qdrant/fastembed). 236 tests, fast (~2s), offline.

See [docs/adr/](docs/adr/) for our own design-decision records.

## Contributing

Issues and PRs welcome. See [CONTRIBUTING.md](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.

<p align="center">
  <a href="https://www.buymeacoffee.com/mohamadzubi">
    <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50" width="210">
  </a>
</p>

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](LICENSE). Use it, fork it, ship it.
