feat(engine): add opencode CLI review engine
Gadfly review (reusable) / review (pull_request) Successful in 5s
Adversarial Review (Gadfly) / review (pull_request) Successful in 5s
Build & push image / build-and-push (pull_request) Successful in 2m43s

Add a third review harness alongside the in-process majordomo loop and the
claude-code CLI shell-out: the OpenCode CLI (opencode.ai) driving an ollama-cloud
model, selected by an "opencode/<model>" spec. The goal is to benchmark gadfly's
boutique executus harness against a freely-available agentic harness on the SAME
model (e.g. "ollama-cloud/glm-5.2" vs "opencode/glm-5.2").

OpenCode has no --append-system-prompt flag, so the lens system prompt and the
read-only discipline are delivered through a generated config injected via
OPENCODE_CONFIG_CONTENT: a "gadfly" agent whose prompt is the system prompt with
edit/bash denied at both the global and agent level, plus a "gadfly" ollama-cloud
provider. That env var is the highest-precedence config source in the container,
so a reviewed repo's own opencode.json can't re-enable edits on the reviewer.

Spec forms: "opencode/<model>" (wrapped in the generated provider), the
"open-code/" alias, "opencode/<provider>/<model>" pass-through to OpenCode's own
registry, and bare "opencode". Model ids are taken verbatim so colon-bearing
ollama ids (qwen3-coder:480b-cloud) survive. Auth reuses OLLAMA_CLOUD_API_KEY
(mapped to OLLAMA_API_KEY, referenced as {env:OLLAMA_API_KEY} in config, never a
literal secret). Knobs mirror GADFLY_CLAUDE_*: GADFLY_OPENCODE_BIN/MODEL/BASE_URL/
EXTRA_ARGS. openCodeEnv() forwards OLLAMA_API_KEY (the inverse of claudeEnv) but
still withholds the Gitea/findings/Anthropic secrets.

main.go engine selection is now a switch (claude-code / opencode / majordomo), and
the auto-select path uses a type-check instead of a boolean so a shell-out engine
can never hit the *majordomoEngine assertion. auto-select and delegate_investigation
stay majordomo-only and are skipped for opencode (the CLI does its own legwork).

Dockerfile bundles opencode-ai (npm auto-selects its musl build on alpine) with a
best-effort version check + provider pre-warm that never fails the shared image
build. README/examples/CLAUDE.md/scripts updated per the maintenance rules.

Tests: new opencode_test.go mirrors engine_test.go (spec/model/args/config/env-
filter + stub-CLI runtime tests). Verified end-to-end with a fake opencode CLI:
correct argv, injected config, and consolidated markdown output.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-18 01:00:54 -04:00
co-authored by Claude Opus 4.8
parent f468fe6245
commit 5ab4074e9c
12 changed files with 753 additions and 16 deletions
+50
View File
@@ -140,6 +140,55 @@ as an example, not wired or tested here.
> specialist selection and the `delegate_investigation` worker are majordomo-only and are skipped
> with this engine (Claude Code does its own legwork).
### OpenCode engine (`opencode`)
The same shell-out idea, but with a **freely-available** harness: Gadfly can review through the
**[OpenCode](https://opencode.ai) CLI**, which — like Claude Code — brings its own read tools and
verifies findings against the checked-out repo, but drives an **ollama-cloud** model. The point is
to benchmark gadfly's boutique executus harness against a good open harness *on the same model*:
run `ollama-cloud/glm-5.2` (majordomo loop) and `opencode/glm-5.2` (OpenCode) side by side and
compare their findings. This is the wired, no-proxy version of the "alternate backends" comparison
described above. The CLI is bundled in the image (Node + `opencode-ai`).
Select it as a model id:
| Spec | Meaning |
|------|---------|
| `opencode/glm-5.2` | serve `glm-5.2` via ollama-cloud through OpenCode |
| `open-code/glm-5.2` | accepted alias spelling (`opencode` is canonical) |
| `opencode/qwen3-coder:480b-cloud` | model ids are taken **verbatim** — colons are preserved (no `:thinking` suffix here, unlike claude-code) |
| `opencode/<provider>/<model>` | escape hatch: pass `<provider>/<model>` straight to OpenCode's own provider registry/auth (e.g. `opencode/anthropic/claude-sonnet-4-6`) |
| `opencode` | bare: OpenCode's configured default model |
```yaml
GADFLY_MODELS: "ollama-cloud/glm-5.2,opencode/glm-5.2" # the benchmark pairing
```
Auth reuses **`OLLAMA_CLOUD_API_KEY`** (the same secret the ollama-cloud path uses; it's mapped to
`OLLAMA_API_KEY`, which the generated provider references as `{env:OLLAMA_API_KEY}` — never a literal
secret in config). Tuning knobs (all optional):
| Env | Default | Meaning |
|-----|---------|---------|
| `GADFLY_OPENCODE_MODEL` | *(from the spec suffix)* | overrides the model |
| `GADFLY_OPENCODE_BASE_URL` | `https://ollama.com/v1` | ollama-cloud endpoint; point at a local Ollama (`http://localhost:11434/v1`) or any OpenAI-compatible server |
| `GADFLY_OPENCODE_EXTRA_ARGS` | *(unset)* | extra `opencode run` args, **whitespace-split**, appended before the positional task |
| `GADFLY_OPENCODE_BIN` | `opencode` | CLI binary path |
> **Read-only is enforced through config, not a flag.** OpenCode has no `--append-system-prompt`, so
> Gadfly generates a per-lens config — the lens system prompt as a `gadfly` agent's prompt, with
> `edit`/`bash` denied at both the global and agent level — and injects it via `OPENCODE_CONFIG_CONTENT`.
> That env var is the highest-precedence config source in the container, so it **outranks any
> `opencode.json` a reviewed repo ships** — a repo can't re-enable edits on the reviewer. The
> subprocess runs with a **minimal environment** (`OLLAMA_API_KEY` + `PATH`/`HOME`/locale/`OPENCODE_*`/
> `GADFLY_OPENCODE_*`), not the runner's full env; the Gitea token, Anthropic/Claude keys, and findings
> token aren't handed to the CLI.
> **Newly wired, lightly tested.** Like the claude-code engine, `auto` specialist selection and the
> `delegate_investigation` worker are majordomo-only and are skipped here (OpenCode does its own
> legwork). Output capture reads OpenCode's default text output, so treat the engine as new and
> sanity-check a run before trusting a benchmark.
### Endpoint aliases via env vars
For multiple named backends (e.g. a couple of Ollama boxes on your LAN), register them by
@@ -384,6 +433,7 @@ The reviewer binary reads these (the stub/entrypoint set sane defaults):
| `GADFLY_BASE_URL` | — | override endpoint (OpenAI/Ollama-compatible servers) |
| `GADFLY_API_KEY` | — | provider key; falls back to the provider's standard env |
| `claude-code` model id | — | route a model through the bundled Claude Code CLI (`claude-code` / `claude-code/<model>`); see [Claude Code engine](#claude-code-engine-claude-code) for its `GADFLY_CLAUDE_*` knobs |
| `opencode` model id | — | route an ollama-cloud model through the bundled OpenCode CLI (`opencode/<model>`); see [OpenCode engine](#opencode-engine-opencode) for its `GADFLY_OPENCODE_*` knobs |
| `GADFLY_SPECIALISTS` | default suite | csv of lenses, `all`, or `auto` (dynamic selection) |
| `GADFLY_SELECTOR_MODEL` | review model | model that picks lenses in `auto` mode |
| `GADFLY_WORKER_MODEL` | — | cheap model for `delegate_investigation`; unset = no delegation |