codeskeleton 0.1.2

Turn any folder of code into a queryable knowledge graph. Single binary, zero runtime dependencies, blazing fast.
Documentation
//! Generate GRAPH_REPORT.md from analysis results.

use crate::analyze::Analysis;
use std::collections::HashMap;
use std::fmt::Write;

/// Render the full GRAPH_REPORT.md content.
pub fn render_report(
    analysis: &Analysis,
    communities: &HashMap<usize, Vec<String>>,
) -> String {
    let mut report = String::with_capacity(4096);

    // Header
    writeln!(report, "# Knowledge Graph Report").unwrap();
    writeln!(report).unwrap();
    writeln!(report, "> Generated by [codeskeleton](https://github.com/DhanushNehru/codeskeleton)").unwrap();
    writeln!(report).unwrap();

    // Stats
    writeln!(report, "## Overview").unwrap();
    writeln!(report).unwrap();
    writeln!(report, "| Metric | Value |").unwrap();
    writeln!(report, "|--------|-------|").unwrap();
    writeln!(report, "| Files analyzed | {} |", analysis.stats.files_analyzed).unwrap();
    writeln!(report, "| Total nodes | {} |", analysis.stats.total_nodes).unwrap();
    writeln!(report, "| Total edges | {} |", analysis.stats.total_edges).unwrap();
    writeln!(report, "| Communities | {} |", analysis.stats.total_communities).unwrap();
    writeln!(report).unwrap();

    // God nodes
    writeln!(report, "## God Nodes").unwrap();
    writeln!(report).unwrap();
    writeln!(
        report,
        "Highest-degree concepts — what everything connects through."
    )
    .unwrap();
    writeln!(report).unwrap();

    if analysis.god_nodes.is_empty() {
        writeln!(report, "_No god nodes found._").unwrap();
    } else {
        writeln!(report, "| Rank | Node | Kind | Degree | Community |").unwrap();
        writeln!(report, "|------|------|------|--------|-----------|").unwrap();
        for (i, node) in analysis.god_nodes.iter().enumerate() {
            writeln!(
                report,
                "| {} | **{}** | {} | {} | {} |",
                i + 1,
                node.label,
                node.kind,
                node.degree,
                node.community.map_or("".to_string(), |c| c.to_string())
            )
            .unwrap();
        }
    }
    writeln!(report).unwrap();

    // Surprising connections
    writeln!(report, "## Surprising Connections").unwrap();
    writeln!(report).unwrap();
    writeln!(
        report,
        "Cross-community edges that reveal hidden structure."
    )
    .unwrap();
    writeln!(report).unwrap();

    if analysis.surprising_connections.is_empty() {
        writeln!(report, "_No surprising connections found._").unwrap();
    } else {
        for (i, conn) in analysis.surprising_connections.iter().enumerate() {
            writeln!(report, "{}. {}", i + 1, conn.why).unwrap();
        }
    }
    writeln!(report).unwrap();

    // Communities
    writeln!(report, "## Communities").unwrap();
    writeln!(report).unwrap();

    let mut sorted_communities: Vec<_> = communities.iter().collect();
    sorted_communities.sort_by_key(|(id, _)| *id);

    for (cid, nodes) in &sorted_communities {
        let cohesion = analysis
            .community_scores
            .get(cid)
            .copied()
            .unwrap_or(0.0);
        writeln!(
            report,
            "### Community {} ({} nodes, cohesion: {:.2})",
            cid,
            nodes.len(),
            cohesion
        )
        .unwrap();
        writeln!(report).unwrap();

        // List first 20 nodes
        let display_count = std::cmp::min(nodes.len(), 20);
        for node in &nodes[..display_count] {
            writeln!(report, "- `{}`", node).unwrap();
        }
        if nodes.len() > 20 {
            writeln!(report, "- _... and {} more_", nodes.len() - 20).unwrap();
        }
        writeln!(report).unwrap();
    }

    // Suggested questions
    writeln!(report, "## Suggested Questions").unwrap();
    writeln!(report).unwrap();
    writeln!(
        report,
        "Questions this graph is uniquely positioned to answer:"
    )
    .unwrap();
    writeln!(report).unwrap();

    for (i, question) in analysis.suggested_questions.iter().enumerate() {
        writeln!(report, "{}. {}", i + 1, question).unwrap();
    }
    writeln!(report).unwrap();

    // Confidence legend
    writeln!(report, "---").unwrap();
    writeln!(report).unwrap();
    writeln!(report, "### Confidence Labels").unwrap();
    writeln!(report).unwrap();
    writeln!(report, "| Label | Meaning |").unwrap();
    writeln!(report, "|-------|---------|").unwrap();
    writeln!(
        report,
        "| EXTRACTED | Found directly in source (import, direct call) |"
    )
    .unwrap();
    writeln!(
        report,
        "| INFERRED | Reasonable deduction (call-graph pass, name matching) |"
    )
    .unwrap();
    writeln!(
        report,
        "| AMBIGUOUS | Uncertain — flagged for review |"
    )
    .unwrap();

    report
}