pub const CLASS_INDEX_TOOL: &str = "list_tool_classes";
pub const CLASSES: [(&str, &[&str]); 4] = [
(
"query",
&[
"context",
"explain",
"list_kind",
"list_projects",
"path",
"search",
],
),
(
"quality",
&[
"check",
"config_secrets",
"coupling",
"debt",
"debt_density",
],
),
("security", &["security_list", "security_status"]),
("sandbox", &["sandbox_clear", "sandbox_status"]),
];
#[must_use]
pub fn tools_in(class: &str) -> Option<&'static [&'static str]> {
CLASSES
.iter()
.find(|(name, _)| *name == class)
.map(|(_, tools)| *tools)
}
#[must_use]
pub fn class_of(tool: &str) -> Option<&'static str> {
CLASSES
.iter()
.find(|(_, tools)| tools.contains(&tool))
.map(|(name, _)| *name)
}
#[must_use]
pub fn class_names() -> Vec<&'static str> {
CLASSES.iter().map(|(name, _)| *name).collect()
}
const LOADED: &str = "loaded";
const WITHHELD: &str = "withheld";
const UNAVAILABLE: &str = "unavailable";
const NOT_LOADED_HERE: &str = "not-loaded-here";
const PARTLY_LOADED: &str = "partly-loaded";
const STATE_GLOSSARY: [(&str, &str); 5] = [
(LOADED, "advertised here"),
(
WITHHELD,
"this build has the tool but the operator's `--tools` did not select its class",
),
(NOT_LOADED_HERE, "no tool of the class is advertised here"),
(
PARTLY_LOADED,
"some of the class is advertised, some withheld",
),
(
UNAVAILABLE,
"this build or surface does not carry it at all, and no startup flag reaches it",
),
];
const STATE_GUIDANCE: &str = "`withheld`, `not-loaded-here` and `partly-loaded` are STARTUP CHOICES made to keep unused tool descriptions out of every turn's prompt (`roteiro serve --tools query,quality`, or `[mcp] tools` in `roteiro.toml`) — they are NOT capabilities Roteiro lacks. Name the class so the user can restart the server with it, rather than reporting that Roteiro cannot answer the question.";
fn definition_prefix(state: &str) -> String {
format!("`{state}` = ")
}
fn note() -> String {
let defined: Vec<String> = STATE_GLOSSARY
.iter()
.map(|(name, meaning)| format!("{}{meaning}", definition_prefix(name)))
.collect();
format!("`state` values: {}. {STATE_GUIDANCE}", defined.join("; "))
}
fn tool_state(in_build: bool, advertised: bool) -> &'static str {
match (in_build, advertised) {
(false, _) => UNAVAILABLE,
(true, true) => LOADED,
(true, false) => WITHHELD,
}
}
#[must_use]
pub fn report(
in_build: impl Fn(&str) -> bool,
advertised: impl Fn(&str) -> bool,
) -> serde_json::Value {
let classes: Vec<serde_json::Value> = CLASSES
.iter()
.map(|(class, tools)| {
let rows: Vec<serde_json::Value> = tools
.iter()
.map(|tool| {
serde_json::json!({
"tool": tool,
"state": tool_state(in_build(tool), advertised(tool)),
})
})
.collect();
let present = tools.iter().filter(|t| in_build(t)).count();
let loaded = tools
.iter()
.filter(|t| in_build(t) && advertised(t))
.count();
let state = match (present, loaded) {
(0, _) => UNAVAILABLE,
(_, 0) => NOT_LOADED_HERE,
(p, l) if p == l => LOADED,
_ => PARTLY_LOADED,
};
serde_json::json!({ "class": class, "state": state, "tools": rows })
})
.collect();
serde_json::json!({
"classes": classes,
"note": note(),
})
}
#[cfg(test)]
mod tests {
use super::{
CLASS_INDEX_TOOL, CLASSES, STATE_GLOSSARY, class_names, class_of, definition_prefix, note,
report, tools_in,
};
use std::collections::BTreeSet;
#[test]
fn no_tool_belongs_to_two_classes() {
let mut seen: BTreeSet<&str> = BTreeSet::new();
for (class, tools) in CLASSES {
for tool in tools {
assert!(
seen.insert(tool),
"`{tool}` appears twice; the second time in `{class}`"
);
}
}
assert!(
!seen.contains(CLASS_INDEX_TOOL),
"the class index belongs to no class — a restriction able to withhold it \
would remove the only way a client learns what was withheld"
);
}
#[test]
fn a_class_name_is_never_also_a_tool_name() {
let names: BTreeSet<&str> = class_names().into_iter().collect();
assert_eq!(names.len(), CLASSES.len(), "duplicate class name");
for class in class_names() {
assert!(
class_of(class).is_none(),
"`{class}` is both a class and a tool"
);
assert!(tools_in(class).is_some());
}
assert!(tools_in("query").is_some_and(|t| t.contains(&"search")));
assert!(tools_in("nope").is_none());
}
#[test]
fn the_report_separates_a_withheld_tool_from_an_absent_one() {
let doc = report(|t| t != "list_kind", |t| t == "search");
let classes = doc["classes"].as_array().expect("classes array");
let query = classes
.iter()
.find(|c| c["class"] == "query")
.expect("query class");
assert_eq!(query["state"], "partly-loaded", "{doc}");
let state_of = |name: &str| {
query["tools"]
.as_array()
.expect("tools array")
.iter()
.find(|t| t["tool"] == name)
.map(|t| t["state"].clone())
.expect("tool row")
};
assert_eq!(state_of("search"), "loaded", "{doc}");
assert_eq!(state_of("explain"), "withheld", "{doc}");
assert_eq!(state_of("list_kind"), "unavailable", "{doc}");
let security = classes
.iter()
.find(|c| c["class"] == "security")
.expect("security class");
assert_eq!(security["state"], "not-loaded-here", "{doc}");
}
#[test]
fn every_state_the_report_emits_is_defined_in_its_note() {
use std::collections::BTreeSet;
fn states_in(value: &serde_json::Value, found: &mut BTreeSet<String>) {
match value {
serde_json::Value::Object(map) => {
for (key, child) in map {
if key == "state"
&& let Some(state) = child.as_str()
{
found.insert(state.to_owned());
}
states_in(child, found);
}
}
serde_json::Value::Array(items) => {
for item in items {
states_in(item, found);
}
}
_ => {}
}
}
type Scenario = (&'static str, fn(&str) -> bool, fn(&str) -> bool);
let scenarios: [Scenario; 5] = [
("nothing carried", |_| false, |_| false),
("all carried, none advertised", |_| true, |_| false),
("all carried, all advertised", |_| true, |_| true),
(
"all carried, one class advertised",
|_| true,
|name| class_of(name).is_some_and(|c| c == "query"),
),
(
"all carried, one tool of each class advertised",
|_| true,
|name| {
class_of(name)
.and_then(tools_in)
.and_then(<[&str]>::first)
.is_some_and(|first| *first == name)
},
),
];
let mut emitted: BTreeSet<String> = BTreeSet::new();
for (label, in_build, advertised) in scenarios {
let doc = report(in_build, advertised);
let mut here = BTreeSet::new();
states_in(&doc, &mut here);
assert!(!here.is_empty(), "`{label}` produced no state at all");
emitted.extend(here);
}
let note = note();
for state in &emitted {
assert!(
note.contains(&definition_prefix(state)),
"the report can emit `{state}` and the note never defines it. A client \
that sees only this JSON cannot tell it from a capability Roteiro \
lacks, which is the one thing the note exists to prevent. Add it to \
`STATE_GLOSSARY`",
);
}
let defined: BTreeSet<String> = STATE_GLOSSARY
.iter()
.map(|(name, _)| (*name).to_owned())
.collect();
assert_eq!(
emitted, defined,
"the states the report emits and the states the glossary defines must be \
the same set — left is emitted, right is defined",
);
for (name, meaning) in STATE_GLOSSARY {
assert!(
!meaning.contains(';'),
"`{name}`'s definition contains the separator `note` joins with, so it \
reads as two entries: {meaning:?}",
);
}
}
#[test]
fn an_unrestricted_surface_reports_every_class_loaded() {
let doc = report(|_| true, |_| true);
for class in doc["classes"].as_array().expect("classes array") {
assert_eq!(class["state"], "loaded", "{doc}");
}
}
}