weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::model::{AppRecord, DomainBatch, DomainRecord, LinkRecord, app_kind, node_kind};
use crate::language::{
    BoundEdgeFact, DomainFact, FileFacts, ReferenceFact, SymbolFact, SymbolLocator,
};
use std::collections::BTreeMap;
use weavatrix_graph::{EdgeKind, NodeKind, SourceSpan};

pub(super) fn to_file_facts(path: &str, batch: &DomainBatch) -> FileFacts {
    let mut facts = FileFacts {
        diagnostics: batch.diagnostics.clone(),
        ..FileFacts::default()
    };
    let mut locators = BTreeMap::<String, SymbolLocator>::new();
    for app in &batch.apps {
        let app_symbol = symbol(&app.name, app_kind(), app.span.clone());
        locators.insert(app.key.clone(), locator(&app_symbol));
        facts.symbols.push(app_symbol);
        facts.domains.push(DomainFact {
            name: format!("mode:{}", app.mode),
            kind: NodeKind::Unknown,
            relation: EdgeKind::Configures,
            span: app.span.clone(),
            owner: Some(SymbolLocator {
                name: app.name.clone(),
                kind: app_kind(),
                span: app.span.clone(),
            }),
        });
        facts.domains.push(DomainFact {
            name: format!("dsl:{}", app.dsl_version),
            kind: NodeKind::Unknown,
            relation: EdgeKind::Configures,
            span: app.span.clone(),
            owner: Some(SymbolLocator {
                name: app.name.clone(),
                kind: app_kind(),
                span: app.span.clone(),
            }),
        });
        for variable in &app.variables {
            let label = format!("{}:{}", variable.scope, variable.name);
            let variable_symbol = symbol(&label, NodeKind::ConfigKey, variable.span.clone());
            locators.insert(variable.key.clone(), locator(&variable_symbol));
            facts.references.push(reference(
                &app.name,
                app_kind(),
                &app.span,
                &label,
                EdgeKind::Contains,
                variable.span.clone(),
            ));
            facts.symbols.push(variable_symbol);
        }
        for node in &app.nodes {
            let node_symbol = symbol(&display_name(node), node_kind(), node.span.clone());
            locators.insert(node.key.clone(), locator(&node_symbol));
            facts.references.push(reference(
                &app.name,
                app_kind(),
                &app.span,
                &display_name(node),
                EdgeKind::Contains,
                node.span.clone(),
            ));
            facts.symbols.push(node_symbol);
            facts.domains.push(DomainFact {
                name: format!("type:{}", node.type_name),
                kind: NodeKind::TypeAlias,
                relation: EdgeKind::Configures,
                span: node.span.clone(),
                owner: Some(SymbolLocator {
                    name: display_name(node),
                    kind: node_kind(),
                    span: node.span.clone(),
                }),
            });
            facts.domains.push(DomainFact {
                name: format!("node_id:{}", node.id),
                kind: NodeKind::Unknown,
                relation: EdgeKind::Configures,
                span: node.span.clone(),
                owner: Some(SymbolLocator {
                    name: display_name(node),
                    kind: node_kind(),
                    span: node.span.clone(),
                }),
            });
        }
        for link in &app.links {
            emit_link(&mut facts, &locators, link);
        }
        for domain in &app.domains {
            emit_domain(&mut facts, &locators, domain);
        }
        emit_coverage(&mut facts, app);
    }
    if batch.truncated {
        facts.diagnostics.push(crate::model::Diagnostic {
            code: "dify.truncated".into(),
            message: "Dify analysis stopped at a declared limit".into(),
            span: Some(super::super::yaml_doc::span_for(path, "", 0, 1)),
        });
    }
    facts
}

fn display_name(node: &super::model::NodeRecord) -> String {
    if node.title.is_empty() {
        node.id.clone()
    } else {
        node.title.clone()
    }
}

fn emit_link(facts: &mut FileFacts, locators: &BTreeMap<String, SymbolLocator>, link: &LinkRecord) {
    let Some(from) = locators.get(&link.from) else {
        return;
    };
    let Some(to) = locators.get(&link.to) else {
        return;
    };
    facts.bound_edges.push(BoundEdgeFact {
        from: from.clone(),
        to: to.clone(),
        kind: link.kind.clone(),
        span: link.span.clone(),
        detail: link.detail.clone(),
    });
    if !link.detail.is_empty() {
        facts.domains.push(DomainFact {
            name: format!("link:{}", link.detail),
            kind: NodeKind::Unknown,
            relation: link.kind.clone(),
            span: link.span.clone(),
            owner: Some(from.clone()),
        });
    }
}

fn emit_domain(
    facts: &mut FileFacts,
    locators: &BTreeMap<String, SymbolLocator>,
    domain: &DomainRecord,
) {
    facts.domains.push(DomainFact {
        name: domain.name.clone(),
        kind: domain.kind.clone(),
        relation: domain.relation.clone(),
        span: domain.span.clone(),
        owner: locators.get(&domain.owner).cloned(),
    });
}

fn emit_coverage(facts: &mut FileFacts, app: &AppRecord) {
    for name in [
        format!(
            "coverage:structure:{}/{}",
            app.coverage.structure_accepted, app.coverage.structure_seen
        ),
        format!(
            "coverage:nodeSemantics:{}/{}",
            app.coverage.semantics_supported, app.coverage.semantics_seen
        ),
        format!(
            "coverage:variables:{}/{}",
            app.coverage.variables_resolved, app.coverage.variables_seen
        ),
        "coverage:runtime:false".into(),
    ] {
        facts.domains.push(DomainFact {
            name,
            kind: NodeKind::Unknown,
            relation: EdgeKind::Configures,
            span: app.span.clone(),
            owner: Some(SymbolLocator {
                name: app.name.clone(),
                kind: app_kind(),
                span: app.span.clone(),
            }),
        });
    }
}

fn symbol(name: &str, kind: NodeKind, span: SourceSpan) -> SymbolFact {
    SymbolFact {
        name: name.to_owned(),
        kind,
        span,
        test_only: false,
        exported: true,
        source_fingerprint: None,
        source_extent: None,
        owner: None,
    }
}

fn locator(symbol: &SymbolFact) -> SymbolLocator {
    SymbolLocator {
        name: symbol.name.clone(),
        kind: symbol.kind.clone(),
        span: symbol.span.clone(),
    }
}

fn reference(
    owner_name: &str,
    owner_kind: NodeKind,
    owner_span: &SourceSpan,
    name: &str,
    kind: EdgeKind,
    span: SourceSpan,
) -> ReferenceFact {
    ReferenceFact {
        name: name.to_owned(),
        kind,
        receiver: None,
        qualified: false,
        span,
        owner: Some(SymbolLocator {
            name: owner_name.to_owned(),
            kind: owner_kind,
            span: owner_span.clone(),
        }),
    }
}