Remove legacy logs/ directory, consolidate all data under .g3/

This change removes the legacy logs/ directory and consolidates all
session data, error logs, and discovery files under the .g3/ directory.

New directory structure:
- .g3/sessions/<session_id>/session.json - session logs
- .g3/errors/ - error logs (was logs/errors/)
- .g3/background_processes/ - background process logs
- .g3/discovery/ - planner discovery files (was workspace/logs/)

Changes:
- paths.rs: Remove get_logs_dir()/logs_dir(), add get_errors_dir(),
  get_background_processes_dir(), get_discovery_dir()
- session.rs: Anonymous sessions now use .g3/sessions/anonymous_<ts>/
- error_handling.rs: Errors now saved to .g3/errors/
- project.rs: Remove logs_dir() and ensure_logs_dir() methods
- feedback_extraction.rs: Remove logs_dir field and fallback logic
- planner: Use .g3/ for workspace data and .g3/discovery/ for reports
- flock.rs: Look for session metrics in .g3/sessions/
- coach_feedback.rs: Remove fallback to logs/ path
- Update all tests to use new paths
- Update README.md and .gitignore
This commit is contained in:
Dhanji R. Prasanna
2026-01-12 18:20:08 +05:30
parent 43a5d27149
commit c2aa80647a
68 changed files with 744 additions and 159 deletions

3
.gitignore vendored
View File

@@ -23,8 +23,7 @@ target
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# Session logs directory # G3 session data directory
logs/
.g3/ .g3/
# g3 artifacts # g3 artifacts

View File

@@ -60,7 +60,7 @@ G3 includes robust error handling with automatic retry logic:
- **Recoverable Error Detection**: Automatically identifies recoverable errors (rate limits, network issues, server errors, timeouts) - **Recoverable Error Detection**: Automatically identifies recoverable errors (rate limits, network issues, server errors, timeouts)
- **Exponential Backoff with Jitter**: Implements intelligent retry delays to avoid overwhelming services - **Exponential Backoff with Jitter**: Implements intelligent retry delays to avoid overwhelming services
- **Detailed Error Logging**: Captures comprehensive error context including stack traces, request/response data, and session information - **Detailed Error Logging**: Captures comprehensive error context including stack traces, request/response data, and session information
- **Error Persistence**: Saves detailed error logs to `logs/errors/` for post-mortem analysis - **Error Persistence**: Saves detailed error logs to `.g3/errors/` for post-mortem analysis
- **Graceful Degradation**: Non-recoverable errors are logged with full context before terminating - **Graceful Degradation**: Non-recoverable errors are logged with full context before terminating
### Tool Call Duplicate Detection ### Tool Call Duplicate Detection
@@ -316,12 +316,12 @@ G3 can interact with your computer's GUI for automation tasks:
## Session Logs ## Session Logs
G3 automatically saves session logs for each interaction in the `logs/` directory. These logs contain: G3 automatically saves session logs for each interaction in the `.g3/sessions/` directory. These logs contain:
- Complete conversation history - Complete conversation history
- Token usage statistics - Token usage statistics
- Timestamps and session status - Timestamps and session status
The `logs/` directory is created automatically on first use and is excluded from version control. The `.g3/` directory is created automatically on first use and is excluded from version control.
## Documentation Map ## Documentation Map

View File

