sh_layer3/memory_system/
project.rs1use crate::memory_system::{DecayPolicy, MemoryStore, TimeBasedDecay};
6use crate::types::{Layer3Result, MemoryEntry, MemoryQuery, MemoryTier};
7use async_trait::async_trait;
8use parking_lot::RwLock;
9use std::path::PathBuf;
10use std::sync::Arc;
11
12#[allow(dead_code)]
16pub struct ProjectMemory {
17 #[allow(dead_code)]
19 project_root: PathBuf,
20 memory_path: PathBuf,
22 cache: Arc<RwLock<Vec<MemoryEntry>>>,
24 #[allow(dead_code)]
26 decay_policy: Box<dyn DecayPolicy>,
27}
28
29impl ProjectMemory {
30 pub fn new(project_root: PathBuf) -> Self {
31 let memory_path = project_root.join(".continuum").join("memory");
32 Self {
33 project_root,
34 memory_path,
35 cache: Arc::new(RwLock::new(Vec::new())),
36 decay_policy: Box::new(TimeBasedDecay::default()),
37 }
38 }
39
40 async fn ensure_dir(&self) -> Layer3Result<()> {
42 tokio::fs::create_dir_all(&self.memory_path).await?;
43 Ok(())
44 }
45}
46
47#[async_trait]
48impl MemoryStore for ProjectMemory {
49 fn tier(&self) -> MemoryTier {
50 MemoryTier::Project
51 }
52
53 async fn store(&self, entry: MemoryEntry) -> Layer3Result<String> {
54 self.ensure_dir().await?;
55 let file_path = self.memory_path.join(format!("{}.json", entry.id));
56 let content = serde_json::to_string(&entry)?;
57 tokio::fs::write(&file_path, content).await?;
58
59 let mut cache = self.cache.write();
60 cache.push(entry.clone());
61
62 Ok(entry.id)
63 }
64
65 async fn get(&self, id: &str) -> Layer3Result<Option<MemoryEntry>> {
66 {
68 let cache = self.cache.read();
69 if let Some(entry) = cache.iter().find(|e| e.id == id) {
70 return Ok(Some(entry.clone()));
71 }
72 }
73
74 let file_path = self.memory_path.join(format!("{}.json", id));
76 if file_path.exists() {
77 let content = tokio::fs::read_to_string(&file_path).await?;
78 let entry: MemoryEntry = serde_json::from_str(&content)?;
79 return Ok(Some(entry));
80 }
81
82 Ok(None)
83 }
84
85 async fn delete(&self, id: &str) -> Layer3Result<bool> {
86 let file_path = self.memory_path.join(format!("{}.json", id));
87 if file_path.exists() {
88 tokio::fs::remove_file(&file_path).await?;
89 self.cache.write().retain(|e| e.id != id);
90 return Ok(true);
91 }
92 Ok(false)
93 }
94
95 async fn query(&self, query: &MemoryQuery) -> Layer3Result<Vec<MemoryEntry>> {
96 let cache = self.cache.read();
97 let results: Vec<MemoryEntry> = cache
98 .iter()
99 .filter(|e| {
100 if let Some(tier) = query.tier {
101 if e.tier != tier {
102 return false;
103 }
104 }
105 e.content.contains(&query.query)
106 })
107 .take(query.limit.unwrap_or(10))
108 .cloned()
109 .collect();
110 Ok(results)
111 }
112
113 async fn list(&self, limit: Option<usize>) -> Layer3Result<Vec<MemoryEntry>> {
114 let cache = self.cache.read();
115 Ok(cache
116 .iter()
117 .take(limit.unwrap_or(usize::MAX))
118 .cloned()
119 .collect())
120 }
121
122 async fn clear(&self) -> Layer3Result<usize> {
123 let count = self.cache.read().len();
124 self.cache.write().clear();
125
126 if self.memory_path.exists() {
128 let mut entries = tokio::fs::read_dir(&self.memory_path).await?;
129 while let Some(entry) = entries.next_entry().await? {
130 tokio::fs::remove_file(entry.path()).await?;
131 }
132 }
133
134 Ok(count)
135 }
136
137 async fn count(&self) -> Layer3Result<usize> {
138 Ok(self.cache.read().len())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_project_memory_tier() {
148 let memory = ProjectMemory::new(PathBuf::from("/tmp/test"));
149 assert_eq!(memory.tier(), MemoryTier::Project);
150 }
151}