use std::path::Path;
use serde::Serialize;
use crate::core::a2a::cost_attribution::CostStore;
use crate::core::context_overhead::tool_tokens;
use crate::core::rules_overhead::{RulesFileCost, collect_rules_files, duplicate_clients};
const LOW_USE_TOKEN_FLOOR: usize = 150;
const LOW_USE_CALL_SHARE: f64 = 0.01;
const STALE_FACT_DAYS: i64 = 30;
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolStatus {
Active,
LowUse,
Unused,
Unknown,
}
impl ToolStatus {
#[must_use]
pub fn label(self) -> &'static str {
match self {
ToolStatus::Active => "active",
ToolStatus::LowUse => "low-use",
ToolStatus::Unused => "unused",
ToolStatus::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ToolEntry {
pub name: String,
pub schema_tokens: usize,
pub calls: u64,
pub last_used: Option<String>,
pub status: ToolStatus,
pub action: String,
pub value_per_1k_tokens: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct RuleEntry {
pub path: String,
pub file_tokens: usize,
pub lean_ctx_tokens: usize,
pub carries_full: bool,
pub clients: Vec<String>,
pub action: String,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct KnowledgeHealth {
pub total_facts: usize,
pub active_facts: usize,
pub stale_facts: usize,
pub action: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ToolHealthReport {
pub tool_profile: String,
pub advertised_tools: usize,
pub tool_schema_tokens: usize,
pub instruction_tokens: usize,
pub rules_tokens: usize,
pub fixed_total_tokens: usize,
pub has_usage_data: bool,
pub total_recorded_calls: u64,
pub unused_tools: usize,
pub unused_tool_tokens: usize,
pub disable_candidates: Vec<String>,
pub reclaimable_tokens: usize,
pub disable_action: String,
pub footprint_note: Option<String>,
pub tools: Vec<ToolEntry>,
pub rules: Vec<RuleEntry>,
pub duplicate_clients: Vec<(String, usize)>,
pub knowledge: KnowledgeHealth,
}
fn classify(has_usage: bool, calls: u64, schema_tokens: usize, total_calls: u64) -> ToolStatus {
if !has_usage {
return ToolStatus::Unknown;
}
if calls == 0 {
return ToolStatus::Unused;
}
if schema_tokens >= LOW_USE_TOKEN_FLOOR
&& (calls as f64) < (total_calls as f64) * LOW_USE_CALL_SHARE
{
return ToolStatus::LowUse;
}
ToolStatus::Active
}
fn value_per_1k(calls: u64, schema_tokens: usize) -> f64 {
if schema_tokens == 0 {
0.0
} else {
calls as f64 / schema_tokens as f64 * 1000.0
}
}
fn action_for(status: ToolStatus, calls: u64, schema_tokens: usize) -> String {
match status {
ToolStatus::Unused => format!(
"never called — trim via a leaner tool profile to reclaim {schema_tokens} tok/session"
),
ToolStatus::LowUse => {
format!("rarely used ({calls}×) yet costs {schema_tokens} tok/session — review")
}
ToolStatus::Active | ToolStatus::Unknown => String::new(),
}
}
fn resolve_alias(name: &str) -> Option<&'static str> {
match name {
"shell" => Some("ctx_shell"),
_ => None,
}
}
#[must_use]
pub fn build_report(
advertised: &[rmcp::model::Tool],
usage: &CostStore,
rules: &[RulesFileCost],
duplicates: Vec<(String, usize)>,
instruction_tokens: usize,
tool_profile: String,
knowledge: KnowledgeHealth,
) -> ToolHealthReport {
let total_recorded_calls: u64 = usage.tools.values().map(|t| t.total_calls).sum();
let has_usage_data = total_recorded_calls > 0;
let mut tools: Vec<ToolEntry> = advertised
.iter()
.map(|t| {
let name = t.name.as_ref().to_string();
let schema_tokens = tool_tokens(t);
let (calls, last_used) = usage
.tools
.get(&name)
.or_else(|| resolve_alias(&name).and_then(|a| usage.tools.get(a)))
.map_or((0, None), |c| (c.total_calls, c.last_used.clone()));
let status = classify(has_usage_data, calls, schema_tokens, total_recorded_calls);
let action = action_for(status, calls, schema_tokens);
ToolEntry {
name,
schema_tokens,
calls,
last_used,
status,
action,
value_per_1k_tokens: value_per_1k(calls, schema_tokens),
}
})
.collect();
tools.sort_by(|a, b| a.name.cmp(&b.name));
let tool_schema_tokens: usize = tools.iter().map(|t| t.schema_tokens).sum();
let unused_tools = tools
.iter()
.filter(|t| t.status == ToolStatus::Unused)
.count();
let unused_tool_tokens = tools
.iter()
.filter(|t| t.status == ToolStatus::Unused)
.map(|t| t.schema_tokens)
.sum();
let low_value = |t: &&ToolEntry| matches!(t.status, ToolStatus::Unused | ToolStatus::LowUse);
let disable_candidates: Vec<String> = tools
.iter()
.filter(low_value)
.map(|t| t.name.clone())
.collect();
let reclaimable_tokens: usize = tools
.iter()
.filter(low_value)
.map(|t| t.schema_tokens)
.sum();
let disable_action = if disable_candidates.is_empty() {
String::new()
} else {
format!(
"consider disabling {} low-value tool(s) to reclaim {reclaimable_tokens} tok/session: {} — apply via `disabled_tools` in config or a leaner `tool_profile`",
disable_candidates.len(),
disable_candidates.join(", ")
)
};
let dup_clients: std::collections::HashSet<&str> =
duplicates.iter().map(|(c, _)| c.as_str()).collect();
let rules_out: Vec<RuleEntry> = rules
.iter()
.map(|r| {
let is_dup = r.carries_full && r.clients.iter().any(|c| dup_clients.contains(c));
let action = if is_dup {
"duplicate full lean-ctx source — run `lean-ctx rules dedup --apply`".to_string()
} else {
String::new()
};
RuleEntry {
path: r.path.clone(),
file_tokens: r.file_tokens,
lean_ctx_tokens: r.lean_ctx_tokens,
carries_full: r.carries_full,
clients: r.clients.iter().map(|c| (*c).to_string()).collect(),
action,
}
})
.collect();
let rules_tokens: usize = rules_out.iter().map(|r| r.file_tokens).sum();
let fixed_total_tokens = tool_schema_tokens + instruction_tokens + rules_tokens;
ToolHealthReport {
tool_profile,
advertised_tools: tools.len(),
tool_schema_tokens,
instruction_tokens,
rules_tokens,
fixed_total_tokens,
has_usage_data,
total_recorded_calls,
unused_tools,
unused_tool_tokens,
disable_candidates,
reclaimable_tokens,
disable_action,
footprint_note: None,
tools,
rules: rules_out,
duplicate_clients: duplicates,
knowledge,
}
}
fn latest_footprint_note() -> Option<String> {
use crate::core::eval_ab::footprint::{FootprintReport, InjectedElement};
let dir = crate::core::data_dir::lean_ctx_data_dir()
.ok()?
.join("eval");
let mut artifacts: Vec<(std::time::SystemTime, std::path::PathBuf)> = std::fs::read_dir(&dir)
.ok()?
.flatten()
.filter_map(|e| {
let path = e.path();
let name = path.file_name()?.to_str()?.to_string();
let is_json = path
.extension()
.and_then(|x| x.to_str())
.is_some_and(|x| x.eq_ignore_ascii_case("json"));
if name.starts_with("footprint-report-v1_") && is_json {
Some((e.metadata().ok()?.modified().ok()?, path))
} else {
None
}
})
.collect();
artifacts.sort_by_key(|a| a.0);
let (_, path) = artifacts.last()?;
let raw = std::fs::read_to_string(path).ok()?;
let report: FootprintReport = serde_json::from_str(&raw).ok()?;
let schemas = report
.elements
.iter()
.find(|e| e.element == InjectedElement::ToolSchemas)?;
let verdict = if schemas.prune_recommended {
"PRUNE-recommended"
} else {
"earns its cost"
};
Some(format!(
"footprint eval ({}): tool schemas {verdict} (Δpass {:+.0}%, cost {} tok)",
report.suite,
schemas.pass_rate_delta * 100.0,
schemas.token_cost
))
}
fn resolve_tool_profile() -> String {
let cfg = crate::core::config::Config::load();
if crate::server::tool_visibility::explicit_profile(&cfg) {
cfg.tool_profile_effective().as_str().to_string()
} else {
"lean (default)".to_string()
}
}
fn knowledge_health(project: &Path) -> KnowledgeHealth {
let Some(knowledge) =
crate::core::knowledge::ProjectKnowledge::load(&project.to_string_lossy())
else {
return KnowledgeHealth::default();
};
let total = knowledge.facts.len();
let current: Vec<_> = knowledge.facts.iter().filter(|f| f.is_current()).collect();
let now = chrono::Utc::now();
let stale = current
.iter()
.filter(|f| (now - f.created_at).num_days() > STALE_FACT_DAYS && f.retrieval_count == 0)
.count();
let action = if stale > 0 {
format!(
"{stale} stale fact(s) (>{STALE_FACT_DAYS}d, never retrieved) — review with `lean-ctx knowledge`"
)
} else {
String::new()
};
KnowledgeHealth {
total_facts: total,
active_facts: current.len(),
stale_facts: stale,
action,
}
}
#[must_use]
pub fn compute(home: &Path, project: &Path) -> ToolHealthReport {
let advertised = crate::server::tool_visibility::advertised_tool_defs_default();
let usage = CostStore::load();
let rules = collect_rules_files(home, project);
let duplicates = duplicate_clients(&rules);
let instructions = crate::instructions::build_instructions(crate::tools::CrpMode::effective());
let instruction_tokens = crate::core::tokens::count_tokens(&instructions);
let knowledge = knowledge_health(project);
let mut report = build_report(
&advertised,
&usage,
&rules,
duplicates,
instruction_tokens,
resolve_tool_profile(),
knowledge,
);
report.footprint_note = latest_footprint_note();
report
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::a2a::cost_attribution::ToolCost;
fn tool(name: &'static str) -> rmcp::model::Tool {
crate::tool_defs::tool_def(
name,
"a representative description used to give the schema some token weight",
serde_json::json!({
"type": "object",
"properties": { "path": { "type": "string", "description": "file path" } }
}),
)
}
fn usage_with(calls: &[(&str, u64)]) -> CostStore {
let mut store = CostStore::default();
for (name, n) in calls {
store.tools.insert(
(*name).to_string(),
ToolCost {
tool_name: (*name).to_string(),
total_calls: *n,
last_used: Some("2026-06-01T00:00:00+00:00".to_string()),
..Default::default()
},
);
}
store
}
#[test]
fn classify_unknown_without_usage_history() {
assert_eq!(classify(false, 0, 500, 0), ToolStatus::Unknown);
assert_eq!(classify(false, 9, 500, 0), ToolStatus::Unknown);
}
#[test]
fn classify_unused_when_history_exists_but_tool_never_called() {
assert_eq!(classify(true, 0, 500, 1000), ToolStatus::Unused);
}
#[test]
fn classify_low_use_for_expensive_rarely_called_tool() {
assert_eq!(classify(true, 1, 400, 10_000), ToolStatus::LowUse);
assert_eq!(classify(true, 1, 50, 10_000), ToolStatus::Active);
}
#[test]
fn classify_active_for_well_used_tool() {
assert_eq!(classify(true, 500, 400, 1000), ToolStatus::Active);
}
#[test]
fn build_report_flags_unused_and_sorts_tools() {
let advertised = vec![tool("ctx_search"), tool("ctx_read"), tool("ctx_shell")];
let usage = usage_with(&[("ctx_read", 40)]);
let report = build_report(
&advertised,
&usage,
&[],
Vec::new(),
100,
"lean (default)".to_string(),
KnowledgeHealth::default(),
);
assert!(report.has_usage_data);
assert_eq!(report.total_recorded_calls, 40);
let names: Vec<&str> = report.tools.iter().map(|t| t.name.as_str()).collect();
assert_eq!(names, vec!["ctx_read", "ctx_search", "ctx_shell"]);
assert_eq!(report.unused_tools, 2);
assert!(report.unused_tool_tokens > 0);
let read = report.tools.iter().find(|t| t.name == "ctx_read").unwrap();
assert_eq!(read.status, ToolStatus::Active);
assert!(read.last_used.is_some());
assert_eq!(
report.fixed_total_tokens,
report.tool_schema_tokens + 100 + report.rules_tokens
);
}
#[test]
fn value_per_1k_rewards_cheap_well_used_tools() {
assert!(value_per_1k(100, 50) > value_per_1k(100, 500));
assert_eq!(value_per_1k(0, 100), 0.0);
assert_eq!(value_per_1k(10, 0), 0.0, "no schema cost → no division");
}
#[test]
fn build_report_recommends_disabling_low_value_tools() {
let advertised = vec![tool("ctx_read"), tool("ctx_search"), tool("ctx_shell")];
let usage = usage_with(&[("ctx_read", 40)]);
let report = build_report(
&advertised,
&usage,
&[],
Vec::new(),
0,
"lean (default)".to_string(),
KnowledgeHealth::default(),
);
assert!(
report
.disable_candidates
.contains(&"ctx_search".to_string())
);
assert!(report.disable_candidates.contains(&"ctx_shell".to_string()));
assert!(
!report.disable_candidates.contains(&"ctx_read".to_string()),
"an active tool is never a disable candidate"
);
assert!(report.reclaimable_tokens > 0);
assert!(report.disable_action.contains("consider disabling"));
assert!(
report.footprint_note.is_none(),
"the pure builder never reads disk artifacts"
);
let read = report.tools.iter().find(|t| t.name == "ctx_read").unwrap();
assert!(read.value_per_1k_tokens > 0.0);
}
#[test]
fn build_report_unknown_status_without_history() {
let advertised = vec![tool("ctx_read")];
let report = build_report(
&advertised,
&CostStore::default(),
&[],
Vec::new(),
0,
"lean (default)".to_string(),
KnowledgeHealth::default(),
);
assert!(!report.has_usage_data);
assert_eq!(report.unused_tools, 0, "never flag rot without history");
assert_eq!(report.tools[0].status, ToolStatus::Unknown);
}
#[test]
fn build_report_marks_duplicate_rules() {
let rules = vec![
RulesFileCost {
path: "a/.cursor/rules/lean-ctx.mdc".into(),
file_tokens: 200,
lean_ctx_tokens: 200,
carries_full: true,
clients: vec!["cursor"],
},
RulesFileCost {
path: "a/.cursorrules".into(),
file_tokens: 150,
lean_ctx_tokens: 150,
carries_full: true,
clients: vec!["cursor"],
},
];
let dups = duplicate_clients(&rules);
let report = build_report(
&[],
&CostStore::default(),
&rules,
dups,
0,
"lean (default)".to_string(),
KnowledgeHealth::default(),
);
assert_eq!(report.rules.len(), 2);
assert!(
report.rules.iter().all(|r| r.action.contains("dedup")),
"both cursor full sources flagged as duplicates"
);
assert_eq!(report.rules_tokens, 350);
}
#[test]
fn compute_smoke_runs_and_counts_advertised_tools() {
let _iso = crate::core::data_dir::isolated_data_dir();
let tmp = tempfile::tempdir().unwrap();
let report = compute(tmp.path(), tmp.path());
let expected = crate::server::tool_visibility::advertised_tool_defs_default().len();
assert_eq!(report.advertised_tools, expected);
assert!(report.tool_schema_tokens > 0);
assert!(report.fixed_total_tokens >= report.tool_schema_tokens);
}
#[test]
fn shell_alias_inherits_ctx_shell_usage_838() {
let shell_tool = tool("shell");
let mut usage = CostStore::default();
let mut tc = ToolCost::default();
tc.total_calls = 42;
tc.last_used = Some("2026-07-15".to_string());
usage.tools.insert("ctx_shell".to_string(), tc);
let report = build_report(
&[shell_tool],
&usage,
&[],
Vec::new(),
0,
"lean".to_string(),
KnowledgeHealth::default(),
);
assert_eq!(report.tools[0].name, "shell");
assert_eq!(
report.tools[0].calls, 42,
"shell must inherit ctx_shell calls"
);
assert_ne!(
report.tools[0].status,
ToolStatus::Unused,
"shell must not be flagged unused"
);
}
}