minni 0.1.0

Local memory, task, and codebase indexing tool for AI agents
Documentation
use crate::cli::init::ensure_initialized;
use crate::cli::JournalCommands;
use crate::db::Database;
use crate::journal::JournalManager;
use anyhow::Result;
use colored::Colorize;
use std::env;

pub fn run(command: JournalCommands) -> Result<()> {
    let project_root = env::current_dir()?;

    // Resume command auto-initializes with full setup
    if matches!(command, JournalCommands::Resume) {
        ensure_initialized(&project_root, true)?;
    } else {
        // Other commands need minni initialized, except hooks-install/uninstall
        let needs_minni = !matches!(
            command,
            JournalCommands::HooksInstall | JournalCommands::HooksUninstall
        );

        if needs_minni && !Database::minni_dir_exists(&project_root) {
            println!(
                "{} Minni not initialized. Run {} first.",
                "Error:".red(),
                "minni init".cyan()
            );
            return Ok(());
        }
    }

    let journal = JournalManager::new(&project_root);

    match command {
        JournalCommands::Record => {
            // Called by git hook - record the commit
            match journal.record_commit() {
                Ok(entry) => {
                    println!("{} Recorded commit to journal", "".green());
                    if let Some(msg) = entry.commit_message {
                        println!("  {}", msg.dimmed());
                    }
                }
                Err(e) => {
                    // Silent fail for hook - don't break git workflow
                    eprintln!("{} Failed to record: {}", "!".yellow(), e);
                }
            }
        }

        JournalCommands::Show { count } => {
            let md = journal.to_markdown(count)?;
            println!("{}", md);
        }

        JournalCommands::Note { message } => {
            journal.add_note(&message)?;
            println!("{} Note added to journal", "".green());
        }

        JournalCommands::Clear => {
            journal.clear()?;
            println!("{} Journal cleared", "".green());
        }

        JournalCommands::HooksInstall => {
            // Ensure .minni exists first
            if !Database::minni_dir_exists(&project_root) {
                println!(
                    "{} Minni not initialized. Run {} first.",
                    "Error:".red(),
                    "minni init".cyan()
                );
                return Ok(());
            }

            match journal.install_hooks() {
                Ok(()) => {
                    println!("{} Git hooks installed!", "".green());
                    println!("  Journal will auto-record on each commit.");
                    println!(
                        "  Run {} to see recent activity.",
                        "minni journal show".cyan()
                    );
                }
                Err(e) => {
                    println!("{} Failed to install hooks: {}", "Error:".red(), e);
                }
            }
        }

        JournalCommands::HooksUninstall => match journal.uninstall_hooks() {
            Ok(()) => {
                println!("{} Git hooks removed", "".green());
            }
            Err(e) => {
                println!("{} Failed to remove hooks: {}", "Error:".red(), e);
            }
        },

        JournalCommands::Resume => {
            // Output journal in AI-friendly format for session resumption
            let entries = journal.get_recent(10)?;

            if entries.is_empty() {
                println!("{}", "No recent activity recorded.".dimmed());
                return Ok(());
            }

            println!("{}", "=".repeat(60).dimmed());
            println!("{}", "SESSION CONTEXT - Recent Activity".bold());
            println!("{}", "=".repeat(60).dimmed());
            println!();

            // Show current git state
            if let Some(ref branch) = entries.last().and_then(|e| e.git_branch.clone()) {
                println!("{}: {}", "Branch".yellow(), branch);
            }
            if let Some(ref commit) = entries.last().and_then(|e| e.git_commit.clone()) {
                println!("{}: {}", "HEAD".yellow(), &commit[..7.min(commit.len())]);
            }
            println!();

            // Recent commits
            println!("{}", "Recent commits:".bold());
            for entry in entries.iter().rev().take(5) {
                if let Some(ref msg) = entry.commit_message {
                    let hash = entry
                        .git_commit
                        .as_ref()
                        .map(|c| format!("[{}]", &c[..7.min(c.len())]))
                        .unwrap_or_default();
                    println!("  {} {}", hash.dimmed(), msg);
                }
            }
            println!();

            // Files recently modified
            let mut recent_files: Vec<&str> = vec![];
            for entry in entries.iter().rev().take(5) {
                for fc in &entry.files_changed {
                    if !recent_files.contains(&fc.path.as_str()) {
                        recent_files.push(&fc.path);
                    }
                }
            }

            if !recent_files.is_empty() {
                println!("{}", "Recently modified files:".bold());
                for f in recent_files.iter().take(10) {
                    println!("  {}", f.cyan());
                }
                println!();
            }

            // Any notes
            let notes: Vec<_> = entries
                .iter()
                .filter_map(|e| e.user_note.as_ref())
                .collect();

            if !notes.is_empty() {
                println!("{}", "Notes:".bold());
                for note in notes.iter().rev().take(3) {
                    println!("  {}", note);
                }
                println!();
            }

            println!("{}", "=".repeat(60).dimmed());
        }
    }

    Ok(())
}