const MAX_SESSIONS: usize = 10;
const MAX_DECISIONS: usize = 8;
const REQUEST_CHAR_LIMIT: usize = 120;
const COMPLETED_CHAR_LIMIT: usize = 200;
pub(super) 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()
}