diff --git a/lib/crates/arc-agent/src/project_docs.rs b/lib/crates/arc-agent/src/project_docs.rs index 5e95e71d8..0669011d1 100644 --- a/lib/crates/arc-agent/src/project_docs.rs +++ b/lib/crates/arc-agent/src/project_docs.rs @@ -1,5 +1,6 @@ use crate::sandbox::Sandbox; use arc_llm::provider::Provider; +use std::collections::HashSet; use tracing::{debug, info, warn}; const BUDGET_BYTES: usize = 32768; @@ -26,6 +27,7 @@ pub async fn discover_project_docs( let mut results = Vec::new(); let mut budget_remaining = BUDGET_BYTES; + let mut seen_content = HashSet::new(); for dir in &directories { for filename in &candidate_filenames { @@ -35,6 +37,10 @@ pub async fn discover_project_docs( warn!(path = %path, "Project doc file empty, skipping"); continue; } + if !seen_content.insert(content.clone()) { + debug!(path = %path, "Project doc duplicate content, skipping"); + continue; + } if content.len() <= budget_remaining { debug!(path = %path, size_bytes = content.len(), "Project doc loaded"); budget_remaining -= content.len(); @@ -184,6 +190,35 @@ mod tests { assert!(docs[0].len() + docs[1].len() <= BUDGET_BYTES); } + #[tokio::test] + async fn deduplicates_symlinked_files() { + let mut files = HashMap::new(); + files.insert("/repo/AGENTS.md".into(), "shared instructions".into()); + files.insert("/repo/CLAUDE.md".into(), "shared instructions".into()); + let env: Arc = Arc::new(MockSandbox { + files, + ..Default::default() + }); + let docs = discover_project_docs(env.as_ref(), "/repo", "/repo", Provider::Anthropic).await; + assert_eq!(docs.len(), 1); + assert_eq!(docs[0], "shared instructions"); + } + + #[tokio::test] + async fn deduplicates_across_directories() { + let mut files = HashMap::new(); + files.insert("/repo/AGENTS.md".into(), "shared instructions".into()); + files.insert("/repo/src/AGENTS.md".into(), "shared instructions".into()); + let env: Arc = Arc::new(MockSandbox { + files, + ..Default::default() + }); + let docs = + discover_project_docs(env.as_ref(), "/repo", "/repo/src", Provider::Anthropic).await; + assert_eq!(docs.len(), 1); + assert_eq!(docs[0], "shared instructions"); + } + #[tokio::test] async fn walks_directory_hierarchy() { let mut files = HashMap::new();