use std::collections::BTreeMap;
use std::fmt::Write as _;
use trusty_mpm::core::agent_metadata::agent_metadata_from_str;
use trusty_mpm::core::content_source::AgentRoster;
use trusty_mpm::core::manifest::framework_agent_categories;
fn gate_by_stem() -> BTreeMap<String, (&'static str, String)> {
let Ok(categories) = framework_agent_categories() else {
return BTreeMap::new();
};
let mut map = BTreeMap::new();
for stem in &categories.universal {
map.insert(stem.clone(), ("universal", "always".to_string()));
}
for stem in &categories.deprecated {
map.insert(stem.clone(), ("deprecated", "never".to_string()));
}
for (label, list) in [
("language", &categories.language),
("framework", &categories.framework),
("platform", &categories.platform),
] {
for entry in list {
let markers = entry
.markers
.iter()
.map(|marker| format!("`{marker}`"))
.collect::<Vec<_>>()
.join(" or ");
map.insert(entry.stem.clone(), (label, markers));
}
}
map
}
pub(crate) fn render(roster: &AgentRoster) -> String {
let mut agents: Vec<(String, trusty_mpm::core::agent_metadata::AgentMetadata)> = roster
.iter()
.filter(|(name, _)| !name.starts_with("BASE-"))
.map(|(name, contents)| {
let stem = name.strip_suffix(".md").unwrap_or(name).to_string();
(stem, agent_metadata_from_str(contents))
})
.collect();
agents.sort_by(|(a, _), (b, _)| a.cmp(b));
let mut out = String::new();
out.push_str("# Agent Roster Reference\n\n");
out.push_str(
"Generated from content/agents (the agent roster) + \
`agent_metadata::agent_metadata_from_str` — the same frontmatter \
parser `agent_builder` uses at compose time — with the **Category** \
and **Deploys When** columns read from the bundled \
`framework-manifest.toml`, the same file the deployer consults — this \
table is that manifest\'s rendered view, never a second copy of it. \
Regenerate with `tm generate capabilities`.\n\n",
);
out.push_str(
"**Category** is the DEPLOYMENT gate (#4760) and **Deploys When** is \
the condition it resolves to (#4765): `universal` deploys to every \
project with no detection; `language`, `framework`, and `platform` \
deploy only when one of the listed markers is present at the project \
root or at a declared workspace member; `deprecated` never deploys. A \
marker written `path::needle` is a bounded content probe of that \
file. This axis is distinct from ADR-0025's four-category agent \
model, which classifies by who authored an agent and where it lives — \
every row below is an ADR-0025 category-1 or category-2 bundled \
agent.\n\n",
);
out.push_str(
"Every agent below transitively `extends: base-agent` (directly, or \
via `base-engineer`/`base-qa`/`base-ops`/`base-research`) — see \
`agents/BASE-AGENT.md` and its four role-specific bases for the \
shared foundation, not repeated per row here.\n\n",
);
let _ = writeln!(out, "{} concrete agents.\n", agents.len());
let gates = gate_by_stem();
out.push_str(
"| Agent | Category | Deploys When | Role | Extends | Description | Declared Skills |\n\
|---|---|---|---|---|---|---|\n",
);
for (stem, meta) in &agents {
let (category, gate) = gates.get(stem).cloned().unwrap_or(("", String::new()));
let role = meta.role.clone().unwrap_or_default();
let extends = meta.extends.clone().unwrap_or_default();
let description = meta.description.clone().unwrap_or_default();
let skills = meta.skills.join(", ");
let _ = writeln!(
out,
"| `{stem}` | {category} | {gate} | {role} | {extends} | {description} | {skills} |"
);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn agents_render_contains_known_agent() {
let rendered = render(crate::commands::install::test_roster_ref());
assert!(rendered.contains("`rust-engineer`"), "{rendered}");
assert!(rendered.contains("`code-critic`"), "{rendered}");
}
#[test]
fn agents_render_excludes_base_files() {
let rendered = render(crate::commands::install::test_roster_ref());
assert!(!rendered.contains("`BASE-AGENT`"), "{rendered}");
assert!(!rendered.contains("`BASE-ENGINEER`"), "{rendered}");
}
#[test]
fn agents_render_carries_categories() {
let rendered = render(crate::commands::install::test_roster_ref());
for (stem, category) in [
("engineer", "universal"),
("rust-engineer", "language"),
("react-engineer", "framework"),
("vercel-ops", "platform"),
("elixir-engineer", "language"),
] {
assert!(
rendered.contains(&format!("| `{stem}` | {category} |")),
"expected `{stem}` to render as {category}:\n{rendered}"
);
}
}
#[test]
fn agents_render_carries_markers() {
let rendered = render(crate::commands::install::test_roster_ref());
for (stem, gate) in [
("rust-engineer", "| language | `Cargo.toml` |"),
("engineer", "| universal | always |"),
("vercel-ops", "`vercel.json`"),
] {
assert!(
rendered.contains(gate),
"expected `{stem}` to render its gate {gate}:\n{rendered}"
);
}
}
#[test]
fn agents_render_is_deterministic() {
assert_eq!(
render(crate::commands::install::test_roster_ref()),
render(crate::commands::install::test_roster_ref())
);
}
}