gitignore awareness
This commit is contained in:
@@ -14,6 +14,7 @@ The heart of the agent system, containing:
|
||||
- **Context Window Management**: Intelligent tracking of token usage with context thinning (50-80%) and auto-summarization at 80% capacity
|
||||
- **Tool System**: Built-in tools for file operations, shell commands, computer control, TODO management, and structured output
|
||||
- **Streaming Response Parser**: Real-time parsing of LLM responses with tool call detection and execution
|
||||
- **Smart Project Awareness**: Automatically detects and respects `.gitignore` patterns, informing the agent about ignored files
|
||||
- **Task Execution**: Support for single and iterative task execution with automatic retry logic
|
||||
|
||||
#### **g3-providers**
|
||||
@@ -97,7 +98,10 @@ These commands give you fine-grained control over context management, allowing y
|
||||
- **Final Output**: Formatted result presentation
|
||||
|
||||
### Provider Flexibility
|
||||
- Support for multiple LLM providers through a unified interface
|
||||
|
||||
### Smart Project Awareness
|
||||
|
||||
- Automatically detects and respects `.gitignore` when present
|
||||
- Hot-swappable providers without code changes
|
||||
- Provider-specific optimizations and feature support
|
||||
- Local model support for offline operation
|
||||
|
||||
76
crates/g3-core/src/gitignore_prompt_tests.rs
Normal file
76
crates/g3-core/src/gitignore_prompt_tests.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
#[cfg(test)]
|
||||
mod gitignore_prompt_tests {
|
||||
use crate::Agent;
|
||||
use crate::ui_writer::UiWriter;
|
||||
|
||||
// Mock UI writer for testing
|
||||
struct MockUiWriter;
|
||||
|
||||
impl UiWriter for MockUiWriter {
|
||||
fn print_agent_prompt(&self) {}
|
||||
fn print_agent_response(&self, _text: &str) {}
|
||||
fn print(&self, _message: &str) {}
|
||||
fn print_inline(&self, _message: &str) {}
|
||||
fn print_tool_output_line(&self, _line: &str) {}
|
||||
fn print_system_prompt(&self, _text: &str) {}
|
||||
fn print_tool_header(&self, _tool_name: &str) {}
|
||||
fn print_tool_arg(&self, _key: &str, _value: &str) {}
|
||||
fn print_tool_output_header(&self) {}
|
||||
fn update_tool_output_line(&self, _line: &str) {}
|
||||
fn print_tool_output_summary(&self, _total_lines: usize) {}
|
||||
fn print_tool_timing(&self, _duration: &str) {}
|
||||
fn print_context_status(&self, _message: &str) {}
|
||||
fn print_context_thinning(&self, _message: &str) {}
|
||||
fn println(&self, _text: &str) {}
|
||||
fn flush(&self) {}
|
||||
fn notify_sse_received(&self) {}
|
||||
fn wants_full_output(&self) -> bool { false }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gitignore_prompt_snippet_with_file() {
|
||||
// Create a temporary .gitignore file
|
||||
let test_gitignore = "# Test comment\ntarget/\n*.log\n\n# Another comment\nlogs/\n";
|
||||
std::fs::write(".gitignore.test", test_gitignore).unwrap();
|
||||
|
||||
// Temporarily rename actual .gitignore if it exists
|
||||
let has_real_gitignore = std::path::Path::new(".gitignore").exists();
|
||||
if has_real_gitignore {
|
||||
std::fs::rename(".gitignore", ".gitignore.backup").unwrap();
|
||||
}
|
||||
|
||||
// Rename test file to .gitignore
|
||||
std::fs::rename(".gitignore.test", ".gitignore").unwrap();
|
||||
|
||||
let snippet = Agent::<MockUiWriter>::get_gitignore_prompt_snippet();
|
||||
|
||||
// Restore original .gitignore
|
||||
std::fs::remove_file(".gitignore").unwrap();
|
||||
if has_real_gitignore {
|
||||
std::fs::rename(".gitignore.backup", ".gitignore").unwrap();
|
||||
}
|
||||
|
||||
assert!(snippet.contains("IMPORTANT"));
|
||||
assert!(snippet.contains(".gitignore"));
|
||||
assert!(snippet.contains("target/"));
|
||||
assert!(snippet.contains("*.log"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gitignore_prompt_snippet_without_file() {
|
||||
// Temporarily rename .gitignore if it exists
|
||||
let has_gitignore = std::path::Path::new(".gitignore").exists();
|
||||
if has_gitignore {
|
||||
std::fs::rename(".gitignore", ".gitignore.backup").unwrap();
|
||||
}
|
||||
|
||||
let snippet = Agent::<MockUiWriter>::get_gitignore_prompt_snippet();
|
||||
|
||||
// Restore .gitignore
|
||||
if has_gitignore {
|
||||
std::fs::rename(".gitignore.backup", ".gitignore").unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(snippet, "");
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ mod tilde_expansion_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod error_handling_test;
|
||||
|
||||
#[cfg(test)]
|
||||
mod gitignore_prompt_tests;
|
||||
use anyhow::Result;
|
||||
use g3_computer_control::WebDriverController;
|
||||
use g3_config::Config;
|
||||
@@ -1031,6 +1034,28 @@ impl<W: UiWriter> Agent<W> {
|
||||
Ok(context_length)
|
||||
}
|
||||
|
||||
/// Check if .gitignore exists and return a prompt snippet about respecting it
|
||||
fn get_gitignore_prompt_snippet() -> String {
|
||||
// Check if .gitignore exists in the current directory
|
||||
if std::path::Path::new(".gitignore").exists() {
|
||||
// Try to read it to show some examples of what's ignored
|
||||
if let Ok(gitignore_content) = std::fs::read_to_string(".gitignore") {
|
||||
// Extract non-comment, non-empty lines as examples
|
||||
let patterns: Vec<&str> = gitignore_content
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
|
||||
.take(5) // Show up to 5 patterns
|
||||
.collect();
|
||||
|
||||
if !patterns.is_empty() {
|
||||
return format!("\n\nIMPORTANT: This project has a .gitignore file. When using ripgrep or other file operations, be aware that the following patterns are ignored: {}. Ripgrep automatically respects .gitignore by default.", patterns.join(", "));
|
||||
}
|
||||
}
|
||||
return "\n\nIMPORTANT: This project has a .gitignore file. When using ripgrep or other file operations, ripgrep automatically respects .gitignore by default.".to_string();
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn get_provider_info(&self) -> Result<(String, String)> {
|
||||
let provider = self.providers.get(None)?;
|
||||
Ok((provider.name().to_string(), provider.model().to_string()))
|
||||
@@ -1135,7 +1160,9 @@ impl<W: UiWriter> Agent<W> {
|
||||
// Only add system message if this is the first interaction (empty conversation history)
|
||||
if self.context_window.conversation_history.is_empty() {
|
||||
let provider = self.providers.get(None)?;
|
||||
let system_prompt = if provider.has_native_tool_calling() {
|
||||
let gitignore_snippet = Self::get_gitignore_prompt_snippet();
|
||||
|
||||
let mut system_prompt = if provider.has_native_tool_calling() {
|
||||
// For native tool calling providers, use a more explicit system prompt
|
||||
"You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.
|
||||
|
||||
@@ -1267,6 +1294,9 @@ Template:
|
||||
|
||||
".to_string()
|
||||
};
|
||||
|
||||
// Append gitignore snippet if present
|
||||
system_prompt.push_str(&gitignore_snippet);
|
||||
|
||||
if show_prompt {
|
||||
self.ui_writer.print_system_prompt(&system_prompt);
|
||||
|
||||
Reference in New Issue
Block a user