codebase-graph 1.1.6

Native codebaseGraph CLI and MCP server for local code knowledge graphs.
use super::{NativeBuilder, Owner};
use crate::graph_rows::{GraphEdgeRow, GraphNodeRow};
use crate::syntax_materializer::{empty_metadata, graph_id};
use serde_json::{Map, Value};

impl NativeBuilder {
    pub(in crate::syntax_materializer) fn connect_owner(
        &mut self,
        owner: &Owner,
        semantic: &GraphNodeRow,
    ) -> Result<(), String> {
        self.edge(
            "Contains",
            &owner.node_id,
            &semantic.id,
            &format!("contains_{}", semantic.table.to_lowercase()),
            empty_metadata(),
        )?;
        if !owner.scope_id.is_empty() {
            self.edge(
                "Contains",
                &owner.scope_id,
                &semantic.id,
                &format!("scope_contains_{}", semantic.table.to_lowercase()),
                empty_metadata(),
            )?;
        }
        Ok(())
    }

    pub(in crate::syntax_materializer) fn derived_from(
        &mut self,
        semantic_id: &str,
        syntax_id: &str,
    ) -> Result<(), String> {
        if self.nodes.contains_key(syntax_id) {
            self.edge(
                "DerivedFrom",
                semantic_id,
                syntax_id,
                "parser_capture",
                empty_metadata(),
            )?;
        }
        Ok(())
    }

    pub(in crate::syntax_materializer) fn edge_if_allowed(
        &mut self,
        edge_type: &str,
        source_id: &str,
        target_id: &str,
        kind: &str,
        metadata: Map<String, Value>,
    ) -> Result<(), String> {
        let Some(source) = self.nodes.get(source_id) else {
            return Ok(());
        };
        let Some(target) = self.nodes.get(target_id) else {
            return Ok(());
        };
        if self
            .relation_allowlist
            .allows(edge_type, &source.table, &target.table)
        {
            self.edge(edge_type, source_id, target_id, kind, metadata)?;
        }
        Ok(())
    }

    pub(in crate::syntax_materializer) fn edge(
        &mut self,
        edge_type: &str,
        source_id: &str,
        target_id: &str,
        kind: &str,
        metadata: Map<String, Value>,
    ) -> Result<GraphEdgeRow, String> {
        let source_table = self.nodes.get(source_id).map(|node| node.table.clone());
        let target_table = self.nodes.get(target_id).map(|node| node.table.clone());
        if let (Some(source_table), Some(target_table)) = (&source_table, &target_table) {
            if !self
                .relation_allowlist
                .allows(edge_type, source_table, target_table)
            {
                return Err(format!(
                    "relation {edge_type} does not allow {source_table} -> {target_table}"
                ));
            }
        }
        let canonical_key = format!("{}|{}|{}|{}", edge_type, source_id, target_id, kind);
        let mut edge_metadata = empty_metadata();
        edge_metadata.insert(
            "canonical_key".to_string(),
            Value::String(canonical_key.clone()),
        );
        for (key, value) in metadata {
            edge_metadata.insert(key, value);
        }
        let edge = GraphEdgeRow {
            id: graph_id("edge", &canonical_key),
            edge_type: edge_type.to_string(),
            source_id: source_id.to_string(),
            target_id: target_id.to_string(),
            kind: kind.to_string(),
            confidence: 1.0,
            line_start: None,
            line_end: None,
            byte_start: None,
            byte_end: None,
            metadata: Value::Object(edge_metadata),
        };
        self.edges
            .entry(edge.id.clone())
            .or_insert_with(|| edge.clone());
        Ok(self.edges.get(&edge.id).cloned().unwrap_or(edge))
    }
}