One checklist stretched across everything; each concern gets a fraction of the model's attention.
Open-source agentic code review
A merge gate built from small reviewers you write and own.
A single model reviewing the whole diff loses recall on individual concerns as its checklist grows. Bastion splits review into many narrow reviewers, each checking exactly one concern. They run in parallel and aggregate into one verdict; the gate blocks whenever a gate reviewer can't return a clean pass, while advisors comment without blocking. You author them, version them in your repo, and run them on your own infrastructure.
- Single-concern reviewers
- Fails closed
- Local loop + CI
- Open source, self-hostable
# Each reviewer owns exactly one concern.
reviewers:
- name: single-responsibility
trigger: [src/**/*.rs]
mode: gate
prompt: |
Block if any one file has taken on multiple
unrelated concerns; otherwise approve it.
- name: parse-dont-validate
trigger: [src/**/*.rs]
mode: advisor
prompt: |
Flag boundaries that pass raw strings where a
parsed type would remove a class of bugs.bastion review --base main- single-responsibilitypass
- error-handlingblock
- parse-dont-validateadvisor
Recall falls as one reviewer takes on more concerns.
One model reviews the whole diff and leaves comments for a person to act on. Its recall on individual concerns drops as the checklist grows.
You cover more ground by adding narrow reviewers, not by broadening one. Each stays at high recall.
One concern per reviewer.
A reviewer is a focused fitness function: a prompt, a trigger, a mode. A cross-cutting property like tenant isolation or migration safety is just another reviewer whose single concern is that property.
| reviewer | concern | trigger | mode |
|---|---|---|---|
| single-responsibility | No one file concentrates multiple unrelated concerns. | src/**/*.rs | gate |
| error-handling | No recoverable error can panic in production code. | src/**/*.rs | gate |
| api-compatibility | Public API changes stay backward-compatible. | src/api/** | gate |
| release-surface-robustness | Installers verify checksums and fail closed on any mismatch. | scripts/** | gate |
| parse-dont-validate | Boundary data is parsed once into a precise type. | src/**/*.rs | advisor |
| test-coverage | New or changed behavior is covered by tests. | src/**/*.rs | advisor |
| docs-in-sync | User-visible changes update the matching docs. | docs/**/*.md | advisor |
Gates block. Advisors comment.
A gate holds the merge until it passes. Anadvisor reports findings without blocking. Same reviewer shape, different authority, your call per concern.
Declarative and static
Reviewers live in checked-in YAML, not generated by code. That keeps them reviewable and the trigger set stable, so a change to review policy is a diff you can read.
Composable and parallel
They run independently and aggregate at the end. Add a reviewer for a new concern without touching the others; a slow one never blocks a fast one.
How the gate aggregates
Reviewers run in parallel with per-reviewer timeouts; their verdicts aggregate into one decision. A gate that crashes, times out, or can't produce a valid verdict resolves to block, never a silent pass. Advisors are best-effort: when one fails, it's ignored.
| reviewer | what happened | counts as |
|---|---|---|
| single-responsibility | returned pass | pass |
| error-handling | returned block | block |
| e2e-checkout-flow | timed out | block |
| migration-safety | crashed | block |
| no-verdict | malformed output | block |
| test-coverageadvisor | errored | skipped |
Every gate returned pass?No. Four gates resolved to block. merge gateblock | ||
You govern the policy.
The human moves from reviewing every diff to authoring and governing the reviewers that do. Your interface becomes the registry and the escape feed.
# Changing the review policy is itself reviewed.
.bastion.yaml @your-org/platformBecause reviewers are declarative and static, any PR that weakens the gate, loosens a prompt, drops a trigger, or downgrades a gate to an advisor shows up as a diff a human must approve. The policy can't quietly erode.
- A reviewer encodes a check you'd otherwise run by hand.
- Even an earnest agent can game a check; a reviewed policy diff makes that visible.
- 01
An escape slips through
A change merges that a reviewer should have caught. Inevitable, especially early. It's also your most valuable signal.
- 02
Triage which reviewer missed
Find the concern that wasn't covered, or the reviewer whose prompt was too loose to catch it.
- 03
Adjust the policy
Tighten a prompt, add a narrow reviewer, or harden a trigger. It's a diff against .bastion.yaml, reviewed like any other.
- 04
The gate gets sharper
The next changeset meets a better policy. The gate improves as you feed escapes back into it.
The same reviewers, locally and in CI.
A reviewer is a fitness function the author can optimize against before anyone opens a PR. The same reviewers run in your local loop (fast, pre-PR) and in CI (authoritative). CI runs the same gate, so its result matches what the author already saw.
The author loop
An authoring agent runs bastion review against the working tree, exactly as CI would, and loops until green.
# fix what blocked, then run again
$ while ! bastion review --base main; do
agent fixes the blocking findings
$ done
all gates pass · ready to open a PRThe confirmation
On the PR, the same gate runs as the authoritative check. Because the author already drove it green, CI mostly just agrees.
# uses: jssblck/bastion@v0
✓ bastion / gate
single-responsibility pass
error-handling pass
release-surface pass
merge gate: passBastion doesn't own your CI. It runs anywhere code can run, and stays portable across the local and GitHub surfaces by keeping them deliberate mirror images.
Open source and self-hostable.
Read every reviewer, run them on your own infrastructure through the model provider and billing you already use, and own the gate outright. The reviewers, the verdicts, and the aggregation are all in the open.
Open source
AGPL-3.0. Every reviewer, every verdict, the whole gate. Nothing about how your code is judged is hidden behind a service.
Self-hostable
Runs anywhere code can run, local or any CI. It drives the agent backends you already use (Claude Code, Codex, Pi) and reuses your own auth and billing.
No lock-in
Your policy is one declarative file in your repo. Take it with you. It's plain YAML you can read, diff, and version, with no proprietary format in the way.
Early software, fails closed.
Bastion is early and moving fast. The runner, the gate, and the Claude Code, Codex, and Pi backends run reviewers for real. Anything not yet implemented returns block instead of pass, so an unfinished capability can't wave code through by claiming a review it never ran.
From nothing to your first gate in five minutes.
The installer detects your platform, downloads the matching release, verifies its SHA-256 checksum, and puts bastionon your PATH. It fails closed on any checksum problem.
curl -sSfL https://raw.githubusercontent.com/jssblck/bastion/main/scripts/install.sh | bashirm https://raw.githubusercontent.com/jssblck/bastion/main/scripts/install.ps1 | iexInstall the agent skill so your coding agents know how to drive Bastion (commit what it writes), then loop a reviewer until the gate is green:
$ bastion skills install
$ bastion review --base main