use crate::agents::{AgentDrain, AgentRegistry};
use crate::app::effect::{AppEffect, AppEffectHandler, AppEffectResult};
use crate::config::Config;
use crate::files::{
delete_commit_message_file_with_workspace, read_commit_message_file_with_workspace,
write_commit_message_file_with_workspace,
};
use crate::logger::Colors;
use crate::logger::Logger;
use crate::prompts::TemplateContext;
use crate::workspace::Workspace;
use crate::ProcessExecutor;
use std::sync::Arc;
pub struct CommitGenerationConfig<'a> {
pub config: &'a Config,
pub template_context: &'a TemplateContext,
pub workspace: &'a dyn crate::workspace::Workspace,
pub workspace_arc: Arc<dyn crate::workspace::Workspace>,
pub registry: &'a AgentRegistry,
pub logger: &'a Logger,
pub colors: Colors,
pub developer_agent: &'a str,
pub reviewer_agent: &'a str,
pub executor: Arc<dyn ProcessExecutor>,
}
fn resolve_commit_message_agents(registry: &AgentRegistry, reviewer_agent: &str) -> Vec<String> {
if let Some(commit_binding) = registry.resolved_drain(AgentDrain::Commit) {
return commit_binding.agents.clone();
}
let review_chain = registry
.resolved_drain(AgentDrain::Review)
.map_or(&[] as &[String], |binding| binding.agents.as_slice());
if !review_chain.is_empty() {
return review_chain.to_vec();
}
vec![reviewer_agent.to_string()]
}
#[cfg(any(test, feature = "test-utils"))]
#[must_use]
pub fn resolve_commit_message_agents_for_testing(
config: &CommitGenerationConfig<'_>,
) -> Vec<String> {
resolve_commit_message_agents(config.registry, config.reviewer_agent)
}
pub fn get_commit_message_from_workspace(workspace: &dyn Workspace) -> anyhow::Result<String> {
read_commit_message_file_with_workspace(workspace).map_err(anyhow::Error::from)
}
pub fn handle_apply_commit_with_handler<H: AppEffectHandler>(
workspace: &dyn Workspace,
handler: &mut H,
logger: &Logger,
colors: Colors,
) -> anyhow::Result<()> {
let commit_msg = read_commit_message_file_with_workspace(workspace)?;
logger.info("Staging all changes...");
match handler.execute(AppEffect::GitAddAll) {
AppEffectResult::Ok | AppEffectResult::Bool(true | false) => {
}
AppEffectResult::Error(e) => anyhow::bail!("Failed to stage changes: {e}"),
other => anyhow::bail!("Unexpected result from GitAddAll: {other:?}"),
}
logger.info(&format!(
"Commit message: {}{}{}",
colors.cyan(),
commit_msg,
colors.reset()
));
logger.info("Creating commit...");
match handler.execute(AppEffect::GitCommit {
message: commit_msg,
user_name: None,
user_email: None,
}) {
AppEffectResult::String(oid)
| AppEffectResult::Commit(crate::app::effect::CommitResult::Success(oid)) => {
logger.success(&format!("Commit created successfully: {oid}"));
if let Err(err) = delete_commit_message_file_with_workspace(workspace) {
logger.warn(&format!("Failed to delete commit-message.txt: {err}"));
}
Ok(())
}
AppEffectResult::Commit(crate::app::effect::CommitResult::NoChanges)
| AppEffectResult::Ok => {
logger.warn("Nothing to commit (working tree clean)");
Ok(())
}
AppEffectResult::Error(e) => anyhow::bail!("Failed to create commit: {e}"),
other => anyhow::bail!("Unexpected result from GitCommit: {other:?}"),
}
}
pub fn handle_generate_commit_msg(
config: &CommitGenerationConfig<'_>,
app_handler: &mut dyn AppEffectHandler,
) -> anyhow::Result<()> {
config.logger.info("Generating commit message...");
let diff = match app_handler.execute(AppEffect::GitDiff) {
AppEffectResult::String(diff) => diff,
AppEffectResult::Error(err) => {
anyhow::bail!("Failed to get git diff: {err}");
}
other => anyhow::bail!("Unexpected result from GitDiff: {other:?}"),
};
if diff.trim().is_empty() {
config
.logger
.warn("No changes detected to generate a commit message for");
anyhow::bail!("No changes to commit");
}
let agents = resolve_commit_message_agents(config.registry, config.reviewer_agent);
let result = crate::app::plumbing_boundary::generate_commit_message_for_plumbing(
config, &diff, &agents,
)?;
let commit_message = match result.outcome {
crate::phases::commit::CommitMessageOutcome::Message(message) => message,
crate::phases::commit::CommitMessageOutcome::Skipped { reason } => {
config.logger.warn(&format!(
"No commit needed (agent requested skip): {reason}"
));
return Ok(());
}
};
config.logger.success("Commit message generated:");
config.logger.info(&format!(
"{}{}{}",
config.colors.cyan(),
commit_message,
config.colors.reset()
));
write_commit_message_file_with_workspace(config.workspace, &commit_message)?;
config
.logger
.info("Message saved to .agent/commit-message.txt");
config
.logger
.info("Run 'ralph --apply-commit' to create the commit");
Ok(())
}