package exec import ( "errors" "strings" ) // Result is the structured JSON output from every supervisor invocation. // The JSON schema constant is passed to claude via --json-schema so Claude // validates its own output before returning. type Result struct { Status string `json:"status"` // pass | fail | error Phase string `json:"phase"` // red | green | refactor | retrospective | review | debug | spec | trainer Skill string `json:"skill"` // tdd | review | ... FilePath string `json:"file_path"` // absolute path to generated file RunnerOutput string `json:"runner_output"` // raw stdout+stderr from test runner Verified bool `json:"verified"` // based on exit code, never self-report ModelUsed string `json:"model_used"` // model name or "self" Message string `json:"message"` // one sentence summary Attempts []AttemptRecord `json:"attempts,omitempty"` // populated by orchestrator, not Claude } var validStatuses = map[string]bool{"pass": true, "fail": true, "error": true} var validPhases = map[string]bool{ "red": true, "green": true, "refactor": true, "retrospective": true, "review": true, "debug": true, "spec": true, "trainer": true, } func (r Result) Validate() error { var errs []string if !validStatuses[r.Status] { errs = append(errs, "status must be pass|fail|error, got: "+r.Status) } if !validPhases[r.Phase] { errs = append(errs, "phase must be one of red|green|refactor|retrospective|review|debug|spec|trainer, got: "+r.Phase) } if r.Skill == "" { errs = append(errs, "skill is required") } if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } // Schema is passed to claude --json-schema to enforce structured output. const Schema = `{ "type": "object", "required": ["status","phase","skill","file_path","runner_output","verified","model_used","message"], "properties": { "status": {"type": "string", "enum": ["pass","fail","error"]}, "phase": {"type": "string"}, "skill": {"type": "string"}, "file_path": {"type": "string"}, "runner_output": {"type": "string"}, "verified": {"type": "boolean"}, "model_used": {"type": "string"}, "message": {"type": "string"} } }`