remem-ai 0.6.78

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::Result;
use rusqlite::Connection;

use super::{save_memory, SaveMemoryRequest};
use crate::db::{self, test_support::ScopedTestDataDir};

fn seed_compilable_preference(conn: &Connection) -> Result<()> {
    conn.execute(
        "INSERT INTO memory_candidates
         (id, scope, memory_type, topic_key, text, evidence_event_ids,
          confidence, risk_class, review_status, created_at_epoch, updated_at_epoch,
          source_trust_class)
         VALUES (1, 'project', 'preference', 'package-manager-choice',
                 'Use bun, not npm', '[1,2,3]', 0.95, 'low', 'approved', 1, 3,
                 'user_prompt')",
        [],
    )?;
    conn.execute(
        "INSERT INTO memories
         (id, project, topic_key, title, content, memory_type, created_at_epoch,
          updated_at_epoch, status, scope, source_project, target_project,
          owner_scope, owner_key, context_class, source_candidate_id,
          source_trust_class)
         VALUES (1, 'proj', 'package-manager-choice', 'Package manager',
                 'Use bun, not npm', 'preference', 1, 3, 'active', 'project',
                 'proj', 'proj', 'repo', 'proj', 'startup_core', 1, 'user_prompt')",
        [],
    )?;
    conn.execute(
        "INSERT INTO memory_preference_reinforcements
         (memory_id, reinforcement_count, source_evidence,
          last_reinforced_at_epoch, created_at_epoch, updated_at_epoch,
          machine_checkable, risk_class)
         VALUES (1, 3, '[1,2,3]', 3, 1, 3, 1, 'low')",
        [],
    )?;
    conn.execute(
        "INSERT INTO preference_rule_overrides
         (project, rule_id, source_memory_id, disabled, action_override,
          updated_by, updated_at_epoch)
         VALUES ('proj', 'pref-1-1', 1, 1, 'block', 'user', 4)",
        [],
    )?;
    Ok(())
}

#[test]
fn opposite_direct_save_drops_candidate_rule_state_and_enqueues_compile() -> Result<()> {
    let _dir = ScopedTestDataDir::new("direct-save-opposite-preference-rule-state");
    crate::runtime_config::init_config()?;
    crate::runtime_config::set_config_value("rule_compilation.enabled", "true")?;
    let conn = db::open_db()?;
    seed_compilable_preference(&conn)?;

    let saved = save_memory(
        &conn,
        &SaveMemoryRequest {
            text: "Use npm, not bun".to_string(),
            title: Some("Package manager".to_string()),
            project: Some("proj".to_string()),
            topic_key: Some("package-manager-choice".to_string()),
            memory_type: Some("preference".to_string()),
            scope: Some("project".to_string()),
            local_copy_enabled: Some(false),
            ..SaveMemoryRequest::default()
        },
    )?;

    assert_eq!(saved.id, 1);
    assert_eq!(saved.operation, "update");
    let (content, source_candidate_id): (String, Option<i64>) = conn.query_row(
        "SELECT content, source_candidate_id FROM memories WHERE id = 1",
        [],
        |row| Ok((row.get(0)?, row.get(1)?)),
    )?;
    assert_eq!(content, "Use npm, not bun");
    assert_eq!(source_candidate_id, None);
    let reinforcement_rows: i64 = conn.query_row(
        "SELECT COUNT(*) FROM memory_preference_reinforcements WHERE memory_id = 1",
        [],
        |row| row.get(0),
    )?;
    assert_eq!(reinforcement_rows, 0);
    let override_rows: i64 = conn.query_row(
        "SELECT COUNT(*) FROM preference_rule_overrides WHERE source_memory_id = 1",
        [],
        |row| row.get(0),
    )?;
    assert_eq!(override_rows, 0);
    let compile_jobs: i64 = conn.query_row(
        "SELECT COUNT(*) FROM jobs
         WHERE job_type = 'compile_rules' AND project = 'proj' AND state = 'pending'",
        [],
        |row| row.get(0),
    )?;
    assert_eq!(compile_jobs, 1);
    Ok(())
}

#[test]
fn cross_type_direct_save_does_not_overwrite_preference_rule_source() -> Result<()> {
    let _dir = ScopedTestDataDir::new("direct-save-cross-type-preference-rule-state");
    crate::runtime_config::init_config()?;
    crate::runtime_config::set_config_value("rule_compilation.enabled", "true")?;
    let conn = db::open_db()?;
    seed_compilable_preference(&conn)?;

    let saved = save_memory(
        &conn,
        &SaveMemoryRequest {
            text: "npm and bun are installed in this repository".to_string(),
            title: Some("Installed package managers".to_string()),
            project: Some("proj".to_string()),
            topic_key: Some("package-manager-choice".to_string()),
            memory_type: Some("discovery".to_string()),
            scope: Some("project".to_string()),
            local_copy_enabled: Some(false),
            ..SaveMemoryRequest::default()
        },
    )?;

    assert_ne!(
        saved.id, 1,
        "different memory types must not share an upsert row"
    );
    let original: (String, String, Option<i64>) = conn.query_row(
        "SELECT memory_type, content, source_candidate_id FROM memories WHERE id = 1",
        [],
        |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
    )?;
    assert_eq!(
        original,
        (
            "preference".to_string(),
            "Use bun, not npm".to_string(),
            Some(1)
        )
    );
    let state_rows: i64 = conn.query_row(
        "SELECT COUNT(*) FROM memory_preference_reinforcements WHERE memory_id = 1",
        [],
        |row| row.get(0),
    )?;
    let override_rows: i64 = conn.query_row(
        "SELECT COUNT(*) FROM preference_rule_overrides WHERE source_memory_id = 1",
        [],
        |row| row.get(0),
    )?;
    assert_eq!(state_rows, 1);
    assert_eq!(override_rows, 1);
    Ok(())
}