1use crate::agents::claude_agent::ClaudeAgent;
5use crate::agents::gemini_agent::GeminiAgent;
6use crate::agents::gpt4_agent::GPT4Agent;
7use crate::agents::gpt4_agent::{CognitiveAgent, ProposedEdit};
8use crate::dag::Node;
9use crate::memory::SymbolicContext;
10use std::collections::HashMap;
11use std::fs;
12
13#[allow(dead_code)]
15pub fn load_code_context(file: &str) -> Vec<String> {
16 fs::read_to_string(file)
17 .map(|s| s.lines().map(|l| l.to_string()).collect())
18 .unwrap_or_default()
19}
20
21#[allow(dead_code)]
23pub fn backup_file(file_path: &str) -> Result<String, std::io::Error> {
24 let backup_path = format!("{}.backup", file_path);
25 fs::copy(file_path, &backup_path)?;
26 Ok(backup_path)
27}
28
29#[allow(dead_code)]
31pub fn restore_file(file_path: &str, backup_path: &str) -> Result<(), std::io::Error> {
32 fs::copy(backup_path, file_path)?;
33 fs::remove_file(backup_path)?;
34 Ok(())
35}
36
37#[allow(dead_code)]
39pub fn apply_edit_to_file(
40 file_path: &str,
41 edit: &ProposedEdit,
42) -> Result<(), Box<dyn std::error::Error>> {
43 let lines = load_code_context(file_path);
44 let mut new_lines = lines.clone();
45
46 if edit.line_range.0 == 0 || edit.line_range.1 > lines.len() {
48 return Err(format!(
49 "Invalid line range {:?} for file with {} lines",
50 edit.line_range,
51 lines.len()
52 )
53 .into());
54 }
55
56 let start_idx = edit.line_range.0 - 1;
58 let end_idx = edit.line_range.1.min(lines.len());
59
60 let edit_lines: Vec<String> = edit.new_code.lines().map(|s| s.to_string()).collect();
62 new_lines.splice(start_idx..end_idx, edit_lines);
63
64 let content = new_lines.join("\n");
66 fs::write(file_path, content)?;
67
68 println!("ā
Applied edit to {}: {}", file_path, edit.reason);
69 Ok(())
70}
71
72#[allow(dead_code)]
74pub fn apply_consensus_edits(
75 edits: &[&ProposedEdit],
76) -> Result<Vec<String>, Box<dyn std::error::Error>> {
77 let mut backups = Vec::new();
78
79 let mut edits_by_file: HashMap<String, Vec<&ProposedEdit>> = HashMap::new();
81 for edit in edits {
82 edits_by_file
83 .entry(edit.file.clone())
84 .or_default()
85 .push(edit);
86 }
87
88 for (file_path, file_edits) in edits_by_file {
89 if std::path::Path::new(&file_path).exists() {
91 let backup_path = backup_file(&file_path)?;
92 backups.push((file_path.clone(), backup_path));
93
94 let mut sorted_edits = file_edits;
96 sorted_edits.sort_by(|a, b| b.line_range.0.cmp(&a.line_range.0));
97
98 for edit in sorted_edits {
99 apply_edit_to_file(&file_path, edit)?;
100 }
101 } else {
102 println!("ā ļø File {} does not exist, skipping edit", file_path);
103 }
104 }
105
106 Ok(backups.into_iter().map(|(_, backup)| backup).collect())
107}
108
109#[allow(dead_code)]
111pub fn show_file_diff(
112 file_path: &str,
113 backup_path: &str,
114) -> Result<(), Box<dyn std::error::Error>> {
115 let original = fs::read_to_string(backup_path)?;
116 let edited = fs::read_to_string(file_path)?;
117
118 println!("\nš File: {}", file_path);
119 println!("{}", "=".repeat(60));
120
121 let original_lines: Vec<&str> = original.lines().collect();
122 let edited_lines: Vec<&str> = edited.lines().collect();
123
124 let max_lines = original_lines.len().max(edited_lines.len());
125
126 for i in 0..max_lines {
127 let orig_line = original_lines.get(i).unwrap_or(&"");
128 let edit_line = edited_lines.get(i).unwrap_or(&"");
129
130 if orig_line != edit_line {
131 if !orig_line.is_empty() {
132 println!("- {}: {}", i + 1, orig_line);
133 }
134 if !edit_line.is_empty() {
135 println!("+ {}: {}", i + 1, edit_line);
136 }
137 } else if !orig_line.is_empty() {
138 println!(" {}: {}", i + 1, orig_line);
139 }
140
141 if i > 20 {
143 println!(" ... ({} more lines)", max_lines - i - 1);
144 break;
145 }
146 }
147
148 Ok(())
149}
150
151pub fn merge_edits(edits: &[ProposedEdit]) -> Vec<&ProposedEdit> {
153 let mut votes: HashMap<(String, (usize, usize)), Vec<&ProposedEdit>> = HashMap::new();
154 for edit in edits {
155 votes
156 .entry((edit.file.clone(), edit.line_range))
157 .or_default()
158 .push(edit);
159 }
160 votes
161 .values()
162 .filter(|v| v.len() >= 2)
163 .map(|v| v[0])
164 .collect()
165}
166
167pub fn run_feedback_loop(ctx: &SymbolicContext, _dag: &[Node]) {
169 let agents: Vec<Box<dyn CognitiveAgent>> = vec![
170 Box::new(GPT4Agent),
171 Box::new(ClaudeAgent),
172 Box::new(GeminiAgent),
173 ];
174 let mut all_edits = vec![];
175 for agent in &agents {
176 let edit = agent.propose_edit(ctx);
177 all_edits.push(edit);
178 }
179 let merged = merge_edits(&all_edits);
180 let meta_log = serde_json::json!({
182 "timestamp": chrono::Utc::now().to_rfc3339(),
183 "proposals": all_edits.iter().map(|e| serde_json::json!({
184 "file": e.file,
185 "line_range": e.line_range,
186 "new_code": e.new_code,
187 "reason": e.reason,
188 "confidence": e.confidence,
189 })).collect::<Vec<_>>(),
190 "merged": merged.iter().map(|e| serde_json::json!({
191 "file": e.file,
192 "line_range": e.line_range,
193 "new_code": e.new_code,
194 "reason": e.reason,
195 "confidence": e.confidence,
196 })).collect::<Vec<_>>()
197 });
198 fs::write(
199 "meta_log.json",
200 serde_json::to_string_pretty(&meta_log).unwrap(),
201 )
202 .unwrap();
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::agents::gpt4_agent::ProposedEdit;
209 use crate::memory::SymbolicContext;
210
211 #[test]
212 fn test_merge_edits_consensus() {
213 let edits = vec![
214 ProposedEdit {
215 file: "test.rs".to_string(),
216 line_range: (10, 12),
217 new_code: "// Improved code".to_string(),
218 reason: "Better readability".to_string(),
219 confidence: 0.95,
220 },
221 ProposedEdit {
222 file: "test.rs".to_string(),
223 line_range: (10, 12),
224 new_code: "// Enhanced code".to_string(),
225 reason: "Better performance".to_string(),
226 confidence: 0.90,
227 },
228 ProposedEdit {
229 file: "other.rs".to_string(),
230 line_range: (5, 7),
231 new_code: "// Solo edit".to_string(),
232 reason: "Isolated change".to_string(),
233 confidence: 0.85,
234 },
235 ];
236
237 let merged = merge_edits(&edits);
238 assert_eq!(merged.len(), 1); assert_eq!(merged[0].file, "test.rs");
240 assert_eq!(merged[0].line_range, (10, 12));
241 }
242
243 #[test]
244 fn test_feedback_loop_execution() {
245 let mut ctx = SymbolicContext::new();
246 ctx.set("current_task", "improve code quality");
247
248 let nodes = vec![];
249
250 run_feedback_loop(&ctx, &nodes);
252
253 assert!(std::path::Path::new("meta_log.json").exists());
255
256 let log_content = std::fs::read_to_string("meta_log.json").unwrap();
258 let log: serde_json::Value = serde_json::from_str(&log_content).unwrap();
259
260 assert!(log["proposals"].is_array());
261 assert_eq!(log["proposals"].as_array().unwrap().len(), 3); assert!(log["merged"].is_array());
263
264 std::fs::remove_file("meta_log.json").ok();
266 }
267}