From d74debf170e9bc8357813ccb22745666bfa627bd Mon Sep 17 00:00:00 2001 From: Joah Gerstenberg <2164095+joahg@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:16:07 -0500 Subject: [PATCH 1/4] Publish the ReviewBench image to GHCR Build the linux/amd64 image on tag pushes or manual runs, push it to ghcr.io/block/review, and print the immutable digest in the job summary for registering with ReviewBench. --- .github/workflows/publish-image.yml | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/publish-image.yml diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 0000000..671d7e2 --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,32 @@ +name: Publish image + +on: + push: + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: build + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ github.ref_name }} + ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + - run: echo "ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY" From de75db908e0966618e24f0b583d6d9a249afa5a4 Mon Sep 17 00:00:00 2001 From: Joah Gerstenberg <2164095+joahg@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:16:07 -0500 Subject: [PATCH 2/4] Move the module path to github.com/block/review The repository was renamed, so go install and imports must use the new path. --- README.md | 2 +- cmd/review-panel/main.go | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea6f259..e292f85 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ If a panelist fails, the coordinator covers its lens and the review is marked de ## Usage ```sh -go install github.com/block/review-panel/cmd/review-panel@latest +go install github.com/block/review/cmd/review-panel@latest # Review the current branch against main with Goose's configured provider and model. review-panel review --base main diff --git a/cmd/review-panel/main.go b/cmd/review-panel/main.go index 0e66562..9149799 100644 --- a/cmd/review-panel/main.go +++ b/cmd/review-panel/main.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/block/review-panel/review" + "github.com/block/review/review" ) func main() { diff --git a/go.mod b/go.mod index 6f7b100..9637212 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/block/review-panel +module github.com/block/review go 1.26 From 45a200561aaebc8a89f870255f996a69582a3c7c Mon Sep 17 00:00:00 2001 From: Joah Gerstenberg <2164095+joahg@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:21:48 -0500 Subject: [PATCH 3/4] Rename review-panel to review Match the repository name: the command is now cmd/review, the binary and default ReviewBench agent name are review, and environment overrides use the REVIEW_ prefix. Add CI that checks formatting, vets, tests, and builds the image on every pull request. --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ Dockerfile | 8 ++++---- README.md | 12 ++++++------ cmd/{review-panel => review}/main.go | 28 ++++++++++++++-------------- review/goose.go | 2 +- 5 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/ci.yml rename cmd/{review-panel => review}/main.go (84%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..df2439d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + - run: test -z "$(gofmt -l .)" + - run: go vet ./... + - run: go test ./... + image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - run: docker build --platform linux/amd64 -t review:ci . + - run: docker run --rm --entrypoint goose review:ci --version diff --git a/Dockerfile b/Dockerfile index 012dc8f..604c6da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# review-panel under the ReviewBench agent contract: +# review under the ReviewBench agent contract: # https://github.com/review-bench/ReviewBench/blob/main/AGENT_CONTRACT.md # # Credentials come from the run, never the image: OPENAI_API_KEY for the @@ -9,7 +9,7 @@ FROM golang:1.26 AS build WORKDIR /src COPY . . -RUN CGO_ENABLED=0 go build -trimpath -o /review-panel ./cmd/review-panel +RUN CGO_ENABLED=0 go build -trimpath -o /review ./cmd/review FROM debian:bookworm-slim ARG GOOSE_VERSION=1.43.0 @@ -21,5 +21,5 @@ RUN apt-get update \ && tar -xzf /tmp/goose.tgz -C /usr/local/bin ./goose \ && rm /tmp/goose.tgz \ && apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* -COPY --from=build /review-panel /usr/local/bin/review-panel -ENTRYPOINT ["review-panel", "reviewbench"] +COPY --from=build /review /usr/local/bin/review +ENTRYPOINT ["review", "reviewbench"] diff --git a/README.md b/README.md index e292f85..bad9844 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# review-panel +# review An LLM code reviewer for git changes, built on [Goose](https://github.com/aaif-goose/goose). Every review runs the same pipeline: @@ -25,13 +25,13 @@ If a panelist fails, the coordinator covers its lens and the review is marked de ## Usage ```sh -go install github.com/block/review/cmd/review-panel@latest +go install github.com/block/review/cmd/review@latest # Review the current branch against main with Goose's configured provider and model. -review-panel review --base main +review review --base main # A different model per pass, every finding regardless of the gate, as JSON. -review-panel review --base main --provider openai \ +review review --base main --provider openai \ --role-models behavior_state_data=model-a,failure_concurrency_lifecycle=model-b,security_contracts=model-c,coordinator=model-a,judge=model-b \ --role-efforts behavior_state_data=medium,coordinator=medium \ --all --json @@ -62,8 +62,8 @@ res, err := review.Run(ctx, review.Config{ The image implements the [ReviewBench agent contract](https://github.com/review-bench/ReviewBench/blob/main/AGENT_CONTRACT.md): ```sh -docker build --platform linux/amd64 -t review-panel . -scripts/try-agent.sh review-panel --pr 0 -e OPENAI_API_KEY # from a ReviewBench checkout +docker build --platform linux/amd64 -t review . +scripts/try-agent.sh review --pr 0 -e OPENAI_API_KEY # from a ReviewBench checkout ``` | Setting | Values | Default | diff --git a/cmd/review-panel/main.go b/cmd/review/main.go similarity index 84% rename from cmd/review-panel/main.go rename to cmd/review/main.go index 9149799..f33954f 100644 --- a/cmd/review-panel/main.go +++ b/cmd/review/main.go @@ -1,7 +1,7 @@ -// Command review-panel reviews a git revision range with a Goose review panel. +// Command review reviews a git revision range with a Goose review panel. // -// review-panel review --base main review HEAD against main -// review-panel reviewbench run under the ReviewBench agent contract +// review review --base main review HEAD against main +// review reviewbench run under the ReviewBench agent contract package main import ( @@ -34,13 +34,13 @@ func main() { usage() } if err != nil { - fmt.Fprintln(os.Stderr, "review-panel:", err) + fmt.Fprintln(os.Stderr, "review:", err) os.Exit(1) } } func usage() { - fmt.Fprintln(os.Stderr, "usage: review-panel review --base [flags] | review-panel reviewbench") + fmt.Fprintln(os.Stderr, "usage: review review --base [flags] | review reviewbench") os.Exit(2) } @@ -87,7 +87,7 @@ func reviewCmd(ctx context.Context, args []string) error { } func logPass(p review.PassReport) { - msg := fmt.Sprintf("review-panel: %s %s in %s", p.Role, p.Status, p.Duration.Round(time.Second)) + msg := fmt.Sprintf("review: %s %s in %s", p.Role, p.Status, p.Duration.Round(time.Second)) if p.Error != "" { msg += ": " + firstLine(p.Error) } @@ -110,7 +110,7 @@ func reviewBenchCmd(ctx context.Context) error { return fmt.Errorf("missing %s", k) } } - agent := orDefault(env("RB_AGENT"), "review-panel") + agent := orDefault(env("RB_AGENT"), "review") repo := orDefault(env("RB_REPO"), "/work/repo") intent := "" if raw, err := os.ReadFile(orDefault(env("RB_PR_JSON"), "/work/pr/pr.json")); err == nil { @@ -121,11 +121,11 @@ func reviewBenchCmd(ctx context.Context) error { } provider := orDefault(env("RB_CONFIG_PROVIDER"), "openai") goose := review.GooseConfig{ - Bin: env("REVIEW_PANEL_GOOSE_BIN"), + Bin: env("REVIEW_GOOSE_BIN"), Provider: provider, Model: orDefault(env("RB_CONFIG_MODEL"), "gpt-5.6-sol"), Effort: orDefault(env("RB_CONFIG_EFFORT"), "high"), - StateDir: env("REVIEW_PANEL_STATE_DIR"), + StateDir: env("REVIEW_STATE_DIR"), } if provider == "openai" { baseURL := orDefault(env("RB_MODEL_BASE_URL"), "https://api.openai.com/v1") @@ -143,8 +143,8 @@ func reviewBenchCmd(ctx context.Context) error { JudgeReserve: 2*time.Minute + 30*time.Second, } for name, d := range map[string]*time.Duration{ - "REVIEW_PANEL_PANELIST_TIMEOUT": &cfg.PanelistTimeout, "REVIEW_PANEL_COORDINATOR_TIMEOUT": &cfg.CoordinatorTimeout, - "REVIEW_PANEL_JUDGE_TIMEOUT": &cfg.JudgeTimeout, "REVIEW_PANEL_BUDGET": &cfg.Budget, "REVIEW_PANEL_JUDGE_RESERVE": &cfg.JudgeReserve, + "REVIEW_PANELIST_TIMEOUT": &cfg.PanelistTimeout, "REVIEW_COORDINATOR_TIMEOUT": &cfg.CoordinatorTimeout, + "REVIEW_JUDGE_TIMEOUT": &cfg.JudgeTimeout, "REVIEW_BUDGET": &cfg.Budget, "REVIEW_JUDGE_RESERVE": &cfg.JudgeReserve, } { if v := env(name); v != "" { parsed, err := time.ParseDuration(v) @@ -157,11 +157,11 @@ func reviewBenchCmd(ctx context.Context) error { if env("RB_CONFIG_GATE") == "off" { cfg.Gate = review.NoGate } - fmt.Fprintf(os.Stderr, "review-panel: provider=%s model=%s effort=%s gate=%s role_models=%s role_efforts=%s\n", + fmt.Fprintf(os.Stderr, "review: provider=%s model=%s effort=%s gate=%s role_models=%s role_efforts=%s\n", provider, goose.Model, goose.Effort, orDefault(env("RB_CONFIG_GATE"), "on"), env("RB_CONFIG_ROLE_MODELS"), env("RB_CONFIG_ROLE_EFFORTS")) res, err := review.Run(ctx, cfg, review.Options{OnPass: logPass}) - if path := env("REVIEW_PANEL_RESULT"); path != "" && res != nil { + if path := env("REVIEW_RESULT"); path != "" && res != nil { if payload, err := json.MarshalIndent(res, "", " "); err == nil { os.WriteFile(path, payload, 0o644) } @@ -197,7 +197,7 @@ func reviewBenchCmd(ctx context.Context) error { restored++ } } - fmt.Fprintf(os.Stderr, "review-panel: %d finding(s) reported, %d returned (%d restored by the judge), %d dropped by the coordinator\n", + fmt.Fprintf(os.Stderr, "review: %d finding(s) reported, %d returned (%d restored by the judge), %d dropped by the coordinator\n", len(res.Findings), len(res.Ungated), restored, len(res.Dropped)) return os.WriteFile(env("RB_OUT"), payload, 0o644) } diff --git a/review/goose.go b/review/goose.go index 9862792..1debffb 100644 --- a/review/goose.go +++ b/review/goose.go @@ -105,7 +105,7 @@ func (r GooseRunner) Run(ctx context.Context, pass Pass) (string, error) { return "", err } } else { - dir, err := os.MkdirTemp("", "review-panel-goose-") + dir, err := os.MkdirTemp("", "review-goose-") if err != nil { return "", err } From b79b46432594e848448c421878699e2cc278c419 Mon Sep 17 00:00:00 2001 From: Joah Gerstenberg <2164095+joahg@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:24:31 -0500 Subject: [PATCH 4/4] Pass workflow context to the summary step through env Interpolating context values into run scripts is a shell injection risk. --- .github/workflows/publish-image.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 671d7e2..c8e1482 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -29,4 +29,7 @@ jobs: tags: | ghcr.io/${{ github.repository }}:${{ github.ref_name }} ghcr.io/${{ github.repository }}:sha-${{ github.sha }} - - run: echo "ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY" + - env: + REPOSITORY: ${{ github.repository }} + DIGEST: ${{ steps.build.outputs.digest }} + run: echo "ghcr.io/${REPOSITORY}@${DIGEST}" >> "$GITHUB_STEP_SUMMARY"