weavatrix_rust/operations/
mod.rs1mod architecture;
2mod build;
3mod catalog;
4mod graph;
5mod health;
6mod history;
7mod memory;
8mod semantic;
9mod source;
10mod syntax;
11mod token_budget;
12mod transport_contracts;
13mod vector;
14mod workflow;
15
16pub use catalog::{ToolDefinition, ToolProfile, catalog, catalog_for_profile};
17
18use crate::engine::{RepositoryState, Weavatrix};
19use blazingly_json::{Value, json};
20
21#[allow(clippy::needless_pass_by_value)]
28pub fn call(weavatrix: &mut Weavatrix, name: &str, arguments: Value) -> Result<Value, String> {
29 let mut report = dispatch(weavatrix, name, &arguments)?;
30 token_budget::annotate_unapplied(name, &arguments, &mut report)?;
33 Ok(report)
34}
35
36fn dispatch(weavatrix: &mut Weavatrix, name: &str, arguments: &Value) -> Result<Value, String> {
37 if name == "trace_api_contract" {
38 return workflow::trace_api_cached(weavatrix, arguments);
39 }
40 let state = weavatrix.state();
41 match name {
42 "graph_stats" => graph::stats(state, arguments),
43 "get_node" => graph::get_node(state, arguments),
44 "get_neighbors" => graph::neighbors(state, arguments),
45 "query_graph" => graph::query(state, arguments),
46 "god_nodes" => Ok(graph::hubs(state, arguments)),
47 "shortest_path" => graph::path(state, arguments),
48 "get_dependents" => graph::dependents(state, arguments),
49 "change_impact" => workflow::change_impact(state, arguments),
50 "map_stacktrace" => workflow::map_stacktrace(state, arguments),
51 "select_tests" => workflow::select_tests(state, arguments),
52 "git_history" => history::history(state, arguments),
53 "cross_repo_git" => history::cross_repo(state, arguments),
54 "verified_change" => workflow::verified_change(state, arguments),
55 "get_community" | "list_communities" => graph::communities(state, arguments),
56 "search_code" => source::search(state, arguments),
57 "read_source" => source::read_source(state, arguments),
58 "inspect_symbol" => source::inspect(state, arguments),
59 "context_bundle" => source::context(state, arguments),
60 "find_duplicates" => health::duplicates(state, arguments),
61 "find_dead_code" => health::dead_code(state, arguments),
62 "run_audit" => health::audit(state, arguments),
63 "coverage_map" => health::coverage(state, arguments),
64 "hot_path_review" => health::hot_paths(state, arguments),
65 "module_map" => graph::module_map(state, arguments),
66 "build_graph" => build::build_graph(state, arguments),
67 "list_endpoints" => graph::endpoints(state, arguments),
68 "trace_endpoint" => graph::trace_endpoint(state, arguments),
69 "graph_diff" => history::graph_diff(state, arguments),
70 "get_architecture_contract" => architecture::contract(state, arguments),
71 "prepare_change" => architecture::prepare(state, arguments),
72 "verify_architecture" => architecture::verify(state),
73 "verify_capabilities" => architecture::verify_capabilities(state, arguments),
74 "explain_architecture_violation" => architecture::explain(state, arguments),
75 "propose_architecture_exception" => architecture::propose_exception(state, arguments),
76 "semantic_link" => semantic::semantic_link(state, arguments),
77 "vector_search" => vector::search(arguments),
78 "seo_link_suggestions" => semantic::seo_links(state, arguments),
79 "memory_context" => memory::context(state, arguments),
80 "rebuild_graph" => {
81 let before = graph::stats(state, arguments)?;
82 weavatrix.rebuild().map_err(|error| error.to_string())?;
83 Ok(json!({"before": before, "after": graph::stats(weavatrix.state(), arguments)?}))
84 }
85 "open_repo" => {
86 let path = arg_str(arguments, "path")?.to_owned();
87 let should_build = arg_bool(arguments, "build").unwrap_or(true);
88 let graph_built = weavatrix
89 .open_repository_with_build(&path, should_build)
90 .map_err(|error| error.to_string())?;
91 Ok(json!({
92 "repository": weavatrix.state().root(),
93 "built": graph_built,
94 "graph": graph::stats(weavatrix.state(), arguments)?
95 }))
96 }
97 "list_known_repos" => Ok(json!({
98 "repositories": weavatrix.known_roots().collect::<Vec<_>>()
99 })),
100 _ => Err(format!("unknown tool: {name}")),
101 }
102}
103
104fn arg_value<'value, T>(
105 args: &'value Value,
106 key: &str,
107 expected: &str,
108 extract: impl FnOnce(&'value Value) -> Option<T>,
109) -> Result<T, String> {
110 args.get(key)
111 .and_then(extract)
112 .ok_or_else(|| format!("{key} must be {expected}"))
113}
114
115pub(crate) fn arg_str<'value>(args: &'value Value, key: &str) -> Result<&'value str, String> {
116 arg_value(args, key, "a string", Value::as_str)
117}
118
119pub(crate) fn arg_u64(args: &Value, key: &str) -> Result<u64, String> {
120 arg_value(args, key, "a non-negative integer", Value::as_u64)
121}
122
123pub(crate) fn arg_bool(args: &Value, key: &str) -> Result<bool, String> {
124 arg_value(args, key, "a boolean", Value::as_bool)
125}
126
127pub(crate) fn optional_str<'value>(
128 args: &'value Value,
129 key: &str,
130) -> Result<Option<&'value str>, String> {
131 args.get(key)
132 .map(|value| {
133 value
134 .as_str()
135 .ok_or_else(|| format!("{key} must be a string"))
136 })
137 .transpose()
138}
139
140pub(crate) fn optional_u64(args: &Value, key: &str) -> Result<Option<u64>, String> {
141 args.get(key)
142 .map(|value| {
143 value
144 .as_u64()
145 .ok_or_else(|| format!("{key} must be a non-negative integer"))
146 })
147 .transpose()
148}
149
150pub(crate) fn optional_bool(args: &Value, key: &str) -> Result<Option<bool>, String> {
151 args.get(key)
152 .map(|value| {
153 value
154 .as_bool()
155 .ok_or_else(|| format!("{key} must be a boolean"))
156 })
157 .transpose()
158}
159
160pub(crate) fn require_graph_precision(args: &Value) -> Result<(), String> {
161 let Some(precision) = optional_str(args, "precision")? else {
162 return Ok(());
163 };
164 if precision == "graph" {
165 return Ok(());
166 }
167 Err(format!(
168 "precision '{precision}' is unsupported; this operation supports only 'graph' bounded static precision"
169 ))
170}
171
172#[cfg(any(feature = "semantic", feature = "vector"))]
173fn vector_values(value: &Value, array_error: &str) -> Result<Vec<f32>, String> {
174 value
175 .as_array()
176 .ok_or_else(|| array_error.to_owned())?
177 .iter()
178 .map(|value| {
179 let value = value
180 .as_f64()
181 .filter(|value| value.is_finite())
182 .ok_or_else(|| "vector value must be finite".to_owned())?;
183 if !(f64::from(f32::MIN)..=f64::from(f32::MAX)).contains(&value) {
184 return Err("vector value is outside finite f32 range".to_owned());
185 }
186 value
187 .to_string()
188 .parse::<f32>()
189 .map_err(|error| format!("invalid vector value: {error}"))
190 })
191 .collect()
192}
193
194pub(crate) fn node_path(node: &weavatrix_graph::Node) -> Option<&str> {
196 node.span
197 .as_ref()
198 .map(|span| span.file.as_str())
199 .or_else(|| (node.kind == weavatrix_graph::NodeKind::File).then_some(node.label.as_str()))
200}
201
202pub(crate) fn node_is_visible(state: &RepositoryState, slot: usize, args: &Value) -> bool {
209 let index = weavatrix_graph::NodeIndex::new(u32::try_from(slot).unwrap_or(u32::MAX));
210 let Some(node) = state.graph().node_at(index) else {
211 return true;
212 };
213 if node_path(node).is_some() {
214 return evidence_node_is_visible(node, args);
215 }
216 let mut declared = false;
220 for edge in state.graph().incoming_at(index) {
221 let Some(source) = state.graph().node(edge.source.as_str()) else {
222 continue;
223 };
224 if node_path(source).is_none() {
225 continue;
226 }
227 declared = true;
228 if evidence_node_is_visible(source, args) {
229 return true;
230 }
231 }
232 !declared
235}
236
237fn evidence_node_is_visible(node: &weavatrix_graph::Node, args: &Value) -> bool {
238 if matches!(
239 node.attributes.get("test_only"),
240 Some(weavatrix_graph::AttributeValue::Bool(true))
241 ) {
242 return args.get("include_tests").and_then(Value::as_bool) == Some(true);
243 }
244 node_path(node).is_none_or(|path| health::path_is_visible(path, args))
245}