Skip to main content

weavatrix_rust/operations/
mod.rs

1mod agent;
2mod architecture;
3mod args;
4mod build;
5mod catalog;
6mod diagram;
7mod dify;
8mod domain_walk;
9mod graph;
10mod health;
11mod history;
12mod memory;
13mod n8n;
14mod occurrence;
15mod perf;
16mod semantic;
17mod source;
18mod syntax;
19mod token_budget;
20mod transport_contracts;
21mod vector;
22mod visibility;
23mod web3;
24mod workflow;
25
26pub use catalog::{ToolDefinition, ToolProfile, catalog, catalog_for_profile};
27pub(crate) use catalog::{reject_unknown_arguments, require_valid_output_format};
28
29pub(crate) use args::{
30    arg_bool, arg_str, arg_u64, optional_bool, optional_str, optional_u64, require_graph_precision,
31};
32pub(crate) use visibility::{node_is_visible, node_path};
33
34use crate::engine::Weavatrix;
35use blazingly_json::{Value, json};
36
37/// Executes one bounded read-only repository tool.
38///
39/// # Errors
40///
41/// Returns invalid arguments, unavailable optional capabilities, or analysis
42/// failures without mutating repository source.
43#[allow(clippy::needless_pass_by_value)]
44pub fn call(weavatrix: &mut Weavatrix, name: &str, arguments: Value) -> Result<Value, String> {
45    weavatrix.prepare();
46    require_valid_output_format(&arguments)?;
47    expect_repository(weavatrix, &arguments)?;
48    let mut report = dispatch(weavatrix, name, &arguments)?;
49    // A budget an operation cannot apply is reported, not refused: the answer
50    // itself is never withheld.
51    token_budget::annotate_unapplied(name, &arguments, &mut report)?;
52    attach_repository_context(weavatrix, &mut report);
53    Ok(report)
54}
55
56/// Fails fast when the caller names the repository it expects and this
57/// process is targeting a different one. A stateful server that silently
58/// answers about the previously opened repository produces confidently wrong
59/// evidence, which is worse than an error.
60fn expect_repository(weavatrix: &Weavatrix, args: &Value) -> Result<(), String> {
61    let Some(expected) = optional_str(args, "expected_repository")? else {
62        return Ok(());
63    };
64    let active = weavatrix.state().root();
65    let canonical = std::path::Path::new(expected).canonicalize().ok();
66    if canonical.as_deref() == Some(active) {
67        return Ok(());
68    }
69    let folder = |path: &std::path::Path| {
70        path.file_name()
71            .map(|name| name.to_string_lossy().to_ascii_lowercase())
72    };
73    if folder(std::path::Path::new(expected)) == folder(active) {
74        return Ok(());
75    }
76    Err(format!(
77        "active repository is {}, not the expected {expected}; call open_repo first",
78        active.display()
79    ))
80}
81
82/// Every answer names the repository, revision and graph age it came from, so
83/// a caller can detect a stale graph or a wrong active repository without a
84/// second round trip.
85fn attach_repository_context(weavatrix: &Weavatrix, report: &mut Value) {
86    let state = weavatrix.state();
87    let Some(object) = report.as_object_mut() else {
88        return;
89    };
90    object.insert(
91        "repository_context".to_owned(),
92        json!({
93            "root": state.root(),
94            "scan_revision": state.snapshot().revision,
95            "git_head": crate::engine::git_head(state.root()),
96            "graph_age_seconds": state.graph_age_seconds()
97        }),
98    );
99}
100
101fn dispatch(weavatrix: &mut Weavatrix, name: &str, arguments: &Value) -> Result<Value, String> {
102    if name == "trace_api_contract" {
103        return workflow::trace_api_cached(weavatrix, arguments);
104    }
105    let state = weavatrix.state();
106    match name {
107        "graph_stats" => graph::stats(state, arguments),
108        "get_node" => graph::get_node(state, arguments),
109        "get_neighbors" => graph::neighbors(state, arguments),
110        "query_graph" => graph::query(state, arguments),
111        "god_nodes" => Ok(graph::hubs(state, arguments)),
112        "shortest_path" => graph::path(state, arguments),
113        "get_dependents" => graph::dependents(state, arguments),
114        "change_impact" => workflow::change_impact(state, arguments),
115        "map_stacktrace" => workflow::map_stacktrace(state, arguments),
116        "select_tests" => workflow::select_tests(state, arguments),
117        "git_history" => history::history(state, arguments),
118        "git_read_blob" => history::read_blob(state, arguments),
119        "cross_repo_git" => history::cross_repo(state, arguments),
120        "verified_change" => workflow::verified_change(state, arguments),
121        "get_community" | "list_communities" => graph::communities(state, arguments),
122        "search_code" => source::search(state, arguments),
123        "read_source" => source::read_source(state, arguments),
124        "inspect_symbol" => source::inspect(state, arguments),
125        "go_to_definition" => occurrence::go_to_definition(state, arguments),
126        "find_references" => occurrence::find_references(state, arguments),
127        "context_bundle" => source::context(state, arguments),
128        "find_duplicates" => health::duplicates(state, arguments),
129        "find_dead_code" => health::dead_code(state, arguments),
130        "run_audit" => health::audit(state, arguments),
131        "coverage_map" => health::coverage(state, arguments),
132        "hot_path_review" => health::hot_paths(state, arguments),
133        "perf_attribution" => perf::attribution(state, arguments),
134        "module_map" => graph::module_map(state, arguments),
135        "build_graph" => build::build_graph(state, arguments),
136        "list_endpoints" => graph::endpoints(state, arguments),
137        "trace_endpoint" => graph::trace_endpoint(state, arguments),
138        "graph_diff" => history::graph_diff(state, arguments),
139        "get_architecture_contract" => architecture::contract(state, arguments),
140        "prepare_change" => architecture::prepare(state, arguments),
141        "verify_architecture" => architecture::verify(state),
142        "verify_capabilities" => architecture::verify_capabilities(state, arguments),
143        "explain_architecture_violation" => architecture::explain(state, arguments),
144        "propose_architecture_exception" => architecture::propose_exception(state, arguments),
145        "semantic_link" => semantic::semantic_link(state, arguments),
146        "vector_search" => vector::search(arguments),
147        "seo_link_suggestions" => semantic::seo_links(state, arguments),
148        "memory_context" => memory::context(state, arguments),
149        "n8n_inventory" => n8n::inventory(state, arguments),
150        "n8n_trace" => n8n::trace(state, arguments),
151        "n8n_context" => n8n::context(state, arguments),
152        "dify_inventory" => dify::inventory(state, arguments),
153        "dify_trace" => dify::trace(state, arguments),
154        "dify_context" => dify::context(state, arguments),
155        "agent_inventory" => agent::inventory(state, arguments),
156        "agent_trace" => agent::trace(state, arguments),
157        "agent_context" => agent::context(state, arguments),
158        "agent_change_impact" => agent::change_impact(state, arguments),
159        "diagram_inventory" => diagram::inventory(state, arguments),
160        "diagram_trace" => diagram::trace(state, arguments),
161        "diagram_context" => diagram::context(state, arguments),
162        "web3_inventory" => web3::inventory(state, arguments),
163        "web3_trace" => web3::trace(state, arguments),
164        "web3_impact" => web3::impact(state, arguments),
165        "web3_context" => web3::context(state, arguments),
166        "rebuild_graph" => {
167            let before = graph::stats(state, arguments)?;
168            weavatrix.rebuild().map_err(|error| error.to_string())?;
169            Ok(json!({"before": before, "after": graph::stats(weavatrix.state(), arguments)?}))
170        }
171        "open_repo" => {
172            let path = arg_str(arguments, "path")?.to_owned();
173            let should_build = arg_bool(arguments, "build").unwrap_or(true);
174            let graph_built = weavatrix
175                .open_repository_with_build(&path, should_build)
176                .map_err(|error| error.to_string())?;
177            Ok(json!({
178                "repository": weavatrix.state().root(),
179                "built": graph_built,
180                "graph": graph::stats(weavatrix.state(), arguments)?
181            }))
182        }
183        "list_known_repos" => Ok(json!({
184            "repositories": weavatrix.known_roots().collect::<Vec<_>>(),
185        })),
186        _ => Err(format!("unknown tool: {name}")),
187    }
188}