kodegraf-cli 0.1.0

Structural code intelligence for AI coding assistants — CLI
use anyhow::Result;
use kodegraf_core::config::Config;
use kodegraf_core::graph::GraphStore;
use kodegraf_core::signals::models::{InsightsSummary, TrendComparison};
use kodegraf_core::signals::store::SignalStore;
use kodegraf_core::signals::analyzer::detect_patterns;

pub fn run(json: bool) -> Result<()> {
    let repo_root = kodegraf_core::config::find_repo_root()?;
    let db_path = Config::graph_db_path(&repo_root);

    if !db_path.exists() {
        anyhow::bail!("Graph not built. Run `kodegraf init && kodegraf build` first.");
    }

    let store = GraphStore::open(&db_path)?;
    let signal_store = SignalStore::new(store.connection());

    let (total, sessions, errors, warnings) = signal_store.get_total_counts()?;

    if total == 0 {
        if json {
            println!("{{\"status\": \"no_data\", \"message\": \"No signals collected yet.\"}}");
        } else {
            println!("No data yet. Start a coding session with DiffGuard active.");
            println!();
            println!("DiffGuard collects signals automatically via PostToolUse hooks.");
            println!("Run `kodegraf install` to configure hooks for your AI coding tool.");
        }
        return Ok(());
    }

    let self_correction_rate = signal_store.get_self_correction_rate()?;
    let breakdown = signal_store.get_check_type_breakdown()?;
    let (current_errors, previous_errors) = signal_store.get_trend()?;
    let patterns = detect_patterns(store.connection(), 3)?;
    let active_rules = signal_store.get_active_rules()?;

    let direction = if current_errors < previous_errors {
        "improving"
    } else if current_errors > previous_errors {
        "worsening"
    } else {
        "stable"
    };

    let summary = InsightsSummary {
        total_signals: total,
        total_sessions: sessions,
        total_errors: errors,
        total_warnings: warnings,
        self_correction_rate,
        top_failure_patterns: patterns.into_iter().take(10).collect(),
        active_rules: active_rules.clone(),
        check_type_breakdown: breakdown.clone(),
        recent_trend: TrendComparison {
            current_period_errors: current_errors,
            previous_period_errors: previous_errors,
            direction: direction.to_string(),
        },
    };

    if json {
        println!("{}", serde_json::to_string_pretty(&summary)?);
        return Ok(());
    }

    // Terminal dashboard
    println!("Kodegraf Insights");
    println!("=================");
    println!();
    println!("Sessions: {}    Signals: {}    Errors: {}    Warnings: {}",
        sessions, total, errors, warnings);
    println!("Self-correction rate: {:.0}%", self_correction_rate * 100.0);
    println!();

    // Trend
    let trend_icon = match direction {
        "improving" => "v (improving)",
        "worsening" => "^ (worsening)",
        _ => "= (stable)",
    };
    println!("Trend (7d): {} errors (prev: {}) {}",
        current_errors, previous_errors, trend_icon);
    println!();

    // Breakdown
    if !breakdown.is_empty() {
        println!("Errors by check type:");
        for ct in &breakdown {
            println!("  {:20} {}", ct.check_type, ct.count);
        }
        println!();
    }

    // Top patterns
    if !summary.top_failure_patterns.is_empty() {
        println!("Top failure patterns:");
        for (i, pattern) in summary.top_failure_patterns.iter().enumerate() {
            let wrote = if pattern.wrote_values.is_empty() {
                "?".to_string()
            } else {
                pattern.wrote_values.join(", ")
            };
            println!(
                "  {}. {} in {} — wrote `{}` ({} times, {} sessions)",
                i + 1,
                pattern.check_type,
                pattern.region,
                wrote,
                pattern.occurrence_count,
                pattern.sessions_count,
            );
        }
        println!();
    }

    // Active rules
    if !active_rules.is_empty() {
        println!("Active rules ({}):", active_rules.len());
        for rule in &active_rules {
            println!("  {}", rule.rule_text);
        }
        println!();
    } else {
        println!("No active rules. Rules are generated when patterns recur 3+ times.");
        println!("Run `kodegraf generate-rules` to generate rules from current data.");
        println!();
    }

    Ok(())
}