memrecd 0.3.0

Local-first AI memory with project isolation — for terminal, for private use
Documentation
use anyhow::Result;
use std::sync::Arc;
use chrono::Utc;
use tracing::info;

use memrec_common::MemoryConfig;
use crate::storage::MemoryStorage;
use crate::importance::ImportanceCalculator;

pub struct LifecycleManager {
    storage: Arc<dyn MemoryStorage>,
    calculator: ImportanceCalculator,
    config: MemoryConfig,
}

impl LifecycleManager {
    pub fn new(
        storage: Arc<dyn MemoryStorage>,
        calculator: ImportanceCalculator,
        config: MemoryConfig,
    ) -> Self {
        Self {
            storage,
            calculator,
            config,
        }
    }
    
    pub async fn recalculate_importance(&self) -> Result<()> {
        info!("Recalculating importance for all memories");
        
        let memories = self.storage.list(1000).await?;
        
        for memory in memories {
            let new_importance = self.calculator.calculate(&memory);
            
            if memory.importance != new_importance {
                let mut updated = memory;
                updated.importance = new_importance;
                self.storage.update(&updated).await?;
            }
        }
        
        info!("Importance recalculation completed");
        Ok(())
    }
    
    pub async fn cleanup_cycle(&self) -> Result<()> {
        info!("Starting cleanup cycle");
        
        self.cleanup_deleted().await?;
        self.cleanup_low_importance().await?;
        
        info!("Cleanup cycle completed");
        Ok(())
    }
    
    async fn cleanup_deleted(&self) -> Result<()> {
        let deleted = self.storage.list_deleted().await?;
        let now = Utc::now();
        
        for memory in deleted {
            if let Some(deleted_at) = memory.deleted_at {
                let days_since_delete = (now - deleted_at).num_days();
                
                if days_since_delete > self.config.soft_delete_recovery_days as i64 {
                    info!("Hard deleting memory {} (deleted {} days ago)", 
                        memory.id, days_since_delete);
                    self.storage.delete(&memory.id).await?;
                }
            }
        }
        
        Ok(())
    }
    
    async fn cleanup_low_importance(&self) -> Result<()> {
        let low_importance = self.storage.list_by_importance(
            0.0,
            self.config.hard_delete_importance,
        ).await?;
        
        let now = Utc::now();
        
        for memory in low_importance {
            let days_inactive = (now - memory.last_accessed).num_days();
            
            if days_inactive > self.config.hard_delete_inactive_days as i64 {
                info!("Deleting low importance memory {} (inactive {} days)", 
                    memory.id, days_inactive);
                self.storage.delete(&memory.id).await?;
            }
        }
        
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::{MemoryStore, RocksDBStore};
    use tempfile::tempdir;
    use memrec_common::{Memory, MemoryType};
    
    #[tokio::test]
    async fn test_recalculate_importance() {
        let dir = tempdir().unwrap();
        let rocksdb = RocksDBStore::open(dir.path()).unwrap();
        let storage = Arc::new(MemoryStore::new(rocksdb));
        
        let memory = Memory::new("test".to_string(), MemoryType::Knowledge);
        storage.save(&memory).await.unwrap();
        
        let calc = ImportanceCalculator::default();
        let config = MemoryConfig::default();
        
        let manager = LifecycleManager::new(storage.clone(), calc, config);
        
        manager.recalculate_importance().await.unwrap();
        
        let retrieved = storage.get(&memory.id).await.unwrap().unwrap();
        assert!(retrieved.importance > 0.0);
    }
}