frigg 0.3.2

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

use frigg::playbooks::run_hybrid_playbook_regressions;
use frigg::searcher::TextSearcher;
use frigg::settings::FriggConfig;
use serde_json::to_string_pretty;

pub(crate) fn run_hybrid_playbook_command(
    config: &FriggConfig,
    playbooks_root: &Path,
    enforce_targets: bool,
    output_path: Option<&Path>,
    trace_root: Option<&Path>,
) -> Result<(), Box<dyn Error>> {
    let searcher = TextSearcher::new(config.clone());
    let summary =
        run_hybrid_playbook_regressions(&searcher, playbooks_root, enforce_targets, trace_root)?;

    for outcome in &summary.outcomes {
        println!(
            "playbook result playbook_id={} file={} semantic_status={} status_allowed={} duration_ms={} execution_error={} trace_path={} required_missing={:?} target_missing={:?} hits={:?}",
            outcome.playbook_id,
            outcome.file_name,
            outcome.semantic_status,
            outcome.status_allowed,
            outcome.duration_ms,
            outcome.execution_error.as_deref().unwrap_or("-"),
            outcome.trace_path.as_deref().unwrap_or("-"),
            outcome.required_missing(),
            outcome.target_missing(),
            outcome.matched_paths
        );
    }

    if let Some(output_path) = output_path {
        let parent = output_path.parent().ok_or_else(|| {
            io::Error::other(format!(
                "playbook summary output path has no parent: {}",
                output_path.display()
            ))
        })?;
        std::fs::create_dir_all(parent)?;
        std::fs::write(output_path, to_string_pretty(&summary)?)?;
    }

    println!(
        "playbook summary status=ok playbooks={} required_failures={} target_failures={} enforce_targets={} output={} trace_root={}",
        summary.playbook_count,
        summary.required_failures,
        summary.target_failures,
        summary.enforce_targets,
        output_path
            .map(|path| path.display().to_string())
            .unwrap_or_else(|| "-".to_owned()),
        trace_root
            .map(|path| path.display().to_string())
            .unwrap_or_else(|| "-".to_owned())
    );
    Ok(())
}