gobby-code 0.9.9

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
use crate::config::{CodeVectorSettings, Context};
use crate::models::{ProjectionMetadata, ProjectionProvenance};
use std::path::PathBuf;

use super::generation::generate_report_from_snapshot;
use super::render::render_markdown;
use super::types::{
    BridgeEdgeInput, GraphHotspot, ReportCodeEdge, ReportGraphSnapshot, ReportNode,
};
use super::*;

#[test]
fn report_shape() {
    let snapshot = ReportGraphSnapshot {
        nodes: vec![
            ReportNode::new("src/lib.rs", "src/lib.rs", "file"),
            ReportNode::new("mod:api", "api", "module"),
            ReportNode::new("sym:handler", "handler", "function").with_file_path("src/lib.rs"),
            ReportNode::new("sym:parse", "parse", "function").with_file_path("src/lib.rs"),
            ReportNode::new("unresolved:do_work", "do_work", "unresolved"),
            ReportNode::new("external:serde_json", "serde_json", "external"),
        ],
        code_edges: vec![
            ReportCodeEdge::new("src/lib.rs", "sym:handler", "DEFINES"),
            ReportCodeEdge::new("src/lib.rs", "mod:api", "IMPORTS"),
            ReportCodeEdge::new("sym:handler", "sym:parse", "CALLS"),
            ReportCodeEdge::new("sym:parse", "unresolved:do_work", "CALLS"),
            ReportCodeEdge::new("sym:handler", "external:serde_json", "CALLS"),
        ],
        bridge_edges: BridgeEdgeInput::available(vec![BridgeEdgeHypothesis::inferred(
            "memory-1",
            "sym:handler",
            RELATES_TO_CODE,
            "gobby-memory",
            Some(0.72),
        )]),
        ..ReportGraphSnapshot::default()
    };

    let report = generate_report_from_snapshot("project-1", "2026-05-28T00:00:00Z", snapshot);
    let json = serde_json::to_value(&report).expect("report serializes");

    assert_eq!(json["project_id"], "project-1");
    assert_eq!(json["summary"]["node_count"], 6);
    assert_eq!(json["summary"]["edge_count"], 5);
    assert_eq!(json["summary"]["code_edge_counts"]["CALLS"], 3);
    assert_eq!(json["hotspots"]["high_degree_files"][0]["id"], "src/lib.rs");
    assert_eq!(
        json["hotspots"]["incoming_call_hotspots"][0]["id"],
        "sym:parse"
    );
    assert_eq!(json["unresolved_targets"][0]["name"], "do_work");
    assert_eq!(json["external_targets"][0]["name"], "serde_json");
    assert_eq!(json["bridge_summary"]["relation"], RELATES_TO_CODE);
    assert_eq!(json["bridge_summary"]["confidence_range"]["min"], 0.72);
    assert!(json["markdown"].as_str().unwrap().contains("project-1"));
    assert!(
        !json["suggested_investigation_questions"]
            .as_array()
            .unwrap()
            .is_empty()
    );
}

#[test]
fn bridge_edges_are_read_only() {
    let edge = BridgeEdgeHypothesis::new(
        "memory-1",
        "symbol-1",
        RELATES_TO_CODE,
        ProjectionMetadata::gcode_extracted(),
    );

    assert!(edge.read_only);
    assert_eq!(edge.label, "inferred hypothesis");
    assert_eq!(edge.metadata.provenance, ProjectionProvenance::Extracted);

    let snapshot = ReportGraphSnapshot {
        nodes: vec![ReportNode::new("symbol-1", "handler", "function")],
        code_edges: vec![],
        bridge_edges: BridgeEdgeInput::available(vec![edge]),
        ..ReportGraphSnapshot::default()
    };
    let report = generate_report_from_snapshot("project-1", "2026-05-28T00:00:00Z", snapshot);
    let json = serde_json::to_value(&report).expect("report serializes");

    assert_eq!(json["bridge_edges"][0]["read_only"], true);
    assert_eq!(
        json["bridge_edges"][0]["metadata"]["provenance"],
        "EXTRACTED"
    );
}

