Fan code reviewers across multiple agent harnesses and merge one verdict.
Project description
aeview
Fan code reviewers across multiple AI agent harnesses, then merge one deduplicated verdict.
aeview runs the same change past several reviewers — each a prompt you check into your repo —
across several agent harnesses (Claude Code, Codex, Copilot), in parallel. It collects every
finding, deduplicates them with an LLM judge, and writes a single report.json plus an exit code
you can loop on: 0 approve · 1 needs-attention · 2 error.
The bet: a reviewer is a versioned artifact (a prompt + the harnesses it runs on), and a panel of independent models catches what any single model misses.
uv tool install --prerelease=allow aeview # pip/pipx need no flag; see Install
cd your-repo
aeview run # review your current changes (auto mode); exit 1 if anything needs attention
Contents
- How it works
- Requirements
- Install
- Quickstart
- Scopes — what to review
- Reviewers
- Auto mode, activation &
.aeviewignore - Harnesses
- Configuration
- Commands
- The report & exit codes
- Runs, lifecycle & output
- Development
- License
How it works
A reviewer is a prompt (REVIEWER.md) plus a set of harness instances (an agent + a model).
One aeview run does this:
- Resolve reviewers — walk up from your cwd to home collecting
.aeview/reviewers/*. - Resolve the scope — turn
--scopeinto a concrete git diff (working tree, a branch, a PR, …). - Bundle the diff — small diffs are frozen inline; large ones hand the agent read-only git to self-collect, so nothing is silently truncated.
- Fan out — run every
reviewer × harnesspair concurrently, each in a read-only sandbox (read anywhere, write nothing). Each emits findings against a fixed schema. - Dedupe & merge — an LLM judge groups duplicate findings across the panel; survivors are kept verbatim with their provenance and an agreement count.
- Report — write
report.jsonand exit0/1/2.
Everything is persisted under ~/.aeview/runs/<id>/, so a killed run can be resumed.
Requirements
- Python 3.14+ (the
uvinstaller can fetch one for you). - macOS or Linux.
- Harness auth. aeview drives each harness through its Python SDK, which bundles a pinned CLI
binary — you don't install Claude Code / Codex / Copilot separately. You do need to be
authenticated with each harness a reviewer uses. Run
aeview doctorto see exactly what's missing for the reviewers you have. gh(GitHub CLI) — for--scope pr, and to auto-detect a branch's base from its open PR. Optional: aeview falls back toorigin/HEAD, thenmain/master/trunk, whenghor a PR isn't available.
Install
uv tool install --prerelease=allow aeview # recommended (see the note); also fetches Python 3.14
# or
pipx install aeview
Why
--prerelease=allowfor uv? aeview pulls a prerelease Codex runtime (the Codex Python SDK is still in beta), so you'll seeopenai-codex/openai-codex-cli-binalpha/beta versions. pip / pipx allow that automatically (the dependency specifier names the prerelease), but uv requires--prerelease=allow(orUV_PRERELEASE=allow) for a transitive prerelease. The flag goes away onceopenai-codexships a stable release. (Homebrew support is planned.)
After install, sanity-check your setup:
aeview doctor
Quickstart
cd your-repo
# Review your changes (auto mode: uncommitted work if the tree is dirty, else your branch vs base):
aeview run
# Preview the plan (roster + scope + bundle size) without spending anything:
aeview run --dry-run
# Review a branch against its base, with every reviewer you have:
aeview run --scope branch --reviewers all
# Get machine-readable output and act on the exit code:
aeview run --json && echo "approved" || echo "needs attention (or error)"
A run prints a human summary (or --json), writes report.json, and exits with the verdict code.
Scopes — what to review
--scope <type>[:value]. Omit --scope entirely for auto. Where a type takes a value, the
"bare" form (no :value) uses the default shown:
| Scope | Bare default | Reviews |
|---|---|---|
working-tree |
— | all uncommitted changes |
staged |
— | only the staged changes |
branch[:base] |
base = auto | your branch vs base |
pr[:number] |
current branch's PR | a PR's diff (via gh) |
effective-pr[:base] |
base = auto | branch commits + uncommitted work, vs base |
commits[:a,b,c] |
HEAD |
exactly the commits listed, each vs its own parent |
range:A..B |
value required | the diff between A and B |
patch:file |
value required | a diff file (or - for stdin) |
auto |
— | dirty → working-tree, else your branch vs base |
Notes:
commitsis a set, not a range:--scope commits:a,c,ereviews exactly those three commits, each shown against its own parent (a union of per-commit patches). A single commit is just the one-element case; barecommitsisHEAD.--include-dirtyfolds uncommitted work onto a committed scope — use it withbranch,auto, orstaged; it's a no-op onworking-tree.--allow-conflictsreviews despite an in-progress merge/rebase (refused by default).- Base resolution order: explicit value → the PR base →
origin/HEAD→main/master/trunk.
Reviewers
A reviewer lives at .aeview/reviewers/<name>/REVIEWER.md: YAML frontmatter + a prompt body.
---
name: security
description: Hunts auth, injection, and trust-boundary bugs.
harnesses:
- { harness: claude-code, model: claude-opus-4-8 }
- { harness: codex, model: gpt-5.5, thinking: xhigh }
auto-activate-paths:
- "src/api/**"
---
You are a security reviewer. Find the injection, the auth gap, the unsafe
deserialization... (the rest of the prompt)
Frontmatter fields:
| Field | Required | Meaning |
|---|---|---|
name |
yes | Must equal the directory name. |
description |
no | One line, shown by aeview reviewers. |
harnesses |
no | List of { harness, model, thinking? }. Omit to use the global fallbackReviewerHarnesses. |
auto-activate-paths |
no | Globs that opt this reviewer into auto mode. |
Resolution (walk-up)
aeview climbs from your cwd through its parent directories up to your home directory, looking for
<dir>/.aeview/reviewers/<name>/REVIEWER.md. First match wins, so a repo-local reviewer shadows
a personal one of the same name. Your home ~/.aeview/reviewers/ holds reviewers available
everywhere — including the seeded default reviewer (an adversarial general-purpose reviewer).
Selecting reviewers
aeview run --reviewers security # one
aeview run --reviewers security,tests # several (comma-separated)
aeview run --reviewers security --reviewers tests # or repeated
aeview run --reviewers all # every reviewer visible here
With no --reviewers, aeview uses auto mode.
Resource references
The reviewer's own directory (absolute path) is prepended to its prompt, so you can drop supporting
files beside REVIEWER.md (references/checklist.md, scripts, …) and reference them with relative
paths. Reviewers can read anywhere, so the links resolve.
Scaffolding
aeview init security # create .aeview/reviewers/security/REVIEWER.md
aeview init security --with-harness # also scaffold a harnesses: block
Auto mode, activation & .aeviewignore
Auto mode (no --reviewers) builds the roster as:
default(always) ∪ every reviewer whoseauto-activate-pathsmatches a changed file.
A reviewer with no auto-activate-paths never auto-runs (select it by name or with all). Matching
uses literal globs (Python's PurePath.full_match), anchored at the reviewer's .aeview parent
directory: a single * stops at /, ** crosses directories, there's no negation, and it's
case-sensitive. So write backend/**, not backend/.
Auto-activation needs repo-root-relative paths, so --scope patch (and running outside a git repo)
runs only default — name any extra reviewers explicitly there.
.aeviewignore filters files out of the diff before it's reviewed — handy for lockfiles,
generated code, vendored trees. It uses faithful gitignore semantics (the pathspec library):
uv.lock
dist/
**/__snapshots__/
!important.generated.ts
Files are collected on the same cwd→home walk (your home file is ~/.aeviewignore); each file's
patterns anchor at its own directory, nearest rules win, and ! negation is supported.
Like auto-activation, .aeviewignore is skipped for --scope patch (its paths aren't
repo-root-relative) — a patch is reviewed exactly as supplied.
Harnesses
A harness instance is { harness, model, thinking? }. Supported harnesses:
harness |
Driven via | Example model |
|---|---|---|
claude-code |
claude-agent-sdk |
claude-opus-4-8 |
codex |
openai-codex |
gpt-5.5 (e.g. thinking: xhigh) |
copilot |
github-copilot-sdk |
a Copilot-served model |
Every harness runs read-anywhere, write-nowhere: a reviewer can read any file (to gather context) but cannot modify your repo. Claude Code and Codex enforce this with their native read-only sandboxes; Copilot uses a deny-by-default permission handler that only approves reads.
Each SDK ships a pinned binary, so aeview is insulated from your own CLI upgrades. To point a
harness at a different binary, set overrideHarnessBinaries.
Configuration
Global settings live in ~/.aeview/settings.json, seeded (write-if-absent) on first run. Its keys
are camelCase; the JSON run artifacts (report.json, review.json) use snake_case.
{
"fallbackReviewerHarnesses": [
{ "harness": "claude-code", "model": "claude-opus-4-8" }
],
"deduplicationHarness": { "harness": "claude-code", "model": "claude-opus-4-8" },
"retention": { "keepLast": 20, "ttlDays": 14 },
"reviewTimeoutSeconds": 1200,
"overrideHarnessBinaries": { "codex": "/usr/local/bin/codex" }
}
| Key | Meaning |
|---|---|
fallbackReviewerHarnesses |
Harnesses a reviewer runs on when its REVIEWER.md has no harnesses: block. |
deduplicationHarness |
The harness used to deduplicate findings across the panel. |
retention |
Auto-prune old runs: keep at least the newest keepLast, and drop runs that are also older than ttlDays days. |
reviewTimeoutSeconds |
Timeout (seconds) per harness attempt; transient errors retry, so a review's total time can exceed it. A timed-out review fails fast (no retry); resume re-runs it. |
overrideHarnessBinaries |
Optional per-harness override of the bundled CLI binary, by path. Keys: claude-code, codex, copilot. |
Commands
| Command | What it does |
|---|---|
aeview run |
Run reviewers over a scope; print the gate (verdict + findings, see below) and exit 0/1/2. |
aeview status [run-id] |
Per-review progress + coverage (defaults to the latest run). --wait blocks to a terminal state and adopts its exit code. |
aeview result [run-id] |
Print a finished run's full report; exit with its 0/1/2 code. |
aeview resume <run-id> |
Re-run a run's non-done reviews against its frozen bundle, then re-merge. |
aeview list |
Recent runs, newest first: id, time, scope, verdict, coverage. |
aeview reviewers [name] |
List the reviewers visible here (walk-up), or show one's detail. |
aeview init <name> |
Scaffold a repo reviewer. --with-harness adds a harnesses: block. |
aeview doctor |
Preflight: reviewer config, harness binaries + auth, and gh. Exits 1 on failure. |
aeview version |
Print the version. |
Common run flags: --scope, --reviewers, --include-dirty, --allow-conflicts, --dry-run,
--json. Most commands accept --json for machine-readable output.
The report & exit codes
The full merged artifact is report.json — what aeview result and the on-disk file give you:
{
"verdict": "needs-attention", // "approve" | "needs-attention"
"summary": "...",
"findings": [
{
"id": "f3", // stable run-local id assigned during merge
"title": "Unvalidated path used in file read",
"body": "...",
"severity": "high", // critical | high | medium | low
"category": "security", // bug | security | regression | test_gap | maintainability
"confidence": 0.9, // 0.0–1.0
"location": { "file": "src/api/files.py", "line_start": 42, "line_end": 48 },
"recommendation": "...",
"agreement": 2, // size of the dedup group (findings merged into this one)
"sources": [ // one entry per merged finding, with its originating review
{ "review": "security__codex-gpt-5.5", "severity": "high", "confidence": 0.9 },
{ "review": "security__claude-code-claude-opus-4-8", "severity": "high", "confidence": 0.85 }
]
}
],
"next_steps": [ { "source": "security__...", "steps": ["..."] } ],
"coverage": { "contributed": 3, "failed": 0 }, // reviews that completed vs. reviews that failed
"dedup": { "status": "ok", "harness": "...", "reason": null, "warning": null },
"usage": { "reviews": {...}, "dedup": {...}, "total": { "input_tokens": 0, "output_tokens": 0, "cost_usd": 0.0 } }
}
aeview run prints a gate, not the full report: it's report.json with a top-level run_id
added and the result-only detail omitted — findings[].id, next_steps, usage, and the dedup
fields other than status. The kept fields keep their report.json names, so a consumer that reads
only the gate's fields works against both run and result. The dropped detail (token/cost
accounting, per-review next steps, dedup provenance, per-finding ids) lives in aeview result.
Exit codes (the loop-until-clean contract):
| Code | Meaning |
|---|---|
0 |
approve — no actionable findings |
1 |
needs-attention — at least one finding to act on |
2 |
error — the run couldn't be trusted (e.g. every review failed) |
Runs, lifecycle & output
Each run is a directory under ~/.aeview/runs/<id>/:
runs/<id>/
run.json # the manifest: scope, roster, dedup plan, state, pid
bundle/ # the frozen diff under review
reviewers/<reviewer>/<instance>/
review.json # that review's findings + status + usage
review.log # raw harness-SDK event stream (JSONL)
dedup/<instance>/result.json # the dedup judge's grouping decision
report.json # the merged verdict
- Timeout & fail-fast. Each harness attempt is bounded by
reviewTimeoutSeconds; a timed-out review fails fast (timeouts aren't retried). A failed review is recorded — the run continues — andresumere-runs it. - Crash recovery. Runs record their pid; a crashed run is reconciled to
interruptedand can beresumed.aeview status --waitblocks until a run reaches a terminal state. - Retention. On each
run, terminal runs outside the newestkeepLastand older thanttlDaysare pruned (keepLastis a guaranteed floor).
Development
git clone https://github.com/MarlzRana/aeview
cd aeview
uv sync # install deps (incl. dev group)
uv run pytest # the offline test suite (no model calls)
uv run ruff check # lint
uv run pyright # type-check
uv run aeview --help
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aeview-0.0.2.tar.gz.
File metadata
- Download URL: aeview-0.0.2.tar.gz
- Upload date:
- Size: 73.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3db96a4ce6126355cad86ca48020a9e2d305dc0c133f1c6a7f9a8e81aa8331b8
|
|
| MD5 |
7232b7081150a067a073e804038d437b
|
|
| BLAKE2b-256 |
e5c5565f064cabdf4e59b27bb52adde922125b46a527d29ba82229fcc077dca0
|
Provenance
The following attestation bundles were made for aeview-0.0.2.tar.gz:
Publisher:
release.yml on MarlzRana/aeview
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aeview-0.0.2.tar.gz -
Subject digest:
3db96a4ce6126355cad86ca48020a9e2d305dc0c133f1c6a7f9a8e81aa8331b8 - Sigstore transparency entry: 1830792176
- Sigstore integration time:
-
Permalink:
MarlzRana/aeview@20be6e42db033fb027729c8a91c551e42c7ab799 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/MarlzRana
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@20be6e42db033fb027729c8a91c551e42c7ab799 -
Trigger Event:
push
-
Statement type:
File details
Details for the file aeview-0.0.2-py3-none-any.whl.
File metadata
- Download URL: aeview-0.0.2-py3-none-any.whl
- Upload date:
- Size: 88.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3db91ba790375d1b5f3a4df778f86b7b09e9bf63e2b66b51005217e6d4a108ea
|
|
| MD5 |
48c5a5e35698d88bc750c70d7458caee
|
|
| BLAKE2b-256 |
bb787f99dca5abce613a91a86671e332522eb7071fae0c7aa89cb114a4875537
|
Provenance
The following attestation bundles were made for aeview-0.0.2-py3-none-any.whl:
Publisher:
release.yml on MarlzRana/aeview
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aeview-0.0.2-py3-none-any.whl -
Subject digest:
3db91ba790375d1b5f3a4df778f86b7b09e9bf63e2b66b51005217e6d4a108ea - Sigstore transparency entry: 1830792245
- Sigstore integration time:
-
Permalink:
MarlzRana/aeview@20be6e42db033fb027729c8a91c551e42c7ab799 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/MarlzRana
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@20be6e42db033fb027729c8a91c551e42c7ab799 -
Trigger Event:
push
-
Statement type: