use std::sync::Arc;
use brainwires_core::Provider;
use serde::{Deserialize, Serialize};
use super::investigator::InvestigationResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeResult {
pub success: bool,
pub files_modified: Vec<String>,
pub summary: String,
pub diff_lines: u32,
pub iterations: u32,
}
pub struct ChangeMaker {
provider: Arc<dyn Provider>,
_max_iterations: u32,
}
impl ChangeMaker {
pub fn new(provider: Arc<dyn Provider>, max_iterations: u32) -> Self {
Self {
provider,
_max_iterations: max_iterations,
}
}
pub async fn make_changes(
&self,
investigation: &InvestigationResult,
repo_path: &str,
) -> anyhow::Result<ChangeResult> {
let prompt = format!(
"Fix the following issue in the codebase at {repo_path}:\n\n\
Summary: {}\n\
Approach: {}\n\
Affected files: {}\n\n\
Make the necessary code changes to fix this issue.",
investigation.summary,
investigation.approach,
investigation.affected_files.join(", ")
);
let messages = vec![brainwires_core::Message::user(prompt)];
let options = brainwires_core::ChatOptions::default();
match self.provider.chat(&messages, None, &options).await {
Ok(_response) => {
let diff_output = tokio::process::Command::new("git")
.args(["diff", "--stat"])
.current_dir(repo_path)
.output()
.await?;
let diff = String::from_utf8_lossy(&diff_output.stdout);
let diff_lines = diff.lines().count() as u32;
let files_output = tokio::process::Command::new("git")
.args(["diff", "--name-only"])
.current_dir(repo_path)
.output()
.await?;
let files: Vec<String> = String::from_utf8_lossy(&files_output.stdout)
.lines()
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(ChangeResult {
success: !files.is_empty(),
files_modified: files,
summary: "Changes applied based on investigation".to_string(),
diff_lines,
iterations: 1,
})
}
Err(e) => Ok(ChangeResult {
success: false,
files_modified: Vec::new(),
summary: format!("Failed to make changes: {e}"),
diff_lines: 0,
iterations: 1,
}),
}
}
pub async fn commit_changes(
&self,
repo_path: &str,
issue_number: u64,
summary: &str,
) -> anyhow::Result<String> {
let _ = tokio::process::Command::new("git")
.args(["add", "-A"])
.current_dir(repo_path)
.output()
.await?;
let message = format!("fix(#{issue_number}): {summary}");
let commit = tokio::process::Command::new("git")
.args(["commit", "-m", &message])
.current_dir(repo_path)
.output()
.await?;
if !commit.status.success() {
let stderr = String::from_utf8_lossy(&commit.stderr);
anyhow::bail!("git commit failed: {stderr}");
}
let hash = tokio::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(repo_path)
.output()
.await?;
Ok(String::from_utf8_lossy(&hash.stdout).trim().to_string())
}
}