use crate::agents::{AgentDrain, AgentRegistry};
use crate::checkpoint::execution_history::ExecutionHistory;
use crate::checkpoint::RunContext;
use crate::config::Config;
use crate::guidelines::ReviewGuidelines;
use crate::logger::{Colors, Logger};
use crate::logging::RunLogContext;
use crate::pipeline::Timer;
use crate::prompts::template_context::TemplateContext;
use crate::workspace::Workspace;
use crate::ProcessExecutor;
#[cfg(test)]
use crate::workspace::MemoryWorkspace;
use std::path::Path;
pub struct PhaseContext<'a> {
pub config: &'a Config,
pub registry: &'a AgentRegistry,
pub logger: &'a Logger,
pub colors: &'a Colors,
pub timer: &'a mut Timer,
pub developer_agent: &'a str,
pub reviewer_agent: &'a str,
pub review_guidelines: Option<&'a ReviewGuidelines>,
pub template_context: &'a TemplateContext,
pub run_context: RunContext,
pub execution_history: ExecutionHistory,
pub executor: &'a dyn ProcessExecutor,
pub executor_arc: std::sync::Arc<dyn ProcessExecutor>,
pub repo_root: &'a Path,
pub workspace: &'a dyn Workspace,
pub workspace_arc: std::sync::Arc<dyn Workspace>,
pub run_log_context: &'a RunLogContext,
pub cloud_reporter: Option<&'a dyn crate::cloud::CloudReporter>,
pub cloud: &'a crate::config::types::CloudConfig,
pub env: &'a dyn crate::runtime::environment::GitEnvironment,
}
impl PhaseContext<'_> {
pub fn record_developer_iteration(&mut self) {
self.run_context = self.run_context.clone().record_developer_iteration();
}
pub fn record_reviewer_pass(&mut self) {
self.run_context = self.run_context.clone().record_reviewer_pass();
}
}
#[must_use]
pub fn get_primary_commit_agent(ctx: &PhaseContext<'_>) -> Option<String> {
if let Some(commit_binding) = ctx.registry.resolved_drain(AgentDrain::Commit) {
return commit_binding.agents.first().cloned();
}
let reviewer_agents = ctx
.registry
.resolved_drain(AgentDrain::Review)
.map_or(&[] as &[String], |binding| binding.agents.as_slice());
if !reviewer_agents.is_empty() {
return reviewer_agents.first().cloned();
}
Some(ctx.reviewer_agent.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::executor::MockProcessExecutor;
use crate::logger::{Colors, Logger};
use crate::pipeline::Timer;
use crate::prompts::template_context::TemplateContext;
use std::path::PathBuf;
struct TestFixture {
config: Config,
colors: Colors,
logger: Logger,
timer: Timer,
template_context: TemplateContext,
executor_arc: std::sync::Arc<dyn crate::executor::ProcessExecutor>,
repo_root: PathBuf,
workspace: MemoryWorkspace,
workspace_arc: std::sync::Arc<dyn Workspace>,
run_log_context: crate::logging::RunLogContext,
}
impl TestFixture {
fn new() -> Self {
let colors = Colors { enabled: false };
let executor_arc = std::sync::Arc::new(MockProcessExecutor::new())
as std::sync::Arc<dyn crate::executor::ProcessExecutor>;
let repo_root = PathBuf::from("/test/repo");
let workspace = MemoryWorkspace::new(repo_root.clone());
let workspace_arc =
std::sync::Arc::new(workspace.clone()) as std::sync::Arc<dyn Workspace>;
let run_log_context = crate::logging::RunLogContext::new(&workspace).unwrap();
Self {
config: Config::default(),
colors,
logger: Logger::new(colors),
timer: Timer::new(),
template_context: TemplateContext::default(),
executor_arc,
repo_root,
workspace,
workspace_arc,
run_log_context,
}
}
}
#[test]
fn test_get_primary_commit_agent_uses_commit_chain_first() {
let toml_str = r#"
[agent_chain]
commit = ["commit-agent-1", "commit-agent-2"]
reviewer = ["reviewer-agent"]
developer = ["developer-agent"]
"#;
let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
let registry = AgentRegistry::new()
.unwrap()
.apply_unified_config(&unified)
.unwrap();
let mut fixture = TestFixture::new();
let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
let ctx = PhaseContext {
config: &fixture.config,
registry: ®istry,
logger: &fixture.logger,
colors: &fixture.colors,
timer: &mut fixture.timer,
developer_agent: "developer-agent",
reviewer_agent: "reviewer-agent",
review_guidelines: None,
template_context: &fixture.template_context,
run_context: RunContext::new(),
execution_history: ExecutionHistory::new(),
executor: fixture.executor_arc.as_ref(),
executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
repo_root: &fixture.repo_root,
workspace: &fixture.workspace,
workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
run_log_context: &fixture.run_log_context,
cloud_reporter: None,
cloud: &crate::config::types::CloudConfig::disabled(),
env: &git_env,
};
let result = get_primary_commit_agent(&ctx);
assert_eq!(
result,
Some("commit-agent-1".to_string()),
"Should use first agent from commit chain when configured"
);
}
#[test]
fn test_get_primary_commit_agent_falls_back_to_reviewer_chain() {
let toml_str = r#"
[agent_chain]
reviewer = ["reviewer-agent-1", "reviewer-agent-2"]
developer = ["developer-agent"]
"#;
let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
let registry = AgentRegistry::new()
.unwrap()
.apply_unified_config(&unified)
.unwrap();
let mut fixture = TestFixture::new();
let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
let ctx = PhaseContext {
config: &fixture.config,
registry: ®istry,
logger: &fixture.logger,
colors: &fixture.colors,
timer: &mut fixture.timer,
developer_agent: "developer-agent",
reviewer_agent: "reviewer-agent-1",
review_guidelines: None,
template_context: &fixture.template_context,
run_context: RunContext::new(),
execution_history: ExecutionHistory::new(),
executor: fixture.executor_arc.as_ref(),
executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
repo_root: &fixture.repo_root,
workspace: &fixture.workspace,
workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
run_log_context: &fixture.run_log_context,
cloud_reporter: None,
cloud: &crate::config::types::CloudConfig::disabled(),
env: &git_env,
};
let result = get_primary_commit_agent(&ctx);
assert_eq!(
result,
Some("reviewer-agent-1".to_string()),
"Should fall back to first agent from reviewer chain when commit chain is not configured"
);
}
#[test]
fn test_get_primary_commit_agent_uses_context_reviewer_as_last_resort() {
let registry = AgentRegistry::new().unwrap();
let mut fixture = TestFixture::new();
let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
let ctx = PhaseContext {
config: &fixture.config,
registry: ®istry,
logger: &fixture.logger,
colors: &fixture.colors,
timer: &mut fixture.timer,
developer_agent: "fallback-developer",
reviewer_agent: "fallback-reviewer",
review_guidelines: None,
template_context: &fixture.template_context,
run_context: RunContext::new(),
execution_history: ExecutionHistory::new(),
executor: fixture.executor_arc.as_ref(),
executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
repo_root: &fixture.repo_root,
workspace: &fixture.workspace,
workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
run_log_context: &fixture.run_log_context,
cloud_reporter: None,
cloud: &crate::config::types::CloudConfig::disabled(),
env: &git_env,
};
let result = get_primary_commit_agent(&ctx);
assert!(
result.is_some(),
"Should return Some agent even with no chains configured"
);
assert_ne!(
result.as_deref(),
Some("fallback-developer"),
"Should NOT fall back to developer agent - should use reviewer"
);
}
}