frigg 0.3.2

Local-first MCP server for code understanding.
Documentation
use std::error::Error;
use std::io;

use frigg::settings::FriggConfig;
use frigg::storage::Storage;

use crate::cli_runtime::storage_paths::{
    ensure_storage_db_path_for_write, resolve_storage_db_path,
};

#[derive(Debug, Clone, Copy)]
pub(crate) enum StorageBootstrapCommand {
    Init,
    Verify,
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum StorageMaintenanceCommand {
    RepairSemanticVectorStore,
    Prune {
        keep_manifest_snapshots: usize,
        keep_provenance_events: usize,
    },
}

pub(crate) fn run_storage_bootstrap_command(
    config: &FriggConfig,
    command: StorageBootstrapCommand,
) -> Result<(), Box<dyn Error>> {
    let repositories = config.repositories();
    let command_name = match command {
        StorageBootstrapCommand::Init => "init",
        StorageBootstrapCommand::Verify => "verify",
    };

    for repo in &repositories {
        let root = config.root_by_repository_id(&repo.repository_id.0).ok_or_else(|| {
            io::Error::other(format!(
                "{command_name} summary status=failed repository_id={} error=workspace root lookup failed",
                repo.repository_id.0
            ))
        })?;
        let db_path = match command {
            StorageBootstrapCommand::Init => ensure_storage_db_path_for_write(root, command_name)?,
            StorageBootstrapCommand::Verify => resolve_storage_db_path(root, command_name)?,
        };
        let storage = Storage::new(&db_path);

        let operation_result = match command {
            StorageBootstrapCommand::Init => storage.initialize(),
            StorageBootstrapCommand::Verify => storage.verify(),
        };

        if let Err(err) = operation_result {
            println!(
                "{command_name} summary status=failed repositories={} repository_id={} root={} db={} error={}",
                repositories.len(),
                repo.repository_id.0,
                root.display(),
                db_path.display(),
                err
            );
            return Err(Box::new(io::Error::other(format!(
                "{command_name} failed for repository_id={} root={} db={}: {err}",
                repo.repository_id.0,
                root.display(),
                db_path.display()
            ))));
        }

        println!(
            "{command_name} ok repository_id={} root={} db={}",
            repo.repository_id.0,
            root.display(),
            db_path.display()
        );
    }

    println!(
        "{command_name} summary status=ok repositories={}",
        repositories.len()
    );
    Ok(())
}

pub(crate) fn run_storage_maintenance_command(
    config: &FriggConfig,
    command: StorageMaintenanceCommand,
) -> Result<(), Box<dyn Error>> {
    let repositories = config.repositories();
    let command_name = match command {
        StorageMaintenanceCommand::RepairSemanticVectorStore => "repair-storage",
        StorageMaintenanceCommand::Prune { .. } => "prune-storage",
    };
    let mut total_repaired = 0usize;
    let mut total_manifest_snapshots_deleted = 0usize;
    let mut total_provenance_events_deleted = 0usize;

    for repo in &repositories {
        let root = config.root_by_repository_id(&repo.repository_id.0).ok_or_else(|| {
            io::Error::other(format!(
                "{command_name} summary status=failed repository_id={} error=workspace root lookup failed",
                repo.repository_id.0
            ))
        })?;
        let db_path = resolve_storage_db_path(root, command_name)?;
        let storage = Storage::new(&db_path);

        match command {
            StorageMaintenanceCommand::RepairSemanticVectorStore => {
                let repair_summary = match storage.repair_storage_invariants() {
                    Ok(summary) => summary,
                    Err(err) => {
                        println!(
                            "{command_name} summary status=failed repositories={} repository_id={} root={} db={} error={}",
                            repositories.len(),
                            repo.repository_id.0,
                            root.display(),
                            db_path.display(),
                            err
                        );
                        return Err(Box::new(io::Error::other(format!(
                            "{command_name} failed for repository_id={} root={} db={}: {err}",
                            repo.repository_id.0,
                            root.display(),
                            db_path.display()
                        ))));
                    }
                };

                if let Err(err) = storage.verify() {
                    println!(
                        "{command_name} summary status=failed repositories={} repository_id={} root={} db={} error={}",
                        repositories.len(),
                        repo.repository_id.0,
                        root.display(),
                        db_path.display(),
                        err
                    );
                    return Err(Box::new(io::Error::other(format!(
                        "{command_name} failed for repository_id={} root={} db={}: {err}",
                        repo.repository_id.0,
                        root.display(),
                        db_path.display()
                    ))));
                }

                total_repaired += 1;
                let repaired_categories = if repair_summary.repaired_categories.is_empty() {
                    "none".to_string()
                } else {
                    repair_summary.repaired_categories.join(",")
                };
                println!(
                    "{command_name} ok repository_id={} root={} db={} repaired={}",
                    repo.repository_id.0,
                    root.display(),
                    db_path.display(),
                    repaired_categories
                );
            }
            StorageMaintenanceCommand::Prune {
                keep_manifest_snapshots,
                keep_provenance_events,
            } => {
                let deleted_manifest_snapshots = storage
                    .prune_repository_snapshots(&repo.repository_id.0, keep_manifest_snapshots)
                    .map_err(|err| {
                        io::Error::other(format!(
                            "{command_name} failed for repository_id={} root={} db={}: {err}",
                            repo.repository_id.0,
                            root.display(),
                            db_path.display()
                        ))
                    })?;
                let deleted_provenance_events = storage
                    .prune_provenance_events(keep_provenance_events)
                    .map_err(|err| {
                        io::Error::other(format!(
                            "{command_name} failed for repository_id={} root={} db={}: {err}",
                            repo.repository_id.0,
                            root.display(),
                            db_path.display()
                        ))
                    })?;

                total_manifest_snapshots_deleted += deleted_manifest_snapshots;
                total_provenance_events_deleted += deleted_provenance_events;
                println!(
                    "{command_name} ok repository_id={} root={} db={} keep_manifest_snapshots={} keep_provenance_events={} manifest_snapshots_deleted={} provenance_events_deleted={}",
                    repo.repository_id.0,
                    root.display(),
                    db_path.display(),
                    keep_manifest_snapshots,
                    keep_provenance_events,
                    deleted_manifest_snapshots,
                    deleted_provenance_events
                );
            }
        }
    }

    match command {
        StorageMaintenanceCommand::RepairSemanticVectorStore => {
            println!(
                "{command_name} summary status=ok repositories={} repaired={}",
                repositories.len(),
                total_repaired
            );
        }
        StorageMaintenanceCommand::Prune {
            keep_manifest_snapshots,
            keep_provenance_events,
        } => {
            println!(
                "{command_name} summary status=ok repositories={} keep_manifest_snapshots={} keep_provenance_events={} manifest_snapshots_deleted={} provenance_events_deleted={}",
                repositories.len(),
                keep_manifest_snapshots,
                keep_provenance_events,
                total_manifest_snapshots_deleted,
                total_provenance_events_deleted
            );
        }
    }

    Ok(())
}