use super::*;
#[must_use]
pub fn selector_is_segment_prefix(selector: &str, path: &str) -> bool {
match path.strip_prefix(selector) {
Some(rest) => rest.is_empty() || rest.starts_with('.'),
None => false,
}
}
#[must_use]
pub fn label_selectors() -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for class in Class::ALL {
let segs: Vec<&str> = class.path().split('.').collect();
for i in 1..=segs.len() {
let prefix = segs[..i].join(".");
if !out.contains(&prefix) {
out.push(prefix);
}
}
}
out
}
#[must_use]
pub fn selector_is_valid(selector: &str) -> bool {
let bare = selector.strip_suffix(".*").unwrap_or(selector);
!bare.is_empty()
&& Class::ALL
.iter()
.any(|c| selector_is_segment_prefix(bare, c.path()))
}
#[must_use]
pub fn selector_matches(selector: &str, path: &str) -> bool {
if let Some(prefix) = selector.strip_suffix(".*") {
return selector_is_segment_prefix(prefix, path);
}
if !selector_is_segment_prefix(selector, path) {
return false;
}
if selector.contains('.') {
return true; }
Class::ALL
.iter()
.find(|c| c.path() == path)
.is_none_or(|c| c.llm_visible())
}
#[must_use]
pub fn label_selected(selectors: &[String], path: &str) -> bool {
selectors.is_empty() || selectors.iter().any(|s| selector_matches(s, path))
}
#[derive(Debug, Clone, Copy)]
pub struct LabelFilter<'a> {
include: &'a [String],
exclude: &'a [String],
}
impl<'a> LabelFilter<'a> {
#[must_use]
pub fn new(include: &'a [String], exclude: &'a [String]) -> Self {
Self { include, exclude }
}
#[must_use]
pub fn all() -> LabelFilter<'static> {
LabelFilter {
include: &[],
exclude: &[],
}
}
#[must_use]
pub fn selected(&self, path: &str) -> bool {
label_selected(self.include, path)
&& !self.exclude.iter().any(|s| selector_matches(s, path))
}
#[must_use]
pub fn is_statically_empty(&self) -> bool {
Class::ALL.iter().all(|c| !self.selected(c.path()))
}
}
pub(crate) fn parse_label_selector(s: &str) -> Result<String, String> {
let s = s.trim();
if selector_is_valid(s) {
return Ok(s.to_string());
}
let legacy = match s {
"thinking" => Some("agent.thinking"),
"tool" => Some("agent.tool"),
"tool-response" => Some("agent.tool.result"),
_ => None,
};
let hint = legacy.map_or(String::new(), |t| {
format!(" ('{s}' is the pre-v0.5 flat spelling — today that is `{t}`.)")
});
Err(format!(
"unknown label selector '{s}'.{hint} A selector is a dotted role.class.sub path, any \
prefix of one (a bare role selects its LLM-visible leaves only), or a prefix + `.*` \
(every leaf under it, visibility ignored). Valid: {}",
label_selectors().join(", ")
))
}