package main import ( "context" "flag" "fmt" "io" "os" "strings" "gitea.stevedudenhoeffer.com/steve/executus/config" "gitea.stevedudenhoeffer.com/steve/executus/fanout" "gitea.stevedudenhoeffer.com/steve/executus/model" ) // DefaultLenses is the canary's review suite (mirrors gadfly's default). var DefaultLenses = []Lens{ {Name: "security", Focus: "auth, injection, secret leakage, unsafe deserialization, SSRF."}, {Name: "correctness", Focus: "logic errors, broken invariants, off-by-one, contract violations."}, {Name: "error-handling", Focus: "swallowed errors, missing timeouts, races, unhandled edge cases."}, } // Reviewer is configured entirely from the environment (the GADFLY_*-style light // host): REVIEWER_MODELS (csv of tier/spec), REVIEWER_MODEL_TIER_ overrides, // REVIEWER_MAX_CONCURRENT, REVIEWER_PROVIDER_CONCURRENCY. The diff is read from // -diff or stdin. // // REVIEWER_MODELS=fast,thinking ANTHROPIC_API_KEY=... go run ./examples/reviewer < my.diff func main() { cfg := config.Env("REVIEWER_") // Tier table from env, with code defaults. model.Configure(cfg, map[string]string{ "fast": "anthropic/claude-haiku-4-5", "thinking": "anthropic/claude-opus-4-8", }, 0) fleet := splitCSV(cfg.String("models", "fast")) maxConc := cfg.Int("max_concurrent", 6) perProvider := cfg.Int("provider_concurrency", 3) diffFlag := flag.String("diff", "", "diff text to review; reads stdin when empty") flag.Parse() diff := *diffFlag if strings.TrimSpace(diff) == "" { // Guard against blocking forever on an interactive TTY (no piped input). if fi, _ := os.Stdin.Stat(); fi != nil && fi.Mode()&os.ModeCharDevice != 0 { fmt.Fprintln(os.Stderr, "reviewer: no diff (pass -diff or pipe one on stdin)") os.Exit(2) } b, err := io.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "reviewer: reading stdin: %v\n", err) os.Exit(2) } diff = string(b) } if strings.TrimSpace(diff) == "" { fmt.Fprintln(os.Stderr, "reviewer: no diff (pass -diff or pipe one on stdin)") os.Exit(2) } ctx := context.Background() var models []NamedModel for _, spec := range fleet { _, m, err := model.ParseModelForContext(ctx, spec) if err != nil { fmt.Fprintf(os.Stderr, "reviewer: resolve model %q: %v\n", spec, err) os.Exit(1) } models = append(models, NamedModel{Name: spec, Provider: providerOf(spec), Model: m}) } results := Review(ctx, models, DefaultLenses, diff, fanout.Options[cell]{ MaxConcurrent: maxConc, PerKey: perKeyCaps(models, perProvider), }) fmt.Print(Consolidate(results)) } func splitCSV(s string) []string { var out []string for _, p := range strings.Split(s, ",") { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out } // providerOf returns a model spec's provider (the first path segment, e.g. // "anthropic/claude-…" → "anthropic"; a bare tier name → itself). func providerOf(spec string) string { if i := strings.IndexByte(spec, '/'); i > 0 { return spec[:i] } return spec // bare tier name → its own bucket (don't collapse distinct tiers) } // perKeyCaps builds the PerKey map: each distinct provider capped at perProvider. func perKeyCaps(models []NamedModel, perProvider int) map[string]int { if perProvider <= 0 { return nil } caps := map[string]int{} for _, m := range models { caps[m.Provider] = perProvider } return caps }