Skip to main content

sh_layer3/memory_system/
project.rs

1//! # Project Memory
2//!
3//! 项目记忆:项目级别的知识库,持久化到文件系统。
4
5use 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/// Project Memory 实现
13///
14/// 持久化到项目目录下的 `.continuum/memory/`。
15#[allow(dead_code)]
16pub struct ProjectMemory {
17    /// 项目根目录
18    #[allow(dead_code)]
19    project_root: PathBuf,
20    /// 内存文件路径
21    memory_path: PathBuf,
22    /// 内存缓存
23    cache: Arc<RwLock<Vec<MemoryEntry>>>,
24    /// 衰减策略
25    #[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    /// 确保内存目录存在
41    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        // 先查缓存
67        {
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        // 从文件读取
75        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        // 删除文件
127        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}