use std::process::Command;
use basemind::cli::context::build_server;
use basemind::config::DocumentsCliOverrides;
use basemind::store::VIEW_WORKING;
fn bin() -> &'static str {
env!("CARGO_BIN_EXE_basemind")
}
fn tool_to_cli() -> Vec<(&'static str, &'static str)> {
#[allow(unused_mut)]
let mut m: Vec<(&str, &str)> = vec![
("outline", "query outline"),
("search_symbols", "query search"),
("find_references", "query references"),
("find_callers", "query callers"),
("goto_definition", "query goto-definition"),
("find_implementations", "query implementations"),
("call_graph", "query call-graph"),
("workspace_grep", "query grep"),
("list_files", "query list-files"),
("dependents", "query dependents"),
("status", "query status"),
("repo_info", "query repo-info"),
("symbol_history", "git symbol-history"),
("rescan", "rescan"),
("telemetry_summary", "telemetry"),
("search_code", "query search-code"),
("get_chunk", "query get-chunk"),
("expand", "query expand"),
("compress", "compress-output"),
("delta", "delta"),
("checkpoint", "checkpoint"),
("detect_waste", "detect-waste"),
("working_tree_status", "git working-tree-status"),
("recent_changes", "git recent-changes"),
("commits_touching", "git commits-touching"),
("find_commits_by_path", "git find-commits-by-path"),
("diff_file", "git diff-file"),
("diff_outline", "git diff-outline"),
("hot_files", "git hot-files"),
("blame_file", "git blame-file"),
("blame_symbol", "git blame-symbol"),
("search_git_history", "git search"),
("memory_put", "memory put"),
("memory_get", "memory get"),
("memory_list", "memory list"),
("memory_search", "memory search"),
("memory_delete", "memory delete"),
("search_documents", "memory search-documents"),
("proposals_mine", "governance mine"),
("proposals_list", "governance proposals"),
("proposal_accept", "governance accept"),
("proposal_reject", "governance reject"),
("memory_audit", "governance audit"),
("cache_stats", "cache stats"),
("cache_gc", "cache gc"),
("cache_clear", "cache clear"),
];
#[cfg(feature = "crawl")]
m.extend([
("web_scrape", "web scrape"),
("web_crawl", "web crawl"),
("web_map", "web map"),
]);
#[cfg(all(feature = "comms", any(unix, windows)))]
m.extend([
("agent_register", "comms register"),
("agent_list", "comms agents"),
("room_create", "comms room-create"),
("room_join", "comms join"),
("room_leave", "comms leave"),
("room_list", "comms rooms"),
("room_post", "comms post"),
("room_history", "comms history"),
("dm_send", "comms dm"),
("inbox_read", "comms inbox"),
("inbox_ack", "comms inbox"),
("message_get", "comms read"),
("get_or_create_chat_room_for_path", "comms room-for-path"),
]);
#[cfg(all(feature = "shells", any(unix, windows)))]
m.extend([
("shell_spawn", "shells spawn"),
("shell_send", "shells send"),
("shell_capture", "shells capture"),
("shell_kill", "shells kill"),
("shell_broadcast", "shells broadcast"),
("shell_list", "shells list"),
]);
m
}
fn advertised_tools() -> Vec<String> {
let tmp = tempfile::tempdir().expect("tempdir");
let server =
build_server(tmp.path(), VIEW_WORKING, DocumentsCliOverrides::default()).expect("build one-shot server");
server.tool_names()
}
#[test]
fn every_mcp_tool_has_a_cli_command() {
let tools = advertised_tools();
let map = tool_to_cli();
let mapped: std::collections::HashSet<&str> = map.iter().map(|(t, _)| *t).collect();
let unmapped: Vec<&String> = tools.iter().filter(|t| !mapped.contains(t.as_str())).collect();
assert!(
unmapped.is_empty(),
"MCP tools with no CLI mapping (add the CLI command + a TOOL_TO_CLI row): {unmapped:?}"
);
let live: std::collections::HashSet<&str> = tools.iter().map(String::as_str).collect();
let stale: Vec<&str> = map.iter().map(|(t, _)| *t).filter(|t| !live.contains(t)).collect();
assert!(
stale.is_empty(),
"TOOL_TO_CLI rows for tools no longer advertised (remove or rename them): {stale:?}"
);
}
#[test]
fn every_mapped_cli_command_resolves() {
for (tool, cli) in tool_to_cli() {
let mut args: Vec<&str> = cli.split(' ').collect();
args.push("--help");
let output = Command::new(bin())
.args(&args)
.output()
.unwrap_or_else(|e| panic!("spawn `basemind {cli} --help`: {e}"));
assert!(
output.status.success(),
"`basemind {cli} --help` (for tool `{tool}`) exited {:?}\nstderr: {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
}
}