remem-ai 0.4.9

Persistent memory for Claude Code and Codex — single binary, automatic context
Documentation
use anyhow::Result;

use crate::cli::types::CommitAction;
use crate::{db, git_trace};

pub(in crate::cli) fn run_commit(action: CommitAction) -> Result<()> {
    let conn = db::open_db()?;
    match action {
        CommitAction::Show { sha, project, json } => {
            let results = git_trace::lookup_commit(&conn, project.as_deref(), &sha)?;
            if json {
                println!("{}", serde_json::to_string_pretty(&results)?);
                return Ok(());
            }
            if results.is_empty() {
                println!("No commit metadata found for {sha}");
                return Ok(());
            }
            for (index, result) in results.iter().enumerate() {
                if index > 0 {
                    println!();
                }
                print_commit_lookup(result);
            }
        }
        CommitAction::Session {
            session_id,
            project,
            limit,
            json,
        } => {
            let results =
                git_trace::commits_for_session(&conn, project.as_deref(), &session_id, limit)?;
            if json {
                println!("{}", serde_json::to_string_pretty(&results)?);
                return Ok(());
            }
            if results.is_empty() {
                println!("No commits linked to session {session_id}");
                return Ok(());
            }
            println!("Session: {session_id}");
            for result in &results {
                println!();
                print_git_metadata(&result.git);
                println!("Link metadata:");
                println!("  content session: {}", result.link.session_id);
                if let Some(memory_session_id) = &result.link.memory_session_id {
                    println!("  memory session:  {memory_session_id}");
                }
                println!("  source:          {}", result.link.source);
                println!(
                    "  linked at:       {}",
                    format_epoch(Some(result.link.linked_at_epoch))
                );
                print_summary(result.link.summary.as_ref());
            }
        }
    }
    Ok(())
}

fn print_commit_lookup(result: &git_trace::CommitLookup) {
    print_git_metadata(&result.git);
    println!("Linked sessions:");
    if result.sessions.is_empty() {
        println!("  none");
        return;
    }
    for session in &result.sessions {
        println!("  content session: {}", session.session_id);
        if let Some(memory_session_id) = &session.memory_session_id {
            println!("  memory session:  {memory_session_id}");
        }
        println!("  source:          {}", session.source);
        println!(
            "  linked at:       {}",
            format_epoch(Some(session.linked_at_epoch))
        );
        print_summary(session.summary.as_ref());
    }
}

fn print_git_metadata(git: &git_trace::GitCommitRecord) {
    println!("Commit {}", git.short_sha);
    println!("Git metadata:");
    println!("  full sha:  {}", git.sha);
    println!("  project:   {}", git.project);
    println!("  repo:      {}", git.repo_path);
    if let Some(branch) = &git.branch {
        println!("  branch:    {branch}");
    }
    if let Some(message) = &git.message {
        println!("  message:   {message}");
    }
    println!("  authored:  {}", format_epoch(git.authored_at_epoch));
    if git.changed_files.is_empty() {
        println!("  files:     none recorded");
    } else {
        println!("  files:");
        for file in &git.changed_files {
            println!("    {file}");
        }
    }
}

fn print_summary(summary: Option<&git_trace::SessionSummaryTrace>) {
    let Some(summary) = summary else {
        println!("  memory summary: none linked");
        return;
    };
    println!("  memory summary:");
    print_optional_summary_field("request", summary.request.as_deref());
    print_optional_summary_field("completed", summary.completed.as_deref());
    print_optional_summary_field("decisions", summary.decisions.as_deref());
    print_optional_summary_field("learned", summary.learned.as_deref());
    print_optional_summary_field("next steps", summary.next_steps.as_deref());
    print_optional_summary_field("preferences", summary.preferences.as_deref());
}

fn print_optional_summary_field(label: &str, value: Option<&str>) {
    if let Some(value) = value {
        println!("    {label}: {value}");
    }
}

fn format_epoch(epoch: Option<i64>) -> String {
    epoch
        .and_then(|value| chrono::DateTime::from_timestamp(value, 0))
        .map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
        .unwrap_or_else(|| "unknown".to_string())
}