ralph_workflow/app/
plumbing.rs

1//! Plumbing commands for low-level git operations.
2//!
3//! This module handles plumbing commands that operate directly on
4//! commit messages and git state without running the full pipeline:
5//! - `--show-commit-msg`: Display the stored commit message
6//! - `--apply-commit`: Stage and commit using the stored message
7//! - `--generate-commit-msg`: Generate a commit message for staged changes
8
9use crate::agents::AgentRegistry;
10use crate::config::Config;
11use crate::files::{
12    delete_commit_message_file, read_commit_message_file, write_commit_message_file,
13};
14use crate::git_helpers::{
15    get_repo_root, git_add_all, git_commit, git_diff, git_snapshot, require_git_repo,
16};
17use crate::logger::Colors;
18use crate::logger::Logger;
19use crate::phases::generate_commit_message;
20use crate::pipeline::PipelineRuntime;
21use crate::pipeline::Timer;
22use crate::prompts::TemplateContext;
23use std::env;
24
25/// Handles the `--show-commit-msg` command.
26///
27/// Reads and displays the commit message from `.agent/commit-message.txt`.
28///
29/// # Returns
30///
31/// Returns `Ok(())` on success or an error if the file cannot be read.
32pub fn handle_show_commit_msg() -> anyhow::Result<()> {
33    require_git_repo()?;
34    let repo_root = get_repo_root()?;
35    env::set_current_dir(&repo_root)?;
36
37    match read_commit_message_file() {
38        Ok(msg) => {
39            println!("{msg}");
40            Ok(())
41        }
42        Err(e) => {
43            anyhow::bail!("Failed to read commit message: {e}");
44        }
45    }
46}
47
48/// Handles the `--apply-commit` command.
49///
50/// Stages all changes and creates a commit using the stored commit message.
51/// After successful commit, deletes the commit message file.
52///
53/// # Arguments
54///
55/// * `logger` - Logger for info/warning messages
56/// * `colors` - Color configuration for output
57///
58/// # Returns
59///
60/// Returns `Ok(())` on success or an error if commit fails.
61pub fn handle_apply_commit(logger: &Logger, colors: Colors) -> anyhow::Result<()> {
62    require_git_repo()?;
63    let repo_root = get_repo_root()?;
64    env::set_current_dir(&repo_root)?;
65
66    let commit_msg = read_commit_message_file()?;
67
68    logger.info("Staging all changes...");
69    git_add_all()?;
70
71    // Show what we're committing (using libgit2 via git_snapshot)
72    if let Ok(status) = git_snapshot() {
73        if !status.is_empty() {
74            println!("{}Changes to commit:{}", colors.bold(), colors.reset());
75            for line in status.lines().take(20) {
76                println!("  {}{}{}", colors.dim(), line, colors.reset());
77            }
78            println!();
79        }
80    }
81
82    logger.info(&format!(
83        "Commit message: {}{}{}",
84        colors.cyan(),
85        commit_msg,
86        colors.reset()
87    ));
88
89    logger.info("Creating commit...");
90    // Note: Plumbing commands don't have access to config, so we use None
91    // for git identity and fall back to git config (via repo.signature())
92    if let Some(oid) = git_commit(&commit_msg, None, None)? {
93        logger.success(&format!("Commit created successfully: {oid}"));
94        // Clean up the commit message file
95        if let Err(err) = delete_commit_message_file() {
96            logger.warn(&format!("Failed to delete commit-message.txt: {err}"));
97        }
98    } else {
99        logger.warn("Nothing to commit (working tree clean)");
100    }
101
102    Ok(())
103}
104
105/// Handles the `--generate-commit-msg` command.
106///
107/// Generates a commit message for current changes using the standard pipeline.
108/// Uses the same `generate_commit_message()` function as the main workflow,
109/// ensuring consistent behavior with proper fallback chain support and logging.
110///
111/// # Arguments
112///
113/// * `config` - The pipeline configuration
114/// * `registry` - The agent registry
115/// * `logger` - Logger for info/warning messages
116/// * `colors` - Color configuration for output
117/// * `developer_agent` - Name of the developer agent to use (for commit generation)
118/// * `_reviewer_agent` - Name of the reviewer agent (not used, kept for API compatibility)
119///
120/// # Returns
121///
122/// Returns `Ok(())` on success or an error if generation fails.
123pub fn handle_generate_commit_msg(
124    config: &Config,
125    template_context: &TemplateContext,
126    registry: &AgentRegistry,
127    logger: &Logger,
128    colors: Colors,
129    developer_agent: &str,
130    _reviewer_agent: &str,
131) -> anyhow::Result<()> {
132    logger.info("Generating commit message...");
133
134    // Generate the commit message using the standard pipeline
135    let diff = git_diff()?;
136    if diff.trim().is_empty() {
137        logger.warn("No changes detected to generate a commit message for");
138        anyhow::bail!("No changes to commit");
139    }
140
141    // Create a timer for the pipeline runtime
142    let mut timer = Timer::new();
143
144    // Set up the pipeline runtime
145    let mut runtime = PipelineRuntime {
146        timer: &mut timer,
147        logger,
148        colors: &colors,
149        config,
150        #[cfg(any(test, feature = "test-utils"))]
151        agent_executor: None,
152    };
153
154    // Use the standard commit message generation from phases/commit.rs
155    // This provides:
156    // - Proper fallback chain support via run_with_fallback()
157    // - Structured logging to .agent/logs/
158    // - Meaningful error diagnostics
159    let result = generate_commit_message(
160        &diff,
161        registry,
162        &mut runtime,
163        developer_agent,
164        template_context,
165        &std::collections::HashMap::new(), // Empty prompt history for plumbing command
166    )
167    .map_err(|e| anyhow::anyhow!("Failed to generate commit message: {e}"))?;
168
169    if !result.success || result.message.trim().is_empty() {
170        anyhow::bail!("Commit message generation failed");
171    }
172
173    let commit_message = result.message;
174
175    logger.success("Commit message generated:");
176    println!();
177    println!("{}{}{}", colors.cyan(), commit_message, colors.reset());
178    println!();
179
180    // Write the message to file for use with --apply-commit
181    write_commit_message_file(&commit_message)?;
182
183    logger.info("Message saved to .agent/commit-message.txt");
184    logger.info("Run 'ralph --apply-commit' to create the commit");
185
186    Ok(())
187}