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::platforms;
use kodegraf_core::{build, config};

pub fn run(platform_filter: Option<&str>, dry_run: bool) -> Result<()> {
    let repo_root = config::find_repo_root()?;

    let platforms = if let Some(name) = platform_filter {
        let all = platforms::all_platforms();
        let matched: Vec<_> = all.into_iter().filter(|p| p.name == name).collect();
        if matched.is_empty() {
            let names: Vec<&str> = platforms::all_platforms().iter().map(|p| p.name).collect();
            anyhow::bail!(
                "Unknown platform '{}'. Supported: {}",
                name,
                names.join(", ")
            );
        }
        matched
    } else {
        platforms::detect_platforms()
    };

    if platforms.is_empty() {
        println!("No AI coding platforms detected.");
        println!();
        println!("Supported platforms:");
        for p in platforms::all_platforms() {
            println!("  {} ({})", p.name, p.display_name);
        }
        println!();
        println!("Install one of these, then run `kodegraf install` again.");
        println!("Or target a specific platform: `kodegraf install --platform claude-code`");
        return Ok(());
    }

    println!("Detected {} platform(s):", platforms.len());
    for p in &platforms {
        println!("  {} ({})", p.display_name, p.name);
    }
    println!();

    if dry_run {
        println!("[dry-run] Would configure:");
        println!();
        println!("  MCP configs:");
        for p in &platforms {
            let entry = platforms::mcp_server_entry();
            println!("    {}: {}", p.display_name, serde_json::to_string_pretty(&entry)?);
        }
        println!();
        println!("  Instruction files: CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules");
        println!("  Hooks: .claude/settings.json (PostToolUse + SessionStart)");
        println!("  Skills: .claude/skills/ (4 workflow skills)");
        println!("  Auto: init + build (if not already built)");
        println!();
        println!("Run without --dry-run to apply changes.");
        return Ok(());
    }

    // ── Step 1: Auto-init if not initialized ──
    let config_path = Config::config_path(&repo_root);
    let db_path = Config::graph_db_path(&repo_root);

    let cfg = if config_path.exists() {
        println!("Config: already initialized.");
        Config::load(&config_path)?
    } else {
        println!("Initializing Kodegraf...");
        let config_dir = Config::config_dir(&repo_root);
        std::fs::create_dir_all(&config_dir)?;
        let cfg = Config::detect(&repo_root)?;
        cfg.save(&config_path)?;
        println!("  Config: .kodegraf/config.toml ({})", cfg.scan.languages.join(", "));
        cfg
    };

    // ── Step 2: Auto-build if graph is empty or missing ──
    let needs_build = if db_path.exists() {
        let store = GraphStore::open(&db_path)?;
        let stats = store.get_stats()?;
        stats.total_nodes == 0
    } else {
        true
    };

    if needs_build {
        println!();
        println!("Building knowledge graph...");
        let store = GraphStore::open(&db_path)?;
        let result = build::full_build(&repo_root, &cfg, &store)?;
        let stats = store.get_stats()?;
        println!(
            "  {} files parsed → {} nodes, {} edges ({})",
            result.files_parsed,
            stats.total_nodes,
            stats.total_edges,
            if stats.languages.is_empty() { "no languages".to_string() } else { stats.languages.join(", ") }
        );
    } else {
        println!("Graph: already built (use `kodegraf build` to rebuild).");
    }
    println!();

    // ── Step 3: Write MCP configs ──
    println!("MCP server configs:");
    for p in &platforms {
        let path = platforms::write_platform_config(p, &repo_root)?;
        println!("  {}{}", p.display_name, path.display());
    }
    println!();

    // ── Step 4: Inject CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules ──
    println!("Instruction files:");
    let instruction_files = platforms::inject_all_instruction_files(&repo_root)?;
    for file in &instruction_files {
        println!("  {}", file);
    }
    println!();

    // ── Step 5: Write hooks ──
    let hooks_path = platforms::write_hooks_config(&repo_root)?;
    println!("Hooks: {}", hooks_path.display());
    println!("  PostToolUse → kodegraf update --fast (on Edit|Write|Bash)");
    println!("  SessionStart → kodegraf session-greeting");
    println!();

    // ── Step 6: Write skills ──
    println!("Skills:");
    let skills = platforms::write_skill_files(&repo_root)?;
    for skill in &skills {
        println!("  .claude/skills/{}", skill);
    }
    println!();

    println!("Done. Open Claude Code — Kodegraf is ready.");

    Ok(())
}