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;

/// Handle PostToolUse hook event from Claude Code.
/// Reads tool name and file path, runs DiffGuard checks, outputs JSON results.
pub fn run_post_tool_use() -> Result<()> {
    let repo_root = kodegraf_core::config::find_repo_root()?;
    let config_path = Config::config_path(&repo_root);
    let db_path = Config::graph_db_path(&repo_root);

    // Silently exit if not initialized — hooks shouldn't crash
    if !db_path.exists() {
        return Ok(());
    }

    // Read hook input from environment variables (Claude Code hook format)
    let tool_name = std::env::var("CLAUDE_TOOL_NAME").unwrap_or_default();
    let file_path = std::env::var("CLAUDE_FILE_PATH").unwrap_or_default();

    // Only check Write and Edit operations
    if !matches!(tool_name.as_str(), "Write" | "Edit" | "write" | "edit") {
        return Ok(());
    }

    if file_path.is_empty() {
        return Ok(());
    }

    let full_path = repo_root.join(&file_path);
    if !full_path.exists() {
        return Ok(());
    }

    let config = Config::load(&config_path).unwrap_or_default();
    let store = GraphStore::open(&db_path)?;

    let source = std::fs::read_to_string(&full_path).unwrap_or_default();

    let results = kodegraf_checks::run_all_checks(
        &file_path,
        &source,
        &store,
        &config.checks.severities,
    );

    if !results.is_empty() {
        // Output JSON for the hook system to consume
        let json = kodegraf_checks::reporter::format_results_json(&results);
        println!("{}", json);
    }

    Ok(())
}

/// Handle SessionStart hook event — update graph if HEAD changed.
pub fn run_session_start() -> Result<()> {
    let repo_root = kodegraf_core::config::find_repo_root()?;
    let config_path = Config::config_path(&repo_root);
    let db_path = Config::graph_db_path(&repo_root);

    if !db_path.exists() {
        return Ok(());
    }

    let store = GraphStore::open(&db_path)?;

    // Check if HEAD has changed since last build
    let current_ref = get_current_ref(&repo_root).unwrap_or_default();
    let stored_ref = store.get_metadata("git_ref").ok().flatten().unwrap_or_default();

    if current_ref != stored_ref && !current_ref.is_empty() {
        eprintln!("Kodegraf: HEAD changed ({}{}), updating graph...", stored_ref, current_ref);
        let config = Config::load(&config_path).unwrap_or_default();
        let result = kodegraf_core::build::incremental_update(&repo_root, &config, &store, "HEAD~1")?;
        eprintln!(
            "Kodegraf: Updated {} files ({} nodes, {} edges)",
            result.files_parsed, result.nodes_created, result.edges_created
        );
    }

    Ok(())
}

fn get_current_ref(repo_root: &std::path::Path) -> Option<String> {
    let output = std::process::Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .current_dir(repo_root)
        .output()
        .ok()?;

    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        None
    }
}