use crate::agents::{AgentDrain, DrainMode};
use crate::reducer::effect::Effect;
use crate::reducer::event::CheckpointTrigger;
use crate::reducer::state::{PipelineState, PromptMode};
pub const REQUIRED_FILES_ISSUES: &[&str] = &[".agent/tmp/issues.xml"];
pub const REQUIRED_FILES_FIX: &[&str] = &[
".agent/tmp/fix_result.xml",
".agent/tmp/development_result.xml",
];
pub(super) fn determine_review_effect(state: &PipelineState) -> Effect {
let runtime_drain = state.runtime_drain();
let in_fix_to_analysis_transition = state.fix_agent_invoked_pass == Some(state.reviewer_pass)
&& state.agent_chain.current_drain == AgentDrain::Analysis
&& state.fix_analysis_agent_invoked_pass.is_none();
let in_fix_flow = runtime_drain == AgentDrain::Fix
|| (state.fix_analysis_agent_invoked_pass.is_some()
&& state.agent_chain.current_drain == AgentDrain::Analysis)
|| in_fix_to_analysis_transition;
if in_fix_flow {
let in_transition_to_analysis = state.agent_chain.current_drain == AgentDrain::Analysis
&& state.fix_analysis_agent_invoked_pass.is_none();
let last_effect_was_fix_agent = state
.continuation
.last_effect_kind
.as_deref()
.is_some_and(|k| k.contains("InvokeFixAgent"));
let current_drain_is_default = state.agent_chain.current_drain == AgentDrain::Planning;
let has_fix_progress = state.fix_prompt_prepared_pass.is_some()
|| state.fix_agent_invoked_pass.is_some()
|| state.fix_required_files_cleaned_pass.is_some();
let is_legacy_continue = current_drain_is_default && has_fix_progress;
let chain_matches = state.agent_chain.matches_runtime_drain(AgentDrain::Fix);
let fix_not_started = state.fix_agent_invoked_pass.is_none()
&& !(last_effect_was_fix_agent
&& state.fix_prompt_prepared_pass == Some(state.reviewer_pass)
&& state.fix_required_files_cleaned_pass == Some(state.reviewer_pass)
&& state.runtime_drain() == AgentDrain::Fix);
let should_initialize_fix_chain = !in_transition_to_analysis
&& !is_legacy_continue
&& fix_not_started
&& (state.agent_chain.agents.is_empty() || !chain_matches);
if should_initialize_fix_chain {
return Effect::InitializeAgentChain {
drain: AgentDrain::Fix,
};
}
if state.agent_chain.current_drain == AgentDrain::Analysis {
} else if state.fix_prompt_prepared_pass != Some(state.reviewer_pass) {
let prompt_mode = if state.continuation.fix_continue_pending
|| state.agent_chain.current_mode == crate::agents::DrainMode::Continuation
{
PromptMode::Continuation
} else {
PromptMode::Normal
};
return Effect::PrepareFixPrompt {
pass: state.reviewer_pass,
prompt_mode,
};
}
if state.fix_required_files_cleaned_pass != Some(state.reviewer_pass) {
return Effect::CleanupRequiredFiles {
files: REQUIRED_FILES_FIX.iter().map(ToString::to_string).collect(),
};
}
let last_effect_was_fix_agent = state
.continuation
.last_effect_kind
.as_deref()
.is_some_and(|k| k.contains("InvokeFixAgent"));
let effective_fix_agent_invoked = state.fix_agent_invoked_pass == Some(state.reviewer_pass)
|| (last_effect_was_fix_agent
&& state.fix_prompt_prepared_pass == Some(state.reviewer_pass)
&& state.fix_required_files_cleaned_pass == Some(state.reviewer_pass)
&& state.runtime_drain() == AgentDrain::Fix
&& state.agent_chain.current_mode == DrainMode::Normal);
if !effective_fix_agent_invoked {
return Effect::InvokeFixAgent {
pass: state.reviewer_pass,
};
}
if effective_fix_agent_invoked
&& state.fix_analysis_agent_invoked_pass != Some(state.reviewer_pass)
{
if state.agent_chain.current_drain != AgentDrain::Analysis {
return Effect::InitializeAgentChain {
drain: AgentDrain::Analysis,
};
}
return Effect::InvokeFixAnalysisAgent {
pass: state.reviewer_pass,
};
}
if state.fix_result_xml_extracted_pass != Some(state.reviewer_pass) {
return Effect::ExtractFixResultXml {
pass: state.reviewer_pass,
};
}
let fix_validated_is_for_pass = state
.fix_validated_outcome
.as_ref()
.is_some_and(|o| o.pass == state.reviewer_pass);
if !fix_validated_is_for_pass {
return Effect::ValidateFixResultXml {
pass: state.reviewer_pass,
};
}
if state.fix_result_xml_archived_pass != Some(state.reviewer_pass) {
return Effect::ArchiveFixResultXml {
pass: state.reviewer_pass,
};
}
if crate::reducer::orchestration::is_recovery_state_active(state)
&& state.fix_result_xml_archived_pass == Some(state.reviewer_pass)
{
return Effect::EmitRecoverySuccess {
level: state.recovery_escalation_level,
total_attempts: state.dev_fix_attempt_count,
};
}
return Effect::ApplyFixOutcome {
pass: state.reviewer_pass,
};
}
if state.agent_chain.agents.is_empty()
|| !state.agent_chain.matches_runtime_drain(AgentDrain::Review)
{
return Effect::InitializeAgentChain {
drain: AgentDrain::Review,
};
}
let consumer_signature_sha256 = state.agent_chain.consumer_signature_sha256();
let review_pass_needs_work = state.reviewer_pass < state.total_reviewer_passes
|| (state.reviewer_pass == state.total_reviewer_passes && state.total_reviewer_passes > 0);
if review_pass_needs_work {
if state.review_context_prepared_pass != Some(state.reviewer_pass) {
return Effect::PrepareReviewContext {
pass: state.reviewer_pass,
};
}
if state.review_prompt_prepared_pass != Some(state.reviewer_pass) {
let review_inputs_materialized_for_pass =
state.prompt_inputs.review.as_ref().is_some_and(|p| {
p.pass == state.reviewer_pass
&& p.plan.consumer_signature_sha256 == consumer_signature_sha256
&& p.diff.consumer_signature_sha256 == consumer_signature_sha256
});
if !review_inputs_materialized_for_pass {
return Effect::MaterializeReviewInputs {
pass: state.reviewer_pass,
};
}
return Effect::PrepareReviewPrompt {
pass: state.reviewer_pass,
prompt_mode: PromptMode::Normal,
};
}
if state.review_required_files_cleaned_pass != Some(state.reviewer_pass) {
return Effect::CleanupRequiredFiles {
files: REQUIRED_FILES_ISSUES
.iter()
.map(ToString::to_string)
.collect(),
};
}
if state.review_agent_invoked_pass != Some(state.reviewer_pass) {
return Effect::InvokeReviewAgent {
pass: state.reviewer_pass,
};
}
if state.review_issues_xml_extracted_pass != Some(state.reviewer_pass) {
return Effect::ExtractReviewIssuesXml {
pass: state.reviewer_pass,
};
}
let review_validated_is_for_pass = state
.review_validated_outcome
.as_ref()
.is_some_and(|o| o.pass == state.reviewer_pass);
if !review_validated_is_for_pass {
return Effect::ValidateReviewIssuesXml {
pass: state.reviewer_pass,
};
}
if state.review_issues_markdown_written_pass != Some(state.reviewer_pass) {
return Effect::WriteIssuesMarkdown {
pass: state.reviewer_pass,
};
}
if state.review_issue_snippets_extracted_pass != Some(state.reviewer_pass) {
return Effect::ExtractReviewIssueSnippets {
pass: state.reviewer_pass,
};
}
if state.review_issues_xml_archived_pass != Some(state.reviewer_pass) {
return Effect::ArchiveReviewIssuesXml {
pass: state.reviewer_pass,
};
}
if crate::reducer::orchestration::is_recovery_state_active(state)
&& state.review_issues_xml_archived_pass == Some(state.reviewer_pass)
{
return Effect::EmitRecoverySuccess {
level: state.recovery_escalation_level,
total_attempts: state.dev_fix_attempt_count,
};
}
let outcome = state.review_validated_outcome.as_ref();
match outcome {
Some(outcome) => Effect::ApplyReviewOutcome {
pass: outcome.pass,
issues_found: outcome.issues_found,
clean_no_issues: outcome.clean_no_issues,
},
None => Effect::SaveCheckpoint {
trigger: CheckpointTrigger::PhaseTransition,
},
}
} else {
Effect::SaveCheckpoint {
trigger: CheckpointTrigger::PhaseTransition,
}
}
}