c0d0152a34
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>
102 lines
3.1 KiB
Go
102 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
llm "gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake"
|
|
)
|
|
|
|
func TestShouldRecheck(t *testing.T) {
|
|
t.Setenv("GADFLY_RECHECK", "") // default on
|
|
|
|
if shouldRecheck("VERDICT: Blocking issues found\n- something is wrong") != true {
|
|
t.Error("a draft with findings should be rechecked")
|
|
}
|
|
if shouldRecheck("No material issues found.") != false {
|
|
t.Error("a clean draft should skip recheck")
|
|
}
|
|
if shouldRecheck("### review\n\nNo material issues found.\n") != false {
|
|
t.Error("clean draft detection should be case/whitespace tolerant")
|
|
}
|
|
|
|
// Explicit disable wins even when there are findings.
|
|
t.Setenv("GADFLY_RECHECK", "0")
|
|
if shouldRecheck("Blocking issues found\n- x") != false {
|
|
t.Error("GADFLY_RECHECK=0 must disable recheck")
|
|
}
|
|
t.Setenv("GADFLY_RECHECK", "false")
|
|
if shouldRecheck("Blocking issues found\n- x") != false {
|
|
t.Error("GADFLY_RECHECK=false must disable recheck")
|
|
}
|
|
}
|
|
|
|
func TestRecheckEnabled(t *testing.T) {
|
|
for _, v := range []string{"", "1", "true", "yes", "anything"} {
|
|
t.Setenv("GADFLY_RECHECK", v)
|
|
if !recheckEnabled() {
|
|
t.Errorf("GADFLY_RECHECK=%q should be enabled", v)
|
|
}
|
|
}
|
|
for _, v := range []string{"0", "false", "no", "off", "OFF", " False "} {
|
|
t.Setenv("GADFLY_RECHECK", v)
|
|
if recheckEnabled() {
|
|
t.Errorf("GADFLY_RECHECK=%q should be disabled", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildRecheckTask(t *testing.T) {
|
|
t.Setenv("GADFLY_MAX_DIFF_CHARS", "")
|
|
draft := "VERDICT: Blocking issues found\n- foo.go:1 broken"
|
|
out := buildRecheckTask(draft, "diff --git a/x b/x\n+y\n")
|
|
if !strings.Contains(out, draft) {
|
|
t.Error("recheck task must include the draft review")
|
|
}
|
|
if !strings.Contains(out, "Verify") || !strings.Contains(out, "drop every finding you cannot confirm") {
|
|
t.Errorf("recheck task missing the verify instruction:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "diff --git") {
|
|
t.Error("recheck task should include the diff")
|
|
}
|
|
}
|
|
|
|
// fakeModel builds a fake majordomo model that always replies with the given
|
|
// text (no tool calls), so the agent loop ends on its first step.
|
|
func fakeModel(t *testing.T, reply string) llm.Model {
|
|
t.Helper()
|
|
p := fake.New("fake", fake.WithDefault(func(string, llm.Request) fake.Step {
|
|
return fake.Reply(reply)
|
|
}))
|
|
m, err := p.Model("mock")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestRunAgent_ReturnsOutput(t *testing.T) {
|
|
fs, err := newRepoFS(t.TempDir(), "diff")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mdl := fakeModel(t, " corrected review: No material issues found. ")
|
|
out, err := runAgent(context.Background(), mdl, fs, "sys", "task", 4)
|
|
if err != nil {
|
|
t.Fatalf("runAgent: %v", err)
|
|
}
|
|
if out != "corrected review: No material issues found." {
|
|
t.Errorf("runAgent should return trimmed model output, got %q", out)
|
|
}
|
|
}
|
|
|
|
func TestRunAgent_EmptyIsError(t *testing.T) {
|
|
fs, _ := newRepoFS(t.TempDir(), "diff")
|
|
mdl := fakeModel(t, " ")
|
|
if _, err := runAgent(context.Background(), mdl, fs, "sys", "task", 4); err == nil {
|
|
t.Error("runAgent should error on empty model output")
|
|
}
|
|
}
|