diff --git a/cmd/roborev/analyze.go b/cmd/roborev/analyze.go index 908a0d1c4..8eba8c731 100644 --- a/cmd/roborev/analyze.go +++ b/cmd/roborev/analyze.go @@ -170,7 +170,7 @@ To fix an existing analysis job, use: roborev fix cmd.Flags().StringVar(&agentName, "agent", "", "agent to use for analysis (default: from config)") cmd.Flags().StringVar(&model, "model", "", "model for analysis agent") - cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, or thorough") + cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, medium, thorough, or maximum") cmd.Flags().BoolVar(&wait, "wait", false, "wait for job to complete and show result") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress output (just enqueue)") cmd.Flags().BoolVar(&listTypes, "list", false, "list available analysis types") diff --git a/cmd/roborev/ci.go b/cmd/roborev/ci.go index 40a9210b1..6cf81d6f1 100644 --- a/cmd/roborev/ci.go +++ b/cmd/roborev/ci.go @@ -83,7 +83,7 @@ Flags override config values. When run inside GitHub ` + cmd.Flags().StringVar(&reviewTypes, "review-types", "", "comma-separated review types (overrides config)") cmd.Flags().StringVar(&reasoning, "reasoning", "", - "reasoning level: thorough, standard, fast") + "reasoning level: fast, standard, medium, thorough, or maximum") cmd.Flags().StringVar(&minSeverity, "min-severity", "", "minimum severity filter: critical, high, medium, low") cmd.Flags().StringVar(&synthesisAgent, "synthesis-agent", "", diff --git a/cmd/roborev/compact.go b/cmd/roborev/compact.go index e89cddc36..3f8f9cf71 100644 --- a/cmd/roborev/compact.go +++ b/cmd/roborev/compact.go @@ -89,7 +89,7 @@ Examples: cmd.Flags().StringVar(&agentName, "agent", "", "agent to use for verification (defaults to fix agent from config)") cmd.Flags().StringVar(&model, "model", "", "model to use") - cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level (fast/standard/thorough)") + cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level (fast/standard/medium/thorough/maximum)") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress progress output") cmd.Flags().BoolVar(&allBranches, "all-branches", false, "include all branches") cmd.Flags().StringVar(&branch, "branch", "", "filter by branch (default: current branch)") diff --git a/cmd/roborev/fix.go b/cmd/roborev/fix.go index 5aa83d650..5bbabb988 100644 --- a/cmd/roborev/fix.go +++ b/cmd/roborev/fix.go @@ -208,7 +208,7 @@ Examples: cmd.Flags().StringVar(&agentName, "agent", "", "agent to use for fixes (default: from config)") cmd.Flags().StringVar(&model, "model", "", "model for agent") - cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, or thorough") + cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, medium, thorough, or maximum") cmd.Flags().StringVar(&minSeverity, "min-severity", "", "minimum finding severity to address: critical, high, medium, or low") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress progress output") cmd.Flags().BoolVar(&open, "open", false, "fix all open completed jobs for the current repo") diff --git a/cmd/roborev/local_review_integration_test.go b/cmd/roborev/local_review_integration_test.go index f20c8668f..44e0f57c1 100644 --- a/cmd/roborev/local_review_integration_test.go +++ b/cmd/roborev/local_review_integration_test.go @@ -17,7 +17,9 @@ func TestLocalReviewReasoningLevels(t *testing.T) { }{ {name: "Fast", reasoning: string(agent.ReasoningFast), expected: "reasoning: " + string(agent.ReasoningFast)}, {name: "Standard", reasoning: string(agent.ReasoningStandard), expected: "reasoning: " + string(agent.ReasoningStandard)}, + {name: "Medium", reasoning: string(agent.ReasoningMedium), expected: "reasoning: " + string(agent.ReasoningMedium)}, {name: "Thorough", reasoning: string(agent.ReasoningThorough), expected: "reasoning: " + string(agent.ReasoningThorough)}, + {name: "Maximum", reasoning: string(agent.ReasoningMaximum), expected: "reasoning: " + string(agent.ReasoningMaximum)}, {name: "Default", reasoning: "", expected: "reasoning: " + string(agent.ReasoningThorough)}, // default (agent defaults) } diff --git a/cmd/roborev/refine.go b/cmd/roborev/refine.go index bba3bda73..a6057cd87 100644 --- a/cmd/roborev/refine.go +++ b/cmd/roborev/refine.go @@ -132,7 +132,7 @@ Use --all-branches to discover and refine all branches with failed reviews.`, cmd.Flags().StringVar(&opts.agentName, "agent", "", "agent to use for addressing findings (default: from config)") cmd.Flags().StringVar(&opts.model, "model", "", "model for agent (format varies: opencode uses provider/model, others use model name)") - cmd.Flags().StringVar(&opts.reasoning, "reasoning", "", "reasoning level: fast, standard (default), or thorough") + cmd.Flags().StringVar(&opts.reasoning, "reasoning", "", "reasoning level: fast, standard (default), medium, thorough, or maximum") cmd.Flags().StringVar(&opts.minSeverity, "min-severity", "", "minimum finding severity to address: critical, high, medium, or low") cmd.Flags().BoolVar(&fast, "fast", false, "shorthand for --reasoning fast") cmd.Flags().IntVar(&opts.maxIterations, "max-iterations", 10, "maximum refinement iterations") diff --git a/cmd/roborev/review.go b/cmd/roborev/review.go index 825cdd95f..fdef2464f 100644 --- a/cmd/roborev/review.go +++ b/cmd/roborev/review.go @@ -344,7 +344,7 @@ Examples: cmd.Flags().StringVar(&sha, "sha", "HEAD", "commit SHA to review (used when no positional args)") cmd.Flags().StringVar(&agent, "agent", "", "agent to use (codex, claude-code, gemini, copilot, opencode, cursor, kiro, kilo, pi)") cmd.Flags().StringVar(&model, "model", "", "model for agent (format varies: opencode uses provider/model, others use model name)") - cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: thorough (default), standard, or fast") + cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, medium, thorough (default), or maximum") cmd.Flags().BoolVar(&fast, "fast", false, "shorthand for --reasoning fast") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress output (for use in hooks)") cmd.Flags().BoolVar(&dirty, "dirty", false, "review uncommitted changes instead of a commit") diff --git a/cmd/roborev/run.go b/cmd/roborev/run.go index 1e17f0140..c2fa64d42 100644 --- a/cmd/roborev/run.go +++ b/cmd/roborev/run.go @@ -68,7 +68,7 @@ Examples: cmd.Flags().StringVar(&agentName, "agent", "", "agent to use (default: from config)") cmd.Flags().StringVar(&model, "model", "", "model for agent (format varies: opencode uses provider/model, others use model name)") - cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, or thorough (default)") + cmd.Flags().StringVar(&reasoning, "reasoning", "", "reasoning level: fast, standard, medium, thorough (default), or maximum") cmd.Flags().BoolVar(&wait, "wait", false, "wait for job to complete and show result") cmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "suppress output (just enqueue)") cmd.Flags().BoolVar(&noContext, "no-context", false, "don't include repository context in prompt") diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 3443f2eec..f0baa36c3 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -15,9 +15,13 @@ import ( type ReasoningLevel string const ( - // ReasoningThorough uses maximum reasoning for deep analysis (slower) + // ReasoningMaximum uses the highest available reasoning (e.g., codex xhigh, claude max) + ReasoningMaximum ReasoningLevel = "maximum" + // ReasoningThorough uses deep reasoning for thorough analysis (slower) ReasoningThorough ReasoningLevel = "thorough" - // ReasoningStandard uses balanced reasoning (default) + // ReasoningMedium uses moderate reasoning (e.g., claude --effort medium) + ReasoningMedium ReasoningLevel = "medium" + // ReasoningStandard uses the model's default reasoning (no effort override) ReasoningStandard ReasoningLevel = "standard" // ReasoningFast uses minimal reasoning for quick responses ReasoningFast ReasoningLevel = "fast" @@ -25,17 +29,21 @@ const ( // ReasoningLevels returns the canonical reasoning level names. func ReasoningLevels() []string { - return []string{string(ReasoningFast), string(ReasoningStandard), string(ReasoningThorough)} + return []string{string(ReasoningFast), string(ReasoningStandard), string(ReasoningMedium), string(ReasoningThorough), string(ReasoningMaximum)} } // ParseReasoningLevel converts a string to ReasoningLevel, defaulting to standard func ParseReasoningLevel(s string) ReasoningLevel { switch s { + case "maximum", "max", "xhigh": + return ReasoningMaximum case "thorough", "high": return ReasoningThorough + case "medium": + return ReasoningMedium case "fast", "low": return ReasoningFast - case "standard", "medium", "": + case "standard", "": return ReasoningStandard default: return ReasoningStandard diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 71734081c..379165106 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -135,12 +135,15 @@ func TestParseReasoningLevel(t *testing.T) { input string want ReasoningLevel }{ + {"maximum", ReasoningMaximum}, + {"max", ReasoningMaximum}, + {"xhigh", ReasoningMaximum}, {"thorough", ReasoningThorough}, {"high", ReasoningThorough}, {"fast", ReasoningFast}, {"low", ReasoningFast}, + {"medium", ReasoningMedium}, {"standard", ReasoningStandard}, - {"medium", ReasoningStandard}, {"", ReasoningStandard}, {"unknown", ReasoningStandard}, } @@ -155,6 +158,7 @@ func TestCodexReasoningEffortMapping(t *testing.T) { level ReasoningLevel want string }{ + {ReasoningMaximum, "xhigh"}, {ReasoningThorough, "high"}, {ReasoningFast, "low"}, {ReasoningStandard, ""}, @@ -168,6 +172,26 @@ func TestCodexReasoningEffortMapping(t *testing.T) { } } +func TestClaudeEffortMapping(t *testing.T) { + tests := []struct { + level ReasoningLevel + want string + }{ + {ReasoningMaximum, "max"}, + {ReasoningThorough, "high"}, + {ReasoningMedium, "medium"}, + {ReasoningFast, "low"}, + {ReasoningStandard, ""}, + } + + for _, tt := range tests { + a := NewClaudeAgent("").WithReasoning(tt.level) + claude, ok := a.(*ClaudeAgent) + require.True(t, ok, "expected ClaudeAgent, got %T", a) + assert.Equal(t, tt.want, claude.claudeEffort(), "claudeEffort(%q)", tt.level) + } +} + type agentTestDef struct { name string factory func(string) Agent @@ -247,6 +271,21 @@ func TestWithModelEmptyPreservesDefault(t *testing.T) { } } +func TestClaudeBuildArgsEffort(t *testing.T) { + a := NewClaudeAgent("").WithModel("opus").WithReasoning(ReasoningMaximum) + cmdLine := a.CommandLine() + + assert.Contains(t, cmdLine, "--effort max") + assert.Contains(t, cmdLine, "--model opus") +} + +func TestClaudeBuildArgsNoEffortForStandard(t *testing.T) { + a := NewClaudeAgent("").WithReasoning(ReasoningStandard) + cmdLine := a.CommandLine() + + assert.NotContains(t, cmdLine, "--effort") +} + func TestAgentBuildArgsWithModel(t *testing.T) { for _, tt := range agentFixtures { t.Run(tt.name+" with explicit model", func(t *testing.T) { diff --git a/internal/agent/claude.go b/internal/agent/claude.go index f5474d194..4c374a12d 100644 --- a/internal/agent/claude.go +++ b/internal/agent/claude.go @@ -18,14 +18,16 @@ import ( type ClaudeAgent struct { Command string // The claude command to run (default: "claude") Model string // Model to use (e.g., "opus", "sonnet", or full name) - Reasoning ReasoningLevel // Reasoning level (for future extended thinking support) + Reasoning ReasoningLevel // Reasoning level mapped to --effort flag Agentic bool // Whether agentic mode is enabled (allow file edits) SessionID string // Existing session ID to resume } const claudeDangerousFlag = "--dangerously-skip-permissions" +const claudeEffortFlag = "--effort" var claudeDangerousSupport sync.Map +var claudeEffortSupport sync.Map // NewClaudeAgent creates a new Claude Code agent func NewClaudeAgent(command string) *ClaudeAgent { @@ -35,7 +37,7 @@ func NewClaudeAgent(command string) *ClaudeAgent { return &ClaudeAgent{Command: command, Reasoning: ReasoningStandard} } -// WithReasoning returns a copy of the agent with the model preserved (reasoning not yet supported). +// WithReasoning returns a copy of the agent with the specified reasoning level. func (a *ClaudeAgent) WithReasoning(level ReasoningLevel) Agent { return &ClaudeAgent{ Command: a.Command, @@ -82,6 +84,22 @@ func (a *ClaudeAgent) WithSessionID(sessionID string) Agent { } } +// claudeEffort maps ReasoningLevel to Claude Code's --effort flag values +func (a *ClaudeAgent) claudeEffort() string { + switch a.Reasoning { + case ReasoningMaximum: + return "max" + case ReasoningThorough: + return "high" + case ReasoningMedium: + return "medium" + case ReasoningFast: + return "low" + default: + return "" // use claude default (standard = no override) + } +} + func (a *ClaudeAgent) Name() string { return "claude-code" } @@ -92,11 +110,11 @@ func (a *ClaudeAgent) CommandName() string { func (a *ClaudeAgent) CommandLine() string { agenticMode := a.Agentic || AllowUnsafeAgents() - args := a.buildArgs(agenticMode) + args := a.buildArgs(agenticMode, true) return a.Command + " " + strings.Join(args, " ") } -func (a *ClaudeAgent) buildArgs(agenticMode bool) []string { +func (a *ClaudeAgent) buildArgs(agenticMode, includeEffort bool) []string { sessionID := sanitizedResumeSessionID(a.SessionID) // Always use stdin piping + stream-json for non-interactive execution // (following claude-code-action pattern from Anthropic) @@ -109,6 +127,12 @@ func (a *ClaudeAgent) buildArgs(agenticMode bool) []string { args = append(args, "--resume", sessionID) } + if includeEffort { + if effort := a.claudeEffort(); effort != "" { + args = append(args, claudeEffortFlag, effort) + } + } + if agenticMode { // Agentic mode: Claude can use tools and make file changes args = append(args, claudeDangerousFlag) @@ -134,6 +158,17 @@ func claudeSupportsDangerousFlag(ctx context.Context, command string) (bool, err return supported, nil } +func claudeSupportsEffortFlag(ctx context.Context, command string) bool { + if cached, ok := claudeEffortSupport.Load(command); ok { + return cached.(bool) + } + cmd := exec.CommandContext(ctx, command, "--help") + output, _ := cmd.CombinedOutput() + supported := strings.Contains(string(output), claudeEffortFlag) + claudeEffortSupport.Store(command, supported) + return supported +} + func (a *ClaudeAgent) Review(ctx context.Context, repoPath, commitSHA, prompt string, output io.Writer) (string, error) { // Use agentic mode if either per-job setting or global setting enables it agenticMode := a.Agentic || AllowUnsafeAgents() @@ -148,8 +183,11 @@ func (a *ClaudeAgent) Review(ctx context.Context, repoPath, commitSHA, prompt st } } + // Only pass --effort if the installed Claude Code supports it + includeEffort := a.claudeEffort() != "" && claudeSupportsEffortFlag(ctx, a.Command) + // Build args - always uses stdin piping + stream-json for non-interactive execution - args := a.buildArgs(agenticMode) + args := a.buildArgs(agenticMode, includeEffort) cmd := exec.CommandContext(ctx, a.Command, args...) cmd.Dir = repoPath diff --git a/internal/agent/claude_test.go b/internal/agent/claude_test.go index 4e02931f2..ba71be139 100644 --- a/internal/agent/claude_test.go +++ b/internal/agent/claude_test.go @@ -48,7 +48,7 @@ func TestClaudeBuildArgs(t *testing.T) { t.Run("ReviewMode", func(t *testing.T) { // Non-agentic mode (review only): read-only tools, no dangerous flag - args := a.buildArgs(false) + args := a.buildArgs(false, false) for _, req := range []string{"--output-format", "stream-json", "--verbose", "-p", "--allowedTools"} { assertContainsArg(t, args, req) } @@ -61,7 +61,7 @@ func TestClaudeBuildArgs(t *testing.T) { t.Run("AgenticMode", func(t *testing.T) { // Agentic mode: write tools + dangerous flag - args := a.buildArgs(true) + args := a.buildArgs(true, false) assertContainsArg(t, args, claudeDangerousFlag) assertContainsArg(t, args, "--allowedTools") @@ -71,13 +71,13 @@ func TestClaudeBuildArgs(t *testing.T) { }) t.Run("ResumeSession", func(t *testing.T) { - args := a.WithSessionID("session-123").(*ClaudeAgent).buildArgs(false) + args := a.WithSessionID("session-123").(*ClaudeAgent).buildArgs(false, false) assertContainsArg(t, args, "--resume") assertContainsArg(t, args, "session-123") }) t.Run("RejectInvalidResumeSession", func(t *testing.T) { - args := a.WithSessionID("-bad-session").(*ClaudeAgent).buildArgs(false) + args := a.WithSessionID("-bad-session").(*ClaudeAgent).buildArgs(false, false) assertNotContainsArg(t, args, "--resume") assertNotContainsArg(t, args, "-bad-session") }) diff --git a/internal/agent/codex.go b/internal/agent/codex.go index 9582d765d..639d69199 100644 --- a/internal/agent/codex.go +++ b/internal/agent/codex.go @@ -86,6 +86,8 @@ func (a *CodexAgent) WithSessionID(sessionID string) Agent { // codexReasoningEffort maps ReasoningLevel to codex-specific effort values func (a *CodexAgent) codexReasoningEffort() string { switch a.Reasoning { + case ReasoningMaximum: + return "xhigh" case ReasoningThorough: return "high" case ReasoningFast: diff --git a/internal/agent/droid.go b/internal/agent/droid.go index 285d3a1d0..aeb75ff0e 100644 --- a/internal/agent/droid.go +++ b/internal/agent/droid.go @@ -46,7 +46,7 @@ func (a *DroidAgent) WithModel(model string) Agent { // droidReasoningEffort maps ReasoningLevel to droid-specific effort values func (a *DroidAgent) droidReasoningEffort() string { switch a.Reasoning { - case ReasoningThorough: + case ReasoningMaximum, ReasoningThorough: return "high" case ReasoningFast: return "low" diff --git a/internal/agent/kilo.go b/internal/agent/kilo.go index bf4a38cd6..c2b074fa5 100644 --- a/internal/agent/kilo.go +++ b/internal/agent/kilo.go @@ -84,7 +84,7 @@ func (a *KiloAgent) CommandName() string { // kiloVariant maps ReasoningLevel to kilo's --variant flag values func (a *KiloAgent) kiloVariant() string { switch a.Reasoning { - case ReasoningThorough: + case ReasoningMaximum, ReasoningThorough: return "high" case ReasoningFast: return "minimal" diff --git a/internal/agent/pi.go b/internal/agent/pi.go index 2a0325b52..88f34c780 100644 --- a/internal/agent/pi.go +++ b/internal/agent/pi.go @@ -134,7 +134,7 @@ func (a *PiAgent) buildArgs(repoPath string) []string { func (a *PiAgent) thinkingLevel() string { switch a.Reasoning { - case ReasoningThorough: + case ReasoningMaximum, ReasoningThorough: return "high" case ReasoningFast: return "low" diff --git a/internal/config/config.go b/internal/config/config.go index 83503f4af..9c6d03164 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -65,43 +65,63 @@ type Config struct { ReviewAgent string `toml:"review_agent"` ReviewAgentFast string `toml:"review_agent_fast"` ReviewAgentStandard string `toml:"review_agent_standard"` + ReviewAgentMedium string `toml:"review_agent_medium"` ReviewAgentThorough string `toml:"review_agent_thorough"` + ReviewAgentMaximum string `toml:"review_agent_maximum"` RefineAgent string `toml:"refine_agent"` RefineAgentFast string `toml:"refine_agent_fast"` RefineAgentStandard string `toml:"refine_agent_standard"` + RefineAgentMedium string `toml:"refine_agent_medium"` RefineAgentThorough string `toml:"refine_agent_thorough"` + RefineAgentMaximum string `toml:"refine_agent_maximum"` ReviewModel string `toml:"review_model"` ReviewModelFast string `toml:"review_model_fast"` ReviewModelStandard string `toml:"review_model_standard"` + ReviewModelMedium string `toml:"review_model_medium"` ReviewModelThorough string `toml:"review_model_thorough"` + ReviewModelMaximum string `toml:"review_model_maximum"` RefineModel string `toml:"refine_model"` RefineModelFast string `toml:"refine_model_fast"` RefineModelStandard string `toml:"refine_model_standard"` + RefineModelMedium string `toml:"refine_model_medium"` RefineModelThorough string `toml:"refine_model_thorough"` + RefineModelMaximum string `toml:"refine_model_maximum"` FixAgent string `toml:"fix_agent"` FixAgentFast string `toml:"fix_agent_fast"` FixAgentStandard string `toml:"fix_agent_standard"` + FixAgentMedium string `toml:"fix_agent_medium"` FixAgentThorough string `toml:"fix_agent_thorough"` + FixAgentMaximum string `toml:"fix_agent_maximum"` FixModel string `toml:"fix_model"` FixModelFast string `toml:"fix_model_fast"` FixModelStandard string `toml:"fix_model_standard"` + FixModelMedium string `toml:"fix_model_medium"` FixModelThorough string `toml:"fix_model_thorough"` + FixModelMaximum string `toml:"fix_model_maximum"` SecurityAgent string `toml:"security_agent"` SecurityAgentFast string `toml:"security_agent_fast"` SecurityAgentStandard string `toml:"security_agent_standard"` + SecurityAgentMedium string `toml:"security_agent_medium"` SecurityAgentThorough string `toml:"security_agent_thorough"` + SecurityAgentMaximum string `toml:"security_agent_maximum"` SecurityModel string `toml:"security_model"` SecurityModelFast string `toml:"security_model_fast"` SecurityModelStandard string `toml:"security_model_standard"` + SecurityModelMedium string `toml:"security_model_medium"` SecurityModelThorough string `toml:"security_model_thorough"` + SecurityModelMaximum string `toml:"security_model_maximum"` DesignAgent string `toml:"design_agent"` DesignAgentFast string `toml:"design_agent_fast"` DesignAgentStandard string `toml:"design_agent_standard"` + DesignAgentMedium string `toml:"design_agent_medium"` DesignAgentThorough string `toml:"design_agent_thorough"` + DesignAgentMaximum string `toml:"design_agent_maximum"` DesignModel string `toml:"design_model"` DesignModelFast string `toml:"design_model_fast"` DesignModelStandard string `toml:"design_model_standard"` + DesignModelMedium string `toml:"design_model_medium"` DesignModelThorough string `toml:"design_model_thorough"` + DesignModelMaximum string `toml:"design_model_maximum"` // Backup agents for failover ReviewBackupAgent string `toml:"review_backup_agent"` @@ -540,8 +560,8 @@ type RepoCIConfig struct { // the ReviewTypes x Agents cross-product for this repo. Reviews map[string][]string `toml:"reviews" comment:"Explicit CI review matrix for this repo: agent name to review types."` - // Reasoning overrides the reasoning level for CI reviews (thorough, standard, fast). - Reasoning string `toml:"reasoning" comment:"Override the CI reasoning level for this repo: fast, standard, or thorough."` + // Reasoning overrides the reasoning level for CI reviews. + Reasoning string `toml:"reasoning" comment:"Override the CI reasoning level for this repo: fast, standard, medium, thorough, or maximum."` // MinSeverity overrides the minimum severity filter for CI synthesis. MinSeverity string `toml:"min_severity" comment:"Override the minimum CI severity included in synthesized output."` @@ -563,9 +583,9 @@ type RepoConfig struct { ExcludedBranches []string `toml:"excluded_branches" comment:"Branches that should be skipped for automatic review in this repo."` ExcludedCommitPatterns []string `toml:"excluded_commit_patterns" comment:"Commit message substrings that should skip review for this repo."` DisplayName string `toml:"display_name" comment:"Display name shown for this repo in the TUI and output."` - ReviewReasoning string `toml:"review_reasoning" comment:"Reasoning level for reviews in this repo: fast, standard, or thorough."` // Reasoning level for reviews: thorough, standard, fast - RefineReasoning string `toml:"refine_reasoning" comment:"Reasoning level for refine in this repo: fast, standard, or thorough."` // Reasoning level for refine: thorough, standard, fast - FixReasoning string `toml:"fix_reasoning" comment:"Reasoning level for fix in this repo: fast, standard, or thorough."` // Reasoning level for fix: thorough, standard, fast + ReviewReasoning string `toml:"review_reasoning" comment:"Reasoning level for reviews in this repo: fast, standard, medium, thorough, or maximum."` + RefineReasoning string `toml:"refine_reasoning" comment:"Reasoning level for refine in this repo: fast, standard, medium, thorough, or maximum."` + FixReasoning string `toml:"fix_reasoning" comment:"Reasoning level for fix in this repo: fast, standard, medium, thorough, or maximum."` FixMinSeverity string `toml:"fix_min_severity" comment:"Minimum severity for fix in this repo: critical, high, medium, or low."` // Minimum severity for fix: critical, high, medium, low RefineMinSeverity string `toml:"refine_min_severity" comment:"Minimum severity for refine in this repo: critical, high, medium, low."` // Minimum severity for refine: critical, high, medium, low ExcludePatterns []string `toml:"exclude_patterns" comment:"Filenames or glob patterns to exclude from review diffs for this repo."` @@ -580,43 +600,63 @@ type RepoConfig struct { ReviewAgent string `toml:"review_agent" comment:"Agent override for standard review in this repo."` ReviewAgentFast string `toml:"review_agent_fast" comment:"Agent override for fast review in this repo."` ReviewAgentStandard string `toml:"review_agent_standard" comment:"Agent override for standard review in this repo."` + ReviewAgentMedium string `toml:"review_agent_medium" comment:"Agent override for medium review in this repo."` ReviewAgentThorough string `toml:"review_agent_thorough" comment:"Agent override for thorough review in this repo."` + ReviewAgentMaximum string `toml:"review_agent_maximum" comment:"Agent override for maximum review in this repo."` RefineAgent string `toml:"refine_agent" comment:"Agent override for refine in this repo."` RefineAgentFast string `toml:"refine_agent_fast" comment:"Agent override for fast refine in this repo."` RefineAgentStandard string `toml:"refine_agent_standard" comment:"Agent override for standard refine in this repo."` + RefineAgentMedium string `toml:"refine_agent_medium" comment:"Agent override for medium refine in this repo."` RefineAgentThorough string `toml:"refine_agent_thorough" comment:"Agent override for thorough refine in this repo."` + RefineAgentMaximum string `toml:"refine_agent_maximum" comment:"Agent override for maximum refine in this repo."` ReviewModel string `toml:"review_model" comment:"Model override for standard review in this repo."` ReviewModelFast string `toml:"review_model_fast" comment:"Model override for fast review in this repo."` ReviewModelStandard string `toml:"review_model_standard" comment:"Model override for standard review in this repo."` + ReviewModelMedium string `toml:"review_model_medium" comment:"Model override for medium review in this repo."` ReviewModelThorough string `toml:"review_model_thorough" comment:"Model override for thorough review in this repo."` + ReviewModelMaximum string `toml:"review_model_maximum" comment:"Model override for maximum review in this repo."` RefineModel string `toml:"refine_model" comment:"Model override for standard refine in this repo."` RefineModelFast string `toml:"refine_model_fast" comment:"Model override for fast refine in this repo."` RefineModelStandard string `toml:"refine_model_standard" comment:"Model override for standard refine in this repo."` + RefineModelMedium string `toml:"refine_model_medium" comment:"Model override for medium refine in this repo."` RefineModelThorough string `toml:"refine_model_thorough" comment:"Model override for thorough refine in this repo."` + RefineModelMaximum string `toml:"refine_model_maximum" comment:"Model override for maximum refine in this repo."` FixAgent string `toml:"fix_agent" comment:"Agent override for fix in this repo."` FixAgentFast string `toml:"fix_agent_fast" comment:"Agent override for fast fix in this repo."` FixAgentStandard string `toml:"fix_agent_standard" comment:"Agent override for standard fix in this repo."` + FixAgentMedium string `toml:"fix_agent_medium" comment:"Agent override for medium fix in this repo."` FixAgentThorough string `toml:"fix_agent_thorough" comment:"Agent override for thorough fix in this repo."` + FixAgentMaximum string `toml:"fix_agent_maximum" comment:"Agent override for maximum fix in this repo."` FixModel string `toml:"fix_model" comment:"Model override for standard fix in this repo."` FixModelFast string `toml:"fix_model_fast" comment:"Model override for fast fix in this repo."` FixModelStandard string `toml:"fix_model_standard" comment:"Model override for standard fix in this repo."` + FixModelMedium string `toml:"fix_model_medium" comment:"Model override for medium fix in this repo."` FixModelThorough string `toml:"fix_model_thorough" comment:"Model override for thorough fix in this repo."` + FixModelMaximum string `toml:"fix_model_maximum" comment:"Model override for maximum fix in this repo."` SecurityAgent string `toml:"security_agent" comment:"Agent override for security review in this repo."` SecurityAgentFast string `toml:"security_agent_fast" comment:"Agent override for fast security review in this repo."` SecurityAgentStandard string `toml:"security_agent_standard" comment:"Agent override for standard security review in this repo."` + SecurityAgentMedium string `toml:"security_agent_medium" comment:"Agent override for medium security review in this repo."` SecurityAgentThorough string `toml:"security_agent_thorough" comment:"Agent override for thorough security review in this repo."` + SecurityAgentMaximum string `toml:"security_agent_maximum" comment:"Agent override for maximum security review in this repo."` SecurityModel string `toml:"security_model" comment:"Model override for standard security review in this repo."` SecurityModelFast string `toml:"security_model_fast" comment:"Model override for fast security review in this repo."` SecurityModelStandard string `toml:"security_model_standard" comment:"Model override for standard security review in this repo."` + SecurityModelMedium string `toml:"security_model_medium" comment:"Model override for medium security review in this repo."` SecurityModelThorough string `toml:"security_model_thorough" comment:"Model override for thorough security review in this repo."` + SecurityModelMaximum string `toml:"security_model_maximum" comment:"Model override for maximum security review in this repo."` DesignAgent string `toml:"design_agent" comment:"Agent override for design review in this repo."` DesignAgentFast string `toml:"design_agent_fast" comment:"Agent override for fast design review in this repo."` DesignAgentStandard string `toml:"design_agent_standard" comment:"Agent override for standard design review in this repo."` + DesignAgentMedium string `toml:"design_agent_medium" comment:"Agent override for medium design review in this repo."` DesignAgentThorough string `toml:"design_agent_thorough" comment:"Agent override for thorough design review in this repo."` + DesignAgentMaximum string `toml:"design_agent_maximum" comment:"Agent override for maximum design review in this repo."` DesignModel string `toml:"design_model" comment:"Model override for standard design review in this repo."` DesignModelFast string `toml:"design_model_fast" comment:"Model override for fast design review in this repo."` DesignModelStandard string `toml:"design_model_standard" comment:"Model override for standard design review in this repo."` + DesignModelMedium string `toml:"design_model_medium" comment:"Model override for medium design review in this repo."` DesignModelThorough string `toml:"design_model_thorough" comment:"Model override for thorough design review in this repo."` + DesignModelMaximum string `toml:"design_model_maximum" comment:"Model override for maximum design review in this repo."` // Backup agents for failover ReviewBackupAgent string `toml:"review_backup_agent" comment:"Backup agent for review in this repo."` @@ -1074,7 +1114,7 @@ func ValidateReviewTypes(types []string) ([]string, error) { } // NormalizeReasoning validates and normalizes a reasoning level string. -// Returns the canonical form (thorough, standard, fast) or an error if invalid. +// Returns the canonical form (maximum, thorough, medium, standard, fast) or an error if invalid. // Returns empty string (no error) for empty input. func NormalizeReasoning(value string) (string, error) { normalized := strings.ToLower(strings.TrimSpace(value)) @@ -1083,9 +1123,13 @@ func NormalizeReasoning(value string) (string, error) { } switch normalized { + case "maximum", "max", "xhigh": + return "maximum", nil case "thorough", "high": return "thorough", nil - case "standard", "medium": + case "medium": + return "medium", nil + case "standard": return "standard", nil case "fast", "low": return "fast", nil diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 55a0b83fd..5ddcbc88d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1449,6 +1449,19 @@ func TestResolveAgentForWorkflow(t *testing.T) { {"design global level-specific", "", nil, &Config{DesignAgent: "codex", DesignAgentThorough: "claude"}, "design", "thorough", "claude"}, {"design isolated from review", "", M{"review_agent": "claude"}, &Config{DefaultAgent: "codex"}, "design", "fast", "codex"}, {"design isolated from security", "", M{"security_agent": "claude"}, &Config{DefaultAgent: "codex"}, "design", "fast", "codex"}, + + // Maximum level + {"maximum repo level-specific", "", M{"review_agent_maximum": "claude"}, nil, "review", "maximum", "claude"}, + {"maximum global level-specific", "", nil, &Config{ReviewAgentMaximum: "claude"}, "review", "maximum", "claude"}, + {"maximum falls back to workflow", "", M{"review_agent": "claude"}, nil, "review", "maximum", "claude"}, + {"maximum falls back to generic", "", M{"agent": "claude"}, nil, "review", "maximum", "claude"}, + {"fix maximum level-specific", "", M{"fix_agent_maximum": "claude"}, nil, "fix", "maximum", "claude"}, + + // Medium level + {"medium repo level-specific", "", M{"review_agent_medium": "claude"}, nil, "review", "medium", "claude"}, + {"medium global level-specific", "", nil, &Config{ReviewAgentMedium: "claude"}, "review", "medium", "claude"}, + {"medium falls back to workflow", "", M{"review_agent": "claude"}, nil, "review", "medium", "claude"}, + {"medium falls back to generic", "", M{"agent": "claude"}, nil, "review", "medium", "claude"}, } for _, tt := range tests {