remem-ai 0.3.8

Persistent memory for Claude Code — single binary, zero subprocesses
Documentation
use anyhow::Result;
use rusqlite::Connection;

use crate::memory::{self, Memory};

use super::query_global_preferences;

pub fn dedup_with_claude_md(prefs: &[Memory], cwd: &str) -> Vec<usize> {
    let claude_md_path = std::path::Path::new(cwd).join("CLAUDE.md");
    let claude_md_content = std::fs::read_to_string(&claude_md_path).unwrap_or_default();

    if claude_md_content.is_empty() {
        return (0..prefs.len()).collect();
    }

    let claude_lower = claude_md_content.to_lowercase();
    (0..prefs.len())
        .filter(|&i| {
            let title_lower = prefs[i].title.to_lowercase();
            let search_term = title_lower
                .strip_prefix("preference: ")
                .unwrap_or(&title_lower);
            !claude_lower.contains(search_term)
        })
        .collect()
}

pub fn render_preferences(
    output: &mut String,
    conn: &Connection,
    project: &str,
    cwd: &str,
) -> Result<()> {
    let project_prefs = memory::get_memories_by_type(conn, project, "preference", 20)?;
    let global_prefs = query_global_preferences(conn, 10).unwrap_or_default();

    let mut all_prefs = project_prefs;
    let project_topics: std::collections::HashSet<String> = all_prefs
        .iter()
        .filter_map(|memory| memory.topic_key.clone())
        .collect();
    for global_pref in global_prefs {
        if let Some(ref topic_key) = global_pref.topic_key {
            if !project_topics.contains(topic_key) {
                all_prefs.push(global_pref);
            }
        }
    }

    if all_prefs.is_empty() {
        return Ok(());
    }

    let keep_indices = dedup_with_claude_md(&all_prefs, cwd);
    if keep_indices.is_empty() {
        return Ok(());
    }

    output.push_str("## Your Preferences (always apply these)\n");
    let mut total_chars = 0;
    const MAX_CHARS: usize = 1500;

    for &idx in &keep_indices {
        let pref = &all_prefs[idx];
        let text = pref.text.trim();
        let line = if text.len() > 120 {
            format!(
                "- {}\n",
                &text[..text.chars().take(120).map(|ch| ch.len_utf8()).sum()]
            )
        } else {
            format!("- {}\n", text)
        };
        if total_chars + line.len() > MAX_CHARS && total_chars > 0 {
            break;
        }
        output.push_str(&line);
        total_chars += line.len();
    }
    output.push('\n');

    Ok(())
}