use serde::{Deserialize, Serialize};
use super::query_classifier::is_camel_case;
fn has_caps_prefix_symbol(s: &str) -> bool {
let chars: Vec<char> = s.chars().collect();
if chars.len() < 3 {
return false;
}
let mut caps_run = 0usize;
for &c in &chars {
if c.is_ascii_uppercase() {
caps_run += 1;
} else {
break;
}
}
if caps_run < 2 || caps_run >= chars.len() {
return false;
}
chars[caps_run..].iter().any(|c| c.is_lowercase())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentRoute {
FilePattern,
Regex,
ExactSymbol,
Structural,
Deep,
Analytical,
Exhaustive,
Semantic,
Architecture,
FeaturePlanning,
}
impl std::str::FromStr for AgentRoute {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"filepattern" | "file_pattern" => Ok(Self::FilePattern),
"regex" => Ok(Self::Regex),
"exactsymbol" | "exact_symbol" => Ok(Self::ExactSymbol),
"structural" => Ok(Self::Structural),
"deep" => Ok(Self::Deep),
"analytical" => Ok(Self::Analytical),
"exhaustive" => Ok(Self::Exhaustive),
"semantic" => Ok(Self::Semantic),
"architecture" => Ok(Self::Architecture),
"featureplanning" | "feature_planning" => Ok(Self::FeaturePlanning),
other => anyhow::bail!("unknown AgentRoute: {other:?}"),
}
}
}
impl std::fmt::Display for AgentRoute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FilePattern => write!(f, "file_pattern"),
Self::Regex => write!(f, "regex"),
Self::ExactSymbol => write!(f, "exact_symbol"),
Self::Structural => write!(f, "structural"),
Self::Deep => write!(f, "deep"),
Self::Analytical => write!(f, "analytical"),
Self::Exhaustive => write!(f, "exhaustive"),
Self::Semantic => write!(f, "semantic"),
Self::Architecture => write!(f, "architecture"),
Self::FeaturePlanning => write!(f, "feature_planning"),
}
}
}
fn is_feature_planning_query(lower: &str) -> bool {
if lower.contains("if i want to add") || lower.contains("if i wanted to add") {
return true;
}
if lower.contains("how do i add") {
return true;
}
if lower.contains("what files would") {
let after = lower.split("what files would").nth(1).unwrap_or("");
let window: String = after.chars().take(80).collect();
let has_verb = window.contains("change")
|| window.contains("need")
|| window.contains("touched")
|| window.contains("update");
if has_verb {
return true;
}
}
false
}
fn has_regex_intent(lower: &str) -> bool {
lower.contains("lines containing")
|| lower.contains("lines with ")
|| lower.contains("lines that match")
|| lower.contains("lines matching")
|| lower.contains("grep for ")
|| lower.contains("search for the string ")
|| lower.contains("occurrences of ")
}
pub fn is_regex(query: &str) -> bool {
let bytes = query.as_bytes();
for i in 0..bytes.len().saturating_sub(1) {
if bytes[i] == b'\\'
&& matches!(
bytes[i + 1],
b'b' | b'B' | b'd' | b'D' | b's' | b'S' | b'w' | b'W'
)
{
return true;
}
}
let trimmed = query.trim();
let quote_wrapped = (trimmed.starts_with('"') && trimmed.ends_with('"'))
|| (trimmed.starts_with('\'') && trimmed.ends_with('\''))
|| (trimmed.starts_with('`') && trimmed.ends_with('`'));
if !quote_wrapped && query.contains('|') {
return true;
}
if let Some(open) = query.find('[')
&& query[open..].contains(']')
{
return true;
}
if let Some(open) = query.find('(') {
let after_open = &query[open + 1..];
if let Some(close) = after_open.find(')') {
let inner = &after_open[..close];
if inner.contains('|')
|| inner.contains('?')
|| inner.contains('*')
|| inner.contains('+')
{
return true;
}
}
}
if query.starts_with('^') || query.ends_with('$') {
return true;
}
false
}
fn is_bare_glob_query(query: &str) -> bool {
let tokens: Vec<&str> = query.split_whitespace().collect();
tokens.len() <= 2
}
pub fn classify_agent_query(query: &str) -> AgentRoute {
if (query.contains('*') || query.contains("**/")) && is_bare_glob_query(query) {
return AgentRoute::FilePattern;
}
if is_bare_glob_query(query) {
let chars: Vec<char> = query.chars().collect();
for i in 0..chars.len() {
if chars[i] == '?' {
let before_non_ws = i > 0 && !chars[i - 1].is_whitespace();
let after_non_ws = i + 1 < chars.len() && !chars[i + 1].is_whitespace();
if before_non_ws && after_non_ws {
return AgentRoute::FilePattern;
}
}
}
}
if is_regex(query) {
return AgentRoute::Regex;
}
{
let trimmed = query.trim();
let (was_wrapped, stripped) = if (trimmed.starts_with('`') && trimmed.ends_with('`'))
|| (trimmed.starts_with('"') && trimmed.ends_with('"'))
|| (trimmed.starts_with('\'') && trimmed.ends_with('\''))
{
(true, &trimmed[1..trimmed.len() - 1])
} else {
(false, trimmed)
};
if !stripped.is_empty()
&& !stripped.contains(char::is_whitespace)
&& (was_wrapped
|| is_camel_case(stripped)
|| has_caps_prefix_symbol(stripped)
|| (stripped.contains('_') && stripped.len() > 2)
|| (stripped.contains('.') && stripped.len() > 2))
{
return AgentRoute::ExactSymbol;
}
}
let lower = query.to_lowercase();
let architecture_keywords = [
"main components",
"main component",
"primary components",
"key components",
"core components",
"main modules",
"primary subsystems",
"key subsystems",
"architecture overview",
"architectural overview",
"system architecture",
"high-level architecture",
"high level architecture",
"god nodes",
"god node",
"entry points",
"primary data flow",
"how do they interact",
"how do these interact",
"overall structure",
"overall organization",
"project structure",
"code organization",
"main subsystems",
];
for kw in &architecture_keywords {
if lower.contains(kw) {
return AgentRoute::Architecture;
}
}
if is_feature_planning_query(&lower) {
return AgentRoute::FeaturePlanning;
}
let structural_keywords = [
"callers",
"callees",
"who calls",
"what calls",
"called by",
"used by",
"uses",
"depends on",
"references",
"call graph",
"type hierarchy",
"imports",
"imported by",
];
for kw in &structural_keywords {
if lower.contains(kw) {
return AgentRoute::Structural;
}
}
let deep_prefixes = [
"how ",
"why ",
"explain ",
"describe ",
"walk me through ",
"what is the flow ",
"trace the ",
];
for prefix in &deep_prefixes {
if lower.starts_with(prefix) {
return AgentRoute::Deep;
}
}
let analytical_keywords = [
"most ",
"least ",
"biggest",
"smallest",
"longest",
"shortest",
"complex",
"complicated",
"important",
"critical",
"dangerous",
"risky",
"review",
"assess",
"evaluate",
"analyze",
"compare",
"difference",
"versus",
" vs ",
];
for kw in &analytical_keywords {
if lower.contains(kw) {
return AgentRoute::Analytical;
}
}
let exhaustive_markers = [
"list all",
"list every",
"find all",
"find every",
"show all",
"show every",
"what are all",
"where are all",
"enumerate all",
"enumerate every",
"enumerate ",
];
for marker in &exhaustive_markers {
if lower.contains(marker) {
return AgentRoute::Exhaustive;
}
}
if has_regex_intent(&lower) {
return AgentRoute::Regex;
}
if let Some(route) = classify_nl_symbol_lookup(query, &lower) {
return route;
}
AgentRoute::Semantic
}
fn is_distinctive_symbol_token(token: &str) -> bool {
if let Some(prefix) = token.strip_suffix('(')
&& prefix.len() >= 2
&& prefix
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '.')
&& prefix.chars().next().is_some_and(|c| !c.is_ascii_digit())
{
return true;
}
let t = token.trim_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '.');
if t.len() < 3 {
return false;
}
is_camel_case(t)
|| has_caps_prefix_symbol(t)
|| (t.contains('_') && t.len() > 2)
|| (t.contains('.') && t.len() > 2)
}
fn classify_nl_symbol_lookup(query: &str, lower: &str) -> Option<AgentRoute> {
if lower.starts_with("what does ")
|| lower.starts_with("what do ")
|| lower.starts_with("what is ")
|| lower.starts_with("what are ")
{
return None;
}
let tokens: Vec<&str> = query.split_whitespace().collect();
if tokens.len() < 2 {
return None;
}
if !tokens.iter().any(|t| is_distinctive_symbol_token(t)) {
return None;
}
let call_usage = lower.contains("where is")
|| lower.contains("where are")
|| lower.contains("call ")
|| lower.contains("calls ")
|| lower.contains("called")
|| lower.contains("invoke")
|| lower.contains("usage of");
if call_usage {
return Some(AgentRoute::Structural);
}
Some(AgentRoute::ExactSymbol)
}
pub fn extract_symbol(query: &str) -> Option<String> {
let tokens: Vec<&str> = query.split_whitespace().collect();
for &token in tokens.iter().rev() {
if token.len() > 2 && token.starts_with('`') && token.ends_with('`') {
return Some(token[1..token.len() - 1].to_string());
}
if token.len() > 2
&& ((token.starts_with('"') && token.ends_with('"'))
|| (token.starts_with('\'') && token.ends_with('\'')))
{
return Some(token[1..token.len() - 1].to_string());
}
}
for &token in tokens.iter().rev() {
if is_camel_case(token) {
return Some(token.to_string());
}
if token.contains('_') && token.len() > 2 {
return Some(token.to_string());
}
if token.contains('.') && !token.contains(' ') && token.len() > 2 {
return Some(token.to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glob_star() {
assert_eq!(
classify_agent_query("**/*.test.ts"),
AgentRoute::FilePattern
);
}
#[test]
fn test_glob_qmark() {
assert_eq!(
classify_agent_query("src/?/index.js"),
AgentRoute::FilePattern
);
}
#[test]
fn test_glob_plain_star() {
assert_eq!(classify_agent_query("*.rs"), AgentRoute::FilePattern);
}
#[test]
fn test_trailing_qmark_not_glob() {
assert_eq!(
classify_agent_query("how does auth work?"),
AgentRoute::Deep
);
}
#[test]
fn test_lib_qmark_glob() {
assert_eq!(classify_agent_query("lib/a?.js"), AgentRoute::FilePattern);
}
#[test]
fn test_regex_word_boundary() {
assert_eq!(classify_agent_query(r"auth\w+Handler"), AgentRoute::Regex);
}
#[test]
fn test_regex_pipe() {
assert_eq!(classify_agent_query("TODO|FIXME|HACK"), AgentRoute::Regex);
}
#[test]
fn test_regex_class() {
assert_eq!(classify_agent_query(r"\bclass\s+Auth"), AgentRoute::Regex);
}
#[test]
fn test_regex_parens() {
assert_eq!(classify_agent_query("(foo|bar)"), AgentRoute::Regex);
}
#[test]
fn test_camel_case() {
assert_eq!(classify_agent_query("AuthService"), AgentRoute::ExactSymbol);
}
#[test]
fn test_snake_case() {
assert_eq!(
classify_agent_query("handle_request"),
AgentRoute::ExactSymbol
);
}
#[test]
fn test_backtick_wrap() {
assert_eq!(
classify_agent_query("`processPayment`"),
AgentRoute::ExactSymbol
);
}
#[test]
fn test_dot_path() {
assert_eq!(
classify_agent_query("auth.middleware"),
AgentRoute::ExactSymbol
);
}
#[test]
fn test_html_parser() {
assert_eq!(classify_agent_query("HTMLParser"), AgentRoute::ExactSymbol);
}
#[test]
fn test_xml_http() {
assert_eq!(
classify_agent_query("XMLHttpRequest"),
AgentRoute::ExactSymbol
);
}
#[test]
fn test_single_lower() {
assert_eq!(classify_agent_query("main"), AgentRoute::Semantic);
}
#[test]
fn test_capitalized_word() {
assert_eq!(classify_agent_query("Main"), AgentRoute::Semantic);
}
#[test]
fn test_who_calls() {
assert_eq!(
classify_agent_query("who calls authenticate"),
AgentRoute::Structural
);
}
#[test]
fn test_depends_on() {
assert_eq!(
classify_agent_query("what depends on DatabasePool"),
AgentRoute::Structural
);
}
#[test]
fn test_callers_of() {
assert_eq!(
classify_agent_query("callers of handleRequest"),
AgentRoute::Structural
);
}
#[test]
fn test_how_query() {
assert_eq!(
classify_agent_query("how does authentication work?"),
AgentRoute::Deep
);
}
#[test]
fn test_explain() {
assert_eq!(
classify_agent_query("explain the payment pipeline"),
AgentRoute::Deep
);
}
#[test]
fn test_why_query() {
assert_eq!(
classify_agent_query("why does the cache invalidate on deploy"),
AgentRoute::Deep
);
}
#[test]
fn test_what_is_foo() {
assert_eq!(classify_agent_query("what is foo?"), AgentRoute::Semantic);
}
#[test]
fn test_most_complex() {
assert_eq!(
classify_agent_query("most complex functions in this repo"),
AgentRoute::Analytical
);
}
#[test]
fn test_compare() {
assert_eq!(
classify_agent_query("compare auth approaches"),
AgentRoute::Analytical
);
}
#[test]
fn test_review() {
assert_eq!(
classify_agent_query("review the error handling"),
AgentRoute::Analytical
);
}
#[test]
fn test_exhaustive_list_all() {
assert_eq!(
classify_agent_query("List all configuration options and environment variables"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_exhaustive_find_all() {
assert_eq!(
classify_agent_query("find all error types defined in this project"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_exhaustive_enumerate_cli() {
assert_eq!(
classify_agent_query("enumerate the CLI flags this project supports"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_exhaustive_what_are_all() {
assert_eq!(
classify_agent_query("what are all the public API endpoints?"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_exhaustive_show_all() {
assert_eq!(
classify_agent_query("show all middleware registered in the app"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_architecture_main_components() {
assert_eq!(
classify_agent_query(
"What are the main components of this project and how do they interact? \
Trace the primary data flow from entry point through the core logic."
),
AgentRoute::Architecture
);
}
#[test]
fn test_architecture_primary_subsystems() {
assert_eq!(
classify_agent_query("identify the primary subsystems"),
AgentRoute::Architecture
);
}
#[test]
fn test_architecture_god_nodes() {
assert_eq!(
classify_agent_query("show the god nodes in this codebase"),
AgentRoute::Architecture
);
}
#[test]
fn former_exhaustive_structural_config_now_exhaustive() {
assert_eq!(
classify_agent_query(
"list all configuration options, environment variables, \
and CLI flags this project supports"
),
AgentRoute::Exhaustive
);
}
#[test]
fn former_exhaustive_structural_list_options_now_exhaustive() {
assert_eq!(
classify_agent_query("list all options"),
AgentRoute::Exhaustive
);
}
#[test]
fn test_exhaustive_plain_still_exhaustive() {
assert_eq!(
classify_agent_query("list all middleware registered in the app"),
AgentRoute::Exhaustive
);
}
#[test]
fn former_dwe_explain_most_complex_now_deep() {
assert_eq!(
classify_agent_query(
"explain the most complex algorithm or data transformation in \
this codebase step by step"
),
AgentRoute::Deep
);
}
#[test]
fn former_dwe_show_me_how_now_semantic() {
assert_eq!(
classify_agent_query("show me how the retry backoff is implemented"),
AgentRoute::Semantic
);
}
#[test]
fn former_dwe_deep_dive_now_semantic() {
assert_eq!(
classify_agent_query("deep dive into the connection pooling logic"),
AgentRoute::Semantic
);
}
#[test]
fn former_dwe_step_by_step_now_semantic() {
assert_eq!(
classify_agent_query("walk me through the auth flow step by step"),
AgentRoute::Deep
);
}
#[test]
fn former_dwe_how_prefix_still_deep() {
assert_eq!(
classify_agent_query("how does the retry mechanism work step by step"),
AgentRoute::Deep
);
}
#[test]
fn former_dwe_bare_most_complex_now_analytical() {
assert_eq!(
classify_agent_query("the most complex algorithm in this repo"),
AgentRoute::Analytical
);
}
#[test]
fn former_dwe_give_examples_now_semantic() {
assert_eq!(
classify_agent_query("give examples of the retry backoff usage"),
AgentRoute::Semantic
);
}
#[test]
fn test_semantic_default() {
assert_eq!(
classify_agent_query("authentication middleware"),
AgentRoute::Semantic
);
}
#[test]
fn test_semantic_multi() {
assert_eq!(
classify_agent_query("database connection pool"),
AgentRoute::Semantic
);
}
#[test]
fn test_empty() {
assert_eq!(classify_agent_query(""), AgentRoute::Semantic);
}
#[test]
fn test_extract_backtick() {
assert_eq!(
extract_symbol("who calls `authenticate`"),
Some("authenticate".into())
);
}
#[test]
fn test_extract_camel() {
assert_eq!(
extract_symbol("callers of AuthService"),
Some("AuthService".into())
);
}
#[test]
fn test_extract_snake() {
assert_eq!(
extract_symbol("what uses handle_request"),
Some("handle_request".into())
);
}
#[test]
fn test_extract_none() {
assert_eq!(extract_symbol("show me the auth flow"), None);
}
#[test]
fn test_extract_dot() {
assert_eq!(
extract_symbol("who calls `auth.service`"),
Some("auth.service".into())
);
}
#[test]
fn q2_already_routes_to_deep_so_no_classifier_fix_needed() {
let q2_exact_wording = "How does this project handle errors? What \
patterns are used for error propagation, \
reporting, and recovery?";
assert_eq!(
classify_agent_query(q2_exact_wording),
AgentRoute::Deep,
"Q2 must route to Deep; if this changes, re-evaluate v0.3.1 \
Item 2 per release-sequence §4.2"
);
}
#[test]
fn fp_if_i_wanted_to_add() {
assert_eq!(
classify_agent_query("if I wanted to add logging, what would change?"),
AgentRoute::FeaturePlanning
);
}
#[test]
fn fp_how_do_i_add() {
assert_eq!(
classify_agent_query("how do I add a new transport"),
AgentRoute::FeaturePlanning
);
}
#[test]
fn fp_what_files_would_change_to() {
assert_eq!(
classify_agent_query("what files would change to support multi-tenant tables"),
AgentRoute::FeaturePlanning
);
}
#[test]
fn fp_what_files_would_need_to() {
assert_eq!(
classify_agent_query("what files would need to be updated to add tracing"),
AgentRoute::FeaturePlanning
);
}
#[test]
fn fp_does_not_match_unrelated_how_question() {
assert_eq!(
classify_agent_query("how does the cache work"),
AgentRoute::Deep
);
}
#[test]
fn fp_does_not_match_plain_architecture_question() {
assert_eq!(
classify_agent_query("what are the main components of this project"),
AgentRoute::Architecture
);
}
#[test]
fn fp_does_not_match_structural_question() {
assert_eq!(
classify_agent_query("who calls authenticate"),
AgentRoute::Structural
);
}
#[test]
fn nl_lookup_the_x_type_is_exact_symbol() {
assert_eq!(
classify_agent_query("the HandlerFunc type"),
AgentRoute::ExactSymbol
);
}
#[test]
fn nl_lookup_the_x_class_is_exact_symbol() {
assert_eq!(
classify_agent_query("the AppContext class"),
AgentRoute::ExactSymbol
);
}
#[test]
fn nl_lookup_find_the_x_struct_is_exact_symbol() {
assert_eq!(
classify_agent_query("find the RouterGroup struct"),
AgentRoute::ExactSymbol
);
}
#[test]
fn nl_lookup_where_is_x_called_is_structural() {
assert_eq!(
classify_agent_query("where is allocateContext called"),
AgentRoute::Structural
);
}
#[test]
fn nl_lookup_places_that_call_x_is_structural() {
assert_eq!(
classify_agent_query("places that call abort("),
AgentRoute::Structural
);
}
#[test]
fn nl_lookup_what_calls_x_is_structural() {
assert_eq!(
classify_agent_query("what calls RouterGroup"),
AgentRoute::Structural
);
}
#[test]
fn nl_lookup_how_does_x_work_stays_deep() {
assert_eq!(
classify_agent_query("how does HandlerFunc work"),
AgentRoute::Deep
);
}
#[test]
fn nl_lookup_explain_x_lifecycle_stays_deep() {
assert_eq!(
classify_agent_query("explain the AppContext lifecycle"),
AgentRoute::Deep
);
}
#[test]
fn nl_lookup_why_does_x_fail_stays_deep() {
assert_eq!(
classify_agent_query("why does AuthService fail on retry"),
AgentRoute::Deep
);
}
#[test]
fn nl_lookup_what_does_x_do_stays_semantic() {
assert_eq!(
classify_agent_query("what does HandlerFunc do"),
AgentRoute::Semantic
);
}
#[test]
fn nl_lookup_authentication_middleware_stays_semantic() {
assert_eq!(
classify_agent_query("authentication middleware"),
AgentRoute::Semantic
);
}
#[test]
fn nl_lookup_error_handling_patterns_stays_semantic() {
assert_eq!(
classify_agent_query("error handling patterns"),
AgentRoute::Semantic
);
}
#[test]
fn regex_intent_lines_containing_is_regex() {
assert_eq!(
classify_agent_query("lines containing a TODO or FIXME marker"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_lines_with_is_regex() {
assert_eq!(
classify_agent_query("lines with a trailing whitespace marker"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_lines_that_match_is_regex() {
assert_eq!(
classify_agent_query("lines that match the deprecated annotation"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_grep_for_is_regex() {
assert_eq!(
classify_agent_query("grep for the panic call"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_search_for_the_string_is_regex() {
assert_eq!(
classify_agent_query("search for the string deadbeef"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_occurrences_of_is_regex() {
assert_eq!(
classify_agent_query("occurrences of the deprecated flag"),
AgentRoute::Regex
);
}
#[test]
fn regex_intent_guard_does_not_steal_deep_synthesis() {
assert_eq!(
classify_agent_query("how does line matching work in the parser"),
AgentRoute::Deep
);
}
#[test]
fn regex_intent_guard_does_not_steal_semantic_nl() {
assert_eq!(
classify_agent_query("error handling patterns"),
AgentRoute::Semantic
);
}
#[test]
fn nl_sentence_with_star_is_not_file_pattern() {
let route = classify_agent_query("functions that return a *Context");
assert_ne!(
route,
AgentRoute::FilePattern,
"NL sentence containing a bare `*` must not route to FilePattern"
);
assert_eq!(
route,
AgentRoute::Semantic,
"documented landing: Semantic (not regex-shaped, genuine NL)"
);
}
#[test]
fn bare_glob_star_test_go_still_file_pattern() {
assert_eq!(classify_agent_query("*_test.go"), AgentRoute::FilePattern);
}
#[test]
fn bare_glob_doublestar_still_file_pattern() {
assert_eq!(classify_agent_query("**/*.go"), AgentRoute::FilePattern);
}
#[test]
fn bare_glob_dir_pattern_still_file_pattern() {
assert_eq!(
classify_agent_query("binding/*.go"),
AgentRoute::FilePattern
);
}
#[test]
fn bare_glob_src_star_py_still_file_pattern() {
assert_eq!(classify_agent_query("src/*.py"), AgentRoute::FilePattern);
}
#[test]
fn from_str_round_trips_display() {
let routes = [
AgentRoute::FilePattern,
AgentRoute::Regex,
AgentRoute::ExactSymbol,
AgentRoute::Structural,
AgentRoute::Deep,
AgentRoute::Analytical,
AgentRoute::Exhaustive,
AgentRoute::Semantic,
AgentRoute::Architecture,
AgentRoute::FeaturePlanning,
];
for route in routes {
let displayed = format!("{route}");
let parsed: AgentRoute = displayed.parse().expect("Display output must round-trip");
assert_eq!(
route, parsed,
"FromStr({displayed:?}) should produce {route:?}"
);
}
}
#[test]
fn from_str_rejects_unknown() {
let result = "totally_unknown_route".parse::<AgentRoute>();
assert!(result.is_err(), "Unknown route must return Err");
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("unknown AgentRoute"),
"Error message should mention unknown: {msg}"
);
}
#[test]
fn from_str_accepts_camel_case_aliases() {
assert_eq!(
"ExactSymbol".parse::<AgentRoute>().unwrap(),
AgentRoute::ExactSymbol
);
assert_eq!(
"FeaturePlanning".parse::<AgentRoute>().unwrap(),
AgentRoute::FeaturePlanning
);
assert_eq!(
"FilePattern".parse::<AgentRoute>().unwrap(),
AgentRoute::FilePattern
);
assert!(
"ExhaustiveStructural".parse::<AgentRoute>().is_err(),
"ExhaustiveStructural is no longer a valid route"
);
assert!(
"DeepWithExamples".parse::<AgentRoute>().is_err(),
"DeepWithExamples is no longer a valid route"
);
}
}