From 1e1702001c52b91c270cc9e38e35a6366f0c4540 Mon Sep 17 00:00:00 2001 From: Jochen Date: Wed, 26 Nov 2025 10:41:35 +1100 Subject: [PATCH] Add logging for discovery --- Cargo.lock | 1 + crates/g3-core/src/lib.rs | 2 - crates/g3-planner/Cargo.toml | 3 +- crates/g3-planner/src/lib.rs | 61 +++++++++++++++++++++++++ crates/g3-planner/tests/logging_test.rs | 60 ++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 crates/g3-planner/tests/logging_test.rs diff --git a/Cargo.lock b/Cargo.lock index 5ceb7e5..ee7b0e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1506,6 +1506,7 @@ name = "g3-planner" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "const_format", "g3-providers", "serde", diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 93b0c95..e13a1fd 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -3805,8 +3805,6 @@ impl Agent { async fn execute_tool_inner_in_dir(&mut self, tool_call: &ToolCall, working_dir: Option<&str>) -> Result { debug!("=== EXECUTING TOOL ==="); debug!("Tool name: {}", tool_call.tool); - eprintln!("[DEBUG execute_tool_inner_in_dir] tool='{}' working_dir={:?} args={}", - tool_call.tool, working_dir, serde_json::to_string(&tool_call.args).unwrap_or_default()); debug!("Working directory passed to execute_tool_inner_in_dir: {:?}", working_dir); debug!("Tool args (raw): {:?}", tool_call.args); debug!( diff --git a/crates/g3-planner/Cargo.toml b/crates/g3-planner/Cargo.toml index ae0b92a..c40e20d 100644 --- a/crates/g3-planner/Cargo.toml +++ b/crates/g3-planner/Cargo.toml @@ -10,4 +10,5 @@ serde = { workspace = true } serde_json = { workspace = true } const_format = "0.2" anyhow = { workspace = true } -tokio = { workspace = true } \ No newline at end of file +tokio = { workspace = true } +chrono = { version = "0.4", features = ["serde"] } \ No newline at end of file diff --git a/crates/g3-planner/src/lib.rs b/crates/g3-planner/src/lib.rs index 2d11afe..3bf4ac9 100644 --- a/crates/g3-planner/src/lib.rs +++ b/crates/g3-planner/src/lib.rs @@ -10,6 +10,9 @@ pub use code_explore::explore_codebase; use anyhow::Result; use g3_providers::{CompletionRequest, LLMProvider, Message, MessageRole}; +use chrono::Local; +use std::fs::{self, OpenOptions}; +use std::io::Write; use prompts::{DISCOVERY_REQUIREMENTS_PROMPT, DISCOVERY_SYSTEM_PROMPT}; /// Type alias for a status callback function @@ -51,6 +54,9 @@ pub async fn get_initial_discovery_messages( // Step 1: Run explore_codebase to get the codebase report let codebase_report = explore_codebase(codebase_path); + // Write the codebase report to logs directory + write_code_report(&codebase_report)?; + // Step 2: Build the prompt with the codebase report appended let user_prompt = if let Some(requirements) = requirements_text { format!( @@ -90,6 +96,9 @@ pub async fn get_initial_discovery_messages( status(&format!("📋 Extracted {} discovery commands", shell_commands.len())); + // Write the discovery commands to logs directory + write_discovery_commands(&shell_commands)?; + // Step 6: Format as tool messages let tool_messages = shell_commands .into_iter() @@ -169,6 +178,58 @@ pub fn extract_summary(response: &str) -> Option { } } +/// Write the codebase report to logs directory +fn write_code_report(report: &str) -> Result<()> { + // Ensure logs directory exists + fs::create_dir_all("logs")?; + + // Generate timestamp in same format as tool_calls log + let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); + let filename = format!("logs/code_report_{}.log", timestamp); + + // Write the report to file + let mut file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&filename)?; + + file.write_all(report.as_bytes())?; + file.flush()?; + + Ok(()) +} + +/// Write the discovery commands to logs directory +fn write_discovery_commands(commands: &[String]) -> Result<()> { + // Ensure logs directory exists + fs::create_dir_all("logs")?; + + // Generate timestamp in same format as tool_calls log + let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); + let filename = format!("logs/discovery_commands_{}.log", timestamp); + + // Write the commands to file + let mut file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&filename)?; + + // Write header + file.write_all(b"# Discovery Commands\n")?; + file.write_all(b"# Generated by g3-planner\n\n")?; + + // Write each command on a separate line + for cmd in commands { + file.write_all(cmd.as_bytes())?; + file.write_all(b"\n")?; + } + file.flush()?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/g3-planner/tests/logging_test.rs b/crates/g3-planner/tests/logging_test.rs new file mode 100644 index 0000000..59ff045 --- /dev/null +++ b/crates/g3-planner/tests/logging_test.rs @@ -0,0 +1,60 @@ +//! Integration tests for logging functionality + +use std::fs; +use std::path::Path; + +#[test] +fn test_log_files_created() { + // This test verifies that the logging functions work correctly + // by checking that files can be created in the logs directory + + // Clean up any existing test logs + let _ = fs::remove_dir_all("logs"); + + // Create logs directory + fs::create_dir_all("logs").expect("Failed to create logs directory"); + + // Verify directory exists + assert!(Path::new("logs").exists()); + assert!(Path::new("logs").is_dir()); + + // Test writing a code report + let test_report = "Test codebase report\nLine 2\nLine 3"; + let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); + let report_filename = format!("logs/code_report_{}.log", timestamp); + + fs::write(&report_filename, test_report).expect("Failed to write code report"); + assert!(Path::new(&report_filename).exists()); + + let content = fs::read_to_string(&report_filename).expect("Failed to read code report"); + assert_eq!(content, test_report); + + // Test writing discovery commands + let commands_filename = format!("logs/discovery_commands_{}.log", timestamp); + let test_commands = "# Discovery Commands\n# Generated by g3-planner\n\nls -la\ncat README.md\n"; + + fs::write(&commands_filename, test_commands).expect("Failed to write discovery commands"); + assert!(Path::new(&commands_filename).exists()); + + let content = fs::read_to_string(&commands_filename).expect("Failed to read discovery commands"); + assert_eq!(content, test_commands); + + // Clean up + let _ = fs::remove_file(&report_filename); + let _ = fs::remove_file(&commands_filename); +} + +#[test] +fn test_filename_format() { + // Verify the filename format matches the tool_calls log format + let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); + + // Check format: YYYYMMDD_HHMMSS + assert_eq!(timestamp.len(), 15); // 8 digits + underscore + 6 digits + assert!(timestamp.contains('_')); + + let parts: Vec<&str> = timestamp.split('_').collect(); + assert_eq!(parts.len(), 2); + assert_eq!(parts[0].len(), 8); // YYYYMMDD + assert_eq!(parts[1].len(), 6); // HHMMSS +}