weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
mod assign;
mod scan;

use super::model::{AppRecord, DomainRecord, VariableRecord, node_key, variable_key};
use crate::language::yaml_doc::Node;
use weavatrix_graph::NodeKind;

pub(super) fn collect(record: &mut AppRecord, root: &Node, path: &str, raw: &str) {
    if let Some(vars) = root
        .get("workflow")
        .and_then(|workflow| workflow.get("conversation_variables"))
        .and_then(Node::items)
    {
        for variable in vars {
            let name = variable
                .get("name")
                .or_else(|| {
                    variable
                        .get("selector")
                        .and_then(Node::items)
                        .and_then(|items| items.last())
                })
                .and_then(Node::as_str)
                .unwrap_or("conversation");
            let span = record.span.clone();
            ensure_variable(record, "conversation", name, &span);
        }
    }
    if let Some(vars) = root
        .get("workflow")
        .and_then(|workflow| workflow.get("environment_variables"))
        .and_then(Node::items)
    {
        for variable in vars {
            let name = variable.get("name").and_then(Node::as_str).unwrap_or("env");
            let label = if super::redaction::secret_label(name) {
                "env:redacted".to_owned()
            } else {
                format!("env:{name}")
            };
            record.domains.push(DomainRecord {
                owner: record.key.clone(),
                name: label,
                kind: NodeKind::ConfigKey,
                relation: weavatrix_graph::EdgeKind::Configures,
                span: record.span.clone(),
            });
        }
    }
    let Some(nodes) = root
        .get("workflow")
        .and_then(|workflow| workflow.get("graph"))
        .and_then(|graph| graph.get("nodes"))
        .and_then(Node::items)
    else {
        return;
    };
    for node in nodes {
        let Some(id) = node.get("id").and_then(Node::as_str) else {
            continue;
        };
        let owner = node_key(&record.key, id);
        let data = node.get("data");
        let type_name = data
            .and_then(|data| data.get("type"))
            .and_then(Node::as_str);
        assign::walk_selectors(record, &owner, node, type_name, path, raw);
        if let Some(data) = data {
            scan::walk_executable_scalars(data, &mut |scalar| {
                scan::bind_markers(record, &owner, scalar, path, raw);
                if type_name == Some("template-transform") {
                    scan::bind_template_locals(record, &owner, Some(data), scalar, path, raw);
                }
            });
        }
    }
}

pub(super) fn resolve_selector_target(
    record: &mut AppRecord,
    segments: &[&str],
    span: &weavatrix_graph::SourceSpan,
) -> Option<String> {
    match segments[0] {
        "conversation" | "sys" => {
            let name = segments.get(1).copied().unwrap_or(segments[0]);
            Some(ensure_variable(record, segments[0], name, span))
        }
        _ => record
            .nodes
            .iter()
            .find(|item| item.id == segments[0])
            .map(|item| item.key.clone()),
    }
}

fn ensure_variable(
    record: &mut AppRecord,
    scope: &str,
    name: &str,
    span: &weavatrix_graph::SourceSpan,
) -> String {
    let key = variable_key(&record.key, scope, name);
    if !record.variables.iter().any(|item| item.key == key) {
        record.variables.push(VariableRecord {
            key: key.clone(),
            scope: scope.to_owned(),
            name: name.to_owned(),
            span: span.clone(),
        });
    }
    key
}