1use crate::agents::AgentRegistry;
7use crate::app::config::{EventLoopConfig, MAX_EVENT_LOOP_ITERATIONS};
8use crate::app::context::PipelineContext;
9use crate::app::effect::AppEffectHandler;
10use crate::checkpoint::{PipelineCheckpoint, RunContext};
11use crate::cli::Args;
12use crate::config::Config;
13use crate::git_helpers::GitHelpers;
14use crate::guidelines::ReviewGuidelines;
15use crate::logger::Colors;
16use crate::phases::PhaseContext;
17use crate::pipeline::Timer;
18use crate::reducer::MainEffectHandler;
19use crate::reducer::PipelineState;
20use crate::workspace::Workspace;
21use std::path::PathBuf;
22use std::sync::Arc;
23
24pub struct PhaseContextWithTimer<'ctx> {
25 pub phase_ctx: PhaseContext<'ctx>,
26 pub timer: Timer,
27}
28
29fn prepare_git_helpers_for_workspace(
30 repo_root: &std::path::Path,
31 workspace: &dyn crate::workspace::Workspace,
32 logger: &crate::logger::Logger,
33 mut git_helpers: GitHelpers,
34 restore_prompt_permissions: bool,
35) -> GitHelpers {
36 crate::app::runner::pipeline_execution::prepare_agent_phase_for_workspace(
37 repo_root,
38 workspace,
39 logger,
40 &mut git_helpers,
41 restore_prompt_permissions,
42 );
43 git_helpers
44}
45
46fn mark_cleanup_guard_owned<'a>(
47 mut cleanup_guard: crate::app::runner::CommandExitCleanupGuard<'a>,
48) -> crate::app::runner::CommandExitCleanupGuard<'a> {
49 cleanup_guard.mark_owned();
50 cleanup_guard
51}
52
53pub fn setup_git_and_agent_phase<'a>(
54 git_helpers: &'a mut GitHelpers,
55 ctx: &'a PipelineContext,
56) -> crate::pipeline::AgentPhaseGuard<'a> {
57 crate::app::runner::pipeline_execution::prepare_agent_phase_for_workspace(
58 &ctx.repo_root,
59 &*ctx.workspace,
60 &ctx.logger,
61 git_helpers,
62 true,
63 );
64 crate::pipeline::AgentPhaseGuard::new(git_helpers, &ctx.logger, &*ctx.workspace)
65}
66
67pub fn setup_phase_context_with_timer<'ctx>(
68 timer: &'ctx mut crate::pipeline::Timer,
69 ctx: &'ctx PipelineContext,
70 config: &'ctx Config,
71 review_guidelines: Option<&'ctx ReviewGuidelines>,
72 run_context: &'ctx RunContext,
73 resume_checkpoint: Option<&'ctx PipelineCheckpoint>,
74 cloud_reporter: &'ctx dyn crate::cloud::CloudReporter,
75) -> crate::phases::PhaseContext<'ctx> {
76 crate::app::runner::pipeline_execution::create_phase_context_with_config(
77 ctx,
78 config,
79 timer,
80 review_guidelines,
81 run_context,
82 resume_checkpoint,
83 cloud_reporter,
84 )
85}
86
87pub fn create_main_effect_handler_boundary(initial_state: PipelineState) -> MainEffectHandler {
88 crate::app::runtime_factory::create_main_effect_handler(initial_state)
89}
90
91pub fn run_event_loop_with_handler_boundary<'r, 'ctx>(
92 phase_ctx: &'r mut PhaseContext<'ctx>,
93 initial_state: PipelineState,
94) -> anyhow::Result<crate::app::EventLoopResult> {
95 let event_loop_config = EventLoopConfig {
96 max_iterations: MAX_EVENT_LOOP_ITERATIONS,
97 };
98 crate::app::core::run_event_loop(phase_ctx, Some(initial_state), event_loop_config)
99}
100
101pub fn create_cleanup_guard_boundary<'a>(
102 logger: &'a crate::logger::Logger,
103 workspace: &'a dyn crate::workspace::Workspace,
104 owned: bool,
105) -> crate::app::runner::CommandExitCleanupGuard<'a> {
106 crate::app::runtime_factory::create_cleanup_guard(logger, workspace, owned)
107}
108
109pub fn create_git_helpers_boundary() -> GitHelpers {
110 crate::app::runtime_factory::create_git_helpers()
111}
112
113pub fn create_effect_handler_boundary() -> crate::app::effect_handler::RealAppEffectHandler {
114 crate::app::runtime_factory::create_effect_handler()
115}
116
117pub fn setup_agent_phase_for_workspace_boundary(
118 repo_root: &std::path::Path,
119 workspace: &dyn crate::workspace::Workspace,
120 logger: &crate::logger::Logger,
121) -> GitHelpers {
122 prepare_git_helpers_for_workspace(
123 repo_root,
124 workspace,
125 logger,
126 crate::app::runtime_factory::create_git_helpers(),
127 false,
128 )
129}
130
131pub struct RepoCommandBoundaryParams<'a> {
132 pub args: &'a Args,
133 pub config: &'a Config,
134 pub registry: &'a AgentRegistry,
135 pub developer_agent: &'a str,
136 pub reviewer_agent: &'a str,
137 pub logger: &'a crate::logger::Logger,
138 pub colors: Colors,
139 pub executor: &'a std::sync::Arc<dyn crate::executor::ProcessExecutor>,
140 pub app_handler: &'a mut dyn crate::app::effect::AppEffectHandler,
141 pub repo_root: &'a std::path::Path,
142 pub workspace: &'a std::sync::Arc<dyn crate::workspace::Workspace>,
143}
144
145pub fn handle_repo_commands_boundary(
146 params: RepoCommandBoundaryParams<'_>,
147) -> anyhow::Result<bool> {
148 let RepoCommandBoundaryParams {
149 args,
150 config,
151 registry,
152 developer_agent,
153 reviewer_agent,
154 logger,
155 colors,
156 executor,
157 app_handler,
158 repo_root,
159 workspace,
160 } = params;
161
162 if args.recovery.dry_run {
163 crate::cli::handle_dry_run(
164 logger,
165 colors,
166 config,
167 developer_agent,
168 reviewer_agent,
169 repo_root,
170 workspace.as_ref(),
171 )?;
172 return Ok(true);
173 }
174
175 if args.rebase_flags.rebase_only {
176 let _cleanup_guard = mark_cleanup_guard_owned(
177 crate::app::runtime_factory::create_cleanup_guard(logger, workspace.as_ref(), false),
178 );
179 let _git_helpers = prepare_git_helpers_for_workspace(
180 repo_root,
181 workspace.as_ref(),
182 logger,
183 crate::app::runtime_factory::create_git_helpers(),
184 false,
185 );
186 let template_context = crate::prompts::TemplateContext::from_user_templates_dir(
187 config.user_templates_dir().cloned(),
188 );
189 crate::app::runner::pipeline_execution::handle_rebase_only(
190 args,
191 config,
192 &template_context,
193 logger,
194 colors,
195 executor,
196 repo_root,
197 )?;
198 return Ok(true);
199 }
200
201 if args.commit_plumbing.generate_commit_msg {
202 let _cleanup_guard = mark_cleanup_guard_owned(
203 crate::app::runtime_factory::create_cleanup_guard(logger, workspace.as_ref(), false),
204 );
205 let _git_helpers = prepare_git_helpers_for_workspace(
206 repo_root,
207 workspace.as_ref(),
208 logger,
209 crate::app::runtime_factory::create_git_helpers(),
210 false,
211 );
212 let template_context = crate::prompts::TemplateContext::from_user_templates_dir(
213 config.user_templates_dir().cloned(),
214 );
215 crate::app::plumbing::handle_generate_commit_msg(
216 &crate::app::plumbing::CommitGenerationConfig {
217 config,
218 template_context: &template_context,
219 workspace: workspace.as_ref(),
220 workspace_arc: std::sync::Arc::clone(workspace),
221 registry,
222 logger,
223 colors,
224 developer_agent,
225 reviewer_agent,
226 executor: std::sync::Arc::clone(executor),
227 },
228 app_handler,
229 )?;
230 return Ok(true);
231 }
232
233 Ok(false)
234}
235
236pub struct PipelineAndRepoRoot {
237 pub ctx: PipelineContext,
238 pub repo_root: PathBuf,
239}
240
241pub struct RunPipelineWithHandlerParams {
242 pub args: Args,
243 pub config: Config,
244 pub registry: AgentRegistry,
245 pub developer_agent: String,
246 pub reviewer_agent: String,
247 pub developer_display: String,
248 pub reviewer_display: String,
249 pub config_path: std::path::PathBuf,
250 pub colors: crate::logger::Colors,
251 pub logger: crate::logger::Logger,
252 pub executor: Arc<dyn crate::executor::ProcessExecutor>,
253 pub template_context: crate::prompts::TemplateContext,
254}
255
256pub fn run_pipeline_with_handler_boundary(
257 params: RunPipelineWithHandlerParams,
258) -> anyhow::Result<PipelineAndRepoRoot> {
259 use crate::app::effect::{AppEffect, AppEffectResult};
260 use crate::app::runner::command_handlers::handle_plumbing_commands;
261 use crate::app::runner::pipeline_execution::{
262 command_requires_prompt_setup, handle_repo_commands_without_prompt_setup,
263 prepare_pipeline_or_exit,
264 };
265 use crate::app::runner::pipeline_execution::{PipelinePreparationParams, RepoCommandParams};
266 use crate::app::runner::AgentSetupParams;
267
268 let RunPipelineWithHandlerParams {
269 args,
270 config,
271 registry,
272 developer_agent,
273 reviewer_agent,
274 developer_display: _,
275 reviewer_display: _,
276 config_path,
277 colors,
278 logger,
279 executor,
280 template_context: _,
281 } = params;
282
283 let handler = &mut crate::app::runtime_factory::create_effect_handler();
284
285 let early_repo_root = {
286 if let Some(dir) = args.working_dir_override.as_deref() {
287 match handler.execute(AppEffect::SetCurrentDir {
288 path: dir.to_path_buf(),
289 }) {
290 AppEffectResult::Ok => {}
291 AppEffectResult::Error(e) => anyhow::bail!(e),
292 other => anyhow::bail!("unexpected result from SetCurrentDir: {other:?}"),
293 }
294 }
295
296 match handler.execute(AppEffect::GitRequireRepo) {
297 AppEffectResult::Ok => {}
298 AppEffectResult::Error(e) => anyhow::bail!("Not in a git repository: {e}"),
299 other => anyhow::bail!("unexpected result from GitRequireRepo: {other:?}"),
300 }
301
302 match handler.execute(AppEffect::GitGetRepoRoot) {
303 AppEffectResult::Path(p) => p,
304 AppEffectResult::Error(e) => anyhow::bail!("Failed to get repo root: {e}"),
305 other => anyhow::bail!("unexpected result from GitGetRepoRoot: {other:?}"),
306 }
307 };
308
309 let workspace: Arc<dyn Workspace> =
310 Arc::new(crate::workspace::WorkspaceFs::new(early_repo_root.clone()));
311
312 if handle_plumbing_commands(&args, &logger, colors, handler, Some(workspace.as_ref()))? {
313 anyhow::bail!("plumbing commands should not return from run_pipeline");
314 }
315
316 if !command_requires_prompt_setup(&args)
317 && handle_repo_commands_without_prompt_setup(RepoCommandParams {
318 args: &args,
319 config: &config,
320 registry: ®istry,
321 developer_agent: &developer_agent,
322 reviewer_agent: &reviewer_agent,
323 logger: &logger,
324 colors,
325 executor: &executor,
326 app_handler: handler,
327 repo_root: workspace.root(),
328 workspace: &workspace,
329 })?
330 {
331 anyhow::bail!("repo commands should not return from run_pipeline");
332 }
333
334 let repo_root = match crate::app::runner::validate_and_setup_agents(
335 &AgentSetupParams {
336 config: &config,
337 registry: ®istry,
338 developer_agent: &developer_agent,
339 reviewer_agent: &reviewer_agent,
340 config_path: &config_path,
341 colors,
342 logger: &logger,
343 working_dir_override: args.working_dir_override.as_deref(),
344 },
345 handler,
346 )? {
347 Some(root) => root,
348 None => anyhow::bail!("agent validation/setup failed"),
349 };
350
351 let params = PipelinePreparationParams {
352 args,
353 config,
354 registry,
355 developer_agent,
356 reviewer_agent,
357 colors,
358 logger,
359 executor,
360 repo_root,
361 workspace,
362 handler,
363 };
364
365 let ctx = prepare_pipeline_or_exit(params)?
366 .ok_or_else(|| anyhow::anyhow!("pipeline preparation returned None"))?;
367
368 Ok(PipelineAndRepoRoot {
369 ctx,
370 repo_root: early_repo_root,
371 })
372}