remem-ai 0.6.81

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
const MAX_SESSIONS: usize = 10;
const MAX_DECISIONS: usize = 8;
const REQUEST_CHAR_LIMIT: usize = 120;
const COMPLETED_CHAR_LIMIT: usize = 200;
pub(crate) const REMEM_FILE: &str = "remem_sessions.md";

pub(super) struct SessionRow {
    pub request: String,
    pub completed: Option<String>,
    pub decisions: Option<String>,
    pub created_at_epoch: i64,
}

pub(super) fn max_sessions() -> usize {
    MAX_SESSIONS
}

pub(super) fn render_memory_content(sessions: &[SessionRow]) -> Option<String> {
    if sessions.is_empty() {
        return None;
    }

    let mut content = String::new();
    content.push_str("---\n");
    content.push_str("name: remem_sessions\n");
    content.push_str(
        "description: 最近会话摘要和关键决策(由 remem 自动生成),详细记忆用 remem search 查询\n",
    );
    content.push_str("type: project\n");
    content.push_str("---\n\n");

    content.push_str("## Recent Sessions\n\n");
    for session in sessions {
        let date = format_date(session.created_at_epoch);
        let request: String = session.request.chars().take(REQUEST_CHAR_LIMIT).collect();
        content.push_str(&format!("**{}** {}\n", date, request));

        if let Some(completed) = &session.completed {
            let first_line = completed
                .lines()
                .find(|line| !line.trim().is_empty())
                .unwrap_or("");
            let truncated: String = first_line.chars().take(COMPLETED_CHAR_LIMIT).collect();
            if !truncated.is_empty() {
                content.push_str(&format!("{}\n", truncated));
            }
        }
        content.push('\n');
    }

    let all_decisions = collect_unique_decisions(sessions);
    if !all_decisions.is_empty() {
        content.push_str("## Key Decisions\n\n");
        for decision in &all_decisions {
            let truncated: String = decision.chars().take(COMPLETED_CHAR_LIMIT).collect();
            content.push_str(&format!("- {}\n", truncated));
        }
        content.push('\n');
    }

    content.push_str("---\n");
    content.push_str("*Auto-generated by remem. 详细记忆: `remem search` MCP 工具 | 数据库: ~/.remem/remem.db*\n");
    Some(content)
}

fn collect_unique_decisions(sessions: &[SessionRow]) -> Vec<String> {
    let mut all_decisions = Vec::new();
    for session in sessions {
        if let Some(decisions) = &session.decisions {
            for line in decisions.lines() {
                let trimmed = line
                    .trim()
                    .trim_start_matches(['', '-', '*', ' ', ':'])
                    .replace("**", "");
                let trimmed = trimmed.trim();
                if !trimmed.is_empty() && all_decisions.len() < MAX_DECISIONS {
                    let prefix: String = trimmed.chars().take(40).collect();
                    if !all_decisions
                        .iter()
                        .any(|existing: &String| existing.starts_with(&prefix))
                    {
                        all_decisions.push(trimmed.to_string());
                    }
                }
            }
        }
    }
    all_decisions
}

fn format_date(epoch: i64) -> String {
    chrono::DateTime::from_timestamp(epoch, 0)
        .map(|dt| {
            use chrono::TimeZone;
            let local = chrono::Local.from_utc_datetime(&dt.naive_utc());
            local.format("%m-%d %H:%M").to_string()
        })
        .unwrap_or_default()
}