feat(engine): add opencode CLI review engine #25

Closed
steve wants to merge 0 commits from feat/opencode-engine into main
Owner

What

Adds 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 point is to benchmark gadfly's boutique executus harness against a freely-available agentic harness on the same model — run ollama-cloud/glm-5.2 (majordomo loop) and opencode/glm-5.2 (OpenCode) side by side and compare findings. This is the wired, no-proxy version of the README's existing "alternate backends" idea.

How it works

  • Read-only via config, not a flag. OpenCode has no --append-system-prompt, so each pass generates an OpenCode config — the lens system prompt as a gadfly agent's prompt with edit/bash denied at both global and agent level, plus a gadfly ollama-cloud provider — and injects it via OPENCODE_CONFIG_CONTENT. 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/auth, and bare opencode. Model ids are taken verbatim, so colon-bearing ollama ids like qwen3-coder:480b-cloud survive (no :thinking suffix like claude-code).
  • 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); the auto-select path uses a type-check instead of a boolean so a shell-out engine can never hit the *majordomoEngine assertion. auto selection and delegate_investigation stay majordomo-only and are skipped for opencode (the CLI does its own legwork).

Files

  • cmd/gadfly/opencode.go (new) — the engine.
  • cmd/gadfly/opencode_test.go (new) — 12 tests mirroring engine_test.go.
  • cmd/gadfly/main.go, engine.go — engine selection switch + doc.
  • 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.md, examples/opencode.yml (new), examples/README.md, CLAUDE.md, scripts/run.sh, entrypoint.sh, .gitea/workflows/review-reusable.yml — docs/comments per the maintenance rules.

Verification

  • go build, go test ./..., go vet, gofmt all clean.
  • Ran the actual built binary end-to-end with a fake opencode CLI: confirmed main.go routes opencode/… to the new engine, emits the correct argv (run --agent gadfly --model gadfly/qwen3-coder:480b-cloud … <task-last>), injects the expected config (env-ref API key, both-level deny, real system prompt), and consolidates output into review markdown.

Not yet verified (needs the built image / a real run)

  • Docker image build — needs registry secrets. npm publishes musl builds of opencode-ai (opencode-linux-x64-musl, etc.) that it auto-selects on alpine, so the musl risk is small, but confirm with docker run --entrypoint opencode <img> --version.
  • A real-model run — that OpenCode's --agent + deny behaves non-interactively as expected and its default text output captures cleanly. Documented honestly as "newly wired, lightly tested" in the README.

To put it on the live fleet: add an opencode/<model> entry to the GADFLY_DEFAULT_MODELS user-scope var (runtime retune, no repo change), pinning consumers to the fresh :sha-<short> since :latest is runner-cached.

🤖 Generated with Claude Code

## What Adds a third review harness alongside the in-process majordomo loop and the claude-code CLI shell-out: the **OpenCode CLI** ([opencode.ai](https://opencode.ai)) driving an **ollama-cloud** model, selected by an `opencode/<model>` spec. The point is to **benchmark gadfly's boutique executus harness against a freely-available agentic harness on the same model** — run `ollama-cloud/glm-5.2` (majordomo loop) and `opencode/glm-5.2` (OpenCode) side by side and compare findings. This is the wired, no-proxy version of the README's existing "alternate backends" idea. ## How it works - **Read-only via config, not a flag.** OpenCode has no `--append-system-prompt`, so each pass generates an OpenCode config — the lens system prompt as a `gadfly` agent's prompt with `edit`/`bash` denied at both global and agent level, plus a `gadfly` ollama-cloud provider — and injects it via `OPENCODE_CONFIG_CONTENT`. 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/auth, and bare `opencode`. Model ids are taken **verbatim**, so colon-bearing ollama ids like `qwen3-coder:480b-cloud` survive (no `:thinking` suffix like claude-code). - **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); the auto-select path uses a type-check instead of a boolean so a shell-out engine can never hit the `*majordomoEngine` assertion. `auto` selection and `delegate_investigation` stay majordomo-only and are skipped for opencode (the CLI does its own legwork). ## Files - **`cmd/gadfly/opencode.go`** (new) — the engine. - **`cmd/gadfly/opencode_test.go`** (new) — 12 tests mirroring `engine_test.go`. - **`cmd/gadfly/main.go`**, **`engine.go`** — engine selection switch + doc. - **`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.md`**, **`examples/opencode.yml`** (new), **`examples/README.md`**, **`CLAUDE.md`**, **`scripts/run.sh`**, **`entrypoint.sh`**, **`.gitea/workflows/review-reusable.yml`** — docs/comments per the maintenance rules. ## Verification - `go build`, `go test ./...`, `go vet`, `gofmt` all clean. - Ran the **actual built binary** end-to-end with a fake `opencode` CLI: confirmed `main.go` routes `opencode/…` to the new engine, emits the correct argv (`run --agent gadfly --model gadfly/qwen3-coder:480b-cloud … <task-last>`), injects the expected config (env-ref API key, both-level deny, real system prompt), and consolidates output into review markdown. ### Not yet verified (needs the built image / a real run) - **Docker image build** — needs registry secrets. npm publishes musl builds of `opencode-ai` (`opencode-linux-x64-musl`, etc.) that it auto-selects on alpine, so the musl risk is small, but confirm with `docker run --entrypoint opencode <img> --version`. - **A real-model run** — that OpenCode's `--agent` + deny behaves non-interactively as expected and its default text output captures cleanly. Documented honestly as "newly wired, lightly tested" in the README. To put it on the live fleet: add an `opencode/<model>` entry to the `GADFLY_DEFAULT_MODELS` user-scope var (runtime retune, no repo change), pinning consumers to the fresh `:sha-<short>` since `:latest` is runner-cached. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
steve added 1 commit 2026-07-18 05:01:27 +00:00
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
5ab4074e9c
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]>
steve added 1 commit 2026-07-18 05:08:06 +00:00
fix(dogfood): thread dispatch pr_number into gadfly's own review caller
Gadfly review (reusable) / review (pull_request) Successful in 1s
Adversarial Review (Gadfly) / review (pull_request) Successful in 1s
Build & push image / build-and-push (pull_request) Successful in 2m13s
3973e469a8
gadfly's self-review adversarial-review.yml calls review-reusable.yml but was
never updated for the Gitea >= 1.27 breaking change (go-gitea#37478): a called
workflow no longer receives the caller's workflow_dispatch inputs in
github.event, so a manual "review PR #N" dispatch reached the reusable with an
empty PR. Commit 64d34bd added the pr_number workflow_call input and mort's
caller threads it; this brings gadfly's own caller in line so the dogfood path
works on the same Gitea version.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
steve closed this pull request 2026-07-18 05:08:34 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/gadfly#25