package ai import ( "encoding/json" "errors" "fmt" "strings" ) // Verdict is the drift judge's call on a single window. type Verdict struct { OnTask bool Reason string } // ErrInvalidVerdict marks a parseable-but-unusable judge response. var ErrInvalidVerdict = errors.New("ai: invalid verdict") type rawVerdict struct { OnTask bool `json:"on_task"` Reason string `json:"reason"` } // parseVerdict extracts and validates a Verdict from raw CLI output. It reuses // extractJSON and the shared empty/no-JSON sentinels. A drifting verdict with no // reason is unusable for the UI and is rejected as ErrInvalidVerdict. func parseVerdict(s string) (Verdict, error) { if strings.TrimSpace(s) == "" { return Verdict{}, ErrEmptyResponse } if strings.IndexByte(s, '{') < 0 { return Verdict{}, ErrNoJSON } jsonStr, err := extractJSON(s) if err != nil { return Verdict{}, fmt.Errorf("%w: %v", ErrInvalidVerdict, err) } var raw rawVerdict if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil { return Verdict{}, fmt.Errorf("%w: %v", ErrInvalidVerdict, err) } v := Verdict{OnTask: raw.OnTask, Reason: strings.TrimSpace(raw.Reason)} if !v.OnTask && v.Reason == "" { return Verdict{}, ErrInvalidVerdict } return v, nil }