weavatrix-rust 2.1.0

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
mod budgets;
mod contract;
mod rules;
mod source_metrics;

use crate::engine::RepositoryState;
use crate::operations::arg_str;
use blazingly_json::{Value, json};
use std::collections::BTreeSet;

pub fn contract(state: &RepositoryState, args: &Value) -> Result<Value, String> {
    if arg_str(args, "action").ok() == Some("approve") {
        return Err("read-only MCP never writes architecture contracts".to_owned());
    }
    match contract::load_optional(state)? {
        Some(value) => Ok(json!({
            "state": "CONFIGURED",
            "source": ".weavatrix/architecture.json",
            "contract": value
        })),
        None if arg_str(args, "action").ok() == Some("preview") => Ok(json!({
            "state": "PREVIEW",
            "source": "derived graph folders",
            "contract": contract::starter(state),
            "warning": "no active architecture contract",
            "write": "NONE"
        })),
        None => Ok(contract::not_configured(
            state,
            "Save the starter as .weavatrix/architecture.json, review it, then verify.",
        )),
    }
}

pub fn prepare(state: &RepositoryState, args: &Value) -> Result<Value, String> {
    let files = args
        .get("files")
        .and_then(Value::as_array)
        .ok_or_else(|| "files must be an array".to_owned())?;
    let Some(value) = contract::load_optional(state)? else {
        return Ok(json!({
            "state": "NOT_CONFIGURED",
            "guidance": "PROVISIONAL_STARTER",
            "enforceable": false,
            "intent": args.get("intent"),
            "files": files,
            "starter": contract::starter(state),
            "remediation": contract::remediation(),
            "write": "NONE"
        }));
    };
    let selected = files
        .iter()
        .filter_map(Value::as_str)
        .map(|file| {
            json!({
                "file": file,
                "component": contract::component_for(&value, file),
                "rules": contract::rules_for_file(&value, file)
            })
        })
        .collect::<Vec<_>>();
    Ok(json!({
        "intent": args.get("intent"),
        "files": selected,
        "contract": ".weavatrix/architecture.json"
    }))
}

pub fn verify(state: &RepositoryState) -> Result<Value, String> {
    let Some(value) = contract::load_optional(state)? else {
        return Ok(json!({
            "state": "NOT_CONFIGURED",
            "enforceable": false,
            "new": [],
            "existing": [],
            "excepted": [],
            "fixed": [],
            "starter": contract::starter(state),
            "remediation": contract::remediation(),
            "write": "NONE"
        }));
    };
    rules::validate(&value)?;
    budgets::validate(&value)?;
    let baseline = value
        .pointer("/ratchet/baseline/fingerprints")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .filter_map(Value::as_str)
        .collect::<BTreeSet<_>>();
    let accepted = accepted_exceptions(&value);
    let violations = all_violations(state, &value)?;
    let present = violations
        .iter()
        .filter_map(|item| item["fingerprint"].as_str().map(str::to_owned))
        .collect::<BTreeSet<_>>();
    let (excepted, active): (Vec<_>, Vec<_>) = violations
        .into_iter()
        .partition(|item| accepted.contains(item["fingerprint"].as_str().unwrap_or_default()));
    let (existing, new): (Vec<_>, Vec<_>) = active
        .into_iter()
        .partition(|item| baseline.contains(item["fingerprint"].as_str().unwrap_or_default()));
    let fixed = baseline
        .iter()
        .filter(|fingerprint| !present.contains(**fingerprint))
        .collect::<Vec<_>>();
    Ok(json!({
        "state": if new.is_empty() {"PASS"} else {"BLOCKED"},
        "enforceable": true,
        "new": new,
        "existing": existing,
        "excepted": excepted,
        "fixed": fixed,
        "contract": ".weavatrix/architecture.json"
    }))
}

pub fn explain(state: &RepositoryState, args: &Value) -> Result<Value, String> {
    let value = match configured_contract(
        state,
        "Create and verify an architecture contract before explaining violations.",
    )? {
        LoadedContract::Configured(value) => value,
        LoadedContract::NotConfigured(response) => return Ok(response),
    };
    let fingerprint = arg_str(args, "fingerprint")?;
    let Some(violation) = active_violation(state, &value, fingerprint)? else {
        return Ok(json!({
            "state": "NOT_FOUND",
            "fingerprint": fingerprint,
            "reason": "the fingerprint is not an active architecture violation"
        }));
    };
    Ok(json!({"violation": violation, "contract": ".weavatrix/architecture.json"}))
}

pub fn propose_exception(state: &RepositoryState, args: &Value) -> Result<Value, String> {
    let value = match configured_contract(
        state,
        "Create and verify an architecture contract before proposing exceptions.",
    )? {
        LoadedContract::Configured(value) => value,
        LoadedContract::NotConfigured(response) => return Ok(response),
    };
    let fingerprint = arg_str(args, "fingerprint")?;
    let reason = arg_str(args, "reason")?;
    let Some(violation) = active_violation(state, &value, fingerprint)? else {
        return Ok(json!({
            "state": "NOT_FOUND",
            "fingerprint": fingerprint,
            "reason": "only an active architecture violation can be proposed as an exception"
        }));
    };
    Ok(json!({
        "state": "PROPOSAL_ONLY",
        "proposal": {
            "fingerprint": fingerprint,
            "reason": reason,
            "expires": args.get("expires"),
            "violation": violation
        },
        "write": "NONE"
    }))
}

fn accepted_exceptions(value: &Value) -> BTreeSet<String> {
    value
        .get("exceptions")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .filter(|exception| {
            exception.get("expires").is_none() || exception["active"] == Value::Bool(true)
        })
        .filter_map(|exception| exception.get("fingerprint")?.as_str().map(str::to_owned))
        .collect()
}

fn all_violations(state: &RepositoryState, value: &Value) -> Result<Vec<Value>, String> {
    let mut violations = rules::dependency_violations(state, value);
    violations.extend(budgets::violations(state, value)?);
    violations.sort_by(|left, right| {
        left["fingerprint"]
            .as_str()
            .cmp(&right["fingerprint"].as_str())
    });
    Ok(violations)
}

fn active_violation(
    state: &RepositoryState,
    value: &Value,
    fingerprint: &str,
) -> Result<Option<Value>, String> {
    Ok(all_violations(state, value)?
        .into_iter()
        .find(|item| item["fingerprint"] == fingerprint))
}

enum LoadedContract {
    Configured(Value),
    NotConfigured(Value),
}

fn configured_contract(state: &RepositoryState, reason: &str) -> Result<LoadedContract, String> {
    Ok(match contract::load_optional(state)? {
        Some(value) => LoadedContract::Configured(value),
        None => LoadedContract::NotConfigured(contract::not_configured(state, reason)),
    })
}