use std::fmt::Write as _;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Capability {
CodeSearchNavigation,
CodeMappingArchitecture,
GitHistory,
AgentComms,
DocumentsRag,
SemanticSearch,
}
impl Capability {
pub const ALL: [Capability; 6] = [
Capability::CodeSearchNavigation,
Capability::CodeMappingArchitecture,
Capability::GitHistory,
Capability::AgentComms,
Capability::DocumentsRag,
Capability::SemanticSearch,
];
pub fn slug(self) -> &'static str {
match self {
Capability::CodeSearchNavigation => "code-search-navigation",
Capability::CodeMappingArchitecture => "code-mapping-architecture",
Capability::GitHistory => "git-history",
Capability::AgentComms => "agent-comms",
Capability::DocumentsRag => "documents-rag",
Capability::SemanticSearch => "semantic-search",
}
}
pub fn label(self) -> &'static str {
match self {
Capability::CodeSearchNavigation => "Code search & navigation (symbols, references, callers, grep)",
Capability::CodeMappingArchitecture => "Code mapping & architecture (file outlines, architecture map)",
Capability::GitHistory => "Git history (recent changes, blame, commits-touching)",
Capability::AgentComms => "Agent comms (multi-agent rooms + inbox)",
Capability::DocumentsRag => "Documents & RAG (PDF / Office / HTML / web search)",
Capability::SemanticSearch => "Semantic (vector) code search",
}
}
pub fn from_slug(slug: &str) -> Option<Capability> {
Capability::ALL.into_iter().find(|c| c.slug() == slug)
}
fn routing_row(self) -> (&'static str, &'static str) {
match self {
Capability::CodeSearchNavigation => (
"`search_symbols` / `find_references` / `find_callers` / `workspace_grep`",
"`grep` / `rg` / opening files to find a symbol",
),
Capability::CodeMappingArchitecture => (
"`outline` / `architecture_map`",
"reading whole files to learn their shape",
),
Capability::GitHistory => (
"`recent_changes` / `blame_symbol` / `commits_touching` / `diff_file`",
"`git log` / `git blame` / `git diff`",
),
Capability::AgentComms => (
"`room_post` / `inbox_read` / `room_history`",
"assuming you're the only agent in the repo",
),
Capability::DocumentsRag => (
"`search_documents` / `web_scrape` / `web_crawl` / `web_map`",
"manually reading PDFs / docs or ad-hoc fetching",
),
Capability::SemanticSearch => (
"semantic code search over the index",
"keyword-only guessing at where a concept lives",
),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct BlockSections {
pub usage_rules: bool,
pub setup_notes: bool,
}
pub fn render_block_body(caps: &[Capability], sections: BlockSections) -> String {
let mut out = String::new();
out.push_str("## basemind — prefer it over grep / read / git\n\n");
out.push_str(
"basemind is this repo's indexed context layer. Prefer it BEFORE grep, before reading \
files to find structure, and before naked `git` — it's the default, not a preference. \
basemind returns paths, lines, and signatures at a fraction of the tokens of reading \
source.\n\n",
);
if sections.usage_rules {
render_usage_rules(&mut out, caps);
}
if sections.setup_notes {
render_setup_notes(&mut out);
}
out
}
fn render_usage_rules(out: &mut String, caps: &[Capability]) {
out.push_str("### Routing\n\n");
out.push_str("| Reach for | Instead of |\n|---|---|\n");
for cap in &Capability::ALL {
if caps.contains(cap) {
let (prefer, fallback) = cap.routing_row();
let _ = writeln!(out, "| {prefer} | {fallback} |");
}
}
out.push('\n');
out.push_str("### Red flags — stop and re-route\n\n");
if caps.contains(&Capability::CodeSearchNavigation) {
out.push_str("- About to `grep` / `rg`? → `workspace_grep`.\n");
out.push_str("- About to open a file just to find a symbol? → `outline` / `search_symbols`.\n");
} else if caps.contains(&Capability::CodeMappingArchitecture) {
out.push_str("- About to open a file just to learn its shape? → `outline`.\n");
}
if caps.contains(&Capability::GitHistory) {
out.push_str("- About to `git log` / `git blame`? → `recent_changes` / `blame_symbol`.\n");
}
out.push_str("- Already mapped a file with basemind? Don't re-read it.\n\n");
}
fn render_setup_notes(out: &mut String) {
out.push_str("### Setup & maintenance\n\n");
out.push_str(
"- Install the basemind Claude Code plugin from its marketplace \
(`/plugin marketplace add Goldziher/basemind`, then install `basemind`).\n",
);
out.push_str(
"- Keep basemind current: enable plugin auto-update, or update the binary regularly so \
the index format and tools stay in sync.\n",
);
out.push_str(
"- Re-run `basemind init` (or `/bm-init`) after enabling new capabilities to refresh this \
block.\n\n",
);
}
pub fn render_ai_rulez_rule(caps: &[Capability], sections: BlockSections) -> String {
let mut out = String::new();
out.push_str("---\npriority: high\n---\n\n");
out.push_str("# basemind usage\n\n");
out.push_str(&render_block_body(caps, sections));
out
}
#[cfg(test)]
mod tests {
use super::*;
fn all() -> Vec<Capability> {
Capability::ALL.to_vec()
}
#[test]
fn from_slug_roundtrips_every_capability() {
for cap in Capability::ALL {
assert_eq!(Capability::from_slug(cap.slug()), Some(cap));
}
assert_eq!(Capability::from_slug("nope"), None);
}
#[test]
fn routing_table_includes_only_selected_capabilities() {
let sections = BlockSections {
usage_rules: true,
setup_notes: false,
};
let only_git = vec![Capability::GitHistory];
let body = render_block_body(&only_git, sections);
assert!(body.contains("`recent_changes`"), "git row present");
assert!(
!body.contains("`workspace_grep`"),
"code-search row absent when unselected"
);
}
#[test]
fn setup_notes_toggle_controls_maintenance_section() {
let with = render_block_body(
&all(),
BlockSections {
usage_rules: false,
setup_notes: true,
},
);
assert!(with.contains("Setup & maintenance"));
let without = render_block_body(
&all(),
BlockSections {
usage_rules: false,
setup_notes: false,
},
);
assert!(!without.contains("Setup & maintenance"));
}
}