Gadfly: agentic adversarial PR reviewer (initial extraction)
Standalone, Docker-packaged extraction of the agentic PR reviewer that runs in Gitea Actions: reads the checked-out repo with read-only tools (read_file/grep/ find_files/get_diff), verifies findings before reporting, two-pass review + adversarial recheck, posts one labeled comment per model. Advisory only. - cmd/gadfly: reviewer binary (majordomo + Ollama Cloud), zero deps beyond stdlib + majordomo - entrypoint.sh: container brains — trigger gating, PR clone, model loop (logic out of YAML) - Dockerfile: multi-stage; build-time module token never reaches the final image - .gitea/workflows/build-image.yml: tag v* → build & push image - examples/: ~15-line consumer stub - system prompt genericized + hardened to re-derive constants/formulas (semantic bugs) Vibe-coded with Claude Code; see README disclosure. Advisory, never blocks merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# 🪰 Gadfly
|
||||
|
||||
**An AI gadfly for your pull requests.** Gadfly is an *adversarial* code reviewer that
|
||||
runs in Gitea Actions: on every PR it reads your actual repository, hunts for real
|
||||
problems, verifies them against the code, and posts its findings as a comment. It does not
|
||||
praise your code. A gadfly does not let things slide.
|
||||
|
||||
> ### 🤖 Heads up: this is a vibe-coded project
|
||||
> Gadfly was built almost entirely by an AI agent (Claude Code), prompts and all — the
|
||||
> reviewer's "brain" is a language model, and so was most of the author. It works and it's
|
||||
> tested, but treat it accordingly: **it is advisory only, it never blocks a merge, and you
|
||||
> should still review its reviews.** Issues and PRs welcome; expect the occasional
|
||||
> AI-flavored rough edge.
|
||||
|
||||
## What makes it different
|
||||
|
||||
Most LLM "review my diff" bots read the diff in isolation and hallucinate problems they
|
||||
can't actually see — a "missing import" that's three lines above the hunk, a "broken
|
||||
caller" in a file they never opened. Gadfly is **agentic**: the model has read-only tools
|
||||
over the checked-out repo and is *required* to use them before reporting anything.
|
||||
|
||||
- **Tools:** `read_file`, `list_dir`, `grep`, `find_files`, `get_diff`.
|
||||
- **Verify-before-claiming discipline:** baked into the system prompt — open the file,
|
||||
grep the symbol, or drop the finding.
|
||||
- **Two passes:** a *review* pass drafts findings, then an adversarial *recheck* pass
|
||||
independently re-verifies each one against the code and drops the ones it can't confirm,
|
||||
recomputing the verdict. This is what kills "confident but wrong."
|
||||
- **Semantic-bug hunting:** it's told not to trust a plausible-looking constant, conversion
|
||||
factor, or formula — re-derive the expected value, because that's where real bugs hide.
|
||||
|
||||
Every review leads with a one-line verdict: **No material issues found**, **Minor issues**,
|
||||
or **Blocking issues found**.
|
||||
|
||||
## Turn it on for a repo
|
||||
|
||||
Gadfly ships as a container image, so consuming repos don't build anything — they just run
|
||||
it. Drop one file in your repo and set a couple of secrets/vars:
|
||||
|
||||
1. Copy [`examples/adversarial-review.yml`](examples/adversarial-review.yml) to
|
||||
`.gitea/workflows/adversarial-review.yml` in your repo.
|
||||
2. Add repo config:
|
||||
- **secret** `OLLAMA_CLOUD_API_KEY` — your [Ollama Cloud](https://ollama.com) key (empty
|
||||
⇒ Gadfly posts a harmless "not configured" notice instead of reviewing).
|
||||
- **var** `OLLAMA_REVIEW_MODELS` *(optional)* — comma-separated model ids
|
||||
(default `qwen3-coder:480b-cloud,gpt-oss:120b-cloud`). One comment per model.
|
||||
- **var** `GADFLY_ALLOWED_USERS` *(optional)* — who may re-trigger via comment; empty ⇒
|
||||
any repo collaborator.
|
||||
|
||||
`GITEA_TOKEN` is provided automatically by Actions; comments post as the `gitea-actions`
|
||||
user, scoped to that repo — no bot account needed.
|
||||
|
||||
### Triggers
|
||||
|
||||
1. A **new/reopened/ready** non-draft PR — automatic.
|
||||
2. Commenting **`@gadfly review`** on a PR — re-review on demand (gated to allowed users).
|
||||
3. **workflow_dispatch** — manual, with a `pr_number` input.
|
||||
|
||||
(Pushing new commits does *not* auto-re-review — comment `@gadfly review` after pushing
|
||||
fixes. This keeps usage down.)
|
||||
|
||||
## How it's packaged
|
||||
|
||||
```
|
||||
cmd/gadfly/ the agentic reviewer binary (majordomo + Ollama Cloud); zero deps beyond stdlib + majordomo
|
||||
scripts/run.sh fetches the PR diff, runs the reviewer, upserts one labeled comment
|
||||
scripts/system-prompt.txt the reviewer persona + verification discipline
|
||||
entrypoint.sh the container brains: trigger gating, clone, model loop (logic lives here, not in YAML)
|
||||
Dockerfile multi-stage; the build-time module token never reaches the final image
|
||||
.gitea/workflows/build-image.yml tags v* → build & push the image
|
||||
examples/ the ~15-line stub a consuming repo drops in
|
||||
```
|
||||
|
||||
The image is published to `gitea.stevedudenhoeffer.com/steve/gadfly`. Push a `v*` tag to
|
||||
build and publish a new version (and `:latest`).
|
||||
|
||||
## Configuration (advanced)
|
||||
|
||||
The reviewer binary reads these (the stub/entrypoint set sane defaults):
|
||||
|
||||
| Env | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `OLLAMA_API_KEY` | — | Ollama Cloud bearer key (required for real reviews) |
|
||||
| `GADFLY_MODEL` | — | model id |
|
||||
| `GADFLY_MAX_STEPS` | 24 | review-pass tool-step cap |
|
||||
| `GADFLY_RECHECK` | on | set `0`/`false` to skip the recheck pass |
|
||||
| `GADFLY_RECHECK_MAX_STEPS` | 16 | recheck-pass step cap |
|
||||
| `GADFLY_TIMEOUT_SECS` | 300 | overall deadline (both passes) |
|
||||
| `GADFLY_MAX_DIFF_CHARS` | 60000 | diff chars embedded in the prompt (full diff via `get_diff`) |
|
||||
| `GADFLY_TRIGGER_PHRASE` | `@gadfly review` | comment phrase that re-triggers |
|
||||
| `GADFLY_ALLOWED_USERS` | *(collaborators)* | comma-separated allow-list for comment triggers |
|
||||
|
||||
## Building locally
|
||||
|
||||
```sh
|
||||
go build ./cmd/gadfly # needs read access to the private majordomo module
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user