use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub id: i64,
pub session_id: Option<String>,
pub project: String,
pub topic_key: Option<String>,
pub title: String,
pub text: String,
pub memory_type: String,
pub files: Option<String>,
pub created_at_epoch: i64,
pub updated_at_epoch: i64,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub branch: Option<String>,
#[serde(default = "default_scope")]
pub scope: String,
}
fn default_scope() -> String {
"project".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub id: i64,
pub session_id: String,
pub project: String,
pub event_type: String,
pub summary: String,
pub detail: Option<String>,
pub files: Option<String>,
pub exit_code: Option<i32>,
pub created_at_epoch: i64,
}
pub const MEMORY_TYPES: &[&str] = &[
"decision",
"discovery",
"bugfix",
"architecture",
"preference",
"procedure",
"session_activity",
];
pub const MEMORY_COLS: &str = "id, session_id, project, topic_key, title, content, memory_type, \
files, created_at_epoch, updated_at_epoch, status, branch, scope";
pub fn map_memory_row_pub(row: &rusqlite::Row) -> rusqlite::Result<Memory> {
map_memory_row(row)
}
pub(super) fn map_memory_row(row: &rusqlite::Row) -> rusqlite::Result<Memory> {
Ok(Memory {
id: row.get(0)?,
session_id: row.get(1)?,
project: row.get(2)?,
topic_key: row.get(3)?,
title: row.get(4)?,
text: row.get(5)?,
memory_type: row.get(6)?,
files: row.get(7)?,
created_at_epoch: row.get(8)?,
updated_at_epoch: row.get(9)?,
status: row.get(10)?,
branch: row.get(11)?,
scope: row
.get::<_, Option<String>>(12)?
.unwrap_or_else(|| "project".to_string()),
})
}
pub(super) fn map_event_row(row: &rusqlite::Row) -> rusqlite::Result<Event> {
Ok(Event {
id: row.get(0)?,
session_id: row.get(1)?,
project: row.get(2)?,
event_type: row.get(3)?,
summary: row.get(4)?,
detail: row.get(5)?,
files: row.get(6)?,
exit_code: row.get(7)?,
created_at_epoch: row.get(8)?,
})
}
#[cfg(test)]
pub mod tests_helper {
use rusqlite::Connection;
pub fn setup_memory_schema(conn: &Connection) {
conn.execute_batch(
"CREATE TABLE memories (
id INTEGER PRIMARY KEY,
session_id TEXT,
project TEXT NOT NULL,
topic_key TEXT,
title TEXT NOT NULL,
content TEXT NOT NULL,
memory_type TEXT NOT NULL,
files TEXT,
search_context TEXT,
created_at_epoch INTEGER NOT NULL,
updated_at_epoch INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
branch TEXT,
scope TEXT DEFAULT 'project'
);
CREATE VIRTUAL TABLE memories_fts USING fts5(
title, content, search_context,
content='memories',
content_rowid='id',
tokenize='trigram'
);
CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN
INSERT INTO memories_fts(rowid, title, content, search_context)
SELECT new.id, new.title, new.content, COALESCE(new.search_context, '')
WHERE new.status = 'active';
END;
CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
INSERT INTO memories_fts(memories_fts, rowid, title, content, search_context)
SELECT 'delete', old.id, old.title, old.content, COALESCE(old.search_context, '')
WHERE old.status = 'active';
INSERT INTO memories_fts(rowid, title, content, search_context)
SELECT new.id, new.title, new.content, COALESCE(new.search_context, '')
WHERE new.status = 'active';
END;
CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN
INSERT INTO memories_fts(memories_fts, rowid, title, content, search_context)
SELECT 'delete', old.id, old.title, old.content, COALESCE(old.search_context, '')
WHERE old.status = 'active';
END;
CREATE TABLE events (
id INTEGER PRIMARY KEY,
session_id TEXT NOT NULL,
project TEXT NOT NULL,
event_type TEXT NOT NULL,
summary TEXT NOT NULL,
detail TEXT,
files TEXT,
exit_code INTEGER,
created_at_epoch INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS entities (
id INTEGER PRIMARY KEY,
canonical_name TEXT NOT NULL COLLATE NOCASE,
entity_type TEXT,
mention_count INTEGER DEFAULT 1,
created_at_epoch INTEGER NOT NULL DEFAULT 0,
UNIQUE(canonical_name)
);
CREATE TABLE IF NOT EXISTS memory_entities (
memory_id INTEGER NOT NULL,
entity_id INTEGER NOT NULL,
PRIMARY KEY(memory_id, entity_id)
);",
)
.unwrap();
}
}