#[test]
fn markdown_inline_code_uses_commonmark_backtick_delimiters() {
    let report = empty_report("project`1");
    let markdown = render_markdown(super::render::RenderMarkdownInput {
        project_id: &report.project_id,
        generated_at: &report.generated_at,
        summary: &report.summary,
        hotspots: &report.hotspots,
        unresolved_targets: &[TargetFrequency {
            id: "target".to_string(),
            name: "call`target".to_string(),
            count: 1,
        }],
        external_targets: &[],
        bridge_summary: None,
        degradation_details: &[],
        top_n: 10,
    });

    assert!(markdown.contains("- Project: ``project`1``"));
    assert!(markdown.contains("- ``call`target`` (1)"));
    assert!(!markdown.contains("\\`"));
}

#[test]
fn markdown_renders_high_degree_modules() {
    let mut report = empty_report("project-1");
    report.hotspots.high_degree_modules.push(GraphHotspot {
        id: "mod:api".to_string(),
        name: "api".to_string(),
        node_type: "module".to_string(),
        degree: 4,
        incoming: 1,
        outgoing: 3,
        file_path: None,
    });
    let markdown = render_markdown(super::render::RenderMarkdownInput {
        project_id: &report.project_id,
        generated_at: &report.generated_at,
        summary: &report.summary,
        hotspots: &report.hotspots,
        unresolved_targets: &[],
        external_targets: &[],
        bridge_summary: None,
        degradation_details: &[],
        top_n: 10,
    });

    assert!(markdown.contains("## High-degree modules"));
    assert!(markdown.contains("- `api` (degree 4, in 1, out 3)"));
}

#[test]
fn report_degradation_contract() {
    let ctx = Context {
        database_url: "postgresql://localhost/unavailable".to_string(),
        project_root: PathBuf::from("/tmp/project"),
        project_id: "project-1".to_string(),
        quiet: true,
        falkordb: None,
        qdrant: None,
        embedding: None,
        code_vectors: CodeVectorSettings::default(),
        daemon_url: None,
        index_scope: crate::config::ProjectIndexScope::Single,
    };
    let err = generate_report(&ctx).expect_err("missing graph service is required");
    assert_eq!(err, ProjectGraphReportError::GraphServiceNotConfigured);

    let report = generate_report_from_snapshot(
        "project-1",
        "2026-05-28T00:00:00Z",
        ReportGraphSnapshot {
            nodes: vec![ReportNode::new("symbol-1", "handler", "function")],
            code_edges: vec![],
            bridge_edges: BridgeEdgeInput::unavailable("bridge *edge* <timed out>"),
            ..ReportGraphSnapshot::default()
        },
    );

    assert_eq!(report.summary.node_count, 1);
    assert_eq!(report.degradation_details.len(), 1);
    assert_eq!(report.degradation_details[0].input, RELATES_TO_CODE);
    assert!(!report.degradation_details[0].required);
    assert!(
        report
            .markdown
            .contains("- `RELATES_TO_CODE`: bridge \\*edge\\* \\<timed out\\>")
    );
}

#[test]
fn bridge_edges_are_hypotheses() {
    let edge = BridgeEdgeHypothesis::inferred(
        "memory-1",
        "symbol-1",
        RELATES_TO_CODE,
        "gobby-memory",
        Some(0.72),
    );

    assert_eq!(edge.label, "inferred hypothesis");
    assert_eq!(edge.metadata.provenance, ProjectionProvenance::Inferred);
    assert!(edge.metadata.is_hypothesis());

    let mut report = empty_report("project-1");
    report.bridge_edges.push(edge);

    let json = serde_json::to_value(&report).expect("report serializes");
    assert_eq!(json["bridge_edges"][0]["label"], "inferred hypothesis");
    assert_eq!(
        json["bridge_edges"][0]["metadata"]["provenance"],
        "INFERRED"
    );
}