weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::super::model::{
    AppRecord, DomainRecord, LinkRecord, binds_input, depends_on_output, reads_variable,
};
use super::resolve_selector_target;
use crate::language::yaml_doc::{self, Node, Scalar};
use weavatrix_graph::NodeKind;

pub(super) fn walk_executable_scalars(node: &Node, visit: &mut impl FnMut(&Scalar)) {
    match node {
        Node::Scalar(scalar) => visit(scalar),
        Node::Sequence { items, .. } => {
            for item in items {
                walk_executable_scalars(item, visit);
            }
        }
        Node::Mapping { entries, .. } => {
            for (key, value) in entries {
                if documentary_key(&key.decoded) {
                    continue;
                }
                if executable_key(&key.decoded) || nested_executable_key(&key.decoded) {
                    walk_executable_scalars(value, visit);
                }
            }
        }
    }
}

fn documentary_key(key: &str) -> bool {
    matches!(
        key,
        "title" | "desc" | "description" | "about" | "label" | "type" | "name"
    )
}

fn executable_key(key: &str) -> bool {
    matches!(
        key,
        "prompt"
            | "text"
            | "query"
            | "instruction"
            | "code"
            | "template"
            | "jinja2"
            | "context"
            | "vision"
            | "answer"
            | "content"
            | "system"
            | "user"
            | "message"
            | "prefix"
            | "suffix"
    )
}

fn nested_executable_key(key: &str) -> bool {
    matches!(
        key,
        "prompt_template" | "messages" | "prompts" | "variables" | "items" | "outputs"
    )
}

pub(super) fn bind_markers(
    record: &mut AppRecord,
    owner: &str,
    scalar: &Scalar,
    path: &str,
    raw: &str,
) {
    let mut rest = scalar.decoded.as_str();
    while let Some(start) = rest.find("{{#") {
        let after = &rest[start + 3..];
        let Some(end) = after.find("#}}") else {
            break;
        };
        let inner = after[..end].trim();
        if !inner.is_empty() && !inner.contains('\n') {
            record.coverage.variables_seen += 1;
            let segments = inner.split('.').collect::<Vec<_>>();
            let span = yaml_doc::span_for(path, raw, scalar.raw_start, scalar.raw_end);
            if let Some(target) = resolve_selector_target(record, &segments, &span) {
                record.coverage.variables_resolved += 1;
                record.links.push(LinkRecord {
                    from: owner.to_owned(),
                    to: target,
                    kind: depends_on_output(),
                    span: span.clone(),
                    detail: inner.to_owned(),
                });
            }
            record.domains.push(DomainRecord {
                owner: owner.to_owned(),
                name: format!("marker:{inner}"),
                kind: NodeKind::Column,
                relation: reads_variable(),
                span,
            });
        }
        rest = &after[end + 3..];
    }
}

pub(super) fn bind_template_locals(
    record: &mut AppRecord,
    owner: &str,
    data: Option<&Node>,
    scalar: &Scalar,
    path: &str,
    raw: &str,
) {
    let locals = data
        .and_then(|data| data.get("variables"))
        .and_then(Node::items)
        .unwrap_or(&[])
        .iter()
        .filter_map(|item| item.get("variable").and_then(Node::as_str))
        .collect::<Vec<_>>();
    let mut rest = scalar.decoded.as_str();
    while let Some(start) = rest.find("{{") {
        if rest[start..].starts_with("{{#") {
            rest = &rest[start + 3..];
            continue;
        }
        let after = &rest[start + 2..];
        let Some(end) = after.find("}}") else {
            break;
        };
        let name = after[..end].trim();
        if locals.contains(&name) {
            record.domains.push(DomainRecord {
                owner: owner.to_owned(),
                name: format!("template:{name}"),
                kind: NodeKind::Binding,
                relation: binds_input(),
                span: yaml_doc::span_for(path, raw, scalar.raw_start, scalar.raw_end),
            });
        }
        rest = &after[end + 2..];
    }
}