1use crate::agents::{AgentDrain, AgentRegistry};
8use crate::checkpoint::execution_history::ExecutionHistory;
9use crate::checkpoint::RunContext;
10use crate::config::Config;
11use crate::guidelines::ReviewGuidelines;
12use crate::logger::{Colors, Logger};
13use crate::logging::RunLogContext;
14use crate::pipeline::Timer;
15use crate::prompts::template_context::TemplateContext;
16use crate::workspace::Workspace;
17use crate::ProcessExecutor;
18#[cfg(test)]
20use crate::workspace::MemoryWorkspace;
21use std::path::Path;
22
23pub struct PhaseContext<'a> {
49 pub config: &'a Config,
51 pub registry: &'a AgentRegistry,
53 pub logger: &'a Logger,
55 pub colors: &'a Colors,
57 pub timer: &'a mut Timer,
59 pub developer_agent: &'a str,
61 pub reviewer_agent: &'a str,
63 pub review_guidelines: Option<&'a ReviewGuidelines>,
65 pub template_context: &'a TemplateContext,
67 pub run_context: RunContext,
69 pub execution_history: ExecutionHistory,
71 pub executor: &'a dyn ProcessExecutor,
73 pub executor_arc: std::sync::Arc<dyn ProcessExecutor>,
75 pub repo_root: &'a Path,
81 pub workspace: &'a dyn Workspace,
90 pub workspace_arc: std::sync::Arc<dyn Workspace>,
92 pub run_log_context: &'a RunLogContext,
98 pub cloud_reporter: Option<&'a dyn crate::cloud::CloudReporter>,
103 pub cloud: &'a crate::config::types::CloudConfig,
108 pub env: &'a dyn crate::runtime::environment::GitEnvironment,
113}
114
115impl PhaseContext<'_> {
116 pub fn record_developer_iteration(&mut self) {
118 self.run_context = self.run_context.clone().record_developer_iteration();
119 }
120
121 pub fn record_reviewer_pass(&mut self) {
123 self.run_context = self.run_context.clone().record_reviewer_pass();
124 }
125}
126
127#[must_use]
133pub fn get_primary_commit_agent(ctx: &PhaseContext<'_>) -> Option<String> {
134 if let Some(commit_binding) = ctx.registry.resolved_drain(AgentDrain::Commit) {
135 return commit_binding.agents.first().cloned();
136 }
137
138 let reviewer_agents = ctx
140 .registry
141 .resolved_drain(AgentDrain::Review)
142 .map_or(&[] as &[String], |binding| binding.agents.as_slice());
143 if !reviewer_agents.is_empty() {
144 return reviewer_agents.first().cloned();
145 }
146
147 Some(ctx.reviewer_agent.to_string())
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::config::Config;
155 use crate::executor::MockProcessExecutor;
156 use crate::logger::{Colors, Logger};
157 use crate::pipeline::Timer;
158 use crate::prompts::template_context::TemplateContext;
159 use std::path::PathBuf;
160
161 struct TestFixture {
166 config: Config,
167 colors: Colors,
168 logger: Logger,
169 timer: Timer,
170 template_context: TemplateContext,
171 executor_arc: std::sync::Arc<dyn crate::executor::ProcessExecutor>,
172 repo_root: PathBuf,
173 workspace: MemoryWorkspace,
174 workspace_arc: std::sync::Arc<dyn Workspace>,
175 run_log_context: crate::logging::RunLogContext,
176 }
177
178 impl TestFixture {
179 fn new() -> Self {
180 let colors = Colors { enabled: false };
181 let executor_arc = std::sync::Arc::new(MockProcessExecutor::new())
182 as std::sync::Arc<dyn crate::executor::ProcessExecutor>;
183 let repo_root = PathBuf::from("/test/repo");
184 let workspace = MemoryWorkspace::new(repo_root.clone());
186 let workspace_arc =
187 std::sync::Arc::new(workspace.clone()) as std::sync::Arc<dyn Workspace>;
188 let run_log_context = crate::logging::RunLogContext::new(&workspace).unwrap();
189 Self {
190 config: Config::default(),
191 colors,
192 logger: Logger::new(colors),
193 timer: Timer::new(),
194 template_context: TemplateContext::default(),
195 executor_arc,
196 repo_root,
197 workspace,
198 workspace_arc,
199 run_log_context,
200 }
201 }
202 }
203
204 #[test]
205 fn test_get_primary_commit_agent_uses_commit_chain_first() {
206 let toml_str = r#"
207 [agent_chain]
208 commit = ["commit-agent-1", "commit-agent-2"]
209 reviewer = ["reviewer-agent"]
210 developer = ["developer-agent"]
211 "#;
212 let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
213 let registry = AgentRegistry::new()
214 .unwrap()
215 .apply_unified_config(&unified)
216 .unwrap();
217
218 let mut fixture = TestFixture::new();
219 let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
220 let ctx = PhaseContext {
221 config: &fixture.config,
222 registry: ®istry,
223 logger: &fixture.logger,
224 colors: &fixture.colors,
225 timer: &mut fixture.timer,
226 developer_agent: "developer-agent",
227 reviewer_agent: "reviewer-agent",
228 review_guidelines: None,
229 template_context: &fixture.template_context,
230 run_context: RunContext::new(),
231 execution_history: ExecutionHistory::new(),
232 executor: fixture.executor_arc.as_ref(),
233 executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
234 repo_root: &fixture.repo_root,
235 workspace: &fixture.workspace,
236 workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
237 run_log_context: &fixture.run_log_context,
238 cloud_reporter: None,
239 cloud: &crate::config::types::CloudConfig::disabled(),
240 env: &git_env,
241 };
242
243 let result = get_primary_commit_agent(&ctx);
244 assert_eq!(
245 result,
246 Some("commit-agent-1".to_string()),
247 "Should use first agent from commit chain when configured"
248 );
249 }
250
251 #[test]
252 fn test_get_primary_commit_agent_falls_back_to_reviewer_chain() {
253 let toml_str = r#"
254 [agent_chain]
255 reviewer = ["reviewer-agent-1", "reviewer-agent-2"]
256 developer = ["developer-agent"]
257 "#;
258 let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
259 let registry = AgentRegistry::new()
260 .unwrap()
261 .apply_unified_config(&unified)
262 .unwrap();
263
264 let mut fixture = TestFixture::new();
265 let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
266 let ctx = PhaseContext {
267 config: &fixture.config,
268 registry: ®istry,
269 logger: &fixture.logger,
270 colors: &fixture.colors,
271 timer: &mut fixture.timer,
272 developer_agent: "developer-agent",
273 reviewer_agent: "reviewer-agent-1",
274 review_guidelines: None,
275 template_context: &fixture.template_context,
276 run_context: RunContext::new(),
277 execution_history: ExecutionHistory::new(),
278 executor: fixture.executor_arc.as_ref(),
279 executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
280 repo_root: &fixture.repo_root,
281 workspace: &fixture.workspace,
282 workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
283 run_log_context: &fixture.run_log_context,
284 cloud_reporter: None,
285 cloud: &crate::config::types::CloudConfig::disabled(),
286 env: &git_env,
287 };
288
289 let result = get_primary_commit_agent(&ctx);
290 assert_eq!(
291 result,
292 Some("reviewer-agent-1".to_string()),
293 "Should fall back to first agent from reviewer chain when commit chain is not configured"
294 );
295 }
296
297 #[test]
298 fn test_get_primary_commit_agent_uses_context_reviewer_as_last_resort() {
299 let registry = AgentRegistry::new().unwrap();
300 let mut fixture = TestFixture::new();
303 let git_env = crate::runtime::environment::mock::MockGitEnvironment::new();
304 let ctx = PhaseContext {
305 config: &fixture.config,
306 registry: ®istry,
307 logger: &fixture.logger,
308 colors: &fixture.colors,
309 timer: &mut fixture.timer,
310 developer_agent: "fallback-developer",
311 reviewer_agent: "fallback-reviewer",
312 review_guidelines: None,
313 template_context: &fixture.template_context,
314 run_context: RunContext::new(),
315 execution_history: ExecutionHistory::new(),
316 executor: fixture.executor_arc.as_ref(),
317 executor_arc: std::sync::Arc::clone(&fixture.executor_arc),
318 repo_root: &fixture.repo_root,
319 workspace: &fixture.workspace,
320 workspace_arc: std::sync::Arc::clone(&fixture.workspace_arc),
321 run_log_context: &fixture.run_log_context,
322 cloud_reporter: None,
323 cloud: &crate::config::types::CloudConfig::disabled(),
324 env: &git_env,
325 };
326
327 let result = get_primary_commit_agent(&ctx);
328
329 assert!(
333 result.is_some(),
334 "Should return Some agent even with no chains configured"
335 );
336
337 assert_ne!(
339 result.as_deref(),
340 Some("fallback-developer"),
341 "Should NOT fall back to developer agent - should use reviewer"
342 );
343 }
344}