ReviewMindBot
LLM-powered pull request review. It reads the diff, finds real problems, and comments them on the lines that caused them — with a spend cap, a response cache, and a CI exit code you can gate on.
Use it on your pull requests
Drop this into .github/workflows/pr-review.yml:
name: PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write # required to post comments
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: 2001-Ankit/pr-review-mind-bot@v1
with:
provider: gemini
api-key: ${{ secrets.GEMINI_API_KEY }}
budget-usd: "0.50"
That's the whole setup. Add GEMINI_API_KEY (or OPENAI_API_KEY /
GROQ_API_KEY) to your repository secrets and open a PR.
Every run posts inline comments on the changed lines plus one summary comment, which is edited in place on each push rather than piling up.
Action inputs
| Input | Default | Description |
|---|---|---|
api-key |
required | Key for the chosen provider |
provider |
gemini |
gemini, openai or groq |
model |
provider default | Override the model |
github-token |
${{ github.token }} |
Needs pull-requests: write |
comment |
true |
Post findings to the PR |
inline |
true |
Include inline comments on changed lines |
fail-on |
none |
Fail the job at low/medium/high findings |
budget-usd |
1.00 |
Stop before exceeding this spend |
max-chunk-size |
12000 |
Characters of diff per request |
pr-url |
triggering PR | Review a specific PR instead |
Action outputs
total, high, cost-usd, and result-json (path to the full JSON result),
so you can branch on the result in later steps.
Forked PRs get a read-only token from GitHub, so comment posting will fail. Guard the job with
if: github.event.pull_request.head.repo.full_name == github.repository, as in.github/workflows/pr-review.yml.
Use it from the terminal
pip install "reviewmindbot[gemini]" # or [openai], [groq], [all]
export GEMINI_API_KEY=...
# A diff file, or straight from git
reviewmindbot changes.diff
git diff main | reviewmindbot -
# A GitHub PR - review it, and post the findings back to it
reviewmindbot --pr https://github.com/owner/repo/pull/123
reviewmindbot --pr https://github.com/owner/repo/pull/123 --comment
# See what a review would cost before running it
reviewmindbot changes.diff --estimate
# Cap the spend; a run that hits the cap reports partial results
reviewmindbot changes.diff --budget-usd 0.25
# Machine-readable, or ready to paste into a PR
reviewmindbot changes.diff --output json
reviewmindbot changes.diff --output markdown
# Draft tests for newly added code
reviewmindbot changes.diff --generate-tests
Requires Python 3.11+.
Configuration
Environment variables, or a .env file at or above your working directory
(see .env.example):
| Provider | Variable | Get a key |
|---|---|---|
gemini |
GEMINI_API_KEY (or GOOGLE_API_KEY) |
https://aistudio.google.com/apikey |
openai |
OPENAI_API_KEY |
https://platform.openai.com/api-keys |
groq |
GROQ_API_KEY |
https://console.groq.com/keys |
Exit codes
| Code | Meaning |
|---|---|
0 |
Ran successfully |
1 |
Error (bad config, unreachable provider, missing file) |
2 |
Findings met the --fail-on threshold |
--fail-on is off by default, so findings alone never fail your build.
Cost control
An LLM tool that runs on every PR has an unbounded bill by default. Three things keep it bounded:
--estimatereports projected requests, tokens and cost without calling the model at all.--budget-usdis checked before each request, so it is a limit and not a post-mortem. Hitting it produces a clearly-labelled partial review rather than a failure — you still get the findings that were paid for.- A response cache (SQLite, per-user, 14-day TTL) means a workflow re-run,
a retried flaky job, or a push that touches one file out of thirty only pays
for what actually changed. Keys cover the provider, model, prompt and review
contract, so a model switch or a prompt change can never serve a stale
answer.
--no-cachebypasses it,--clear-cacheempties it.
Every run reports what it spent, in the text output, the JSON usage block,
and the PR comment footer. Unpriced models report null rather than pretending
to be free.
Noise is filtered before any tokens are spent: lockfiles, minified bundles, binaries and deleted files never reach the model.
As a library
from reviewmindbot import api
result = api.review_diff(open("changes.diff").read())
for finding in result.findings:
print(finding.severity, f"{finding.file_name}:{finding.line}", finding.message)
print(result.usage) # {'requests': 3, 'total_tokens': 8421, 'cost_usd': 0.0021, ...}
print(result.complete) # False if the budget stopped it early
Review a PR and publish the result in one call:
result, outcome = api.review_pull_request(
"https://github.com/owner/repo/pull/123",
publish=True,
)
print(outcome.describe()) # "summary comment updated, 4 inline comment(s) posted"
Lower-level pieces work on their own:
from reviewmindbot.core import DiffParser, build_chunks
from reviewmindbot.engine import Orchestrator, Reviewer, FileRouter
from reviewmindbot.llm import create_provider
files = DiffParser().parse(diff_text)
chunks = build_chunks(FileRouter().select(files), max_chunk_size=12000)
Architecture
reviewmindbot/
├── cli.py argument parsing and exit codes only
├── api.py programmatic entry points
├── config.py env → immutable Config, resolved once
├── errors.py one exception hierarchy the CLI knows how to print
├── usage.py token accounting, pricing, budget enforcement
├── cache.py SQLite response cache, best-effort by design
├── core/
│ ├── diff_parser.py unified diff → FileChange/Hunk, with new-file line numbers
│ └── chunking.py hunks → model-sized Chunks that remember their files
├── engine/
│ ├── file_router.py drops binaries, lockfiles, deleted files
│ ├── reviewer.py Chunk → Findings; cache lookup and budget check
│ ├── aggregator.py dedupe across overlapping chunks, sort by severity
│ ├── orchestrator.py route → chunk → review in parallel → aggregate
│ ├── test_generator.py
│ ├── models.py Finding, ReviewResult, Severity, Category
│ └── prompts.py
├── llm/
│ ├── base.py LLMProvider ABC + retry/backoff + usage accounting
│ ├── factory.py name → client registry
│ ├── gemini.py
│ └── openai.py OpenAIProvider and Groq (OpenAI-compatible)
├── integration/
│ ├── github.py REST client: fetch diffs, upsert comments, post reviews
│ └── publisher.py ReviewResult → PR comments, with line validation
└── reporting/
└── formatters.py text / json / markdown
Adding a provider is one entry in llm/factory.PROVIDERS plus one in
config.PROVIDER_KEY_ENV_VARS. The CLI's --provider choices, config
validation, and the Action's key wiring all derive from those tables — and a
test fails if you add a provider without wiring its key into action.yml.
Adding an output format is one function in reporting/formatters.FORMATTERS.
Behaviour worth knowing
- Findings carry a file and a line. Chunks are rendered with a line-number gutter, so the model cites real line numbers instead of guessing.
- Inline comments are validated against the diff. GitHub rejects an entire review if one comment names a line outside the diff, so anything unverifiable is deliberately routed to the summary instead. A rejected review degrades to a summary-only comment; no finding is ever lost.
- The summary comment is edited, not re-posted — a bot that appends on every push buries the conversation it exists to support.
- A failed chunk does not sink the run. The others still report, and both the summary and the JSON say the review was partial.
- Transient errors (429/5xx/timeouts) are retried with backoff; permanent ones (bad key, unknown model) fail immediately instead of burning quota.
- Overlapping chunks are de-duplicated before output.
- Logs go to a per-user state directory, not your working directory, and an unwritable log path never stops a review.
Development
git clone https://github.com/2001-Ankit/pr-review-mind-bot.git
cd pr-review-mind-bot
pip install -e ".[dev]"
pytest
ruff check .
CI builds the wheel, installs it in a clean venv, and runs the console script —
the check that would have caught the broken 0.1.0 release.
Upgrading
See CHANGELOG.md. 0.2.0 renamed the importable package from
app to reviewmindbot and moved provider SDKs to extras; 0.3.0 is additive
on top of that.
Roadmap
- Post findings as inline PR review comments
- Cache reviews to avoid paying twice for the same diff
- Ship a ready-made GitHub Action
- Custom rule packs and per-repo policies (
.reviewmindbot.yml) - Incremental review of only the commits added since the last run
- Anthropic Claude provider
License
MIT — see LICENSE.
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 reviewmindbot-0.3.0.tar.gz.
File metadata
- Download URL: reviewmindbot-0.3.0.tar.gz
- Upload date:
- Size: 63.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f797a4083c4c14d32f118784ce11d75101d647c2631633fa9ef9c47005520a48
|
|
| MD5 |
81fdb9730c395a2923b8928f4aa10b74
|
|
| BLAKE2b-256 |
91372dcbf917093e2ee8f940e1e87f28c20cb4c3a71e850eae0e089990772326
|
Provenance
The following attestation bundles were made for reviewmindbot-0.3.0.tar.gz:
Publisher:
publish.yml on 2001-Ankit/pr-review-mind-bot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reviewmindbot-0.3.0.tar.gz -
Subject digest:
f797a4083c4c14d32f118784ce11d75101d647c2631633fa9ef9c47005520a48 - Sigstore transparency entry: 2752492855
- Sigstore integration time:
-
Permalink:
2001-Ankit/pr-review-mind-bot@f12c5a114db5679820d1c94fa5bc11912ab0cc14 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/2001-Ankit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f12c5a114db5679820d1c94fa5bc11912ab0cc14 -
Trigger Event:
push
-
Statement type:
File details
Details for the file reviewmindbot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: reviewmindbot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 47.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c843cf292776ed79d4ada47c4f04e2ac0e858cf2d1503e538e5f74f944c3bc90
|
|
| MD5 |
c3b0fb42ad6c8784aea72e38416b68ed
|
|
| BLAKE2b-256 |
7ad3552aef04d8d117f45d58eb0367afbc46f8178fede22d53ea4556a459777c
|
Provenance
The following attestation bundles were made for reviewmindbot-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on 2001-Ankit/pr-review-mind-bot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reviewmindbot-0.3.0-py3-none-any.whl -
Subject digest:
c843cf292776ed79d4ada47c4f04e2ac0e858cf2d1503e538e5f74f944c3bc90 - Sigstore transparency entry: 2752492868
- Sigstore integration time:
-
Permalink:
2001-Ankit/pr-review-mind-bot@f12c5a114db5679820d1c94fa5bc11912ab0cc14 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/2001-Ankit
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f12c5a114db5679820d1c94fa5bc11912ab0cc14 -
Trigger Event:
push
-
Statement type: