weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::facts::{locator, push_meta, symbol};
use super::model::registration_kind;
use super::paths::{package_root, span_of};
use crate::language::FileFacts;

#[cfg(feature = "lang-rust")]
#[must_use]
pub(crate) fn analyze_rust(path: &str, raw: &str) -> Option<FileFacts> {
    if !raw.contains("unknown tool") && !raw.contains("fn dispatch") {
        return None;
    }
    emit(path, raw, rust_arms(raw), "dispatcher")
}

#[must_use]
pub(crate) fn analyze_sdk(path: &str, raw: &str) -> Option<FileFacts> {
    if !(raw.contains("mcp.tool")
        || raw.contains("server.tool")
        || raw.contains("registerTool")
        || raw.contains("@mcp.tool")
        || raw.contains("@server.tool"))
    {
        return None;
    }
    emit(path, raw, sdk_names(raw), "sdk")
}

fn emit(
    path: &str,
    raw: &str,
    names: Vec<(String, String, bool)>,
    proof: &str,
) -> Option<FileFacts> {
    if names.is_empty() {
        return None;
    }
    let mut facts = FileFacts::default();
    for (name, handler, unresolved) in names {
        let span = span_of(path, raw, &name);
        let node = symbol(&name, registration_kind(), span.clone());
        let owner = locator(&node);
        facts.symbols.push(node);
        let mut meta = vec![
            format!("handler:{handler}"),
            format!("impl:{handler}"),
            format!("exported:{name}"),
            format!("proof:{proof}"),
            format!("package:{}", package_root(path)),
            "plane:implemented".to_owned(),
        ];
        if unresolved {
            meta.push("handler:unresolved_factory".into());
        }
        push_meta(&mut facts, &owner, &span, &meta);
    }
    Some(facts)
}

#[cfg(feature = "lang-rust")]
fn rust_arms(raw: &str) -> Vec<(String, String, bool)> {
    let mut out = Vec::new();
    for line in raw.lines() {
        let trimmed = line.trim();
        let Some(rest) = trimmed.strip_prefix('"') else {
            continue;
        };
        let Some((name, after)) = rest.split_once('"') else {
            continue;
        };
        let Some(handler) = after.trim_start().strip_prefix("=>") else {
            continue;
        };
        let handler = handler.trim().trim_end_matches(',');
        if name.is_empty()
            || name.contains(' ')
            || !(handler.contains('(') || handler.contains("::"))
        {
            continue;
        }
        out.push((
            name.to_owned(),
            handler.trim_end_matches('(').to_owned(),
            false,
        ));
    }
    out
}

fn sdk_names(raw: &str) -> Vec<(String, String, bool)> {
    let mut out = Vec::new();
    let mut pending = false;
    let mut pending_public = None;
    for line in raw.lines() {
        let trimmed = line.trim();
        if is_decorator(trimmed) {
            pending = true;
            pending_public = tool_name_arg(trimmed);
            continue;
        }
        if let Some(name) = quoted_after(trimmed, "mcp.tool(")
            .or_else(|| quoted_after(trimmed, "server.tool("))
            .or_else(|| quoted_after(trimmed, "registerTool("))
        {
            out.push((name, "sdk.tool".to_owned(), false));
            pending = false;
            pending_public = None;
            continue;
        }
        if pending {
            if looks_unresolved_call(trimmed) {
                out.push((
                    pending_public
                        .take()
                        .unwrap_or_else(|| "dynamic-factory".to_owned()),
                    "unresolved".to_owned(),
                    true,
                ));
                pending = false;
            } else if let Some(impl_name) = def_name(trimmed) {
                let public = pending_public.take().unwrap_or_else(|| impl_name.clone());
                out.push((public, impl_name, false));
                pending = false;
            }
        }
    }
    out
}

fn is_decorator(line: &str) -> bool {
    line.contains("@mcp.tool") || line.contains("@server.tool") || line.contains("@app.tool")
}

fn tool_name_arg(line: &str) -> Option<String> {
    if let Some(at) = line.find("name=") {
        return quoted(line[at + 5..].trim_start());
    }
    ["@mcp.tool(", "@server.tool(", "@app.tool("]
        .into_iter()
        .find_map(|marker| quoted_after(line, marker))
}

fn quoted(rest: &str) -> Option<String> {
    let mut chars = rest.chars();
    let quote = chars.next()?;
    if quote != '"' && quote != '\'' {
        return None;
    }
    let mut out = String::new();
    for ch in chars {
        if ch == quote {
            return (!out.is_empty()).then_some(out);
        }
        out.push(ch);
    }
    None
}

fn quoted_after(line: &str, marker: &str) -> Option<String> {
    let rest = line.split_once(marker)?.1.trim_start();
    let rest = rest.strip_prefix('"').or_else(|| rest.strip_prefix('\''))?;
    let name = rest.split(['"', '\'']).next()?;
    (!name.is_empty()).then(|| name.to_owned())
}

fn def_name(line: &str) -> Option<String> {
    let rest = line
        .strip_prefix("def ")
        .or_else(|| line.strip_prefix("async def "))
        .or_else(|| line.strip_prefix("function "))
        .or_else(|| line.strip_prefix("async function "))?;
    let name = rest.split(['(', ' ', ':']).next()?;
    (!name.is_empty()).then(|| name.to_owned())
}

fn looks_unresolved_call(line: &str) -> bool {
    (line.contains("mcp.tool(") || line.contains("server.tool("))
        && !line.contains('"')
        && !line.contains('\'')
}