remem-ai 0.6.13

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::fmt::Write;

use anyhow::Result;
use rusqlite::Connection;
use serde::Serialize;

use super::detail::{query_monthly, query_recent_observations};
use super::summary::{query_overview, query_token_economics, query_type_counts};
use super::types::{MonthRow, Overview, RecentObservation, TokenEcon, TypeCount};

#[derive(Debug, Serialize)]
pub(crate) struct TimelineReportData {
    project: String,
    full: bool,
    overview: Overview,
    activity_by_type: Vec<TypeCount>,
    token_economics: TokenEcon,
    #[serde(skip_serializing_if = "Option::is_none")]
    recent_timeline: Option<Vec<RecentObservation>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    monthly_breakdown: Option<Vec<MonthRow>>,
}

fn render_recent_timeline(out: &mut String, recent: &[RecentObservation]) -> Result<()> {
    writeln!(out, "## Timeline (recent first)")?;

    let mut current_date = String::new();
    for observation in recent {
        let date = chrono::DateTime::from_timestamp(observation.created_at_epoch, 0)
            .map(|dt| dt.format("%Y-%m-%d").to_string())
            .unwrap_or_default();
        if date != current_date {
            writeln!(out, "### {}", date)?;
            current_date = date;
        }
        let title = observation.title.as_deref().unwrap_or("(untitled)");
        writeln!(
            out,
            "- #{} [{}] {}",
            observation.id, observation.obs_type, title
        )?;
    }
    writeln!(out)?;
    Ok(())
}

fn render_monthly_breakdown(out: &mut String, monthly: &[MonthRow]) -> Result<()> {
    writeln!(out, "## Monthly Breakdown")?;
    writeln!(out, "| Month | Observations | Sessions | AI Cost |")?;
    writeln!(out, "|-------|-------------|----------|---------|")?;
    for month in monthly {
        writeln!(
            out,
            "| {} | {} | {} | ${:.2} |",
            month.month, month.observations, month.sessions, month.ai_cost
        )?;
    }
    Ok(())
}

pub(crate) fn generate_timeline_report_data(
    conn: &Connection,
    project: &str,
    full: bool,
) -> Result<TimelineReportData> {
    let overview = query_overview(conn, project)?;
    let activity_by_type = query_type_counts(conn, project)?;
    let token_economics = query_token_economics(conn, project)?;
    let recent_timeline = if full {
        Some(query_recent_observations(conn, project, 200)?)
    } else {
        None
    };
    let monthly_breakdown = if full {
        Some(query_monthly(conn, project)?)
    } else {
        None
    };

    Ok(TimelineReportData {
        project: project.to_string(),
        full,
        overview,
        activity_by_type,
        token_economics,
        recent_timeline,
        monthly_breakdown,
    })
}

pub fn generate_timeline_report(conn: &Connection, project: &str, full: bool) -> Result<String> {
    let report = generate_timeline_report_data(conn, project, full)?;
    let mut out = String::with_capacity(4096);
    writeln!(out, "# Journey Into {}\n", project)?;

    writeln!(out, "## Overview")?;
    writeln!(
        out,
        "- Time span: {} -> {} ({} days)",
        report.overview.first_date, report.overview.last_date, report.overview.days_span
    )?;
    writeln!(
        out,
        "- Total observations: {}",
        report.overview.total_observations
    )?;
    writeln!(out, "- Total sessions: {}", report.overview.total_sessions)?;
    writeln!(
        out,
        "- Total memories: {}\n",
        report.overview.total_memories
    )?;

    writeln!(out, "## Activity by Type")?;
    let total = report.overview.total_observations.max(1) as f64;
    for type_count in &report.activity_by_type {
        let pct = (type_count.count as f64 / total * 100.0).round() as i64;
        writeln!(
            out,
            "- {}: {} ({}%)",
            type_count.obs_type, type_count.count, pct
        )?;
    }
    writeln!(out)?;

    writeln!(out, "## Token Economics")?;
    writeln!(
        out,
        "- Total AI cost: ${:.2}",
        report.token_economics.total_ai_cost
    )?;
    let discovery_m = report.token_economics.total_discovery_tokens as f64 / 1_000_000.0;
    writeln!(out, "- Total discovery tokens: {:.1}M", discovery_m)?;
    writeln!(
        out,
        "- Sessions with context injection: {}",
        report.token_economics.sessions_with_context
    )?;
    let recall_savings = report.token_economics.sessions_with_context * 300;
    writeln!(
        out,
        "- Estimated passive recall savings: ~{}K tokens\n",
        recall_savings
    )?;

    if let Some(recent) = report.recent_timeline.as_deref() {
        render_recent_timeline(&mut out, recent)?;
    }
    if let Some(monthly) = report.monthly_breakdown.as_deref() {
        render_monthly_breakdown(&mut out, monthly)?;
    }

    Ok(out)
}