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 repo_root: &'a std::path::Path,
141 pub workspace: &'a std::sync::Arc<dyn crate::workspace::Workspace>,
142}
143
144pub fn handle_repo_commands_boundary(
145 params: RepoCommandBoundaryParams<'_>,
146) -> anyhow::Result<bool> {
147 let RepoCommandBoundaryParams {
148 args,
149 config,
150 registry,
151 developer_agent,
152 reviewer_agent,
153 logger,
154 colors,
155 executor,
156 repo_root,
157 workspace,
158 } = params;
159
160 if args.recovery.dry_run {
161 crate::cli::handle_dry_run(
162 logger,
163 colors,
164 config,
165 developer_agent,
166 reviewer_agent,
167 repo_root,
168 workspace.as_ref(),
169 )?;
170 return Ok(true);
171 }
172
173 if args.rebase_flags.rebase_only {
174 let _cleanup_guard = mark_cleanup_guard_owned(
175 crate::app::runtime_factory::create_cleanup_guard(logger, workspace.as_ref(), false),
176 );
177 let _git_helpers = prepare_git_helpers_for_workspace(
178 repo_root,
179 workspace.as_ref(),
180 logger,
181 crate::app::runtime_factory::create_git_helpers(),
182 false,
183 );
184 let template_context = crate::prompts::TemplateContext::from_user_templates_dir(
185 config.user_templates_dir().cloned(),
186 );
187 crate::app::runner::pipeline_execution::handle_rebase_only(
188 args,
189 config,
190 &template_context,
191 logger,
192 colors,
193 executor,
194 repo_root,
195 )?;
196 return Ok(true);
197 }
198
199 if args.commit_plumbing.generate_commit_msg {
200 let _cleanup_guard = mark_cleanup_guard_owned(
201 crate::app::runtime_factory::create_cleanup_guard(logger, workspace.as_ref(), false),
202 );
203 let _git_helpers = prepare_git_helpers_for_workspace(
204 repo_root,
205 workspace.as_ref(),
206 logger,
207 crate::app::runtime_factory::create_git_helpers(),
208 false,
209 );
210 let template_context = crate::prompts::TemplateContext::from_user_templates_dir(
211 config.user_templates_dir().cloned(),
212 );
213 crate::app::plumbing::handle_generate_commit_msg(
214 &crate::app::plumbing::CommitGenerationConfig {
215 config,
216 template_context: &template_context,
217 workspace: workspace.as_ref(),
218 workspace_arc: std::sync::Arc::clone(workspace),
219 registry,
220 logger,
221 colors,
222 developer_agent,
223 reviewer_agent,
224 executor: std::sync::Arc::clone(executor),
225 },
226 )?;
227 return Ok(true);
228 }
229
230 Ok(false)
231}
232
233pub struct PipelineAndRepoRoot {
234 pub ctx: PipelineContext,
235 pub repo_root: PathBuf,
236}
237
238pub struct RunPipelineWithHandlerParams {
239 pub args: Args,
240 pub config: Config,
241 pub registry: AgentRegistry,
242 pub developer_agent: String,
243 pub reviewer_agent: String,
244 pub developer_display: String,
245 pub reviewer_display: String,
246 pub config_path: std::path::PathBuf,
247 pub colors: crate::logger::Colors,
248 pub logger: crate::logger::Logger,
249 pub executor: Arc<dyn crate::executor::ProcessExecutor>,
250 pub template_context: crate::prompts::TemplateContext,
251}
252
253pub fn run_pipeline_with_handler_boundary(
254 params: RunPipelineWithHandlerParams,
255) -> anyhow::Result<PipelineAndRepoRoot> {
256 use crate::app::effect::{AppEffect, AppEffectResult};
257 use crate::app::runner::command_handlers::handle_plumbing_commands;
258 use crate::app::runner::pipeline_execution::{
259 command_requires_prompt_setup, handle_repo_commands_without_prompt_setup,
260 prepare_pipeline_or_exit,
261 };
262 use crate::app::runner::pipeline_execution::{PipelinePreparationParams, RepoCommandParams};
263 use crate::app::runner::AgentSetupParams;
264
265 let RunPipelineWithHandlerParams {
266 args,
267 config,
268 registry,
269 developer_agent,
270 reviewer_agent,
271 developer_display: _,
272 reviewer_display: _,
273 config_path,
274 colors,
275 logger,
276 executor,
277 template_context: _,
278 } = params;
279
280 let handler = &mut crate::app::runtime_factory::create_effect_handler();
281
282 let early_repo_root = {
283 if let Some(dir) = args.working_dir_override.as_deref() {
284 match handler.execute(AppEffect::SetCurrentDir {
285 path: dir.to_path_buf(),
286 }) {
287 AppEffectResult::Ok => {}
288 AppEffectResult::Error(e) => anyhow::bail!(e),
289 other => anyhow::bail!("unexpected result from SetCurrentDir: {other:?}"),
290 }
291 }
292
293 match handler.execute(AppEffect::GitRequireRepo) {
294 AppEffectResult::Ok => {}
295 AppEffectResult::Error(e) => anyhow::bail!("Not in a git repository: {e}"),
296 other => anyhow::bail!("unexpected result from GitRequireRepo: {other:?}"),
297 }
298
299 match handler.execute(AppEffect::GitGetRepoRoot) {
300 AppEffectResult::Path(p) => p,
301 AppEffectResult::Error(e) => anyhow::bail!("Failed to get repo root: {e}"),
302 other => anyhow::bail!("unexpected result from GitGetRepoRoot: {other:?}"),
303 }
304 };
305
306 let workspace: Arc<dyn Workspace> =
307 Arc::new(crate::workspace::WorkspaceFs::new(early_repo_root.clone()));
308
309 if handle_plumbing_commands(&args, &logger, colors, handler, Some(workspace.as_ref()))? {
310 anyhow::bail!("plumbing commands should not return from run_pipeline");
311 }
312
313 if !command_requires_prompt_setup(&args)
314 && handle_repo_commands_without_prompt_setup(RepoCommandParams {
315 args: &args,
316 config: &config,
317 registry: ®istry,
318 developer_agent: &developer_agent,
319 reviewer_agent: &reviewer_agent,
320 logger: &logger,
321 colors,
322 executor: &executor,
323 repo_root: workspace.root(),
324 workspace: &workspace,
325 })?
326 {
327 anyhow::bail!("repo commands should not return from run_pipeline");
328 }
329
330 let repo_root = match crate::app::runner::validate_and_setup_agents(
331 &AgentSetupParams {
332 config: &config,
333 registry: ®istry,
334 developer_agent: &developer_agent,
335 reviewer_agent: &reviewer_agent,
336 config_path: &config_path,
337 colors,
338 logger: &logger,
339 working_dir_override: args.working_dir_override.as_deref(),
340 },
341 handler,
342 )? {
343 Some(root) => root,
344 None => anyhow::bail!("agent validation/setup failed"),
345 };
346
347 let params = PipelinePreparationParams {
348 args,
349 config,
350 registry,
351 developer_agent,
352 reviewer_agent,
353 colors,
354 logger,
355 executor,
356 repo_root,
357 workspace,
358 handler,
359 };
360
361 let ctx = prepare_pipeline_or_exit(params)?
362 .ok_or_else(|| anyhow::anyhow!("pipeline preparation returned None"))?;
363
364 Ok(PipelineAndRepoRoot {
365 ctx,
366 repo_root: early_repo_root,
367 })
368}