use crate::agents::claude_agent::ClaudeAgent;
use crate::agents::gemini_agent::GeminiAgent;
use crate::agents::gpt4_agent::GPT4Agent;
use crate::agents::gpt4_agent::{CognitiveAgent, ProposedEdit};
use crate::dag::Node;
use crate::memory::SymbolicContext;
use std::collections::HashMap;
use std::fs;
#[allow(dead_code)]
pub fn load_code_context(file: &str) -> Vec<String> {
fs::read_to_string(file)
.map(|s| s.lines().map(|l| l.to_string()).collect())
.unwrap_or_default()
}
#[allow(dead_code)]
pub fn backup_file(file_path: &str) -> Result<String, std::io::Error> {
let backup_path = format!("{}.backup", file_path);
fs::copy(file_path, &backup_path)?;
Ok(backup_path)
}
#[allow(dead_code)]
pub fn restore_file(file_path: &str, backup_path: &str) -> Result<(), std::io::Error> {
fs::copy(backup_path, file_path)?;
fs::remove_file(backup_path)?;
Ok(())
}
#[allow(dead_code)]
pub fn apply_edit_to_file(
file_path: &str,
edit: &ProposedEdit,
) -> Result<(), Box<dyn std::error::Error>> {
let lines = load_code_context(file_path);
let mut new_lines = lines.clone();
if edit.line_range.0 == 0 || edit.line_range.1 > lines.len() {
return Err(format!(
"Invalid line range {:?} for file with {} lines",
edit.line_range,
lines.len()
)
.into());
}
let start_idx = edit.line_range.0 - 1;
let end_idx = edit.line_range.1.min(lines.len());
let edit_lines: Vec<String> = edit.new_code.lines().map(|s| s.to_string()).collect();
new_lines.splice(start_idx..end_idx, edit_lines);
let content = new_lines.join("\n");
fs::write(file_path, content)?;
println!("✅ Applied edit to {}: {}", file_path, edit.reason);
Ok(())
}
#[allow(dead_code)]
pub fn apply_consensus_edits(
edits: &[&ProposedEdit],
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let mut backups = Vec::new();
let mut edits_by_file: HashMap<String, Vec<&ProposedEdit>> = HashMap::new();
for edit in edits {
edits_by_file
.entry(edit.file.clone())
.or_default()
.push(edit);
}
for (file_path, file_edits) in edits_by_file {
if std::path::Path::new(&file_path).exists() {
let backup_path = backup_file(&file_path)?;
backups.push((file_path.clone(), backup_path));
let mut sorted_edits = file_edits;
sorted_edits.sort_by(|a, b| b.line_range.0.cmp(&a.line_range.0));
for edit in sorted_edits {
apply_edit_to_file(&file_path, edit)?;
}
} else {
println!("⚠️ File {} does not exist, skipping edit", file_path);
}
}
Ok(backups.into_iter().map(|(_, backup)| backup).collect())
}
#[allow(dead_code)]
pub fn show_file_diff(
file_path: &str,
backup_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let original = fs::read_to_string(backup_path)?;
let edited = fs::read_to_string(file_path)?;
println!("\n📄 File: {}", file_path);
println!("{}", "=".repeat(60));
let original_lines: Vec<&str> = original.lines().collect();
let edited_lines: Vec<&str> = edited.lines().collect();
let max_lines = original_lines.len().max(edited_lines.len());
for i in 0..max_lines {
let orig_line = original_lines.get(i).unwrap_or(&"");
let edit_line = edited_lines.get(i).unwrap_or(&"");
if orig_line != edit_line {
if !orig_line.is_empty() {
println!("- {}: {}", i + 1, orig_line);
}
if !edit_line.is_empty() {
println!("+ {}: {}", i + 1, edit_line);
}
} else if !orig_line.is_empty() {
println!(" {}: {}", i + 1, orig_line);
}
if i > 20 {
println!(" ... ({} more lines)", max_lines - i - 1);
break;
}
}
Ok(())
}
pub fn merge_edits(edits: &[ProposedEdit]) -> Vec<&ProposedEdit> {
let mut votes: HashMap<(String, (usize, usize)), Vec<&ProposedEdit>> = HashMap::new();
for edit in edits {
votes
.entry((edit.file.clone(), edit.line_range))
.or_default()
.push(edit);
}
votes
.values()
.filter(|v| v.len() >= 2)
.map(|v| v[0])
.collect()
}
pub fn run_feedback_loop(ctx: &SymbolicContext, _dag: &[Node]) {
let agents: Vec<Box<dyn CognitiveAgent>> = vec![
Box::new(GPT4Agent),
Box::new(ClaudeAgent),
Box::new(GeminiAgent),
];
let mut all_edits = vec![];
for agent in &agents {
let edit = agent.propose_edit(ctx);
all_edits.push(edit);
}
let merged = merge_edits(&all_edits);
let meta_log = serde_json::json!({
"timestamp": chrono::Utc::now().to_rfc3339(),
"proposals": all_edits.iter().map(|e| serde_json::json!({
"file": e.file,
"line_range": e.line_range,
"new_code": e.new_code,
"reason": e.reason,
"confidence": e.confidence,
})).collect::<Vec<_>>(),
"merged": merged.iter().map(|e| serde_json::json!({
"file": e.file,
"line_range": e.line_range,
"new_code": e.new_code,
"reason": e.reason,
"confidence": e.confidence,
})).collect::<Vec<_>>()
});
fs::write(
"meta_log.json",
serde_json::to_string_pretty(&meta_log).unwrap(),
)
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents::gpt4_agent::ProposedEdit;
use crate::memory::SymbolicContext;
#[test]
fn test_merge_edits_consensus() {
let edits = vec![
ProposedEdit {
file: "test.rs".to_string(),
line_range: (10, 12),
new_code: "// Improved code".to_string(),
reason: "Better readability".to_string(),
confidence: 0.95,
},
ProposedEdit {
file: "test.rs".to_string(),
line_range: (10, 12),
new_code: "// Enhanced code".to_string(),
reason: "Better performance".to_string(),
confidence: 0.90,
},
ProposedEdit {
file: "other.rs".to_string(),
line_range: (5, 7),
new_code: "// Solo edit".to_string(),
reason: "Isolated change".to_string(),
confidence: 0.85,
},
];
let merged = merge_edits(&edits);
assert_eq!(merged.len(), 1); assert_eq!(merged[0].file, "test.rs");
assert_eq!(merged[0].line_range, (10, 12));
}
#[test]
fn test_feedback_loop_execution() {
let mut ctx = SymbolicContext::new();
ctx.set("current_task", "improve code quality");
let nodes = vec![];
run_feedback_loop(&ctx, &nodes);
assert!(std::path::Path::new("meta_log.json").exists());
let log_content = std::fs::read_to_string("meta_log.json").unwrap();
let log: serde_json::Value = serde_json::from_str(&log_content).unwrap();
assert!(log["proposals"].is_array());
assert_eq!(log["proposals"].as_array().unwrap().len(), 3); assert!(log["merged"].is_array());
std::fs::remove_file("meta_log.json").ok();
}
}