Skip to main content

ralph_workflow/app/runner/
command_handlers.rs

1// Command handlers for listing and plumbing operations.
2//
3// This module contains:
4// - handle_listing_commands: Handles --list-agents, --list-providers, etc.
5// - handle_plumbing_commands: Handles --show-commit-msg, --apply-commit, etc.
6
7use crate::agents::AgentRegistry;
8use crate::app::effect::{AppEffect, AppEffectHandler, AppEffectResult};
9use crate::cli::handlers::template_mgmt::handle_template_commands;
10use crate::cli::{
11    handle_list_agents, handle_list_available_agents, handle_list_providers, handle_show_baseline,
12    Args,
13};
14use crate::logger::{Colors, Logger};
15
16/// Handles listing commands that don't require the full pipeline.
17///
18/// Returns `true` if a listing command was handled and we should exit.
19pub(super) fn handle_listing_commands(
20    args: &Args,
21    registry: &AgentRegistry,
22    colors: Colors,
23) -> bool {
24    if args.agent_list.list_agents {
25        handle_list_agents(registry);
26        return true;
27    }
28    if args.agent_list.list_available_agents {
29        handle_list_available_agents(registry);
30        return true;
31    }
32    if args.provider_list.list_providers {
33        handle_list_providers(colors);
34        return true;
35    }
36
37    // Handle template commands
38    let template_cmds = &args.template_commands;
39    if template_cmds.init_templates_enabled()
40        || template_cmds.validate
41        || template_cmds.show.is_some()
42        || template_cmds.list
43        || template_cmds.list_all
44        || template_cmds.variables.is_some()
45        || template_cmds.render.is_some()
46    {
47        let _ = handle_template_commands(template_cmds, colors);
48        return true;
49    }
50
51    false
52}
53
54/// Handles plumbing commands that require git repo but not full validation.
55///
56/// Returns `Ok(true)` if a plumbing command was handled and we should exit.
57/// Returns `Ok(false)` if we should continue to the main pipeline.
58///
59/// # Workspace Support
60///
61/// When `workspace` is `Some`, the workspace-aware versions of plumbing commands
62/// are used, enabling testing with `MemoryWorkspace`. When `None`, the direct
63/// filesystem versions are used (production behavior).
64pub(crate) fn handle_plumbing_commands<H: AppEffectHandler>(
65    args: &Args,
66    logger: &Logger,
67    colors: Colors,
68    handler: &mut H,
69    workspace: Option<&dyn crate::workspace::Workspace>,
70) -> anyhow::Result<bool> {
71    use crate::app::plumbing::{
72        get_commit_message_from_workspace, handle_apply_commit_with_handler,
73    };
74
75    // Helper to set up working directory for plumbing commands using the effect handler
76    fn setup_working_dir_via_handler<H: crate::app::effect::AppEffectHandler>(
77        override_dir: Option<&std::path::Path>,
78        handler: &mut H,
79    ) -> anyhow::Result<()> {
80        use crate::app::effect::{AppEffect, AppEffectResult};
81
82        if let Some(dir) = override_dir {
83            match handler.execute(AppEffect::SetCurrentDir {
84                path: dir.to_path_buf(),
85            }) {
86                AppEffectResult::Ok => Ok(()),
87                AppEffectResult::Error(e) => anyhow::bail!(e),
88                other => anyhow::bail!("unexpected result from SetCurrentDir: {other:?}"),
89            }
90        } else {
91            // Require git repo
92            match handler.execute(AppEffect::GitRequireRepo) {
93                AppEffectResult::Ok => {}
94                AppEffectResult::Error(e) => anyhow::bail!(e),
95                other => anyhow::bail!("unexpected result from GitRequireRepo: {other:?}"),
96            }
97            // Get repo root
98            let repo_root = match handler.execute(AppEffect::GitGetRepoRoot) {
99                AppEffectResult::Path(p) => p,
100                AppEffectResult::Error(e) => anyhow::bail!(e),
101                other => anyhow::bail!("unexpected result from GitGetRepoRoot: {other:?}"),
102            };
103            // Set current dir to repo root
104            match handler.execute(AppEffect::SetCurrentDir { path: repo_root }) {
105                AppEffectResult::Ok => Ok(()),
106                AppEffectResult::Error(e) => anyhow::bail!(e),
107                other => anyhow::bail!("unexpected result from SetCurrentDir: {other:?}"),
108            }
109        }
110    }
111
112    // Show commit message
113    if args.commit_display.show_commit_msg {
114        setup_working_dir_via_handler(args.working_dir_override.as_deref(), handler)?;
115        let ws = workspace.ok_or_else(|| {
116            anyhow::anyhow!(
117                "--show-commit-msg requires workspace context. Run this command after the pipeline has initialized."
118            )
119        })?;
120        let msg = get_commit_message_from_workspace(ws)?;
121        logger.info(&msg);
122        return Ok(true);
123    }
124
125    // Apply commit
126    if args.commit_plumbing.apply_commit {
127        setup_working_dir_via_handler(args.working_dir_override.as_deref(), handler)?;
128        let ws = workspace.ok_or_else(|| {
129            anyhow::anyhow!(
130                "--apply-commit requires workspace context. Run this command after the pipeline has initialized."
131            )
132        })?;
133        return handle_apply_commit_with_handler(ws, handler, logger, colors).map(|()| true);
134    }
135
136    // Reset start commit
137    if args.commit_display.reset_start_commit {
138        setup_working_dir_via_handler(args.working_dir_override.as_deref(), handler)?;
139
140        // Use the effect handler for reset_start_commit
141        return match handler.execute(AppEffect::GitResetStartCommit) {
142            AppEffectResult::String(oid) => {
143                // Simple case - just got the OID back
144                let short_oid = &oid[..8.min(oid.len())];
145                logger.success(&format!("Starting commit reference reset ({short_oid})"));
146                logger.info(".agent/start_commit has been updated");
147                Ok(true)
148            }
149            AppEffectResult::Error(e) => {
150                logger.error(&format!("Failed to reset starting commit: {e}"));
151                anyhow::bail!("Failed to reset starting commit");
152            }
153            other => anyhow::bail!("unexpected result from GitResetStartCommit: {other:?}"),
154        };
155    }
156
157    // Show baseline state
158    if args.commit_display.show_baseline {
159        setup_working_dir_via_handler(args.working_dir_override.as_deref(), handler)?;
160
161        return match handle_show_baseline() {
162            Ok(()) => Ok(true),
163            Err(e) => {
164                logger.error(&format!("Failed to show baseline: {e}"));
165                anyhow::bail!("Failed to show baseline");
166            }
167        };
168    }
169
170    Ok(false)
171}