From c58aa8093214bd9183b46c73d89df4d3baa67123 Mon Sep 17 00:00:00 2001 From: Jochen Date: Wed, 26 Nov 2025 21:43:59 +1100 Subject: [PATCH 1/3] explain what file was found in workspace --- crates/g3-cli/src/lib.rs | 2 +- crates/g3-core/src/lib.rs | 5 +++++ crates/g3-core/src/project.rs | 14 +++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index 586c442..8c248b5 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -1704,7 +1704,7 @@ async fn run_autonomous( output.print("πŸ”„ Starting coach-player feedback loop..."); // Check if implementation files already exist - let skip_first_player = project.has_implementation_files(); + let skip_first_player = project.has_implementation_files(agent.ui_writer()); if skip_first_player { output.print("πŸ“‚ Detected existing implementation files in workspace"); output.print("⏭️ Skipping first player turn - proceeding directly to coach review"); diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index e13a1fd..a876654 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -1297,6 +1297,11 @@ impl Agent { self.providers.get(None) } + /// Get a reference to the UI writer + pub fn ui_writer(&self) -> &W { + &self.ui_writer + } + /// Get the current session ID for this agent pub fn get_session_id(&self) -> Option<&str> { self.session_id.as_deref() diff --git a/crates/g3-core/src/project.rs b/crates/g3-core/src/project.rs index edaa954..4d0b597 100644 --- a/crates/g3-core/src/project.rs +++ b/crates/g3-core/src/project.rs @@ -1,6 +1,9 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; +use std::process::exit; + +use crate::ui_writer::UiWriter; /// Represents a G3 project with workspace configuration #[derive(Debug, Clone, Serialize, Deserialize)] @@ -99,13 +102,13 @@ impl Project { } /// Check if implementation files exist in the workspace - pub fn has_implementation_files(&self) -> bool { - self.check_dir_for_implementation_files(&self.workspace_dir) + pub fn has_implementation_files(&self, ui_writer: &W) -> bool { + self.check_dir_for_implementation_files(&self.workspace_dir, ui_writer) } /// Recursively check a directory for implementation files #[allow(clippy::only_used_in_recursion)] - fn check_dir_for_implementation_files(&self, dir: &Path) -> bool { + fn check_dir_for_implementation_files(&self, dir: &Path, ui_writer: &W) -> bool { // Common source file extensions let extensions = vec![ "swift", "rs", "py", "js", "ts", "java", "cpp", "c", @@ -121,6 +124,7 @@ impl Project { if let Some(ext) = path.extension() { if let Some(ext_str) = ext.to_str() { if extensions.contains(&ext_str) { + ui_writer.println(&format!("⚠️⚠️Existing implementation file found: {}", path.display())); return true; } } @@ -130,7 +134,7 @@ impl Project { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { if !name.starts_with('.') && name != "logs" && name != "target" && name != "node_modules" { // Recursively check subdirectories - if self.check_dir_for_implementation_files(&path) { + if self.check_dir_for_implementation_files(&path, ui_writer) { return true; } } @@ -181,4 +185,4 @@ impl Project { } Ok(()) } -} \ No newline at end of file +} From 99125fc39ef7970b0d16eaa3a504f06c5d34f66d Mon Sep 17 00:00:00 2001 From: Jochen Date: Thu, 27 Nov 2025 13:21:40 +1100 Subject: [PATCH 2/3] completely remove the skipping first player logic --- crates/g3-cli/src/lib.rs | 10 -------- crates/g3-core/src/lib.rs | 5 ---- crates/g3-core/src/project.rs | 47 ----------------------------------- 3 files changed, 62 deletions(-) diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index 8c248b5..eef7626 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -1703,16 +1703,6 @@ async fn run_autonomous( let loop_start = Instant::now(); output.print("πŸ”„ Starting coach-player feedback loop..."); - // Check if implementation files already exist - let skip_first_player = project.has_implementation_files(agent.ui_writer()); - if skip_first_player { - output.print("πŸ“‚ Detected existing implementation files in workspace"); - output.print("⏭️ Skipping first player turn - proceeding directly to coach review"); - } else { - output.print("πŸ“‚ No existing implementation files detected"); - output.print("🎯 Starting with player implementation"); - } - // Load fast-discovery messages before the loop starts (if enabled) let (discovery_messages, discovery_working_dir): (Vec, Option) = if let Some(ref codebase_path) = codebase_fast_start { diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index a876654..e13a1fd 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -1297,11 +1297,6 @@ impl Agent { self.providers.get(None) } - /// Get a reference to the UI writer - pub fn ui_writer(&self) -> &W { - &self.ui_writer - } - /// Get the current session ID for this agent pub fn get_session_id(&self) -> Option<&str> { self.session_id.as_deref() diff --git a/crates/g3-core/src/project.rs b/crates/g3-core/src/project.rs index 4d0b597..0263b7c 100644 --- a/crates/g3-core/src/project.rs +++ b/crates/g3-core/src/project.rs @@ -1,9 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -use std::process::exit; - -use crate::ui_writer::UiWriter; /// Represents a G3 project with workspace configuration #[derive(Debug, Clone, Serialize, Deserialize)] @@ -101,50 +98,6 @@ impl Project { self.requirements_text.is_some() || self.requirements_path.is_some() } - /// Check if implementation files exist in the workspace - pub fn has_implementation_files(&self, ui_writer: &W) -> bool { - self.check_dir_for_implementation_files(&self.workspace_dir, ui_writer) - } - - /// Recursively check a directory for implementation files - #[allow(clippy::only_used_in_recursion)] - fn check_dir_for_implementation_files(&self, dir: &Path, ui_writer: &W) -> bool { - // Common source file extensions - let extensions = vec![ - "swift", "rs", "py", "js", "ts", "java", "cpp", "c", - "go", "rb", "php", "cs", "kt", "scala", "m", "h" - ]; - - if let Ok(entries) = std::fs::read_dir(dir) { - for entry in entries.flatten() { - let path = entry.path(); - - if path.is_file() { - // Check if it's a source file - if let Some(ext) = path.extension() { - if let Some(ext_str) = ext.to_str() { - if extensions.contains(&ext_str) { - ui_writer.println(&format!("⚠️⚠️Existing implementation file found: {}", path.display())); - return true; - } - } - } - } else if path.is_dir() { - // Skip hidden directories and common non-source directories - if let Some(name) = path.file_name().and_then(|n| n.to_str()) { - if !name.starts_with('.') && name != "logs" && name != "target" && name != "node_modules" { - // Recursively check subdirectories - if self.check_dir_for_implementation_files(&path, ui_writer) { - return true; - } - } - } - } - } - } - false - } - /// Read the requirements file content pub fn read_requirements(&self) -> Result> { // Prioritize requirements text override From 9f6592efc26565b67eb69cefa5cb11ad1609a1c9 Mon Sep 17 00:00:00 2001 From: Jochen Date: Thu, 27 Nov 2025 13:34:54 +1100 Subject: [PATCH 3/3] remove redundant 'if' --- crates/g3-cli/src/lib.rs | 362 +++++++++++++++++++-------------------- 1 file changed, 180 insertions(+), 182 deletions(-) diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index eef7626..8a4c0e3 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -1743,203 +1743,201 @@ async fn run_autonomous( loop { let turn_start_time = Instant::now(); let turn_start_tokens = agent.get_context_window().used_tokens; - // Skip player turn if it's the first turn and implementation files exist - if !(turn == 1 && skip_first_player) { - output.print(&format!( - "\n=== TURN {}/{} - PLAYER MODE ===", - turn, max_turns - )); - // Surface provider info for player agent - agent.print_provider_banner("Player"); + output.print(&format!( + "\n=== TURN {}/{} - PLAYER MODE ===", + turn, max_turns + )); - // Player mode: implement requirements (with coach feedback if available) - let player_prompt = if coach_feedback.is_empty() { - format!( - "You are G3 in implementation mode. Read and implement the following requirements:\n\n{}\n\nRequirements SHA256: {}\n\nImplement this step by step, creating all necessary files and code.", - requirements, requirements_sha - ) - } else { - format!( - "You are G3 in implementation mode. Address the following specific feedback from the coach:\n\n{}\n\nContext: You are improving an implementation based on these requirements:\n{}\n\nFocus on fixing the issues mentioned in the coach feedback above.", - coach_feedback, requirements - ) - }; + // Surface provider info for player agent + agent.print_provider_banner("Player"); - output.print(&format!("🎯 Starting player implementation... (elapsed: {})", format_elapsed_time(loop_start.elapsed()))); + // Player mode: implement requirements (with coach feedback if available) + let player_prompt = if coach_feedback.is_empty() { + format!( + "You are G3 in implementation mode. Read and implement the following requirements:\n\n{}\n\nRequirements SHA256: {}\n\nImplement this step by step, creating all necessary files and code.", + requirements, requirements_sha + ) + } else { + format!( + "You are G3 in implementation mode. Address the following specific feedback from the coach:\n\n{}\n\nContext: You are improving an implementation based on these requirements:\n{}\n\nFocus on fixing the issues mentioned in the coach feedback above.", + coach_feedback, requirements + ) + }; - // Display what feedback the player is receiving - // If there's no coach feedback on subsequent turns, this is an error - if coach_feedback.is_empty() { - if turn > 1 { - return Err(anyhow::anyhow!( - "Player mode error: No coach feedback received on turn {}", - turn - )); - } - output.print("πŸ“‹ Player starting initial implementation (no prior coach feedback)"); - } else { - output.print(&format!( - "πŸ“‹ Player received coach feedback ({} chars):", - coach_feedback.len() - )); - output.print(&coach_feedback.to_string()); - } - output.print(""); // Empty line for readability + output.print(&format!("🎯 Starting player implementation... (elapsed: {})", format_elapsed_time(loop_start.elapsed()))); - // Execute player task with retry on error - let mut _player_retry_count = 0; - const MAX_PLAYER_RETRIES: u32 = 3; - let mut player_failed = false; - - loop { - match agent - .execute_task_with_timing( - &player_prompt, - None, - false, - show_prompt, - show_code, - true, - if has_discovery { - Some(DiscoveryOptions { - messages: &discovery_messages, - fast_start_path: discovery_working_dir.as_deref(), - }) - } else { None }, - ) - .await - { - Ok(result) => { - // Display player's implementation result - output.print("πŸ“ Player implementation completed:"); - output.print_smart(&result.response); - break; - } - Err(e) => { - // Check if this is a context length exceeded error - use g3_core::error_handling::{classify_error, ErrorType, RecoverableError}; - let error_type = classify_error(&e); - - if matches!(error_type, ErrorType::Recoverable(RecoverableError::ContextLengthExceeded)) { - output.print(&format!("⚠️ Context length exceeded in player turn: {}", e)); - output.print("πŸ“ Logging error to session and ending current turn..."); - - // Build forensic context - let forensic_context = format!( - "Turn: {}\n\ - Role: Player\n\ - Context tokens: {}\n\ - Total available: {}\n\ - Percentage used: {:.1}%\n\ - Prompt length: {} chars\n\ - Error occurred at: {}", - turn, - agent.get_context_window().used_tokens, - agent.get_context_window().total_tokens, - agent.get_context_window().percentage_used(), - player_prompt.len(), - chrono::Utc::now().to_rfc3339() - ); - - // Log to session JSON - agent.log_error_to_session(&e, "assistant", Some(forensic_context)); - - // Mark turn as failed and continue to next turn - player_failed = true; - break; - } else if e.to_string().contains("panic") { - output.print(&format!("πŸ’₯ Player panic detected: {}", e)); - - // Generate final report even for panic - let elapsed = start_time.elapsed(); - let context_window = agent.get_context_window(); - - output.print(&format!("\n{}", "=".repeat(60))); - output.print("πŸ“Š AUTONOMOUS MODE SESSION REPORT"); - output.print(&"=".repeat(60)); - - output.print(&format!( - "⏱️ Total Duration: {:.2}s", - elapsed.as_secs_f64() - )); - output.print(&format!("πŸ”„ Turns Taken: {}/{}", turn, max_turns)); - output.print("πŸ“ Final Status: πŸ’₯ PLAYER PANIC"); - - output.print("\nπŸ“ˆ Token Usage Statistics:"); - output.print(&format!( - " β€’ Used Tokens: {}", - context_window.used_tokens - )); - output.print(&format!( - " β€’ Total Available: {}", - context_window.total_tokens - )); - output.print(&format!( - " β€’ Cumulative Tokens: {}", - context_window.cumulative_tokens - )); - output.print(&format!( - " β€’ Usage Percentage: {:.1}%", - context_window.percentage_used() - )); - // Add per-turn histogram - output.print(&generate_turn_histogram(&turn_metrics)); - output.print(&"=".repeat(60)); - - return Err(e); - } - - _player_retry_count += 1; - output.print(&format!( - "⚠️ Player error (attempt {}/{}): {}", - _player_retry_count, MAX_PLAYER_RETRIES, e - )); - - if _player_retry_count >= MAX_PLAYER_RETRIES { - output.print( - "πŸ”„ Max retries reached for player, marking turn as failed...", - ); - player_failed = true; - break; // Exit retry loop - } - output.print("πŸ”„ Retrying player implementation..."); - } - } - } - - // If player failed after max retries, increment turn and continue - if player_failed { - output.print(&format!( - "⚠️ Player turn {} failed after max retries. Moving to next turn.", + // Display what feedback the player is receiving + // If there's no coach feedback on subsequent turns, this is an error + if coach_feedback.is_empty() { + if turn > 1 { + return Err(anyhow::anyhow!( + "Player mode error: No coach feedback received on turn {}", turn )); - // Record turn metrics before incrementing - let turn_duration = turn_start_time.elapsed(); - let turn_tokens = agent.get_context_window().used_tokens.saturating_sub(turn_start_tokens); - turn_metrics.push(TurnMetrics { - turn_number: turn, - tokens_used: turn_tokens, - wall_clock_time: turn_duration, - }); - turn += 1; + } + output.print("πŸ“‹ Player starting initial implementation (no prior coach feedback)"); + } else { + output.print(&format!( + "πŸ“‹ Player received coach feedback ({} chars):", + coach_feedback.len() + )); + output.print(&coach_feedback.to_string()); + } + output.print(""); // Empty line for readability - // Check if we've reached max turns - if turn > max_turns { - output.print("\n=== SESSION COMPLETED - MAX TURNS REACHED ==="); - output.print(&format!("⏰ Maximum turns ({}) reached", max_turns)); + // Execute player task with retry on error + let mut _player_retry_count = 0; + const MAX_PLAYER_RETRIES: u32 = 3; + let mut player_failed = false; + + loop { + match agent + .execute_task_with_timing( + &player_prompt, + None, + false, + show_prompt, + show_code, + true, + if has_discovery { + Some(DiscoveryOptions { + messages: &discovery_messages, + fast_start_path: discovery_working_dir.as_deref(), + }) + } else { None }, + ) + .await + { + Ok(result) => { + // Display player's implementation result + output.print("πŸ“ Player implementation completed:"); + output.print_smart(&result.response); break; } + Err(e) => { + // Check if this is a context length exceeded error + use g3_core::error_handling::{classify_error, ErrorType, RecoverableError}; + let error_type = classify_error(&e); - // Continue to next iteration with empty feedback (restart from scratch) - coach_feedback = String::new(); - continue; + if matches!(error_type, ErrorType::Recoverable(RecoverableError::ContextLengthExceeded)) { + output.print(&format!("⚠️ Context length exceeded in player turn: {}", e)); + output.print("πŸ“ Logging error to session and ending current turn..."); + + // Build forensic context + let forensic_context = format!( + "Turn: {}\n\ + Role: Player\n\ + Context tokens: {}\n\ + Total available: {}\n\ + Percentage used: {:.1}%\n\ + Prompt length: {} chars\n\ + Error occurred at: {}", + turn, + agent.get_context_window().used_tokens, + agent.get_context_window().total_tokens, + agent.get_context_window().percentage_used(), + player_prompt.len(), + chrono::Utc::now().to_rfc3339() + ); + + // Log to session JSON + agent.log_error_to_session(&e, "assistant", Some(forensic_context)); + + // Mark turn as failed and continue to next turn + player_failed = true; + break; + } else if e.to_string().contains("panic") { + output.print(&format!("πŸ’₯ Player panic detected: {}", e)); + + // Generate final report even for panic + let elapsed = start_time.elapsed(); + let context_window = agent.get_context_window(); + + output.print(&format!("\n{}", "=".repeat(60))); + output.print("πŸ“Š AUTONOMOUS MODE SESSION REPORT"); + output.print(&"=".repeat(60)); + + output.print(&format!( + "⏱️ Total Duration: {:.2}s", + elapsed.as_secs_f64() + )); + output.print(&format!("πŸ”„ Turns Taken: {}/{}", turn, max_turns)); + output.print("πŸ“ Final Status: πŸ’₯ PLAYER PANIC"); + + output.print("\nπŸ“ˆ Token Usage Statistics:"); + output.print(&format!( + " β€’ Used Tokens: {}", + context_window.used_tokens + )); + output.print(&format!( + " β€’ Total Available: {}", + context_window.total_tokens + )); + output.print(&format!( + " β€’ Cumulative Tokens: {}", + context_window.cumulative_tokens + )); + output.print(&format!( + " β€’ Usage Percentage: {:.1}%", + context_window.percentage_used() + )); + // Add per-turn histogram + output.print(&generate_turn_histogram(&turn_metrics)); + output.print(&"=".repeat(60)); + + return Err(e); + } + + _player_retry_count += 1; + output.print(&format!( + "⚠️ Player error (attempt {}/{}): {}", + _player_retry_count, MAX_PLAYER_RETRIES, e + )); + + if _player_retry_count >= MAX_PLAYER_RETRIES { + output.print( + "πŸ”„ Max retries reached for player, marking turn as failed...", + ); + player_failed = true; + break; // Exit retry loop + } + output.print("πŸ”„ Retrying player implementation..."); + } + } + } + + // If player failed after max retries, increment turn and continue + if player_failed { + output.print(&format!( + "⚠️ Player turn {} failed after max retries. Moving to next turn.", + turn + )); + // Record turn metrics before incrementing + let turn_duration = turn_start_time.elapsed(); + let turn_tokens = agent.get_context_window().used_tokens.saturating_sub(turn_start_tokens); + turn_metrics.push(TurnMetrics { + turn_number: turn, + tokens_used: turn_tokens, + wall_clock_time: turn_duration, + }); + turn += 1; + + // Check if we've reached max turns + if turn > max_turns { + output.print("\n=== SESSION COMPLETED - MAX TURNS REACHED ==="); + output.print(&format!("⏰ Maximum turns ({}) reached", max_turns)); + break; } - // Give some time for file operations to complete - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + // Continue to next iteration with empty feedback (restart from scratch) + coach_feedback = String::new(); + continue; } + // Give some time for file operations to complete + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + // Create a new agent instance for coach mode to ensure fresh context // Use the same config with overrides that was passed to the player agent let base_config = agent.get_config().clone();