write/read file support
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -784,6 +784,7 @@ name = "g3-cli"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"g3-config",
|
"g3-config",
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ rustyline = "17.0.1"
|
|||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ use g3_config::Config;
|
|||||||
use g3_core::Agent;
|
use g3_core::Agent;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use std::io::Write;
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{Write, BufWriter};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
@@ -218,36 +221,51 @@ async fn run_interactive(mut agent: Agent, show_prompt: bool, show_code: bool) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> {
|
async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||||
println!("🤖 G3 AI Coding Agent - Autonomous Mode");
|
// Set up workspace directory
|
||||||
println!("🎯 Looking for requirements.md in current directory...");
|
let workspace_dir = setup_workspace_directory()?;
|
||||||
|
|
||||||
|
// Set up logging
|
||||||
|
let logger = AutonomousLogger::new(&workspace_dir)?;
|
||||||
|
|
||||||
|
logger.log_section("G3 AUTONOMOUS MODE SESSION STARTED");
|
||||||
|
logger.log(&format!("🤖 G3 AI Coding Agent - Autonomous Mode"));
|
||||||
|
logger.log(&format!("📁 Using workspace directory: {}", workspace_dir.display()));
|
||||||
|
|
||||||
|
// Change to workspace directory
|
||||||
|
std::env::set_current_dir(&workspace_dir)?;
|
||||||
|
logger.log("📂 Changed to workspace directory");
|
||||||
|
|
||||||
|
logger.log("🎯 Looking for requirements.md in workspace directory...");
|
||||||
|
|
||||||
// Check if requirements.md exists
|
// Check if requirements.md exists
|
||||||
let requirements_path = std::path::Path::new("requirements.md");
|
let requirements_path = workspace_dir.join("requirements.md");
|
||||||
if !requirements_path.exists() {
|
if !requirements_path.exists() {
|
||||||
println!("❌ Error: requirements.md not found in current directory");
|
logger.log("❌ Error: requirements.md not found in workspace directory");
|
||||||
println!(" Please create a requirements.md file with your project requirements");
|
logger.log(&format!(" Please create a requirements.md file with your project requirements at:"));
|
||||||
|
logger.log(&format!(" {}", requirements_path.display()));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read requirements.md
|
// Read requirements.md
|
||||||
let requirements = match std::fs::read_to_string(requirements_path) {
|
let requirements = match std::fs::read_to_string(&requirements_path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("❌ Error reading requirements.md: {}", e);
|
logger.log(&format!("❌ Error reading requirements.md: {}", e));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("📋 Requirements loaded from requirements.md");
|
logger.log("📋 Requirements loaded from requirements.md");
|
||||||
println!("🔄 Starting coach-player feedback loop...");
|
logger.log(&format!("Requirements content:\n{}", requirements));
|
||||||
println!();
|
logger.log("🔄 Starting coach-player feedback loop...");
|
||||||
|
logger.log("");
|
||||||
|
|
||||||
const MAX_TURNS: usize = 5;
|
const MAX_TURNS: usize = 5;
|
||||||
let mut turn = 1;
|
let mut turn = 1;
|
||||||
let mut coach_feedback = String::new();
|
let mut coach_feedback = String::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
println!("━━━ Turn {}/{} - Player Mode ━━━", turn, MAX_TURNS);
|
logger.log_section(&format!("TURN {}/{} - PLAYER MODE", turn, MAX_TURNS));
|
||||||
|
|
||||||
// Player mode: implement requirements (with coach feedback if available)
|
// Player mode: implement requirements (with coach feedback if available)
|
||||||
let player_prompt = if coach_feedback.is_empty() {
|
let player_prompt = if coach_feedback.is_empty() {
|
||||||
@@ -262,18 +280,27 @@ async fn run_autonomous(mut agent: Agent, show_prompt: bool, show_code: bool) ->
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.log("🎯 Starting player implementation...");
|
||||||
|
if !coach_feedback.is_empty() {
|
||||||
|
logger.log("📝 Incorporating coach feedback from previous turn");
|
||||||
|
}
|
||||||
|
|
||||||
let _player_result = agent
|
let _player_result = agent
|
||||||
.execute_task_with_timing(&player_prompt, None, false, show_prompt, show_code, true)
|
.execute_task_with_timing(&player_prompt, None, false, show_prompt, show_code, true)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("\n🎯 Player implementation completed");
|
logger.log("🎯 Player implementation completed");
|
||||||
println!();
|
logger.log("");
|
||||||
|
|
||||||
// Create a new agent instance for coach mode to ensure fresh context
|
// Create a new agent instance for coach mode to ensure fresh context
|
||||||
|
// Make sure the coach agent also operates in the workspace directory
|
||||||
let config = g3_config::Config::load(None)?;
|
let config = g3_config::Config::load(None)?;
|
||||||
let mut coach_agent = Agent::new(config).await?;
|
let mut coach_agent = Agent::new(config).await?;
|
||||||
|
|
||||||
|
// Ensure coach agent is also in the workspace directory
|
||||||
|
std::env::set_current_dir(&workspace_dir)?;
|
||||||
|
|
||||||
println!("━━━ Turn {}/{} - Coach Mode ━━━", turn, MAX_TURNS);
|
logger.log_section(&format!("TURN {}/{} - COACH MODE", turn, MAX_TURNS));
|
||||||
|
|
||||||
// Coach mode: critique the implementation
|
// Coach mode: critique the implementation
|
||||||
let coach_prompt = format!(
|
let coach_prompt = format!(
|
||||||
@@ -295,23 +322,28 @@ Keep your response concise and focused on actionable items.",
|
|||||||
requirements
|
requirements
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.log("🎓 Starting coach review...");
|
||||||
|
|
||||||
let coach_result = coach_agent
|
let coach_result = coach_agent
|
||||||
.execute_task_with_timing(&coach_prompt, None, false, show_prompt, show_code, true)
|
.execute_task_with_timing(&coach_prompt, None, false, show_prompt, show_code, true)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!("\n🎓 Coach review completed");
|
logger.log("🎓 Coach review completed");
|
||||||
|
logger.log(&format!("Coach feedback: {}", coach_result));
|
||||||
|
|
||||||
// Check if coach approved the implementation
|
// Check if coach approved the implementation
|
||||||
if coach_result.contains("IMPLEMENTATION_APPROVED") {
|
if coach_result.contains("IMPLEMENTATION_APPROVED") {
|
||||||
println!("\n✅ Coach approved the implementation!");
|
logger.log_section("SESSION COMPLETED - IMPLEMENTATION APPROVED");
|
||||||
println!("🎉 Autonomous mode completed successfully");
|
logger.log("✅ Coach approved the implementation!");
|
||||||
|
logger.log("🎉 Autonomous mode completed successfully");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we've reached max turns
|
// Check if we've reached max turns
|
||||||
if turn >= MAX_TURNS {
|
if turn >= MAX_TURNS {
|
||||||
println!("\n⏰ Maximum turns ({}) reached", MAX_TURNS);
|
logger.log_section("SESSION COMPLETED - MAX TURNS REACHED");
|
||||||
println!("🔄 Autonomous mode completed (max iterations)");
|
logger.log(&format!("⏰ Maximum turns ({}) reached", MAX_TURNS));
|
||||||
|
logger.log("🔄 Autonomous mode completed (max iterations)");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,11 +351,12 @@ Keep your response concise and focused on actionable items.",
|
|||||||
coach_feedback = coach_result;
|
coach_feedback = coach_result;
|
||||||
turn += 1;
|
turn += 1;
|
||||||
|
|
||||||
println!("\n🔄 Coach provided feedback for next iteration");
|
logger.log("🔄 Coach provided feedback for next iteration");
|
||||||
println!("📝 Preparing to incorporate feedback in turn {}", turn);
|
logger.log(&format!("📝 Preparing to incorporate feedback in turn {}", turn));
|
||||||
println!();
|
logger.log("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.log_section("G3 AUTONOMOUS MODE SESSION ENDED");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,4 +380,80 @@ fn display_context_progress(agent: &Agent) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up the workspace directory for autonomous mode
|
||||||
|
/// Uses G3_WORKSPACE environment variable or defaults to ~/tmp/workspace
|
||||||
|
fn setup_workspace_directory() -> Result<PathBuf> {
|
||||||
|
let workspace_dir = if let Ok(env_workspace) = std::env::var("G3_WORKSPACE") {
|
||||||
|
PathBuf::from(env_workspace)
|
||||||
|
} else {
|
||||||
|
// Default to ~/tmp/workspace
|
||||||
|
let home_dir = dirs::home_dir()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
|
||||||
|
home_dir.join("tmp").join("workspace")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the directory if it doesn't exist
|
||||||
|
if !workspace_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&workspace_dir)?;
|
||||||
|
println!("📁 Created workspace directory: {}", workspace_dir.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(workspace_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logger for autonomous mode that writes to both console and log file
|
||||||
|
struct AutonomousLogger {
|
||||||
|
log_writer: Arc<Mutex<BufWriter<std::fs::File>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutonomousLogger {
|
||||||
|
fn new(workspace_dir: &PathBuf) -> Result<Self> {
|
||||||
|
// Create logs subdirectory
|
||||||
|
let logs_dir = workspace_dir.join("logs");
|
||||||
|
if !logs_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&logs_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create log file with timestamp in logs subdirectory
|
||||||
|
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
||||||
|
let log_path = logs_dir.join(format!("g3_autonomous_{}.log", timestamp));
|
||||||
|
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&log_path)?;
|
||||||
|
|
||||||
|
let log_writer = Arc::new(Mutex::new(BufWriter::new(file)));
|
||||||
|
|
||||||
|
println!("📝 Logging autonomous session to: {}", log_path.display());
|
||||||
|
|
||||||
|
Ok(Self { log_writer })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, message: &str) {
|
||||||
|
// Print to console
|
||||||
|
println!("{}", message);
|
||||||
|
|
||||||
|
// Write to log file with timestamp
|
||||||
|
if let Ok(mut writer) = self.log_writer.lock() {
|
||||||
|
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
|
||||||
|
let _ = writeln!(writer, "[{}] {}", timestamp, message);
|
||||||
|
let _ = writer.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_section(&self, section: &str) {
|
||||||
|
let separator = "=".repeat(80);
|
||||||
|
let message = format!("{}\n{}\n{}", separator, section, separator);
|
||||||
|
self.log(&message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_subsection(&self, subsection: &str) {
|
||||||
|
let separator = "-".repeat(60);
|
||||||
|
let message = format!("{}\n{}\n{}", separator, subsection, separator);
|
||||||
|
self.log(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -853,32 +853,32 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
"required": ["file_path", "content"]
|
"required": ["file_path", "content"]
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Tool {
|
// Tool {
|
||||||
name: "edit_file".to_string(),
|
// name: "edit_file".to_string(),
|
||||||
description: "Edit a specific range of lines in a file".to_string(),
|
// description: "Edit a specific range of lines in a file".to_string(),
|
||||||
input_schema: json!({
|
// input_schema: json!({
|
||||||
"type": "object",
|
// "type": "object",
|
||||||
"properties": {
|
// "properties": {
|
||||||
"file_path": {
|
// "file_path": {
|
||||||
"type": "string",
|
// "type": "string",
|
||||||
"description": "The path to the file to edit"
|
// "description": "The path to the file to edit"
|
||||||
},
|
// },
|
||||||
"start_line": {
|
// "start_line": {
|
||||||
"type": "integer",
|
// "type": "integer",
|
||||||
"description": "The starting line number (1-based) of the range to replace"
|
// "description": "The starting line number (1-based) of the range to replace"
|
||||||
},
|
// },
|
||||||
"end_line": {
|
// "end_line": {
|
||||||
"type": "integer",
|
// "type": "integer",
|
||||||
"description": "The ending line number (1-based) of the range to replace"
|
// "description": "The ending line number (1-based) of the range to replace"
|
||||||
},
|
// },
|
||||||
"new_text": {
|
// "new_text": {
|
||||||
"type": "string",
|
// "type": "string",
|
||||||
"description": "The new text to replace the specified range"
|
// "description": "The new text to replace the specified range"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"required": ["file_path", "start_line", "end_line", "new_text"]
|
// "required": ["file_path", "start_line", "end_line", "new_text"]
|
||||||
}),
|
// }),
|
||||||
},
|
// },
|
||||||
Tool {
|
Tool {
|
||||||
name: "final_output".to_string(),
|
name: "final_output".to_string(),
|
||||||
description: "Signal task completion with a detailed summary".to_string(),
|
description: "Signal task completion with a detailed summary".to_string(),
|
||||||
@@ -1074,7 +1074,7 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
} else {
|
} else {
|
||||||
s.clone()
|
s.clone()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => value.to_string(),
|
_ => value.to_string(),
|
||||||
};
|
};
|
||||||
println!("│ {}", value_str);
|
println!("│ {}", value_str);
|
||||||
@@ -1288,7 +1288,10 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
match std::fs::read_to_string(path_str) {
|
match std::fs::read_to_string(path_str) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
let line_count = content.lines().count();
|
let line_count = content.lines().count();
|
||||||
Ok(format!("📄 File content ({} lines):\n{}", line_count, content))
|
Ok(format!(
|
||||||
|
"📄 File content ({} lines):\n{}",
|
||||||
|
line_count, content
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Err(e) => Ok(format!("❌ Failed to read file '{}': {}", path_str, e)),
|
Err(e) => Ok(format!("❌ Failed to read file '{}': {}", path_str, e)),
|
||||||
}
|
}
|
||||||
@@ -1303,24 +1306,34 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
debug!("Processing write_file tool call");
|
debug!("Processing write_file tool call");
|
||||||
let file_path = tool_call.args.get("file_path");
|
let file_path = tool_call.args.get("file_path");
|
||||||
let content = tool_call.args.get("content");
|
let content = tool_call.args.get("content");
|
||||||
|
|
||||||
if let (Some(path_val), Some(content_val)) = (file_path, content) {
|
if let (Some(path_val), Some(content_val)) = (file_path, content) {
|
||||||
if let (Some(path_str), Some(content_str)) = (path_val.as_str(), content_val.as_str()) {
|
if let (Some(path_str), Some(content_str)) =
|
||||||
|
(path_val.as_str(), content_val.as_str())
|
||||||
|
{
|
||||||
debug!("Writing to file: {}", path_str);
|
debug!("Writing to file: {}", path_str);
|
||||||
|
|
||||||
// Create parent directories if they don't exist
|
// Create parent directories if they don't exist
|
||||||
if let Some(parent) = std::path::Path::new(path_str).parent() {
|
if let Some(parent) = std::path::Path::new(path_str).parent() {
|
||||||
if let Err(e) = std::fs::create_dir_all(parent) {
|
if let Err(e) = std::fs::create_dir_all(parent) {
|
||||||
return Ok(format!("❌ Failed to create parent directories for '{}': {}", path_str, e));
|
return Ok(format!(
|
||||||
|
"❌ Failed to create parent directories for '{}': {}",
|
||||||
|
path_str, e
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match std::fs::write(path_str, content_str) {
|
match std::fs::write(path_str, content_str) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let line_count = content_str.lines().count();
|
let line_count = content_str.lines().count();
|
||||||
Ok(format!("✅ Successfully wrote {} lines to '{}'", line_count, path_str))
|
Ok(format!(
|
||||||
|
"✅ Successfully wrote {} lines to '{}'",
|
||||||
|
line_count, path_str
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
Ok(format!("❌ Failed to write to file '{}': {}", path_str, e))
|
||||||
}
|
}
|
||||||
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", path_str, e)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok("❌ Invalid file_path or content argument".to_string())
|
Ok("❌ Invalid file_path or content argument".to_string())
|
||||||
@@ -1331,85 +1344,131 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
}
|
}
|
||||||
"edit_file" => {
|
"edit_file" => {
|
||||||
debug!("Processing edit_file tool call");
|
debug!("Processing edit_file tool call");
|
||||||
|
debug!("Raw tool_call.args: {:?}", tool_call.args);
|
||||||
|
|
||||||
let file_path = tool_call.args.get("file_path");
|
let file_path = tool_call.args.get("file_path");
|
||||||
let start_line = tool_call.args.get("start_line");
|
let start_line = tool_call.args.get("start_line");
|
||||||
let end_line = tool_call.args.get("end_line");
|
let end_line = tool_call.args.get("end_line");
|
||||||
let new_text = tool_call.args.get("new_text");
|
let new_text = tool_call.args.get("new_text");
|
||||||
|
|
||||||
if let (Some(path_val), Some(start_val), Some(end_val), Some(text_val)) =
|
debug!("Extracted values - file_path: {:?}, start_line: {:?}, end_line: {:?}, new_text: {:?}",
|
||||||
(file_path, start_line, end_line, new_text) {
|
file_path, start_line, end_line, new_text);
|
||||||
|
|
||||||
if let (Some(path_str), Some(start_num), Some(end_num), Some(text_str)) =
|
if let (Some(path_val), Some(start_val), Some(end_val), Some(text_val)) =
|
||||||
(path_val.as_str(), start_val.as_i64(), end_val.as_i64(), text_val.as_str()) {
|
(file_path, start_line, end_line, new_text)
|
||||||
|
{
|
||||||
debug!("Editing file: {} (lines {}-{})", path_str, start_num, end_num);
|
debug!("All required arguments present");
|
||||||
|
debug!(
|
||||||
|
"path_val: {:?}, start_val: {:?}, end_val: {:?}, text_val: {:?}",
|
||||||
|
path_val, start_val, end_val, text_val
|
||||||
|
);
|
||||||
|
|
||||||
|
if let (Some(path_str), Some(start_num), Some(end_num), Some(text_str)) = (
|
||||||
|
path_val.as_str(),
|
||||||
|
start_val.as_i64(),
|
||||||
|
end_val.as_i64(),
|
||||||
|
text_val.as_str(),
|
||||||
|
) {
|
||||||
|
debug!("Successfully converted types - path: {}, start: {}, end: {}, text_len: {}",
|
||||||
|
path_str, start_num, end_num, text_str.len());
|
||||||
|
|
||||||
// Validate line numbers
|
// Validate line numbers
|
||||||
if start_num < 1 || end_num < 1 || start_num > end_num {
|
if start_num < 1 || end_num < 1 || start_num > end_num {
|
||||||
return Ok("❌ Invalid line numbers: start_line and end_line must be >= 1 and start_line <= end_line".to_string());
|
return Ok("❌ Invalid line numbers: start_line and end_line must be >= 1 and start_line <= end_line".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the current file content
|
// Read the current file content
|
||||||
let original_content = match std::fs::read_to_string(path_str) {
|
let original_content = match std::fs::read_to_string(path_str) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", path_str, e)),
|
Err(e) => {
|
||||||
|
return Ok(format!("❌ Failed to read file '{}': {}", path_str, e))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lines: Vec<&str> = original_content.lines().collect();
|
let lines: Vec<&str> = original_content.lines().collect();
|
||||||
let total_lines = lines.len();
|
let total_lines = lines.len();
|
||||||
|
debug!("File has {} lines", total_lines);
|
||||||
|
|
||||||
// Convert to 0-based indexing
|
// Convert to 0-based indexing
|
||||||
let start_idx = (start_num - 1) as usize;
|
let start_idx = (start_num - 1) as usize;
|
||||||
let end_idx = (end_num - 1) as usize;
|
let end_idx = (end_num - 1) as usize;
|
||||||
|
debug!(
|
||||||
|
"Using 0-based indices: start_idx={}, end_idx={}",
|
||||||
|
start_idx, end_idx
|
||||||
|
);
|
||||||
|
|
||||||
// Validate line ranges
|
// Validate line ranges
|
||||||
if start_idx >= total_lines {
|
if start_idx >= total_lines {
|
||||||
return Ok(format!("❌ start_line {} is beyond file length ({} lines)", start_num, total_lines));
|
return Ok(format!(
|
||||||
|
"❌ start_line {} is beyond file length ({} lines)",
|
||||||
|
start_num, total_lines
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if end_idx >= total_lines {
|
if end_idx >= total_lines {
|
||||||
return Ok(format!("❌ end_line {} is beyond file length ({} lines)", end_num, total_lines));
|
return Ok(format!(
|
||||||
|
"❌ end_line {} is beyond file length ({} lines)",
|
||||||
|
end_num, total_lines
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split new_text into lines
|
// Split new_text into lines
|
||||||
let new_lines: Vec<&str> = if text_str.is_empty() {
|
let new_lines: Vec<&str> = if text_str.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
text_str.lines().collect()
|
text_str.lines().collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_lines_count = new_lines.len();
|
let new_lines_count = new_lines.len();
|
||||||
|
debug!("New text has {} lines", new_lines_count);
|
||||||
|
|
||||||
// Create the new content
|
// Create the new content
|
||||||
let mut new_content_lines = Vec::new();
|
let mut new_content_lines = Vec::new();
|
||||||
|
|
||||||
// Add lines before the edit range
|
// Add lines before the edit range
|
||||||
new_content_lines.extend_from_slice(&lines[..start_idx]);
|
new_content_lines.extend_from_slice(&lines[..start_idx]);
|
||||||
|
|
||||||
// Add the new lines
|
// Add the new lines
|
||||||
new_content_lines.extend(new_lines);
|
new_content_lines.extend(new_lines);
|
||||||
|
|
||||||
// Add lines after the edit range
|
// Add lines after the edit range
|
||||||
if end_idx + 1 < lines.len() {
|
if end_idx + 1 < lines.len() {
|
||||||
new_content_lines.extend_from_slice(&lines[end_idx + 1..]);
|
new_content_lines.extend_from_slice(&lines[end_idx + 1..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join the lines back together
|
// Join the lines back together
|
||||||
let new_content = new_content_lines.join("\n");
|
let new_content = new_content_lines.join("\n");
|
||||||
|
debug!("New content length: {} characters", new_content.len());
|
||||||
|
|
||||||
// Write the modified content back to the file
|
// Write the modified content back to the file
|
||||||
match std::fs::write(path_str, &new_content) {
|
match std::fs::write(path_str, &new_content) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let old_range_size = end_idx - start_idx + 1;
|
let old_range_size = end_idx - start_idx + 1;
|
||||||
Ok(format!("✅ Successfully edited '{}': replaced {} lines ({}:{}) with {} lines",
|
Ok(format!("✅ Successfully edited '{}': replaced {} lines ({}:{}) with {} lines",
|
||||||
path_str, old_range_size, start_num, end_num, new_lines_count))
|
path_str, old_range_size, start_num, end_num, new_lines_count))
|
||||||
}
|
}
|
||||||
Err(e) => Ok(format!("❌ Failed to write edited content to '{}': {}", path_str, e)),
|
Err(e) => Ok(format!(
|
||||||
|
"❌ Failed to write edited content to '{}': {}",
|
||||||
|
path_str, e
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
debug!("Type conversion failed:");
|
||||||
|
debug!(" path_val.as_str(): {:?}", path_val.as_str());
|
||||||
|
debug!(" start_val.as_i64(): {:?}", start_val.as_i64());
|
||||||
|
debug!(" end_val.as_i64(): {:?}", end_val.as_i64());
|
||||||
|
debug!(" text_val.as_str(): {:?}", text_val.as_str());
|
||||||
Ok("❌ Invalid argument types: file_path must be string, start_line and end_line must be integers, new_text must be string".to_string())
|
Ok("❌ Invalid argument types: file_path must be string, start_line and end_line must be integers, new_text must be string".to_string())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok("❌ Missing required arguments: file_path, start_line, end_line, new_text".to_string())
|
debug!("Missing required arguments:");
|
||||||
|
debug!(" file_path present: {}", file_path.is_some());
|
||||||
|
debug!(" start_line present: {}", start_line.is_some());
|
||||||
|
debug!(" end_line present: {}", end_line.is_some());
|
||||||
|
debug!(" new_text present: {}", new_text.is_some());
|
||||||
|
Ok(
|
||||||
|
"❌ Missing required arguments: file_path, start_line, end_line, new_text"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"final_output" => {
|
"final_output" => {
|
||||||
|
|||||||
Reference in New Issue
Block a user