1use std::collections::HashMap;
16use std::sync::{Arc, RwLock};
17use std::time::{SystemTime, UNIX_EPOCH};
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum CodeEntryType {
21 Function,
23 Type,
25 Import,
27 ErrorSolution,
29 Config,
31 Dependency,
33 Convention,
35 Structure,
37 Note,
39}
40
41#[derive(Debug, Clone)]
42pub struct CodeEntry {
43 pub id: u64,
44 pub entry_type: CodeEntryType,
45 pub title: String,
46 pub code: String,
47 pub file_path: String,
48 pub language: String,
49 pub project: String,
50 pub symbols: Vec<String>,
51 pub tags: Vec<String>,
52 pub solution: Option<String>,
53 pub created_ms: u64,
54 pub access_count: u64,
55}
56
57#[derive(Debug, Clone)]
58pub struct CodeQuery {
59 pub query: String,
60 pub language: Option<String>,
61 pub entry_type: Option<CodeEntryType>,
62 pub project: Option<String>,
63 pub file: Option<String>,
64 pub k: usize,
65}
66
67pub struct CodeMemory {
68 entries: Arc<RwLock<Vec<CodeEntry>>>,
69 next_id: Arc<RwLock<u64>>,
70 project_index: Arc<RwLock<HashMap<String, Vec<u64>>>>,
71 symbol_index: Arc<RwLock<HashMap<String, Vec<u64>>>>,
72}
73
74impl Default for CodeMemory {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl CodeMemory {
81 pub fn new() -> Self {
82 Self {
83 entries: Arc::new(RwLock::new(Vec::new())),
84 next_id: Arc::new(RwLock::new(1)),
85 project_index: Arc::new(RwLock::new(HashMap::new())),
86 symbol_index: Arc::new(RwLock::new(HashMap::new())),
87 }
88 }
89
90 fn nid(&self) -> u64 {
91 let mut id = crate::sync_guard::write_lock(&self.next_id);
92 let n = *id;
93 *id += 1;
94 n
95 }
96 fn now(&self) -> u64 {
97 SystemTime::now()
98 .duration_since(UNIX_EPOCH)
99 .unwrap_or_default()
100 .as_millis() as u64
101 }
102
103 #[allow(clippy::too_many_arguments)]
104 pub fn store(
105 &self,
106 entry_type: CodeEntryType,
107 title: &str,
108 code: &str,
109 file_path: &str,
110 language: &str,
111 project: &str,
112 symbols: Vec<String>,
113 tags: Vec<String>,
114 ) -> u64 {
115 let id = self.nid();
116 let entry = CodeEntry {
117 id,
118 entry_type,
119 title: title.to_string(),
120 code: code.to_string(),
121 file_path: file_path.to_string(),
122 language: language.to_string(),
123 project: project.to_string(),
124 symbols: symbols.clone(),
125 tags,
126 solution: None,
127 created_ms: self.now(),
128 access_count: 0,
129 };
130
131 crate::sync_guard::write_lock(&self.entries).push(entry);
132
133 crate::sync_guard::write_lock(&self.project_index)
135 .entry(project.to_string())
136 .or_default()
137 .push(id);
138 for sym in &symbols {
139 crate::sync_guard::write_lock(&self.symbol_index)
140 .entry(sym.to_lowercase())
141 .or_default()
142 .push(id);
143 }
144
145 id
146 }
147
148 pub fn store_error_solution(
149 &self,
150 error: &str,
151 solution: &str,
152 file_path: &str,
153 language: &str,
154 project: &str,
155 ) -> u64 {
156 let id = self.nid();
157 let entry = CodeEntry {
158 id,
159 entry_type: CodeEntryType::ErrorSolution,
160 title: format!("Error: {}", error.chars().take(80).collect::<String>()),
161 code: error.to_string(),
162 file_path: file_path.to_string(),
163 language: language.to_string(),
164 project: project.to_string(),
165 symbols: vec![],
166 tags: vec!["error".to_string(), "solution".to_string()],
167 solution: Some(solution.to_string()),
168 created_ms: self.now(),
169 access_count: 0,
170 };
171 crate::sync_guard::write_lock(&self.entries).push(entry);
172 crate::sync_guard::write_lock(&self.project_index)
173 .entry(project.to_string())
174 .or_default()
175 .push(id);
176 id
177 }
178
179 pub fn recall(&self, query: &CodeQuery) -> Vec<CodeEntry> {
180 let entries = crate::sync_guard::read_lock(&self.entries);
181 let q = query.query.to_lowercase();
182 let mut scored: Vec<(f64, &CodeEntry)> = Vec::new();
183
184 for entry in entries.iter() {
185 let mut score = 0.0;
186
187 if entry.title.to_lowercase().contains(&q) {
189 score += 3.0;
190 }
191 if entry.code.to_lowercase().contains(&q) {
192 score += 2.0;
193 }
194 if let Some(ref sol) = entry.solution {
195 if sol.to_lowercase().contains(&q) {
196 score += 2.0;
197 }
198 }
199
200 for sym in &entry.symbols {
202 if sym.to_lowercase().contains(&q) {
203 score += 2.0;
204 }
205 }
206
207 if let Some(ref lang) = query.language {
209 if entry.language != *lang {
210 score -= 1.0;
211 }
212 }
213 if let Some(ref etype) = query.entry_type {
214 if entry.entry_type != *etype {
215 score -= 1.0;
216 }
217 }
218 if let Some(ref proj) = query.project {
219 if entry.project != *proj {
220 score -= 0.5;
221 }
222 }
223
224 score += (entry.access_count as f64) * 0.1;
226
227 if score > 0.0 {
228 scored.push((score, entry));
229 }
230 }
231
232 scored.sort_by(|a, b| b.0.total_cmp(&a.0));
233 scored.truncate(query.k);
234
235 scored
236 .into_iter()
237 .map(|(_, e)| {
238 let mut entry = e.clone();
239 entry.access_count += 1;
240 entry
241 })
242 .collect()
243 }
244
245 pub fn recall_by_symbol(&self, symbol: &str) -> Vec<CodeEntry> {
246 let sym_idx = crate::sync_guard::read_lock(&self.symbol_index);
247 let sym = symbol.to_lowercase();
248 let ids: Vec<u64> = sym_idx.get(&sym).cloned().unwrap_or_default();
249
250 let entries = crate::sync_guard::read_lock(&self.entries);
251 ids.iter()
252 .filter_map(|id| entries.iter().find(|e| e.id == *id))
253 .cloned()
254 .collect()
255 }
256
257 pub fn recall_by_project(&self, project: &str) -> Vec<CodeEntry> {
258 let proj_idx = crate::sync_guard::read_lock(&self.project_index);
259 let ids: Vec<u64> = proj_idx.get(project).cloned().unwrap_or_default();
260
261 let entries = crate::sync_guard::read_lock(&self.entries);
262 ids.iter()
263 .filter_map(|id| entries.iter().find(|e| e.id == *id))
264 .cloned()
265 .collect()
266 }
267
268 pub fn list_by_type(&self, etype: CodeEntryType) -> Vec<CodeEntry> {
269 let entries = crate::sync_guard::read_lock(&self.entries);
270 entries
271 .iter()
272 .filter(|e| e.entry_type == etype)
273 .cloned()
274 .collect()
275 }
276
277 pub fn get(&self, id: u64) -> Option<CodeEntry> {
278 crate::sync_guard::read_lock(&self.entries)
279 .iter()
280 .find(|e| e.id == id)
281 .cloned()
282 }
283
284 pub fn stats(&self) -> (usize, usize, Vec<(String, usize)>) {
285 let entries = crate::sync_guard::read_lock(&self.entries);
286 let total = entries.len();
287 let errors = entries
288 .iter()
289 .filter(|e| e.entry_type == CodeEntryType::ErrorSolution)
290 .count();
291
292 let mut projects: HashMap<String, usize> = HashMap::new();
293 for e in entries.iter() {
294 *projects.entry(e.project.clone()).or_default() += 1;
295 }
296 let mut sorted: Vec<_> = projects.into_iter().collect();
297 sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
298
299 (total, errors, sorted)
300 }
301}
302
303impl std::fmt::Display for CodeEntryType {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 match self {
306 CodeEntryType::Function => write!(f, "Function"),
307 CodeEntryType::Type => write!(f, "Type"),
308 CodeEntryType::Import => write!(f, "Import"),
309 CodeEntryType::ErrorSolution => write!(f, "ErrorSolution"),
310 CodeEntryType::Config => write!(f, "Config"),
311 CodeEntryType::Dependency => write!(f, "Dependency"),
312 CodeEntryType::Convention => write!(f, "Convention"),
313 CodeEntryType::Structure => write!(f, "Structure"),
314 CodeEntryType::Note => write!(f, "Note"),
315 }
316 }
317}