Skip to main content

microscope_memory/
code_memory.rs

1//! Code Memory — dedikált kódmemória réteg kódoló agentek számára
2//!
3//! Claude Code, Cline, Kilo Code, OpenCode és más kódoló agentek
4//! memória rétege. Tárolja és visszakeresi:
5//! - Kódrészletek és függvények
6//! - Projektstruktúra és függőségek
7//! - Hiba → megoldás párok
8//! - Kódolási konvenciók
9//! - Refaktorálási előzmények
10//!
11//! Használat:
12//!   microscope-mem code store --func "fn parse()" --file src/parser.rs
13//!   microscope-mem code recall --query "hogyan oldottuk meg a borrow checkert"
14
15use std::collections::HashMap;
16use std::sync::{Arc, RwLock};
17use std::time::{SystemTime, UNIX_EPOCH};
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum CodeEntryType {
21    /// Függvény/metódus definíció
22    Function,
23    /// Osztály/trait/struct definíció
24    Type,
25    /// Import/modul referenciák
26    Import,
27    /// Hiba → megoldás pár
28    ErrorSolution,
29    /// Projekt konfiguráció
30    Config,
31    /// Függőség (crate, package)
32    Dependency,
33    /// Kódolási konvenció
34    Convention,
35    /// Fájl/projekt struktúra
36    Structure,
37    /// Megjegyzés/dokumentáció
38    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        // Indexek
134        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            // Kulcsszó egyezés
188            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            // Szimbólum egyezés
201            for sym in &entry.symbols {
202                if sym.to_lowercase().contains(&q) {
203                    score += 2.0;
204                }
205            }
206
207            // Szűrők
208            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            // Access count boost
225            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}