🪰 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 to .gitea/workflows/adversarial-review.yml in your repo.
  2. Add repo config:
    • secret OLLAMA_CLOUD_API_KEY — your Ollama Cloud 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

go build ./cmd/gadfly      # needs read access to the private majordomo module
go test ./...

License

MIT — see LICENSE.

S
Description
🪰 Agentic adversarial code reviewer for Gitea Actions — hunts real bugs across a swarm of specialist lenses, verifies each against the checked-out repo, and posts one advisory PR comment. Provider-agnostic via majordomo. Advisory only, never blocks a merge.
Readme MIT 1.1 MiB
Languages
Go 84.5%
Shell 14.8%
Dockerfile 0.7%