@@ -63,7 +63,7 @@ pub struct Cli {
#[arg(long, value_name = "MODEL")] #[arg(long, value_name = "MODEL")]
pub model: Option<String>, pub model: Option<String>,
/// Disable log file creation (no logs/ directory or session logs) /// Disable session log file creation (no .g3/sessions/ or error logs)
#[arg(long)] #[arg(long)]
pub quiet: bool, pub quiet: bool,

View File

@@ -54,12 +54,7 @@ pub fn extract_from_logs(
/// Resolve the log file path, trying new path first then falling back to old. /// Resolve the log file path, trying new path first then falling back to old.
fn resolve_log_path(session_id: &str) -> std::path::PathBuf { fn resolve_log_path(session_id: &str) -> std::path::PathBuf {
let new_path = g3_core::get_session_file(session_id); g3_core::get_session_file(session_id)
if new_path.exists() {
new_path
} else {
Path::new("logs").join(format!("g3_session_{}.json", session_id))
}
} }
/// Extract feedback from a session log file. /// Extract feedback from a session log file.

View File

@@ -4,15 +4,17 @@ use tempfile::TempDir;
#[test] #[test]
fn test_extract_coach_feedback_with_timing_message() { fn test_extract_coach_feedback_with_timing_message() {
// Create a temporary directory for logs // Create a temporary directory for session logs
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs"); let sessions_dir = temp_dir.path().join(".g3").join("sessions");
fs::create_dir(&logs_dir).unwrap(); fs::create_dir_all(&sessions_dir).unwrap();
// Create a mock session log with the problematic conversation history // Create a mock session log with the problematic conversation history
// where timing message appears after the tool result // where timing message appears after the tool result
let session_id = "test_session_123"; let session_id = "test_session_123";
let log_file_path = logs_dir.join(format!("g3_session_{}.json", session_id)); let session_dir = sessions_dir.join(session_id);
fs::create_dir_all(&session_dir).unwrap();
let log_file_path = session_dir.join("session.json");
let log_content = json!({ let log_content = json!({
"session_id": session_id, "session_id": session_id,
@@ -93,11 +95,13 @@ fn test_extract_coach_feedback_with_timing_message() {
fn test_extract_only_final_output_tool_results() { fn test_extract_only_final_output_tool_results() {
// Test that we only extract tool results from final_output, not from other tools // Test that we only extract tool results from final_output, not from other tools
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs"); let sessions_dir = temp_dir.path().join(".g3").join("sessions");
fs::create_dir(&logs_dir).unwrap(); fs::create_dir_all(&sessions_dir).unwrap();
let session_id = "test_session_final_output_only"; let session_id = "test_session_final_output_only";
let log_file_path = logs_dir.join(format!("g3_session_{}.json", session_id)); let session_dir = sessions_dir.join(session_id);
fs::create_dir_all(&session_dir).unwrap();
let log_file_path = session_dir.join("session.json");
let log_content = json!({ let log_content = json!({
"session_id": session_id, "session_id": session_id,
@@ -184,14 +188,16 @@ fn test_extract_only_final_output_tool_results() {
#[test] #[test]
fn test_extract_coach_feedback_without_timing_message() { fn test_extract_coach_feedback_without_timing_message() {
// Create a temporary directory for logs // Create a temporary directory for session logs
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs"); let sessions_dir = temp_dir.path().join(".g3").join("sessions");
fs::create_dir(&logs_dir).unwrap(); fs::create_dir_all(&sessions_dir).unwrap();
// Test the case where there's no timing message (backward compatibility) // Test the case where there's no timing message (backward compatibility)
let session_id = "test_session_456"; let session_id = "test_session_456";
let log_file_path = logs_dir.join(format!("g3_session_{}.json", session_id)); let session_dir = sessions_dir.join(session_id);
fs::create_dir_all(&session_dir).unwrap();
let log_file_path = session_dir.join("session.json");
let log_content = json!({ let log_content = json!({
"session_id": session_id, "session_id": session_id,
@@ -256,11 +262,13 @@ fn test_extract_coach_feedback_without_timing_message() {
fn test_extract_coach_feedback_with_multiple_tool_results() { fn test_extract_coach_feedback_with_multiple_tool_results() {
// Test that we get the LAST tool result when there are multiple // Test that we get the LAST tool result when there are multiple
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs"); let sessions_dir = temp_dir.path().join(".g3").join("sessions");
fs::create_dir(&logs_dir).unwrap(); fs::create_dir_all(&sessions_dir).unwrap();
let session_id = "test_session_789"; let session_id = "test_session_789";
let log_file_path = logs_dir.join(format!("g3_session_{}.json", session_id)); let session_dir = sessions_dir.join(session_id);
fs::create_dir_all(&session_dir).unwrap();
let log_file_path = session_dir.join("session.json");
let log_content = json!({ let log_content = json!({
"session_id": session_id, "session_id": session_id,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -129,8 +129,7 @@ impl ErrorContext {
return; return;
} }
let base_logs_dir = crate::logs_dir(); let logs_dir = crate::paths::get_errors_dir();
let logs_dir = base_logs_dir.join("errors");
if !logs_dir.exists() { if !logs_dir.exists() {
if let Err(e) = std::fs::create_dir_all(&logs_dir) { if let Err(e) = std::fs::create_dir_all(&logs_dir) {
error!("Failed to create error logs directory: {}", e); error!("Failed to create error logs directory: {}", e);

View File

@@ -8,10 +8,9 @@
//! //!
//! Used by both autonomous mode (g3-cli) and planning mode (g3-planner). //! Used by both autonomous mode (g3-cli) and planning mode (g3-planner).
use crate::{logs_dir, Agent, TaskResult}; use crate::{Agent, TaskResult};
use crate::ui_writer::UiWriter; use crate::ui_writer::UiWriter;
use serde_json::Value; use serde_json::Value;
use std::path::PathBuf;
use tracing::{debug, warn}; use tracing::{debug, warn};
/// Result of feedback extraction with source information /// Result of feedback extraction with source information
@@ -60,8 +59,6 @@ impl ExtractedFeedback {
pub struct FeedbackExtractionConfig { pub struct FeedbackExtractionConfig {
/// Whether to print debug information /// Whether to print debug information
pub verbose: bool, pub verbose: bool,
/// Custom logs directory (overrides default)
pub logs_dir: Option<PathBuf>,
/// Default feedback message if extraction fails /// Default feedback message if extraction fails
pub default_feedback: String, pub default_feedback: String,
} }
@@ -70,7 +67,6 @@ impl Default for FeedbackExtractionConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
verbose: false, verbose: false,
logs_dir: None,
default_feedback: "The implementation needs review. Please ensure all requirements are met and the code compiles without errors.".to_string(), default_feedback: "The implementation needs review. Please ensure all requirements are met and the code compiles without errors.".to_string(),
} }
} }
@@ -149,17 +145,10 @@ fn try_extract_last_assistant_message(
session_id: &str, session_id: &str,
config: &FeedbackExtractionConfig, config: &FeedbackExtractionConfig,
) -> Option<String> { ) -> Option<String> {
// Try new .g3/sessions/<session_id>/session.json path first let _ = config; // config no longer used but kept for API compatibility
let log_file_path = crate::get_session_file(session_id);
// Fall back to old logs/ path if new path doesn't exist // Use .g3/sessions/<session_id>/session.json path
let log_file_path = if log_file_path.exists() { let log_file_path = crate::get_session_file(session_id);
log_file_path
} else {
let logs_path = config.logs_dir.clone().unwrap_or_else(logs_dir);
logs_path.join(format!("g3_session_{}.json", session_id))
};
if !log_file_path.exists() { if !log_file_path.exists() {
debug!("Session log file not found: {:?}", log_file_path); debug!("Session log file not found: {:?}", log_file_path);
return None; return None;
@@ -214,17 +203,10 @@ fn try_extract_from_session_log(
session_id: &str, session_id: &str,
config: &FeedbackExtractionConfig, config: &FeedbackExtractionConfig,
) -> Option<String> { ) -> Option<String> {
// Try new .g3/sessions/<session_id>/session.json path first let _ = config; // config no longer used but kept for API compatibility
let log_file_path = crate::get_session_file(session_id);
// Fall back to old logs/ path if new path doesn't exist // Use .g3/sessions/<session_id>/session.json path
let log_file_path = if log_file_path.exists() { let log_file_path = crate::get_session_file(session_id);
log_file_path
} else {
let logs_path = config.logs_dir.clone().unwrap_or_else(logs_dir);
logs_path.join(format!("g3_session_{}.json", session_id))
};
if !log_file_path.exists() { if !log_file_path.exists() {
debug!("Session log file not found: {:?}", log_file_path); debug!("Session log file not found: {:?}", log_file_path);
return None; return None;
@@ -358,17 +340,10 @@ fn try_extract_from_conversation_history(
session_id: &str, session_id: &str,
config: &FeedbackExtractionConfig, config: &FeedbackExtractionConfig,
) -> Option<String> { ) -> Option<String> {
// Try new .g3/sessions/<session_id>/session.json path first let _ = config; // config no longer used but kept for API compatibility
let log_file_path = crate::get_session_file(session_id);
// Fall back to old logs/ path if new path doesn't exist // Use .g3/sessions/<session_id>/session.json path
let log_file_path = if log_file_path.exists() { let log_file_path = crate::get_session_file(session_id);
log_file_path
} else {
let logs_path = config.logs_dir.clone().unwrap_or_else(logs_dir);
logs_path.join(format!("g3_session_{}.json", session_id))
};
if !log_file_path.exists() { if !log_file_path.exists() {
return None; return None;
} }
@@ -652,7 +627,6 @@ mod tests {
fn test_feedback_extraction_config_default() { fn test_feedback_extraction_config_default() {
let config = FeedbackExtractionConfig::default(); let config = FeedbackExtractionConfig::default();
assert!(!config.verbose); assert!(!config.verbose);
assert!(config.logs_dir.is_none());
assert!(config.default_feedback.contains("review")); assert!(config.default_feedback.contains("review"));
} }
} }

View File

@@ -53,10 +53,11 @@ use std::time::{Duration, Instant};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
// Re-export path utilities for backward compatibility // Re-export path utilities
pub use paths::{ pub use paths::{
G3_WORKSPACE_PATH_ENV, ensure_session_dir, get_context_summary_file, get_g3_dir, get_logs_dir, G3_WORKSPACE_PATH_ENV, ensure_session_dir, get_context_summary_file, get_g3_dir,
get_session_file, get_session_logs_dir, get_session_todo_path, get_thinned_dir, logs_dir, get_session_file, get_session_logs_dir, get_session_todo_path, get_thinned_dir,
get_errors_dir, get_background_processes_dir, get_discovery_dir,
}; };
use paths::get_todo_path; use paths::get_todo_path;
@@ -291,7 +292,7 @@ impl<W: UiWriter> Agent<W> {
working_dir: None, working_dir: None,
background_process_manager: std::sync::Arc::new( background_process_manager: std::sync::Arc::new(
background_process::BackgroundProcessManager::new( background_process::BackgroundProcessManager::new(
paths::get_logs_dir().join("background_processes") paths::get_background_processes_dir()
)), )),
pending_images: Vec::new(), pending_images: Vec::new(),
is_agent_mode: false, is_agent_mode: false,

View File

@@ -1,8 +1,8 @@
//! Path utilities for G3 session and workspace management. //! Path utilities for G3 session and workspace management.
//! //!
//! This module centralizes all path-related logic for: //! This module centralizes all path-related logic for:
//! - TODO file location //! - TODO file location
//! - Logs directory //! - Error logs directory
//! - Session directories and files //! - Session directories and files
//! - Thinned content storage //! - Thinned content storage
@@ -33,22 +33,29 @@ pub fn get_session_todo_path(session_id: &str) -> PathBuf {
get_session_logs_dir(session_id).join("todo.g3.md") get_session_logs_dir(session_id).join("todo.g3.md")
} }
/// Get the path to the logs directory. /// Get the path to the errors directory.
/// ///
/// Checks for G3_WORKSPACE_PATH environment variable first (used by planning mode), /// Returns `.g3/errors/` in the workspace or current directory.
/// then falls back to "logs" in the current directory. pub fn get_errors_dir() -> PathBuf {
pub fn get_logs_dir() -> PathBuf { get_g3_dir().join("errors")
if let Ok(workspace_path) = std::env::var(G3_WORKSPACE_PATH_ENV) {
PathBuf::from(workspace_path).join("logs")
} else {
std::env::current_dir().unwrap_or_default().join("logs")
}
} }
/// Public accessor for the logs directory path (for use by submodules). /// Get the path to the background processes directory.
/// Alias for `get_logs_dir()` for backward compatibility. ///
pub fn logs_dir() -> PathBuf { /// Returns `.g3/background_processes/` in the workspace or current directory.
get_logs_dir() pub fn get_background_processes_dir() -> PathBuf {
get_g3_dir().join("background_processes")
}
/// Get the path to the discovery logs directory (for planner mode).
///
/// Returns `.g3/discovery/` in the workspace or current directory.
pub fn get_discovery_dir() -> PathBuf {
if let Ok(workspace_path) = std::env::var(G3_WORKSPACE_PATH_ENV) {
PathBuf::from(workspace_path).join(".g3").join("discovery")
} else {
get_g3_dir().join("discovery")
}
} }
/// Get the base .g3 directory path. /// Get the base .g3 directory path.

View File

@@ -127,18 +127,4 @@ impl Project {
std::env::set_current_dir(&self.workspace_dir)?; std::env::set_current_dir(&self.workspace_dir)?;
Ok(()) Ok(())
} }
/// Get the logs directory for the project
pub fn logs_dir(&self) -> PathBuf {
self.workspace_dir.join("logs")
}
/// Ensure the logs directory exists
pub fn ensure_logs_dir(&self) -> Result<()> {
let logs_dir = self.logs_dir();
if !logs_dir.exists() {
std::fs::create_dir_all(&logs_dir)?;
}
Ok(())
}
} }

View File

@@ -5,7 +5,7 @@
//! operations from the Agent, keeping the Agent as a thin orchestrator. //! operations from the Agent, keeping the Agent as a thin orchestrator.
use crate::context_window::ContextWindow; use crate::context_window::ContextWindow;
use crate::paths::{ensure_session_dir, get_context_summary_file, get_g3_dir, get_logs_dir, get_session_file}; use crate::paths::{ensure_session_dir, get_context_summary_file, get_g3_dir, get_session_file};
use g3_providers::MessageRole; use g3_providers::MessageRole;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@@ -90,7 +90,7 @@ pub fn generate_session_id(description: &str, agent_name: Option<&str>) -> Strin
/// Save the context window to a session file. /// Save the context window to a session file.
/// ///
/// If session_id is provided, saves to `.g3/sessions/<session_id>/session.json`. /// If session_id is provided, saves to `.g3/sessions/<session_id>/session.json`.
/// Otherwise, falls back to `logs/g3_context_<timestamp>.json`. /// Otherwise, saves to `.g3/sessions/anonymous_<timestamp>/session.json`.
pub fn save_context_window( pub fn save_context_window(
session_id: Option<&str>, session_id: Option<&str>,
context_window: &ContextWindow, context_window: &ContextWindow,
@@ -110,13 +110,13 @@ pub fn save_context_window(
} }
get_session_file(id) get_session_file(id)
} else { } else {
// Fallback to old logs/ directory for sessions without ID // Create anonymous session for sessions without ID
let logs_dir = get_logs_dir(); let anonymous_id = format!("anonymous_{}", timestamp);
if let Err(e) = std::fs::create_dir_all(&logs_dir) { if let Err(e) = ensure_session_dir(&anonymous_id) {
error!("Failed to create logs directory: {}", e); error!("Failed to create anonymous session directory: {}", e);
return; return;
} }
logs_dir.join(format!("g3_context_{}.json", timestamp)) get_session_file(&anonymous_id)
}; };
let context_data = serde_json::json!({ let context_data = serde_json::json!({
@@ -252,8 +252,8 @@ pub fn log_error_to_session(
.unwrap_or_default() .unwrap_or_default()
.as_secs(); .as_secs();
let logs_dir = get_logs_dir(); // Use the new .g3/sessions/<session_id>/session.json path
let filename = logs_dir.join(format!("g3_session_{}.json", session_id)); let filename = get_session_file(session_id);
// Read existing session log // Read existing session log
let mut session_data: serde_json::Value = if filename.exists() { let mut session_data: serde_json::Value = if filename.exists() {

View File

@@ -29,7 +29,7 @@ echo "Done!"
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap(); fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap();
} }
let log_dir = test_dir.join("logs"); let log_dir = test_dir.join(".g3").join("background_processes");
let manager = BackgroundProcessManager::new(log_dir); let manager = BackgroundProcessManager::new(log_dir);
println!("\n=== Background Process Demo ==="); println!("\n=== Background Process Demo ===");

View File

@@ -53,7 +53,7 @@ done
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap(); fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap();
} }
let log_dir = test_dir.join("logs"); let log_dir = test_dir.join(".g3").join("background_processes");
let manager = BackgroundProcessManager::new(log_dir); let manager = BackgroundProcessManager::new(log_dir);
// Start the process // Start the process
@@ -116,7 +116,7 @@ sleep 30
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap(); fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755)).unwrap();
} }
let log_dir = test_dir.join("logs"); let log_dir = test_dir.join(".g3").join("background_processes");
let manager = BackgroundProcessManager::new(log_dir); let manager = BackgroundProcessManager::new(log_dir);
// Start a process // Start a process
@@ -151,7 +151,7 @@ sleep 30
let _ = fs::remove_dir_all(&test_dir); let _ = fs::remove_dir_all(&test_dir);
fs::create_dir_all(&test_dir).unwrap(); fs::create_dir_all(&test_dir).unwrap();
let log_dir = test_dir.join("logs"); let log_dir = test_dir.join(".g3").join("background_processes");
let manager = BackgroundProcessManager::new(log_dir); let manager = BackgroundProcessManager::new(log_dir);
// Getting a process that doesn't exist should return None // Getting a process that doesn't exist should return None

View File

@@ -91,7 +91,7 @@ fn test_save_and_load_continuation() {
"save_load_test".to_string(), "save_load_test".to_string(),
None, None,
Some("Test summary content".to_string()), Some("Test summary content".to_string()),
"/logs/g3_session_save_load_test.json".to_string(), "/.g3/sessions/save_load_test/session.json".to_string(),
35.5, 35.5,
Some("- [ ] Pending task".to_string()), Some("- [ ] Pending task".to_string()),
temp_dir.path().to_string_lossy().to_string(), temp_dir.path().to_string_lossy().to_string(),
@@ -321,9 +321,9 @@ fn test_has_valid_continuation_with_existing_session_log() {
let (temp_dir, original_dir) = setup_test_env(); let (temp_dir, original_dir) = setup_test_env();
// Create a fake session log file // Create a fake session log file
let logs_dir = temp_dir.path().join("logs"); let session_dir = temp_dir.path().join(".g3").join("sessions").join("valid_test");
fs::create_dir_all(&logs_dir).expect("Failed to create logs dir"); fs::create_dir_all(&session_dir).expect("Failed to create session dir");
let session_log_path = logs_dir.join("g3_session_valid_test.json"); let session_log_path = session_dir.join("session.json");
fs::write(&session_log_path, "{}").expect("Failed to write session log"); fs::write(&session_log_path, "{}").expect("Failed to write session log");
// Create a continuation pointing to the existing session log // Create a continuation pointing to the existing session log

View File

@@ -735,8 +735,8 @@ async fn run_segment(
segment_status.errors += 1; segment_status.errors += 1;
} }
// Try to extract metrics from session log if available // Try to extract metrics from session log if available (check .g3/sessions/)
let log_dir = segment_dir.join("logs"); let log_dir = segment_dir.join(".g3").join("sessions");
if log_dir.exists() { if log_dir.exists() {
if let Ok(entries) = std::fs::read_dir(&log_dir) { if let Ok(entries) = std::fs::read_dir(&log_dir) {
for entry in entries.flatten() { for entry in entries.flatten() {

View File

@@ -66,7 +66,7 @@ pub async fn get_initial_discovery_messages(
// Step 1: Run explore_codebase to get the codebase report // Step 1: Run explore_codebase to get the codebase report
let codebase_report = explore_codebase(codebase_path); let codebase_report = explore_codebase(codebase_path);
// Write the codebase report to logs directory // Write the codebase report to discovery directory
write_code_report(&codebase_report)?; write_code_report(&codebase_report)?;
// Step 2: Build the prompt with the codebase report appended // Step 2: Build the prompt with the codebase report appended
@@ -112,7 +112,7 @@ pub async fn get_initial_discovery_messages(
shell_commands.len() shell_commands.len()
)); ));
// Write the discovery commands to logs directory // Write the discovery commands to discovery directory
write_discovery_commands(&shell_commands)?; write_discovery_commands(&shell_commands)?;
// Step 6: Format as tool messages // Step 6: Format as tool messages
@@ -194,21 +194,21 @@ pub fn extract_summary(response: &str) -> Option<String> {
} }
} }
/// Write the codebase report to logs directory /// Write the codebase report to discovery directory
fn write_code_report(report: &str) -> Result<()> { fn write_code_report(report: &str) -> Result<()> {
// Get logs directory from workspace path or current dir // Get discovery directory from workspace path or current dir
let logs_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") { let discovery_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") {
std::path::PathBuf::from(workspace_path).join("logs") std::path::PathBuf::from(workspace_path).join(".g3").join("discovery")
} else { } else {
std::env::current_dir().unwrap_or_default().join("logs") std::env::current_dir().unwrap_or_default().join(".g3").join("discovery")
}; };
// Ensure logs directory exists // Ensure discovery directory exists
fs::create_dir_all(&logs_dir)?; fs::create_dir_all(&discovery_dir)?;
// Generate timestamp in same format as tool_calls log // Generate timestamp in same format as tool_calls log
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let filename = logs_dir.join(format!("code_report_{}.log", timestamp)); let filename = discovery_dir.join(format!("code_report_{}.log", timestamp));
// Write the report to file // Write the report to file
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
@@ -223,21 +223,21 @@ fn write_code_report(report: &str) -> Result<()> {
Ok(()) Ok(())
} }
/// Write the discovery commands to logs directory /// Write the discovery commands to discovery directory
fn write_discovery_commands(commands: &[String]) -> Result<()> { fn write_discovery_commands(commands: &[String]) -> Result<()> {
// Get logs directory from workspace path or current dir // Get discovery directory from workspace path or current dir
let logs_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") { let discovery_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") {
std::path::PathBuf::from(workspace_path).join("logs") std::path::PathBuf::from(workspace_path).join(".g3").join("discovery")
} else { } else {
std::env::current_dir().unwrap_or_default().join("logs") std::env::current_dir().unwrap_or_default().join(".g3").join("discovery")
}; };
// Ensure logs directory exists // Ensure discovery directory exists
fs::create_dir_all(&logs_dir)?; fs::create_dir_all(&discovery_dir)?;
// Generate timestamp in same format as tool_calls log // Generate timestamp in same format as tool_calls log
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let filename = logs_dir.join(format!("discovery_commands_{}.log", timestamp)); let filename = discovery_dir.join(format!("discovery_commands_{}.log", timestamp));
// Write the commands to file // Write the commands to file
let mut file = OpenOptions::new() let mut file = OpenOptions::new()

View File

@@ -324,15 +324,13 @@ pub async fn call_refinement_llm_with_tools(
let ui_writer = PlannerUiWriter::new(); let ui_writer = PlannerUiWriter::new();
// CRITICAL FIX: Use the actual workspace directory, NOT codepath! // CRITICAL FIX: Use the actual workspace directory, NOT codepath!
// The workspace is where logs should be written (e.g., /tmp/g3_test_workspace) // The workspace is where session data should be written (e.g., /tmp/g3_test_workspace)
// The codepath is where the source code lives (e.g., ~/RustroverProjects/g3) // The codepath is where the source code lives (e.g., ~/RustroverProjects/g3)
// Previous bug: was using codepath as workspace, causing logs to go to wrong location
let workspace_path = std::path::PathBuf::from(workspace); let workspace_path = std::path::PathBuf::from(workspace);
let project = Project::new(workspace_path.clone()); let project = Project::new(workspace_path.clone());
project.ensure_workspace_exists()?; project.ensure_workspace_exists()?;
project.enter_workspace()?; project.enter_workspace()?;
project.ensure_logs_dir()?;
// Create agent - not autonomous mode, just regular agent with tools // Create agent - not autonomous mode, just regular agent with tools
let mut agent = Agent::new_with_readme_and_quiet( let mut agent = Agent::new_with_readme_and_quiet(
planner_config, planner_config,

View File

@@ -777,13 +777,13 @@ pub async fn run_planning_mode(
// Set G3_WORKSPACE_PATH environment variable EARLY for all logging // Set G3_WORKSPACE_PATH environment variable EARLY for all logging
std::env::set_var("G3_WORKSPACE_PATH", workspace_dir.display().to_string()); std::env::set_var("G3_WORKSPACE_PATH", workspace_dir.display().to_string());
// Create logs directory and verify it exists // Create .g3 directory and verify it exists
let logs_dir = workspace_dir.join("logs"); let g3_dir = workspace_dir.join(".g3");
if !logs_dir.exists() { if !g3_dir.exists() {
fs::create_dir_all(&logs_dir) fs::create_dir_all(&g3_dir)
.context("Failed to create logs directory")?; .context("Failed to create .g3 directory")?;
} }
print_msg(&format!("📁 Logs directory: {}", logs_dir.display())); print_msg(&format!("📁 G3 directory: {}", g3_dir.display()));
// Create the LLM provider for planning // Create the LLM provider for planning
print_msg("🔧 Initializing planner provider..."); print_msg("🔧 Initializing planner provider...");

View File

@@ -6,22 +6,22 @@ use std::path::Path;
#[test] #[test]
fn test_log_files_created() { fn test_log_files_created() {
// This test verifies that the logging functions work correctly // This test verifies that the logging functions work correctly
// by checking that files can be created in the logs directory // by checking that files can be created in the discovery directory
// Clean up any existing test logs // Clean up any existing test discovery dir
let _ = fs::remove_dir_all("logs"); let _ = fs::remove_dir_all(".g3/discovery");
// Create logs directory // Create discovery directory
fs::create_dir_all("logs").expect("Failed to create logs directory"); fs::create_dir_all(".g3/discovery").expect("Failed to create discovery directory");
// Verify directory exists // Verify directory exists
assert!(Path::new("logs").exists()); assert!(Path::new(".g3/discovery").exists());
assert!(Path::new("logs").is_dir()); assert!(Path::new(".g3/discovery").is_dir());
// Test writing a code report // Test writing a code report
let test_report = "Test codebase report\nLine 2\nLine 3"; 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 timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
let report_filename = format!("logs/code_report_{}.log", timestamp); let report_filename = format!(".g3/discovery/code_report_{}.log", timestamp);
fs::write(&report_filename, test_report).expect("Failed to write code report"); fs::write(&report_filename, test_report).expect("Failed to write code report");
assert!(Path::new(&report_filename).exists()); assert!(Path::new(&report_filename).exists());
@@ -30,7 +30,7 @@ fn test_log_files_created() {
assert_eq!(content, test_report); assert_eq!(content, test_report);
// Test writing discovery commands // Test writing discovery commands
let commands_filename = format!("logs/discovery_commands_{}.log", timestamp); let commands_filename = format!(".g3/discovery/discovery_commands_{}.log", timestamp);
let test_commands = let test_commands =
"# Discovery Commands\n# Generated by g3-planner\n\nls -la\ncat README.md\n"; "# Discovery Commands\n# Generated by g3-planner\n\nls -la\ncat README.md\n";

View File

@@ -82,7 +82,6 @@ fn test_extracted_feedback_approval_detection() {
fn test_feedback_extraction_config_default() { fn test_feedback_extraction_config_default() {
let config = FeedbackExtractionConfig::default(); let config = FeedbackExtractionConfig::default();
assert!(!config.verbose); assert!(!config.verbose);
assert!(config.logs_dir.is_none());
assert!(config.default_feedback.contains("review")); assert!(config.default_feedback.contains("review"));
} }
@@ -90,14 +89,9 @@ fn test_feedback_extraction_config_default() {
fn test_feedback_extraction_config_custom() { fn test_feedback_extraction_config_custom() {
let config = FeedbackExtractionConfig { let config = FeedbackExtractionConfig {
verbose: true, verbose: true,
logs_dir: Some(std::path::PathBuf::from("/tmp/test_logs")),
default_feedback: "Custom fallback message for testing".to_string(), default_feedback: "Custom fallback message for testing".to_string(),
}; };
assert!(config.verbose); assert!(config.verbose);
assert_eq!(
config.logs_dir,
Some(std::path::PathBuf::from("/tmp/test_logs"))
);
assert!(config.default_feedback.contains("Custom fallback")); assert!(config.default_feedback.contains("Custom fallback"));
} }

File diff suppressed because one or more lines are too long