diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index d8d7ff2..5ccc317 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -275,8 +275,11 @@ pub async fn run() -> Result<()> { std::env::current_dir()? }; - // Check if we're in a project directory and read README if available - // This should happen in both interactive and autonomous modes + // Check if we're in a project directory and read README and AGENTS.md if available + // Load AGENTS.md first (if present) to provide agent-specific instructions + let agents_content = read_agents_config(&workspace_dir); + + // Then load README for project context let readme_content = read_project_readme(&workspace_dir); // Create project model @@ -320,10 +323,21 @@ pub async fn run() -> Result<()> { // Initialize agent let ui_writer = ConsoleUiWriter::new(); + + // Combine AGENTS.md and README content if both exist + let combined_content = match (agents_content.clone(), readme_content.clone()) { + (Some(agents), Some(readme)) => { + Some(format!("{}\n\n{}", agents, readme)) + } + (Some(agents), None) => Some(agents), + (None, Some(readme)) => Some(readme), + (None, None) => None, + }; + let mut agent = if cli.autonomous { - Agent::new_autonomous_with_readme_and_quiet(config.clone(), ui_writer, readme_content.clone(), cli.quiet).await? + Agent::new_autonomous_with_readme_and_quiet(config.clone(), ui_writer, combined_content.clone(), cli.quiet).await? } else { - Agent::new_with_readme_and_quiet(config.clone(), ui_writer, readme_content.clone(), cli.quiet).await? + Agent::new_with_readme_and_quiet(config.clone(), ui_writer, combined_content.clone(), cli.quiet).await? }; // Execute task, autonomous mode, or start interactive mode @@ -364,20 +378,61 @@ pub async fn run() -> Result<()> { cli.show_prompt, cli.show_code, cli.theme, - readme_content, + combined_content, ) .await?; } else { // Use standard terminal UI let output = SimpleOutput::new(); output.print(&format!("📁 Workspace: {}", project.workspace().display())); - run_interactive(agent, cli.show_prompt, cli.show_code, readme_content).await?; + run_interactive(agent, cli.show_prompt, cli.show_code, combined_content).await?; } } Ok(()) } +/// Check if we're in a project directory and read AGENTS.md if available +fn read_agents_config(workspace_dir: &Path) -> Option { + // Look for AGENTS.md in the current directory + let agents_path = workspace_dir.join("AGENTS.md"); + + if agents_path.exists() { + match std::fs::read_to_string(&agents_path) { + Ok(content) => { + // Return the content with a note about which file was read + info!("Loaded AGENTS.md from {}", agents_path.display()); + Some(format!( + "🤖 Agent Configuration (from AGENTS.md):\n\n{}", + content + )) + } + Err(e) => { + // Log the error but continue without the agents config + error!("Failed to read AGENTS.md: {}", e); + None + } + } + } else { + // Check for alternative names + let alt_path = workspace_dir.join("agents.md"); + if alt_path.exists() { + match std::fs::read_to_string(&alt_path) { + Ok(content) => { + info!("Loaded agents.md from {}", alt_path.display()); + Some(format!("🤖 Agent Configuration (from agents.md):\n\n{}", content)) + } + Err(e) => { + error!("Failed to read agents.md: {}", e); + None + } + } + } else { + None + } + } +} + /// Check if we're in a project directory and read README if available fn read_project_readme(workspace_dir: &Path) -> Option { // Check if we're in a project directory (contains .g3 or .git) @@ -478,7 +533,7 @@ async fn run_interactive_retro( show_prompt: bool, show_code: bool, theme_name: Option, - readme_content: Option, + combined_content: Option, ) -> Result<()> { use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use std::time::Duration; @@ -500,24 +555,31 @@ async fn run_interactive_retro( // Create agent with RetroTuiWriter let ui_writer = RetroTuiWriter::new(tui.clone()); - let mut agent = Agent::new_with_readme_and_quiet(config, ui_writer, readme_content.clone(), false).await?; + let mut agent = Agent::new_with_readme_and_quiet(config, ui_writer, combined_content.clone(), false).await?; // Display initial system messages tui.output("SYSTEM: AGENT ONLINE\n\n"); - // Display message if README was loaded - if readme_content.is_some() { - // Extract the first heading or title from the README - let readme_snippet = if let Some(ref content) = readme_content { - extract_readme_heading(content) - .unwrap_or_else(|| "PROJECT DOCUMENTATION LOADED".to_string()) - } else { - "PROJECT DOCUMENTATION LOADED".to_string() - }; - tui.output(&format!( - "SYSTEM: PROJECT README LOADED - {}\n\n", - readme_snippet - )); + // Display message if AGENTS.md or README was loaded + if let Some(ref content) = combined_content { + // Check what was loaded + let has_agents = content.contains("Agent Configuration"); + let has_readme = content.contains("Project README"); + + if has_agents { + tui.output("SYSTEM: AGENT CONFIGURATION LOADED\n\n"); + } + + if has_readme { + // Extract the first heading or title from the README + let readme_snippet = extract_readme_heading(content) + .unwrap_or_else(|| "PROJECT DOCUMENTATION LOADED".to_string()); + + tui.output(&format!( + "SYSTEM: PROJECT README LOADED - {}\n\n", + readme_snippet + )); + } } tui.output("SYSTEM: READY FOR INPUT\n\n"); tui.output("\n\n"); @@ -739,7 +801,7 @@ async fn run_interactive( mut agent: Agent, show_prompt: bool, show_code: bool, - readme_content: Option, + combined_content: Option, ) -> Result<()> { let output = SimpleOutput::new(); @@ -758,17 +820,23 @@ async fn run_interactive( } } - // Display message if README was loaded - if readme_content.is_some() { - // Extract the first heading or title from the README - let readme_snippet = if let Some(ref content) = readme_content { - extract_readme_heading(content) - .unwrap_or_else(|| "Project documentation loaded".to_string()) - } else { - "Project documentation loaded".to_string() - }; + // Display message if AGENTS.md or README was loaded + if let Some(ref content) = combined_content { + // Check what was loaded + let has_agents = content.contains("Agent Configuration"); + let has_readme = content.contains("Project README"); + + if has_agents { + output.print("🤖 AGENTS.md configuration loaded"); + } + + if has_readme { + // Extract the first heading or title from the README + let readme_snippet = extract_readme_heading(content) + .unwrap_or_else(|| "Project documentation loaded".to_string()); - output.print(&format!("📚 detected: {}", readme_snippet)); + output.print(&format!("📚 detected: {}", readme_snippet)); + } } output.print(""); diff --git a/crates/g3-cli/src/retro_tui.rs b/crates/g3-cli/src/retro_tui.rs index b5d0bbf..2aa9e97 100644 --- a/crates/g3-cli/src/retro_tui.rs +++ b/crates/g3-cli/src/retro_tui.rs @@ -250,7 +250,7 @@ impl TerminalState { } /// Parse markdown and convert to styled lines - fn parse_markdown_line(&self, line: &str) -> Line { + fn parse_markdown_line(&self, line: &str) -> Line<'_> { // Skip parsing for special status lines to preserve their formatting if line.starts_with("[SUCCESS]") || line.starts_with("[FAILED]") || diff --git a/crates/g3-core/src/error_handling_test.rs b/crates/g3-core/src/error_handling_test.rs index 1fde9e9..0b0ab97 100644 --- a/crates/g3-core/src/error_handling_test.rs +++ b/crates/g3-core/src/error_handling_test.rs @@ -17,6 +17,7 @@ mod tests { "test prompt".to_string(), None, 100, + false, // quiet parameter ); let result = retry_with_backoff( @@ -55,6 +56,7 @@ mod tests { "test prompt".to_string(), None, 100, + false, // quiet parameter ); let result: Result<&str, _> = retry_with_backoff( @@ -87,6 +89,7 @@ mod tests { "test prompt".to_string(), None, 100, + false, // quiet parameter ); let result: Result<&str, _> = retry_with_backoff( @@ -118,6 +121,7 @@ mod tests { long_prompt, None, 100, + false, // quiet parameter ); // The prompt should be truncated to 1000 chars