lorefs 0.1.0

A lightweight, high-performance Rust native SDK for File-First Agent Memory.
Documentation
use chrono::Local;
use crate::config::LoreConfig;
use crate::error::Result;
use crate::file_manager::FileManager;
use crate::git_manager::GitManager;
use crate::reflection::ReflectionEngine;

pub struct LoreFS {
    config: LoreConfig,
    file_manager: FileManager,
    git_manager: Option<GitManager>,
    reflection_engine: ReflectionEngine,
    turn_count: usize,
}

impl LoreFS {
    pub fn new(config: LoreConfig) -> Result<Self> {
        let file_manager = FileManager::new(config.base_dir.clone())?;
        
        let git_manager = if config.git_enabled {
            Some(GitManager::new(file_manager.base_path())?)
        } else {
            None
        };

        let lore = Self {
            config,
            file_manager,
            git_manager,
            reflection_engine: ReflectionEngine::new(),
            turn_count: 0,
        };

        lore.init_templates()?;

        Ok(lore)
    }

    fn init_templates(&self) -> Result<()> {
        let templates = [
            ("system/persona.md", "# Persona\nDefine the agent's personality and identity here."),
            ("system/preferences.md", "# Preferences\nStore user-specific preferences and global settings."),
            ("system/workflow.md", "# Workflow\nDocument standard operating procedures and task flows."),
            ("MEMORY.md", "# Long-term Memory\nKey facts and decisions go here."),
            ("USER.md", "# User Profile\nInformation about the user."),
        ];

        for (path, content) in templates {
            if self.file_manager.read_file(path).is_err() {
                self.file_manager.write_file(path, content)?;
            }
        }

        if let Some(git) = &self.git_manager {
            git.commit("Initial memory structure")?;
        }

        Ok(())
    }

    pub fn add(&mut self, text: &str, file: &str, tags: &[&str]) -> Result<()> {
        let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
        let tag_str = if tags.is_empty() {
            "".to_string()
        } else {
            format!(" #{}", tags.join(" #"))
        };
        
        let entry = format!("- [{}] {}{}", timestamp, text, tag_str);
        self.file_manager.append_to_file(file, &entry)?;

        self.increment_turn()?;
        
        if self.config.auto_commit {
            if let Some(git) = &self.git_manager {
                git.commit(&format!("Add memory to {}: {}", file, text))?;
            }
        }

        Ok(())
    }

    pub fn update_file(&mut self, path: &str, content: &str) -> Result<()> {
        self.file_manager.write_file(path, content)?;
        
        self.increment_turn()?;

        if self.config.auto_commit {
            if let Some(git) = &self.git_manager {
                git.commit(&format!("Update memory file: {}", path))?;
            }
        }
        Ok(())
    }

    pub fn search(&self, query: &str, _limit: usize, _scope: Option<&str>) -> Result<Vec<(String, String)>> {
        // Simple search implementation
        self.file_manager.search(query)
    }

    pub fn get_memory_tree(&self) -> String {
        self.file_manager.get_tree()
    }

    pub fn reflect(&self) -> Result<()> {
        self.reflection_engine.reflect(&self.file_manager)
    }

    fn increment_turn(&mut self) -> Result<()> {
        self.turn_count += 1;
        if self.turn_count % self.config.reflection_every_n_turns == 0 {
            self.reflect()?;
        }
        Ok(())
    }

    pub fn read_file(&self, path: &str) -> Result<String> {
        self.file_manager.read_file(path)
    }

    pub fn get_pinned_context(&self) -> Result<String> {
        let mut context = String::new();
        context.push_str("=== PINNED MEMORY CONTEXT ===\n\n");
        
        for file in &self.config.pinned_files {
            if let Ok(content) = self.file_manager.read_file(file) {
                context.push_str(&format!("--- {} ---\n", file));
                context.push_str(&content);
                context.push_str("\n\n");
            }
        }
        
        context.push_str("=============================\n");
        Ok(context)
    }
}