1use crate::{AgentError, Result};
7use forge_core::{
8 types::{Symbol, SymbolId},
9 Forge,
10};
11use std::collections::HashMap;
12use std::sync::Arc;
13
14#[derive(Clone)]
18pub struct Observer {
19 forge: Arc<Forge>,
21 cache: Arc<tokio::sync::RwLock<HashMap<String, Observation>>>,
23}
24
25impl Observer {
26 pub fn new(forge: Forge) -> Self {
28 Self {
29 forge: Arc::new(forge),
30 cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
31 }
32 }
33
34 pub async fn gather(&self, query: &str) -> Result<Observation> {
36 {
38 let cache = self.cache.read().await;
39 if let Some(cached) = cache.get(query) {
40 return Ok(cached.clone());
41 }
42 }
43
44 let symbols = self.gather_symbols(query).await?;
46
47 let observation = crate::Observation {
48 query: query.to_string(),
49 symbols,
50 };
51
52 {
54 let mut cache = self.cache.write().await;
55 cache.insert(query.to_string(), observation.clone());
56 }
57
58 Ok(observation)
59 }
60
61 async fn gather_symbols(&self, query: &str) -> Result<Vec<ObservedSymbol>> {
63 let _graph = self.forge.as_ref();
64 let mut symbols = Vec::new();
65
66 let query_lower = query.to_lowercase();
68
69 if query_lower.contains("find") && query_lower.contains("named") {
70 if let Some(pos) = query_lower.find("named") {
72 let remaining = &query[pos + 6..];
73 let name = remaining.trim().trim_end_matches('?').trim().to_string();
74 if !name.is_empty() {
75 symbols.push(ObservedSymbol {
78 id: SymbolId(0),
79 name: name.clone(),
80 kind: forge_core::types::SymbolKind::Function,
81 location: forge_core::types::Location {
82 file_path: std::path::PathBuf::from("<unknown>"),
83 byte_start: 0,
84 byte_end: 0,
85 line_number: 0,
86 },
87 });
88 }
89 }
90 }
91
92 Ok(symbols)
93 }
94
95 pub async fn clear_cache(&self) {
97 let mut cache = self.cache.write().await;
98 cache.clear();
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct ParsedQuery {
105 original: String,
107}
108
109#[derive(Clone, Debug)]
113pub struct Observation {
114 pub query: String,
116 pub symbols: Vec<ObservedSymbol>,
118}
119
120#[derive(Clone, Debug)]
122pub struct ObservedSymbol {
123 pub id: SymbolId,
125 pub name: String,
127 pub kind: forge_core::types::SymbolKind,
129 pub location: forge_core::types::Location,
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use tempfile::TempDir;
137
138 async fn create_test_observer() -> (Observer, TempDir) {
139 let temp_dir = TempDir::new().unwrap();
140 let forge = Forge::open(temp_dir.path()).await.unwrap();
141 let observer = Observer::new(forge);
142 (observer, temp_dir)
143 }
144
145 #[tokio::test]
146 async fn test_observer_creation() {
147 let temp_dir = TempDir::new().unwrap();
148 let forge = Forge::open(temp_dir.path()).await.unwrap();
149 let observer = Observer::new(forge);
150
151 let cache = observer.cache.read().await;
153 assert!(cache.is_empty());
154 }
155
156 #[tokio::test]
157 async fn test_observation_caching() {
158 let (observer, _temp_dir) = create_test_observer().await;
159
160 let result1 = observer.gather("test query").await;
162 assert!(result1.is_ok());
163
164 let result2 = observer.gather("test query").await;
166 assert!(result2.is_ok());
167
168 assert_eq!(result1.unwrap().query, result2.unwrap().query);
170 }
171
172 #[tokio::test]
173 async fn test_clear_cache() {
174 let (observer, _temp_dir) = create_test_observer().await;
175
176 let _ = observer.gather("test query").await;
178
179 observer.clear_cache().await;
181
182 let cache = observer.cache.read().await;
184 assert!(cache.is_empty());
185 }
186}