1mod architecture;
2mod catalog;
3mod catalog_schema;
4mod graph;
5mod graph_trace;
6mod graph_walk;
7mod health;
8mod health_coverage;
9mod health_dependencies;
10mod health_manifests;
11mod health_runtime;
12mod history;
13#[cfg(feature = "git")]
14mod history_analytics;
15#[cfg(feature = "git")]
16mod history_diff;
17mod memory;
18mod semantic;
19mod source;
20mod transport_contracts;
21mod vector;
22mod workflow;
23
24pub use catalog::{ToolDefinition, catalog, catalog_for_profile};
25
26use crate::Weavatrix;
27use blazingly_json::{Value, json};
28
29#[allow(clippy::needless_pass_by_value)]
36pub fn call(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" => Ok(graph::stats(state)),
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 "git_history" => history::history(state, &arguments),
51 "cross_repo_git" => history::cross_repo(state, &arguments),
52 "verified_change" => workflow::verified_change(state, &arguments),
53 "get_community" | "list_communities" => graph::communities(state, &arguments),
54 "search_code" => source::search(state, &arguments),
55 "read_source" => source::read_source(state, &arguments),
56 "inspect_symbol" => source::inspect(state, &arguments),
57 "context_bundle" => source::context(state, &arguments),
58 "find_duplicates" => health::duplicates(state, &arguments),
59 "find_dead_code" => health::dead_code(state, &arguments),
60 "run_audit" => health::audit(state, &arguments),
61 "coverage_map" => health::coverage(state, &arguments),
62 "hot_path_review" => health::hot_paths(state, &arguments),
63 "module_map" => Ok(graph::module_map(state, &arguments)),
64 "list_endpoints" => graph::endpoints(state, &arguments),
65 "trace_endpoint" => graph_trace::endpoint(state, &arguments),
66 "graph_diff" => history::graph_diff(state, &arguments),
67 "get_architecture_contract" => architecture::contract(state, &arguments),
68 "prepare_change" => architecture::prepare(state, &arguments),
69 "verify_architecture" => architecture::verify(state),
70 "explain_architecture_violation" => architecture::explain(state, &arguments),
71 "propose_architecture_exception" => architecture::propose_exception(state, &arguments),
72 "semantic_link" => semantic::semantic_link(state, &arguments),
73 "vector_search" => vector::search(&arguments),
74 "seo_link_suggestions" => semantic::seo_links(state, &arguments),
75 "memory_context" => memory::context(state, &arguments),
76 "rebuild_graph" => {
77 let before = graph::stats(state);
78 weavatrix.rebuild().map_err(|error| error.to_string())?;
79 Ok(json!({"before": before, "after": graph::stats(weavatrix.state())}))
80 }
81 "open_repo" => {
82 let path = arg_str(&arguments, "path")?.to_owned();
83 let should_build = arg_bool(&arguments, "build").unwrap_or(true);
84 let graph_built = weavatrix
85 .open_repository_with_build(&path, should_build)
86 .map_err(|error| error.to_string())?;
87 Ok(json!({
88 "repository": weavatrix.state().root(),
89 "built": graph_built,
90 "graph": graph::stats(weavatrix.state())
91 }))
92 }
93 "list_known_repos" => Ok(json!({
94 "repositories": weavatrix.known_roots().collect::<Vec<_>>()
95 })),
96 _ => Err(format!("unknown tool: {name}")),
97 }
98}
99
100fn arg_value<'value, T>(
101 args: &'value Value,
102 key: &str,
103 expected: &str,
104 extract: impl FnOnce(&'value Value) -> Option<T>,
105) -> Result<T, String> {
106 args.get(key)
107 .and_then(extract)
108 .ok_or_else(|| format!("{key} must be {expected}"))
109}
110
111pub(crate) fn arg_str<'value>(args: &'value Value, key: &str) -> Result<&'value str, String> {
112 arg_value(args, key, "a string", Value::as_str)
113}
114
115pub(crate) fn arg_u64(args: &Value, key: &str) -> Result<u64, String> {
116 arg_value(args, key, "a non-negative integer", Value::as_u64)
117}
118
119pub(crate) fn arg_bool(args: &Value, key: &str) -> Result<bool, String> {
120 arg_value(args, key, "a boolean", Value::as_bool)
121}
122
123pub(crate) fn optional_str<'value>(
124 args: &'value Value,
125 key: &str,
126) -> Result<Option<&'value str>, String> {
127 args.get(key)
128 .map(|value| {
129 value
130 .as_str()
131 .ok_or_else(|| format!("{key} must be a string"))
132 })
133 .transpose()
134}
135
136pub(crate) fn optional_u64(args: &Value, key: &str) -> Result<Option<u64>, String> {
137 args.get(key)
138 .map(|value| {
139 value
140 .as_u64()
141 .ok_or_else(|| format!("{key} must be a non-negative integer"))
142 })
143 .transpose()
144}
145
146pub(crate) fn optional_bool(args: &Value, key: &str) -> Result<Option<bool>, String> {
147 args.get(key)
148 .map(|value| {
149 value
150 .as_bool()
151 .ok_or_else(|| format!("{key} must be a boolean"))
152 })
153 .transpose()
154}
155
156#[cfg(any(feature = "semantic", feature = "vector"))]
157fn vector_values(value: &Value, array_error: &str) -> Result<Vec<f32>, String> {
158 value
159 .as_array()
160 .ok_or_else(|| array_error.to_owned())?
161 .iter()
162 .map(|value| {
163 let value = value
164 .as_f64()
165 .filter(|value| value.is_finite())
166 .ok_or_else(|| "vector value must be finite".to_owned())?;
167 if !(f64::from(f32::MIN)..=f64::from(f32::MAX)).contains(&value) {
168 return Err("vector value is outside finite f32 range".to_owned());
169 }
170 value
171 .to_string()
172 .parse::<f32>()
173 .map_err(|error| format!("invalid vector value: {error}"))
174 })
175 .collect()
176}
177
178pub(crate) fn node_path(node: &weavatrix_graph::Node) -> Option<&str> {
180 node.span
181 .as_ref()
182 .map(|span| span.file.as_str())
183 .or_else(|| (node.kind == weavatrix_graph::NodeKind::File).then_some(node.label.as_str()))
184}
185
186pub(crate) fn node_is_visible(state: &crate::RepositoryState, slot: usize, args: &Value) -> bool {
193 let index = weavatrix_graph::NodeIndex::new(u32::try_from(slot).unwrap_or(u32::MAX));
194 let Some(node) = state.graph().node_at(index) else {
195 return true;
196 };
197 if node_path(node).is_some() {
198 return evidence_node_is_visible(node, args);
199 }
200 let mut declared = false;
204 for edge in state.graph().incoming_at(index) {
205 let Some(source) = state.graph().node(edge.source.as_str()) else {
206 continue;
207 };
208 if node_path(source).is_none() {
209 continue;
210 }
211 declared = true;
212 if evidence_node_is_visible(source, args) {
213 return true;
214 }
215 }
216 !declared
219}
220
221fn evidence_node_is_visible(node: &weavatrix_graph::Node, args: &Value) -> bool {
222 if matches!(
223 node.attributes.get("test_only"),
224 Some(weavatrix_graph::AttributeValue::Bool(true))
225 ) {
226 return args.get("include_tests").and_then(Value::as_bool) == Some(true);
227 }
228 node_path(node).is_none_or(|path| health::path_is_visible(path, args))
229}