use crate::traits::SpecialistKind;
use tracing::warn;
pub fn intersect_tools(
kind: SpecialistKind,
declared: &[String],
role_scope: &[&str],
known_tools: &[&str],
) -> Vec<String> {
let mut out = Vec::new();
for name in declared {
if !known_tools.contains(&name.as_str()) {
warn!(
kind = %kind.as_str(),
tool = %name,
"specialist references unknown tool — dropped"
);
continue;
}
if !role_scope.contains(&name.as_str()) {
warn!(
kind = %kind.as_str(),
tool = %name,
"specialist tool outside role scope — dropped"
);
continue;
}
if !out.contains(name) {
out.push(name.clone());
}
}
out
}
pub fn clamp_max_iterations(kind: SpecialistKind, declared: usize, cap: usize) -> usize {
if declared == 0 {
warn!(kind = %kind.as_str(), "specialist max_iterations=0 → clamped to 1");
return 1;
}
if declared > cap {
warn!(
kind = %kind.as_str(),
declared,
cap,
"specialist max_iterations exceeds cap → clamped"
);
return cap;
}
declared
}
pub fn clamp_timeout(kind: SpecialistKind, declared: u64, cap: u64) -> u64 {
if declared == 0 {
warn!(kind = %kind.as_str(), "specialist timeout_secs=0 → clamped to 1");
return 1;
}
declared.min(cap)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intersect_drops_unknown_tools() {
let out = intersect_tools(
SpecialistKind::Code,
&["read_file".to_string(), "bogus_tool".to_string()],
&["read_file", "write_file"],
&["read_file", "write_file"],
);
assert_eq!(out, vec!["read_file".to_string()]);
}
#[test]
fn intersect_drops_tools_outside_role_scope() {
let out = intersect_tools(
SpecialistKind::Code,
&["read_file".to_string(), "manage_goal_tasks".to_string()],
&["read_file", "write_file"],
&["read_file", "write_file", "manage_goal_tasks"],
);
assert_eq!(out, vec!["read_file".to_string()]);
}
#[test]
fn intersect_deduplicates() {
let out = intersect_tools(
SpecialistKind::Code,
&["read_file".to_string(), "read_file".to_string()],
&["read_file"],
&["read_file"],
);
assert_eq!(out, vec!["read_file".to_string()]);
}
#[test]
fn clamp_max_iterations_low_and_high() {
assert_eq!(clamp_max_iterations(SpecialistKind::Code, 0, 100), 1);
assert_eq!(clamp_max_iterations(SpecialistKind::Code, 50, 100), 50);
assert_eq!(clamp_max_iterations(SpecialistKind::Code, 1000, 100), 100);
}
#[test]
fn clamp_timeout_low_and_high() {
assert_eq!(clamp_timeout(SpecialistKind::Code, 0, 600), 1);
assert_eq!(clamp_timeout(SpecialistKind::Code, 500, 600), 500);
assert_eq!(clamp_timeout(SpecialistKind::Code, 9999, 600), 600);
}
}