Skip to main content

ralph_workflow/app/
pipeline_setup.rs

1//! Pipeline setup boundary module.
2//!
3//! This module contains boundary functions for imperative pipeline setup operations.
4//! As a boundary module, it is exempt from functional programming lints.
5
6use 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: &registry,
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: &registry,
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}