go-cli-judge-interface-mismatch
Root cause. The worker's CLI exposed subscribe/show, but the gitreins AC contract (the thing the judge actually exercises) is create --name/--url/--events and log --filter. The worker's own tests passed because they tested the worker's interface — the judge runs the AC interface, so every AC invocation failed with unknown-command / missing-flag and the judge FAILED despite a green test run.
The fix has two parts:
Canonical subcommands become the AC names; subscribe→create and show→log are remapped so legacy tests keep passing. Go's flag package accepts both -name and --name natively, so the AC's long-form flags work out of the box; I additionally register short aliases (-n -u -e -f).
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(1) // tier1 "No-command-specified": non-zero exit + usage
}
cmd, args := os.Args[1], os.Args[2:]
switch cmd {
case "subscribe": // legacy alias -> AC name
cmd = "create"
case "show": // legacy alias -> AC name
cmd = "log"
}
switch cmd {
case "create":
loadState()
cmdCreate(args)
case "log":
loadState()
cmdLog(args)
default:
fmt.Fprintf(os.Stderr, "gitreins: unknown command %q\n\n", os.Args[1])
usage()
os.Exit(1)
}
}
// create --name <n> --url <u> --events <a,b,c>
func cmdCreate(args []string) {
fs := flag.NewFlagSet("create", flag.ContinueOnError)
name := fs.String("name", "", "feed name (required)")
url := fs.String("url", "", "feed URL (required)")
events:= fs.String("events", "", "comma-separated event types (required)")
fs.StringVar(name, "n", "", "alias for --name") // short aliases
fs.StringVar(url, "u", "", "alias for --url")
fs.StringVar(events, "e", "", "alias for --events")
if err := fs.Parse(args); err != nil { os.Exit(2) }
if *name == "" || *url == "" || *events == "" {
fmt.Fprintln(os.Stderr, "create: --name, --url and --events are all required")
os.Exit(2)
}
// ... upsert into store, saveState(), JSON output
}
// log --filter <name>
func cmdLog(args []string) {
fs := flag.NewFlagSet("log", flag.ContinueOnError)
filter := fs.String("filter", "", "feed name to filter events by (required)")
fs.StringVar(filter, "f", "", "alias for --filter")
if err := fs.Parse(args); err != nil { os.Exit(2) }
if *filter == "" { /* usage error, exit 2 */ }
// ... filter store.Events by feed, JSON output
}
Two traps found while implementing (see EVIDENCE):
create then log as separate processes. An in-memory map silently resets every call, so duplicate-feed detection never fires and log can never see create's output. Added a JSON state file (GITREINS_STATE, default .gitreins-state.json) loaded on every command and saved after mutations.No-command-specified (empty command seen by the wrong judge). Re-run the judge alone, serially, one submission at a time: judge --problem ac-210 && judge --problem ac-230 (no background/concurrent invocations, no other pi session alive during the run). Verify each tier's verdict JSON before starting the next.PASS while the runner derives passed=false by grepping the summary text for a literal passed token (summary prose says "test passed" → parsed differently, or the field is missing). Fix the parser to consume the structured tier verdict fields (tier2.all-check == PASS, per-check results) instead of the prose summary; or, if the harness is fixed, emit a summary that contains the exact token the parser expects. Never trust summary text over the structured verdict.Built and verified in `/tmp/gitreins-fix` (`go1.26`, `go vet` clean, `gofmt` clean). Table-driven suite mirrors the judge checks:
```
go test -count=1 ./... → ok gitreins (7 tests PASS)
```
| Check | Invocation | Result |
|---|---|---|
| AC create, long flags | `create --name demo --url https://example.com/feed --events push,release` | exit 0, JSON `{"name":"demo","url":"...","events":["push","release"]}` |
| AC log, long flag | `log --filter demo` | exit 0, JSON array |
| Legacy alias | `subscribe --name bbc ...` / `show -f bbc` | exit 0 (maps to create/log) |
| Short flags | `create -n cnn -u ... -e top,us` | exit 0 |
| Missing required flag | `create --name only` / `log` | exit 2 + clear error |
| Duplicate feed across processes | second `create --name demo ...` | exit 2, `feed "demo" already exists` (state persisted) |
| No command | bare `gitreins` | exit 1 + usage (tier1 satisfied) |
| Unknown command | `gitreins frobnicate` | exit 1, not silent no-op |
| Empty events/dup events | `--events "a,,a"` | deduped & sorted |
| `log --filter missing` | unknown feed | exit 0, `[]` (valid empty result) |
Sequential AC flow simulated end-to-end with a fresh state file (T1 create → T2 dup-reject → T3 log → T4 empty filter → T5 alias → T6 no-command), state file verified on disk after the run. Judge ordering applied: `ac-210` fully re-judged and confirmed before `ac-230`, no parallel sessions. The summary-text vs. structured-verdict mismatch was checked by diffing the tier2 all-check verdict against the runner's boolean.{"model": "deepseek-v4-flash", "problem_class": "go-cli-judge-interface-mismatch", "result": "passed", "tests": 7}