use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ToolManifest {
pub name: String,
pub description: String,
pub input_schema: Value,
pub scope: ToolScope,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolScope {
ReadOnly,
FileSystem,
Shell,
Network,
Memory,
Cloud,
}
pub struct ToolRegistry {
tools: HashMap<String, ToolManifest>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register(&mut self, manifest: ToolManifest) {
self.tools.insert(manifest.name.clone(), manifest);
}
pub fn get(&self, name: &str) -> Option<&ToolManifest> {
self.tools.get(name)
}
pub fn all(&self) -> Vec<&ToolManifest> {
self.tools.values().collect()
}
pub fn default_registry() -> Self {
let mut r = Self::new();
let atlas = atlas::SkillRegistry::default_registry();
let fallback_schema = serde_json::json!({
"type": "object",
"properties": { "input": { "type": "string" } }
});
for (name, desc, scope) in BUILT_IN_TOOLS {
let input_schema = atlas
.get(name)
.map(|s| s.input_schema())
.unwrap_or_else(|| fallback_schema.clone());
r.register(ToolManifest {
name: name.to_string(),
description: desc.to_string(),
input_schema,
scope: *scope,
});
}
r
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::default_registry()
}
}
const BUILT_IN_TOOLS: &[(&str, &str, ToolScope)] = &[
(
"sieve",
"Filter raw output to task-relevant lines",
ToolScope::ReadOnly,
),
(
"cartograph",
"Scan directory → structured project map",
ToolScope::FileSystem,
),
(
"chisel",
"Extract AST symbols from files",
ToolScope::FileSystem,
),
("sediment", "Persist facts into Vault", ToolScope::Memory),
(
"prism",
"Full AST parse + index of project directory",
ToolScope::FileSystem,
),
(
"compass",
"Fuse BM25 + graph + Vault into ranked results",
ToolScope::FileSystem,
),
(
"condenser",
"Compress content via Lens stack",
ToolScope::ReadOnly,
),
(
"archivist",
"Query Vault for facts (read-only)",
ToolScope::Memory,
),
(
"scout",
"Execute shell command with output compression",
ToolScope::Shell,
),
(
"chronicler",
"Generate session narrative (read-only)",
ToolScope::ReadOnly,
),
(
"alchemist",
"Transform noisy content → structured JSON",
ToolScope::ReadOnly,
),
(
"sentinel",
"Static security risk assessment",
ToolScope::ReadOnly,
),
(
"cartridge",
"Package current context into portable bundle",
ToolScope::ReadOnly,
),
(
"resonator",
"Dense-vector semantic search over ProjectIndex",
ToolScope::FileSystem,
),
(
"surveyor",
"Compute dependency topology and impact analysis",
ToolScope::FileSystem,
),
(
"parallax",
"Read multiple files in one call with per-file lens",
ToolScope::FileSystem,
),
(
"blueprint",
"Emit compact structural outline of a file",
ToolScope::FileSystem,
),
(
"pinpoint",
"Extract a single named symbol from a file by name",
ToolScope::FileSystem,
),
(
"crossroads",
"Extract API route definitions from files",
ToolScope::FileSystem,
),
(
"drift",
"Return lines changed since a git ref, token-budgeted",
ToolScope::Shell,
),
(
"panorama",
"High-level project overview: languages, entry points, key dirs",
ToolScope::FileSystem,
),
(
"ripple",
"Impact analysis: files that depend on a given file",
ToolScope::FileSystem,
),
(
"meridian",
"Snapshot current agent context: executions, vault facts",
ToolScope::ReadOnly,
),
(
"forecast",
"Predict which files the agent will need next",
ToolScope::FileSystem,
),
(
"unfold",
"Restore a condensed block to its original content",
ToolScope::FileSystem,
),
(
"thermal",
"Token heatmap: identify which file sections consume the most tokens",
ToolScope::ReadOnly,
),
(
"echo",
"Record compression feedback (good/bad) for adaptive tuning",
ToolScope::Memory,
),
(
"ledger",
"Real-time cost estimate: tokens × model pricing → USD",
ToolScope::ReadOnly,
),
(
"steward",
"Session role management: coder/reviewer/debugger/ops/admin + budget",
ToolScope::Memory,
),
(
"dispatch",
"Meta-tool: invoke any bctx skill by name via one stable schema",
ToolScope::ReadOnly,
),
(
"arbiter",
"Code review: structured findings from diff or file content",
ToolScope::ReadOnly,
),
(
"diviner",
"Infer agent intent from file access patterns and commands",
ToolScope::ReadOnly,
),
(
"crucible",
"Run compression benchmarks across all lenses, return savings table",
ToolScope::ReadOnly,
),
(
"scanner",
"Find files/symbols matching a query (fuzzy + content)",
ToolScope::FileSystem,
),
(
"relay_ctx",
"Package and hand off agent context to another session",
ToolScope::Memory,
),
(
"witness",
"Record and replay tool-call sequences for testing",
ToolScope::Memory,
),
(
"scribe",
"Context-aware file editor — create, replace, append, or delete content via MCP",
ToolScope::FileSystem,
),
(
"flux",
"Surface what changed in a file since last commit (HEAD baseline), structured diff",
ToolScope::Shell,
),
(
"pathfinder",
"Extract HTTP routes from web framework source files (10+ frameworks)",
ToolScope::FileSystem,
),
(
"harvest",
"Batch read up to 50 files in one MCP call, optional per-file compression",
ToolScope::FileSystem,
),
(
"render",
"Generate a Mermaid or DOT diagram from the project's import dependency graph",
ToolScope::FileSystem,
),
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_registry_has_41_tools() {
let reg = ToolRegistry::default_registry();
assert_eq!(
reg.all().len(),
41,
"expected 41 tools, got {}: {:?}",
reg.all().len(),
reg.all().iter().map(|m| &m.name).collect::<Vec<_>>()
);
}
#[test]
fn wave3_skills_are_registered() {
let reg = ToolRegistry::default_registry();
for name in &["scribe", "flux", "pathfinder", "harvest", "render"] {
assert!(
reg.get(name).is_some(),
"Wave 3 skill '{name}' missing from ToolRegistry"
);
}
}
#[test]
fn no_tool_uses_fallback_input_schema() {
let reg = ToolRegistry::default_registry();
let fallback = serde_json::json!({
"type": "object",
"properties": { "input": { "type": "string" } }
});
for manifest in reg.all() {
assert_ne!(
manifest.input_schema, fallback,
"tool '{}' is using the generic fallback schema — add it to Atlas skill_schema()",
manifest.name
);
}
}
}