ddcf42a3ce
SQLite-backed HTTP store for Gadfly review findings, per-review run timings, and human/Claude grades, with a points-free per-model scoreboard. Pure fact store: it computes no points or rankings (the dashboard maps severity->points client-side and retunes without re-scoring). Findings are content-addressed by location so cross-model reports collapse for consensus; one grade per finding, latest wins. Pure-Go SQLite (CGO-free) + Docker image CI + tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
2.1 KiB
Go
75 lines
2.1 KiB
Go
// Command gadfly-reports is a small, durable store + scoreboard for Gadfly's review
|
|
// findings. Gadfly (and CI) report each model's findings and per-review timing
|
|
// here; a human or Claude later grades each finding (is_real + severity +
|
|
// usefulness). gadfly-reports stores only those RAW facts — it deliberately does NOT
|
|
// compute points or rankings, so the dashboard/client owns the scoring curve
|
|
// (severity -> points, value-per-minute, value-per-token) and can retune it
|
|
// without migrating or re-scoring stored data.
|
|
//
|
|
// Subcommands:
|
|
//
|
|
// gadfly-reports serve [flags] run the HTTP + SQLite store (the long-running daemon)
|
|
//
|
|
// The MCP server Claude calls to record grades lives in ./cmd/mcp, so the daemon
|
|
// stays lean; both are launchable with `go run <module>[/cmd/mcp]@latest`.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
switch os.Args[1] {
|
|
case "serve":
|
|
serveCmd(os.Args[2:])
|
|
default:
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprint(os.Stderr, `gadfly-reports — durable store + scoreboard for Gadfly review findings
|
|
|
|
Usage:
|
|
gadfly-reports serve [flags] run the HTTP + SQLite store
|
|
|
|
Run "gadfly-reports serve -h" for flags.
|
|
`)
|
|
}
|
|
|
|
func serveCmd(args []string) {
|
|
fs := flag.NewFlagSet("serve", flag.ExitOnError)
|
|
addr := fs.String("addr", envOr("GADFLY_REPORTS_ADDR", ":8090"), "listen address")
|
|
dbPath := fs.String("db", envOr("GADFLY_REPORTS_DB", "gadfly-reports.db"), "SQLite database path")
|
|
token := fs.String("token", os.Getenv("GADFLY_REPORTS_TOKEN"), "bearer token callers must present (empty = open)")
|
|
_ = fs.Parse(args)
|
|
|
|
store, err := Open(*dbPath)
|
|
if err != nil {
|
|
log.Fatalf("gadfly-reports: %v", err)
|
|
}
|
|
defer store.Close()
|
|
|
|
log.Printf("gadfly-reports: serving %s (db=%s, auth=%v)", *addr, *dbPath, *token != "")
|
|
srv := &http.Server{Addr: *addr, Handler: newServer(store, *token)}
|
|
if err := srv.ListenAndServe(); err != nil {
|
|
log.Fatalf("gadfly-reports: %v", err)
|
|
}
|
|
}
|
|
|
|
func envOr(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|