ralph_workflow/phases/
context.rs1use crate::agents::{AgentRegistry, AgentRole};
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::pipeline::Stats;
14use crate::pipeline::Timer;
15use crate::prompts::template_context::TemplateContext;
16
17pub struct PhaseContext<'a> {
22 pub config: &'a Config,
24 pub registry: &'a AgentRegistry,
26 pub logger: &'a Logger,
28 pub colors: &'a Colors,
30 pub timer: &'a mut Timer,
32 pub stats: &'a mut Stats,
34 pub developer_agent: &'a str,
36 pub reviewer_agent: &'a str,
38 pub review_guidelines: Option<&'a ReviewGuidelines>,
40 pub template_context: &'a TemplateContext,
42 pub run_context: RunContext,
44 pub execution_history: ExecutionHistory,
46 pub prompt_history: std::collections::HashMap<String, String>,
48}
49
50impl PhaseContext<'_> {
51 pub fn record_developer_iteration(&mut self) {
53 self.run_context.record_developer_iteration();
54 }
55
56 pub fn record_reviewer_pass(&mut self) {
58 self.run_context.record_reviewer_pass();
59 }
60
61 pub fn capture_prompt(&mut self, key: &str, prompt: &str) {
71 self.prompt_history
72 .insert(key.to_string(), prompt.to_string());
73 }
74
75 pub fn clone_prompt_history(&self) -> std::collections::HashMap<String, String> {
80 self.prompt_history.clone()
81 }
82}
83
84pub fn get_primary_commit_agent(ctx: &PhaseContext<'_>) -> Option<String> {
90 let fallback_config = ctx.registry.fallback_config();
91
92 let commit_agents = fallback_config.get_fallbacks(AgentRole::Commit);
94 if !commit_agents.is_empty() {
95 return commit_agents.first().cloned();
97 }
98
99 let reviewer_agents = fallback_config.get_fallbacks(AgentRole::Reviewer);
101 if !reviewer_agents.is_empty() {
102 return reviewer_agents.first().cloned();
103 }
104
105 Some(ctx.reviewer_agent.to_string())
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::config::Config;
113 use crate::logger::{Colors, Logger};
114 use crate::pipeline::{Stats, Timer};
115 use crate::prompts::template_context::TemplateContext;
116
117 struct TestFixture {
119 config: Config,
120 colors: Colors,
121 logger: Logger,
122 timer: Timer,
123 stats: Stats,
124 template_context: TemplateContext,
125 }
126
127 impl TestFixture {
128 fn new() -> Self {
129 let colors = Colors { enabled: false };
130 Self {
131 config: Config::default(),
132 colors,
133 logger: Logger::new(colors),
134 timer: Timer::new(),
135 stats: Stats::default(),
136 template_context: TemplateContext::default(),
137 }
138 }
139 }
140
141 #[test]
142 fn test_get_primary_commit_agent_uses_commit_chain_first() {
143 let mut registry = AgentRegistry::new().unwrap();
144
145 let toml_str = r#"
147 [agent_chain]
148 commit = ["commit-agent-1", "commit-agent-2"]
149 reviewer = ["reviewer-agent"]
150 developer = ["developer-agent"]
151 "#;
152 let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
153 registry.apply_unified_config(&unified);
154
155 let mut fixture = TestFixture::new();
156 let ctx = PhaseContext {
157 config: &fixture.config,
158 registry: ®istry,
159 logger: &fixture.logger,
160 colors: &fixture.colors,
161 timer: &mut fixture.timer,
162 stats: &mut fixture.stats,
163 developer_agent: "developer-agent",
164 reviewer_agent: "reviewer-agent",
165 review_guidelines: None,
166 template_context: &fixture.template_context,
167 run_context: RunContext::new(),
168 execution_history: ExecutionHistory::new(),
169 prompt_history: std::collections::HashMap::new(),
170 };
171
172 let result = get_primary_commit_agent(&ctx);
173 assert_eq!(
174 result,
175 Some("commit-agent-1".to_string()),
176 "Should use first agent from commit chain when configured"
177 );
178 }
179
180 #[test]
181 fn test_get_primary_commit_agent_falls_back_to_reviewer_chain() {
182 let mut registry = AgentRegistry::new().unwrap();
183
184 let toml_str = r#"
186 [agent_chain]
187 reviewer = ["reviewer-agent-1", "reviewer-agent-2"]
188 developer = ["developer-agent"]
189 "#;
190 let unified: crate::config::UnifiedConfig = toml::from_str(toml_str).unwrap();
191 registry.apply_unified_config(&unified);
192
193 let mut fixture = TestFixture::new();
194 let ctx = PhaseContext {
195 config: &fixture.config,
196 registry: ®istry,
197 logger: &fixture.logger,
198 colors: &fixture.colors,
199 timer: &mut fixture.timer,
200 stats: &mut fixture.stats,
201 developer_agent: "developer-agent",
202 reviewer_agent: "reviewer-agent-1",
203 review_guidelines: None,
204 template_context: &fixture.template_context,
205 run_context: RunContext::new(),
206 execution_history: ExecutionHistory::new(),
207 prompt_history: std::collections::HashMap::new(),
208 };
209
210 let result = get_primary_commit_agent(&ctx);
211 assert_eq!(
212 result,
213 Some("reviewer-agent-1".to_string()),
214 "Should fall back to first agent from reviewer chain when commit chain is not configured"
215 );
216 }
217
218 #[test]
219 fn test_get_primary_commit_agent_uses_context_reviewer_as_last_resort() {
220 let registry = AgentRegistry::new().unwrap();
221 let mut fixture = TestFixture::new();
224 let ctx = PhaseContext {
225 config: &fixture.config,
226 registry: ®istry,
227 logger: &fixture.logger,
228 colors: &fixture.colors,
229 timer: &mut fixture.timer,
230 stats: &mut fixture.stats,
231 developer_agent: "fallback-developer",
232 reviewer_agent: "fallback-reviewer",
233 review_guidelines: None,
234 template_context: &fixture.template_context,
235 run_context: RunContext::new(),
236 execution_history: ExecutionHistory::new(),
237 prompt_history: std::collections::HashMap::new(),
238 };
239
240 let result = get_primary_commit_agent(&ctx);
241
242 assert!(
246 result.is_some(),
247 "Should return Some agent even with no chains configured"
248 );
249
250 assert_ne!(
252 result.as_deref(),
253 Some("fallback-developer"),
254 "Should NOT fall back to developer agent - should use reviewer"
255 );
256 }
257}