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