use memchr::memmem;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
const TIER_INSTANT_REJECT: &str = "instant_reject";
const TIER_STRUCTURE_ANALYSIS: &str = "structure_analysis";
const TIER_KEYWORD_FILTER: &str = "keyword_filter";
const TIER_NEVER_INTERCEPT: &str = "never_intercept";
const TIER_FULL_CLASSIFICATION: &str = "full_classification";
pub static COMPILATION_KEYWORDS: &[&str] = &[
"cargo", "rustc", "gcc", "g++", "clang", "clang++", "make", "cmake", "ninja", "meson", "cc",
"c++", "bun", "nextest",
];
pub static NEVER_INTERCEPT: &[&str] = &[
"cargo install",
"cargo publish",
"cargo login",
"cargo fmt",
"cargo fix",
"cargo clean",
"cargo new",
"cargo init",
"cargo add",
"cargo remove",
"cargo update",
"cargo generate-lockfile",
"cargo watch",
"cargo --version",
"cargo -V",
"rustc --version",
"rustc -V",
"gcc --version",
"gcc -v",
"clang --version",
"clang -v",
"make --version",
"make -v",
"cmake --version",
"bun install",
"bun add",
"bun remove",
"bun link",
"bun unlink",
"bun pm",
"bun init",
"bun create",
"bun upgrade",
"bun completions",
"bun run",
"bun build",
"bun --help",
"bun -h",
"bun --version",
"bun -v",
"bun dev",
"bun repl",
"cargo nextest list", "cargo nextest archive", "cargo nextest show", ];
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Classification {
pub is_compilation: bool,
pub confidence: f64,
pub kind: Option<CompilationKind>,
pub reason: Cow<'static, str>,
pub command_prefix: Option<String>,
pub extracted_command: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TierDecision {
Pass,
Reject,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ClassificationTier {
pub tier: u8,
pub name: Cow<'static, str>,
pub decision: TierDecision,
pub reason: Cow<'static, str>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ClassificationDetails {
pub original: String,
pub normalized: String,
pub tiers: Vec<ClassificationTier>,
pub classification: Classification,
}
impl Classification {
pub fn not_compilation(reason: impl Into<Cow<'static, str>>) -> Self {
Self {
is_compilation: false,
confidence: 0.0,
kind: None,
reason: reason.into(),
command_prefix: None,
extracted_command: None,
}
}
pub fn compilation(
kind: CompilationKind,
confidence: f64,
reason: impl Into<Cow<'static, str>>,
) -> Self {
Self {
is_compilation: true,
confidence,
kind: Some(kind),
reason: reason.into(),
command_prefix: None,
extracted_command: None,
}
}
pub fn compound_compilation(
kind: CompilationKind,
confidence: f64,
reason: impl Into<Cow<'static, str>>,
prefix: String,
extracted: String,
) -> Self {
Self {
is_compilation: true,
confidence,
kind: Some(kind),
reason: reason.into(),
command_prefix: Some(prefix),
extracted_command: Some(extracted),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CompilationKind {
CargoBuild,
CargoTest,
CargoCheck,
CargoClippy,
CargoDoc,
CargoNextest,
CargoBench,
Rustc,
Gcc,
Gpp,
Clang,
Clangpp,
Make,
CmakeBuild,
Ninja,
Meson,
BunTest,
BunTypecheck,
}
impl CompilationKind {
pub fn is_test_command(&self) -> bool {
matches!(
self,
CompilationKind::CargoTest
| CompilationKind::CargoNextest
| CompilationKind::CargoBench
| CompilationKind::BunTest
)
}
pub fn command_base(&self) -> &'static str {
match self {
CompilationKind::CargoBuild
| CompilationKind::CargoTest
| CompilationKind::CargoCheck
| CompilationKind::CargoClippy
| CompilationKind::CargoDoc
| CompilationKind::CargoBench => "cargo",
CompilationKind::CargoNextest => "cargo", CompilationKind::Rustc => "rustc",
CompilationKind::Gcc => "gcc",
CompilationKind::Gpp => "g++",
CompilationKind::Clang => "clang",
CompilationKind::Clangpp => "clang++",
CompilationKind::Make => "make",
CompilationKind::CmakeBuild => "cmake",
CompilationKind::Ninja => "ninja",
CompilationKind::Meson => "meson",
CompilationKind::BunTest | CompilationKind::BunTypecheck => "bun",
}
}
}
pub fn classify_command(cmd: &str) -> Classification {
classify_command_inner(cmd, 0)
}
#[allow(dead_code)] const MAX_CLASSIFY_DEPTH: u8 = 1;
#[allow(dead_code)] const MAX_SPLIT_INPUT_LEN: usize = 10 * 1024;
fn classify_command_inner(cmd: &str, depth: u8) -> Classification {
let cmd = cmd.trim();
if cmd.is_empty() {
return Classification::not_compilation("empty command");
}
if depth == 0
&& cmd.len() < MAX_SPLIT_INPUT_LEN
&& let Some(result) = try_classify_compound_command(cmd)
{
return result;
}
if let Some(reason) = check_structure(cmd) {
return Classification::not_compilation(reason);
}
if !contains_compilation_keyword(cmd) {
return Classification::not_compilation("no compilation keyword");
}
let normalized_cow = normalize_command(cmd);
let normalized = normalized_cow.as_ref();
for pattern in NEVER_INTERCEPT {
if let Some(rest) = normalized.strip_prefix(pattern) {
if rest.is_empty() || rest.starts_with(' ') {
return Classification::not_compilation("matches never-intercept pattern");
}
}
}
classify_full(normalized)
}
fn try_classify_compound_command(cmd: &str) -> Option<Classification> {
if !cmd.contains("&&") {
return None;
}
if cmd.contains('|') {
return None; }
if has_file_redirect(cmd) {
return None; }
if cmd.contains('(') || cmd.contains('`') || cmd.contains("$(") {
return None; }
if cmd.contains(';') {
return None; }
let segments = split_and_chain(cmd)?;
if segments.is_empty() {
return None;
}
let last_segment = segments.last()?.trim();
if last_segment.is_empty() {
return None;
}
let last_classification = classify_command_inner(last_segment, 1);
if !last_classification.is_compilation {
return None;
}
let prefix = if segments.len() == 1 {
return None;
} else {
let last_pos = cmd.rfind(last_segment)?;
cmd[..last_pos].to_string()
};
Some(Classification::compound_compilation(
last_classification.kind?,
last_classification.confidence,
"compound command with compilation suffix",
prefix,
last_segment.to_string(),
))
}
fn split_and_chain(cmd: &str) -> Option<Vec<&str>> {
if !cmd.contains("&&") {
return None;
}
let mut segments = Vec::new();
let mut current_start = 0;
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
let bytes = cmd.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if escaped {
escaped = false;
i += 1;
continue;
}
if b == b'\\' {
escaped = true;
i += 1;
continue;
}
if b == b'\'' && !in_double {
in_single = !in_single;
} else if b == b'"' && !in_single {
in_double = !in_double;
} else if !in_single
&& !in_double
&& b == b'&'
&& i + 1 < bytes.len()
&& bytes[i + 1] == b'&'
{
let segment = &cmd[current_start..i];
let trimmed = segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
current_start = i + 2;
i += 1; }
i += 1;
}
let final_segment = &cmd[current_start..];
let trimmed = final_segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
if segments.len() > 1 {
Some(segments)
} else {
None
}
}
#[allow(dead_code)] fn split_multi_command(cmd: &str) -> Option<Vec<&str>> {
if !cmd.contains("&&") && !cmd.contains("||") && !cmd.contains(';') {
return None;
}
let mut segments = Vec::new();
let mut current_start = 0;
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
let chars: Vec<char> = cmd.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if escaped {
escaped = false;
i += 1;
continue;
}
if c == '\\' {
escaped = true;
i += 1;
continue;
}
if c == '\'' && !in_double {
in_single = !in_single;
} else if c == '"' && !in_single {
in_double = !in_double;
} else if !in_single && !in_double {
if c == ';' {
let segment = &cmd[current_start..byte_index(&chars, i)];
let trimmed = segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
current_start = byte_index(&chars, i + 1);
} else if c == '&' && i + 1 < chars.len() && chars[i + 1] == '&' {
let segment = &cmd[current_start..byte_index(&chars, i)];
let trimmed = segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
current_start = byte_index(&chars, i + 2);
i += 1; } else if c == '|' && i + 1 < chars.len() && chars[i + 1] == '|' {
let segment = &cmd[current_start..byte_index(&chars, i)];
let trimmed = segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
current_start = byte_index(&chars, i + 2);
i += 1; }
}
i += 1;
}
let final_segment = &cmd[current_start..];
let trimmed = final_segment.trim();
if !trimmed.is_empty() {
segments.push(trimmed);
}
if segments.len() > 1 {
Some(segments)
} else {
None
}
}
#[allow(dead_code)] fn byte_index(chars: &[char], char_idx: usize) -> usize {
chars.iter().take(char_idx).map(|c| c.len_utf8()).sum()
}
#[allow(dead_code)] fn classify_multi_command(segments: &[&str], depth: u8) -> Classification {
let mut best_compilation: Option<Classification> = None;
for &segment in segments {
let result = classify_command_inner(segment, depth + 1);
if result.is_compilation {
let dominated = match &best_compilation {
Some(prev) => result.confidence > prev.confidence,
None => true,
};
if dominated {
best_compilation = Some(result);
}
}
}
if let Some(compilation) = best_compilation {
return compilation;
}
Classification::not_compilation("no sub-command is compilation")
}
pub fn classify_command_detailed(cmd: &str) -> ClassificationDetails {
let original = cmd.to_string();
let cmd = cmd.trim();
let mut tiers = Vec::new();
if cmd.is_empty() {
let classification = Classification::not_compilation("empty command");
tiers.push(ClassificationTier {
tier: 0,
name: Cow::Borrowed(TIER_INSTANT_REJECT),
decision: TierDecision::Reject,
reason: Cow::Borrowed("empty command"),
});
return ClassificationDetails {
original,
normalized: cmd.to_string(),
tiers,
classification,
};
}
tiers.push(ClassificationTier {
tier: 0,
name: Cow::Borrowed(TIER_INSTANT_REJECT),
decision: TierDecision::Pass,
reason: Cow::Borrowed("command present"),
});
if let Some(reason) = check_structure(cmd) {
let classification = Classification::not_compilation(reason);
tiers.push(ClassificationTier {
tier: 1,
name: Cow::Borrowed(TIER_STRUCTURE_ANALYSIS),
decision: TierDecision::Reject,
reason: Cow::Borrowed(reason),
});
return ClassificationDetails {
original,
normalized: cmd.to_string(),
tiers,
classification,
};
}
tiers.push(ClassificationTier {
tier: 1,
name: Cow::Borrowed(TIER_STRUCTURE_ANALYSIS),
decision: TierDecision::Pass,
reason: Cow::Borrowed("no pipes/redirects/backgrounding"),
});
if !contains_compilation_keyword(cmd) {
let classification = Classification::not_compilation("no compilation keyword");
tiers.push(ClassificationTier {
tier: 2,
name: Cow::Borrowed(TIER_KEYWORD_FILTER),
decision: TierDecision::Reject,
reason: Cow::Borrowed("no compilation keyword"),
});
return ClassificationDetails {
original,
normalized: cmd.to_string(),
tiers,
classification,
};
}
tiers.push(ClassificationTier {
tier: 2,
name: Cow::Borrowed(TIER_KEYWORD_FILTER),
decision: TierDecision::Pass,
reason: Cow::Borrowed("keyword present"),
});
let normalized_cow = normalize_command(cmd);
let normalized = normalized_cow.as_ref();
for pattern in NEVER_INTERCEPT {
if let Some(rest) = normalized.strip_prefix(pattern)
&& (rest.is_empty() || rest.starts_with(' '))
{
let reason: Cow<'static, str> =
Cow::Owned(format!("matches never-intercept: {pattern}"));
let classification = Classification::not_compilation(reason.clone());
tiers.push(ClassificationTier {
tier: 3,
name: Cow::Borrowed(TIER_NEVER_INTERCEPT),
decision: TierDecision::Reject,
reason,
});
return ClassificationDetails {
original,
normalized: normalized.to_string(),
tiers,
classification,
};
}
}
tiers.push(ClassificationTier {
tier: 3,
name: Cow::Borrowed(TIER_NEVER_INTERCEPT),
decision: TierDecision::Pass,
reason: Cow::Borrowed("no never-intercept match"),
});
let classification = classify_full(normalized);
let decision = if classification.is_compilation {
TierDecision::Pass
} else {
TierDecision::Reject
};
tiers.push(ClassificationTier {
tier: 4,
name: Cow::Borrowed(TIER_FULL_CLASSIFICATION),
decision,
reason: classification.reason.clone(),
});
ClassificationDetails {
original,
normalized: normalized.to_string(),
tiers,
classification,
}
}
pub fn normalize_command(cmd: &str) -> Cow<'_, str> {
let mut result = cmd.trim();
let wrappers = [
"sudo", "env", "time", "nice", "ionice", "strace", "ltrace", "perf", "taskset", "numactl",
];
loop {
let mut changed = false;
for wrapper in wrappers {
if let Some(rest) = result.strip_prefix(wrapper) {
if rest.is_empty() || rest.starts_with(char::is_whitespace) {
result = rest.trim_start();
changed = true;
while result.starts_with('-') {
let end_idx = result.find(char::is_whitespace).unwrap_or(result.len());
result = result[end_idx..].trim_start();
}
}
}
}
let chars = result.chars();
let mut token_len = 0;
let mut in_quote = None; let mut escaped = false;
let mut has_equals = false;
let mut has_space = false;
for c in chars {
if escaped {
escaped = false;
token_len += c.len_utf8();
continue;
}
if c == '\\' {
escaped = true;
token_len += c.len_utf8();
continue;
}
if let Some(q) = in_quote {
if c == q {
in_quote = None;
} else if c == '=' {
has_equals = true;
}
token_len += c.len_utf8();
} else if c == '"' || c == '\'' {
in_quote = Some(c);
token_len += c.len_utf8();
} else if c.is_whitespace() {
has_space = true;
break;
} else {
if c == '=' {
has_equals = true;
}
token_len += c.len_utf8();
}
}
if has_equals && in_quote.is_none() && has_space {
result = result[token_len..].trim_start();
changed = true;
}
if result.starts_with('/') {
if let Some(space_idx) = result.find(' ') {
let cmd_part = &result[..space_idx];
if let Some(last_slash) = cmd_part.rfind('/') {
result = &result[last_slash + 1..];
changed = true;
}
} else {
if let Some(last_slash) = result.rfind('/') {
result = &result[last_slash + 1..];
changed = true;
}
}
}
if !changed {
break;
}
}
if result == cmd {
Cow::Borrowed(cmd)
} else {
Cow::Owned(result.to_string())
}
}
fn has_file_redirect(cmd: &str) -> bool {
let bytes = cmd.as_bytes();
let len = bytes.len();
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
let mut i = 0;
while i < len {
let b = bytes[i];
if escaped {
escaped = false;
i += 1;
continue;
}
if b == b'\\' {
escaped = true;
i += 1;
continue;
}
match b {
b'\'' if !in_double => {
in_single = !in_single;
}
b'"' if !in_single => {
in_double = !in_double;
}
b'>' if !in_single && !in_double => {
if i + 1 < len && bytes[i + 1] == b'&' {
i += 2; if i < len && bytes[i].is_ascii_digit() {
i += 1;
}
continue;
}
if i + 1 < len && bytes[i + 1] == b'(' {
i += 1;
continue;
}
return true; }
b'<' if !in_single && !in_double => {
if i + 1 < len && bytes[i + 1] == b'(' {
i += 1;
continue;
}
return true; }
_ => {}
}
i += 1;
}
false
}
fn check_structure(cmd: &str) -> Option<&'static str> {
let bytes = cmd.as_bytes();
let len = bytes.len();
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
let mut found_backgrounded = false;
let mut found_piped = false;
let mut found_subshell = false;
let mut found_output_redirect = false;
let mut found_input_redirect = false;
let mut found_semicolon = false;
let mut found_and_chain = false;
let mut found_or_chain = false;
let mut found_subshell_capture = false;
let mut i = 0;
while i < len {
let b = bytes[i];
if escaped {
escaped = false;
i += 1;
continue;
}
if b == b'\\' {
escaped = true;
i += 1;
continue;
}
if b == b'\'' && !in_double {
in_single = !in_single;
i += 1;
continue;
}
if b == b'"' && !in_single {
in_double = !in_double;
i += 1;
continue;
}
if in_single || in_double {
i += 1;
continue;
}
match b {
b'\n' | b'\r' => return Some("contains embedded newline"),
b'&' => {
if i + 1 < len && bytes[i + 1] == b'&' {
found_and_chain = true;
i += 1; } else {
let prev = if i > 0 { bytes[i - 1] } else { 0 };
let next = if i + 1 < len { bytes[i + 1] } else { 0 };
if prev != b'>' && next != b'>' {
found_backgrounded = true;
}
}
}
b'|' => {
if i + 1 < len && bytes[i + 1] == b'|' {
found_or_chain = true;
i += 1; } else {
found_piped = true;
}
}
b'(' => found_subshell = true,
b'>' => {
if i + 1 < len && bytes[i + 1] == b'(' {
found_subshell = true;
} else if i + 1 < len && bytes[i + 1] == b'&' {
i += 1; if i + 1 < len && bytes[i + 1].is_ascii_digit() {
i += 1; }
} else if i + 1 < len && bytes[i + 1] == b'>' {
found_output_redirect = true;
i += 1; } else {
found_output_redirect = true;
}
}
b'<' => {
if i + 1 < len && bytes[i + 1] == b'(' {
found_subshell = true;
} else {
found_input_redirect = true;
}
}
b';' => found_semicolon = true,
b'`' => found_subshell_capture = true,
b'$' if i + 1 < len && bytes[i + 1] == b'(' => {
found_subshell_capture = true;
}
_ => {}
}
i += 1;
}
if found_backgrounded {
return Some("backgrounded command");
}
if found_piped && !found_or_chain {
return Some("piped command");
}
if found_subshell {
return Some("subshell execution");
}
if found_output_redirect {
return Some("output redirected");
}
if found_input_redirect {
return Some("input redirected");
}
if found_semicolon {
return Some("chained command (;)");
}
if found_and_chain {
return Some("chained command (&&)");
}
if found_or_chain {
return Some("chained command (||)");
}
if found_subshell_capture {
return Some("subshell capture");
}
None
}
fn contains_compilation_keyword(cmd: &str) -> bool {
let cmd_bytes = cmd.as_bytes();
for keyword in COMPILATION_KEYWORDS {
if memmem::find(cmd_bytes, keyword.as_bytes()).is_some() {
return true;
}
}
false
}
fn classify_full(cmd: &str) -> Classification {
if cmd.starts_with("cargo ") || cmd == "cargo" {
return classify_cargo(cmd);
}
if cmd.starts_with("rustc ") || cmd == "rustc" {
return Classification::compilation(CompilationKind::Rustc, 0.95, "rustc invocation");
}
if cmd.starts_with("gcc ")
&& (cmd.contains(" -c ") || cmd.contains(" -o ") || cmd.contains(".c"))
{
return Classification::compilation(CompilationKind::Gcc, 0.90, "gcc compilation");
}
if cmd.starts_with("g++ ")
&& (cmd.contains(" -c ")
|| cmd.contains(" -o ")
|| cmd.contains(".cpp")
|| cmd.contains(".cc"))
{
return Classification::compilation(CompilationKind::Gpp, 0.90, "g++ compilation");
}
if cmd.starts_with("clang ")
&& !cmd.starts_with("clang++ ")
&& (cmd.contains(" -c ") || cmd.contains(" -o ") || cmd.contains(".c"))
{
return Classification::compilation(CompilationKind::Clang, 0.90, "clang compilation");
}
if cmd.starts_with("clang++ ")
&& (cmd.contains(" -c ")
|| cmd.contains(" -o ")
|| cmd.contains(".cpp")
|| cmd.contains(".cc"))
{
return Classification::compilation(CompilationKind::Clangpp, 0.90, "clang++ compilation");
}
if cmd.starts_with("cc ")
&& (cmd.contains(" -c ") || cmd.contains(" -o ") || cmd.contains(".c"))
{
return Classification::compilation(CompilationKind::Gcc, 0.85, "cc compilation");
}
if cmd.starts_with("c++ ")
&& (cmd.contains(" -c ")
|| cmd.contains(" -o ")
|| cmd.contains(".cpp")
|| cmd.contains(".cc"))
{
return Classification::compilation(CompilationKind::Gpp, 0.85, "c++ compilation");
}
if cmd.starts_with("make") && (cmd == "make" || cmd.starts_with("make ")) {
if cmd.contains("clean") || cmd.contains("install") || cmd.contains("distclean") {
return Classification::not_compilation("make maintenance command");
}
return Classification::compilation(CompilationKind::Make, 0.85, "make build");
}
if cmd.starts_with("cmake ") || cmd == "cmake" {
let mut tokens = cmd.split_whitespace();
let _ = tokens.next(); if tokens.any(|token| token == "--build" || token.starts_with("--build=")) {
return Classification::compilation(CompilationKind::CmakeBuild, 0.90, "cmake --build");
}
}
if cmd.starts_with("ninja") && (cmd == "ninja" || cmd.starts_with("ninja ")) {
if cmd.contains("-t clean") || cmd.contains("clean") {
return Classification::not_compilation("ninja clean");
}
return Classification::compilation(CompilationKind::Ninja, 0.90, "ninja build");
}
if cmd.starts_with("meson ") || cmd == "meson" {
let mut tokens = cmd.split_whitespace();
let _ = tokens.next(); let mut subcommand = None;
for token in tokens {
if token.starts_with('-') {
continue;
}
subcommand = Some(token);
break;
}
if matches!(subcommand, Some("compile")) {
return Classification::compilation(CompilationKind::Meson, 0.85, "meson compile");
}
}
let mut tokens = cmd.split_whitespace();
if tokens.next() == Some("bun") {
match tokens.next() {
Some("test") => {
if tokens.any(|a| a == "-w" || a == "--watch") {
return Classification::not_compilation(
"bun test --watch is interactive (not intercepted)",
);
}
return Classification::compilation(
CompilationKind::BunTest,
0.95,
"bun test command",
);
}
Some("typecheck") => {
if tokens.any(|a| a == "-w" || a == "--watch") {
return Classification::not_compilation(
"bun typecheck --watch is interactive (not intercepted)",
);
}
return Classification::compilation(
CompilationKind::BunTypecheck,
0.95,
"bun typecheck command",
);
}
Some("x") => {
return Classification::not_compilation("bun x runs arbitrary packages");
}
_ => {}
}
}
Classification::not_compilation("no matching pattern")
}
fn classify_cargo(cmd: &str) -> Classification {
let mut tokens = cmd.split_whitespace();
let _cargo = tokens.next();
let Some(token1) = tokens.next() else {
return Classification::not_compilation("bare cargo command");
};
let subcommand = if token1.starts_with('+') {
let Some(sub) = tokens.next() else {
return Classification::not_compilation("cargo +toolchain without subcommand");
};
sub
} else {
token1
};
match subcommand {
"build" | "b" => {
Classification::compilation(CompilationKind::CargoBuild, 0.95, "cargo build")
}
"test" | "t" => Classification::compilation(CompilationKind::CargoTest, 0.95, "cargo test"),
"check" | "c" => {
Classification::compilation(CompilationKind::CargoCheck, 0.90, "cargo check")
}
"clippy" => Classification::compilation(CompilationKind::CargoClippy, 0.90, "cargo clippy"),
"doc" => Classification::compilation(CompilationKind::CargoDoc, 0.85, "cargo doc"),
"run" | "r" => {
Classification::compilation(
CompilationKind::CargoBuild,
0.85,
"cargo run (includes build)",
)
}
"bench" => Classification::compilation(CompilationKind::CargoBench, 0.90, "cargo bench"),
"nextest" => {
let Some(nextest_sub) = tokens.next() else {
return Classification::not_compilation("bare cargo nextest without subcommand");
};
match nextest_sub {
"run" | "r" => Classification::compilation(
CompilationKind::CargoNextest,
0.95,
"cargo nextest run",
),
_ => Classification::not_compilation("cargo nextest subcommand not interceptable"),
}
}
_ => Classification::not_compilation("cargo subcommand not interceptable"),
}
}
pub fn split_shell_commands(cmd: &str) -> Vec<&str> {
let bytes = cmd.as_bytes();
let len = bytes.len();
let mut segments: Vec<&str> = Vec::new();
let mut start = 0;
let mut i = 0;
let mut in_single = false;
let mut in_double = false;
let mut in_backtick = false;
let mut escaped = false;
while i < len {
let b = bytes[i];
if escaped {
escaped = false;
i += 1;
continue;
}
if b == b'\\' {
escaped = true;
i += 1;
continue;
}
if !in_double && !in_backtick && b == b'\'' {
in_single = !in_single;
i += 1;
continue;
}
if !in_single && !in_backtick && b == b'"' {
in_double = !in_double;
i += 1;
continue;
}
if !in_single && !in_double && b == b'`' {
in_backtick = !in_backtick;
i += 1;
continue;
}
if in_single || in_double || in_backtick {
i += 1;
continue;
}
if b == b'&' && i + 1 < len && bytes[i + 1] == b'&' {
let seg = cmd[start..i].trim();
if !seg.is_empty() {
segments.push(seg);
}
i += 2;
start = i;
continue;
}
if b == b'|' && i + 1 < len && bytes[i + 1] == b'|' {
let seg = cmd[start..i].trim();
if !seg.is_empty() {
segments.push(seg);
}
i += 2;
start = i;
continue;
}
if b == b';' {
let seg = cmd[start..i].trim();
if !seg.is_empty() {
segments.push(seg);
}
i += 1;
start = i;
continue;
}
i += 1;
}
let seg = cmd[start..].trim();
if !seg.is_empty() {
segments.push(seg);
}
segments
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_guard;
#[test]
fn test_cargo_build_with_toolchain() {
let _guard = test_guard!();
let result = classify_command("cargo +nightly build");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
}
#[test]
fn test_cargo_test_with_toolchain() {
let _guard = test_guard!();
let result = classify_command("cargo +1.80.0 test");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_nextest_with_toolchain() {
let _guard = test_guard!();
let result = classify_command("cargo +nightly nextest run");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
}
#[test]
fn test_cargo_build() {
let _guard = test_guard!();
let result = classify_command("cargo build");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
assert!(result.confidence >= 0.90);
}
#[test]
fn test_cargo_build_release() {
let _guard = test_guard!();
let result = classify_command("cargo build --release");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
}
#[test]
fn test_cargo_test() {
let _guard = test_guard!();
let result = classify_command("cargo test");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_fmt_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo fmt");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_cargo_install_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo install ripgrep");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_piped_command_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build 2>&1 | grep error");
assert!(!result.is_compilation);
assert!(result.reason.contains("piped"));
}
#[test]
fn test_backgrounded_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build &");
assert!(!result.is_compilation);
assert!(result.reason.contains("background"));
}
#[test]
fn test_newline_injection_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build\nrm -rf /");
assert!(!result.is_compilation);
assert!(result.reason.contains("newline"));
let result = classify_command("cargo build\r\nrm -rf /");
assert!(!result.is_compilation);
assert!(result.reason.contains("newline"));
}
#[test]
fn test_redirected_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build > log.txt");
assert!(!result.is_compilation);
assert!(result.reason.contains("redirect"));
}
#[test]
fn test_input_redirected_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build < input.txt");
assert!(!result.is_compilation);
assert!(result.reason.contains("input redirected"));
}
#[test]
fn test_process_substitution_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo build --config <(echo ...)");
assert!(!result.is_compilation);
assert!(result.reason.contains("subshell execution"));
}
#[test]
fn test_subshell_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("(cargo build)");
assert!(!result.is_compilation);
assert!(result.reason.contains("subshell execution"));
}
#[test]
fn test_gcc_compile() {
let _guard = test_guard!();
let result = classify_command("gcc -c main.c -o main.o");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::Gcc));
}
#[test]
fn test_make() {
let _guard = test_guard!();
let result = classify_command("make -j8");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::Make));
}
#[test]
fn test_make_clean_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("make clean");
assert!(!result.is_compilation);
assert!(result.reason.contains("make maintenance command"));
}
#[test]
fn test_cmake_build_requires_cmake_command() {
let _guard = test_guard!();
let result = classify_command("echo cmake --build .");
assert!(!result.is_compilation);
}
#[test]
fn test_cmake_build_with_equals_flag() {
let _guard = test_guard!();
let result = classify_command("cmake --build=build");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CmakeBuild));
}
#[test]
fn test_meson_compile_requires_meson_command() {
let _guard = test_guard!();
let result = classify_command("echo meson compile");
assert!(!result.is_compilation);
}
#[test]
fn test_non_compilation() {
let _guard = test_guard!();
let result = classify_command("ls -la");
assert!(!result.is_compilation);
assert!(result.reason.contains("no compilation keyword"));
}
#[test]
fn test_empty_command() {
let _guard = test_guard!();
let result = classify_command("");
assert!(!result.is_compilation);
assert!(result.reason.contains("empty command"));
}
#[test]
fn test_bun_keyword_detected() {
let _guard = test_guard!();
assert!(contains_compilation_keyword("bun test"));
assert!(contains_compilation_keyword("bun typecheck"));
assert!(contains_compilation_keyword("bun install")); }
#[test]
fn test_bun_install_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun install");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_add_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun add lodash");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_remove_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun remove lodash");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_run_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun run build");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_build_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun build ./src/index.ts");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_version_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun --version");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
let result = classify_command("bun -v");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_dev_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun dev");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_repl_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun repl");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_link_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun link");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_init_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun init");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_create_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun create next-app");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_pm_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun pm cache");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_help_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun --help");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
let result = classify_command("bun -h");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bun_test_classification() {
let _guard = test_guard!();
let result = classify_command("bun test");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
assert!((result.confidence - 0.95).abs() < 0.001);
let result = classify_command("bun test src/");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun test --coverage");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun test auth.test.ts");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun test --bail --timeout 5000");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun test --reporter json");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTest));
}
#[test]
fn test_bun_test_watch_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun test --watch");
assert!(!result.is_compilation);
assert!(result.reason.contains("interactive"));
let result = classify_command("bun test --watch --coverage");
assert!(!result.is_compilation);
assert!(result.reason.contains("interactive"));
let result = classify_command("bun test src/ --watch");
assert!(!result.is_compilation);
let result = classify_command("bun test -w");
assert!(!result.is_compilation);
let result = classify_command("bun test -w src/");
assert!(!result.is_compilation);
}
#[test]
fn test_bun_typecheck_watch_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun typecheck --watch");
assert!(!result.is_compilation);
assert!(result.reason.contains("interactive"));
let result = classify_command("bun typecheck -w");
assert!(!result.is_compilation);
}
#[test]
fn test_bun_typecheck_classification() {
let _guard = test_guard!();
let result = classify_command("bun typecheck");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTypecheck));
assert!((result.confidence - 0.95).abs() < 0.001);
let result = classify_command("bun typecheck src/");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::BunTypecheck));
}
#[test]
fn test_bun_edge_cases_not_matched() {
let _guard = test_guard!();
let result = classify_command("bun testing");
assert!(!result.is_compilation);
assert!(result.reason.contains("no matching pattern"));
let result = classify_command("bun typechecker");
assert!(!result.is_compilation);
assert!(result.reason.contains("no matching pattern"));
let result = classify_command("bun type");
assert!(!result.is_compilation);
assert!(result.reason.contains("no matching pattern"));
let result = classify_command("bun x eslint");
assert!(!result.is_compilation);
assert!(result.reason.contains("bun x runs arbitrary packages"));
let result = classify_command("bun x prettier --write .");
assert!(!result.is_compilation);
assert!(result.reason.contains("bun x runs arbitrary packages"));
let result = classify_command("bun x vitest run");
assert!(!result.is_compilation);
assert!(result.reason.contains("bun x runs arbitrary packages"));
}
#[test]
fn test_bun_test_vs_never_intercept() {
let _guard = test_guard!();
let result = classify_command("bun test");
assert!(!result.reason.contains("never-intercept"));
assert!(result.is_compilation);
}
#[test]
fn test_bun_typecheck_vs_never_intercept() {
let _guard = test_guard!();
let result = classify_command("bun typecheck");
assert!(!result.reason.contains("never-intercept"));
assert!(result.is_compilation);
}
#[test]
fn test_bun_compilation_kind_serde() {
let _guard = test_guard!();
let kind = CompilationKind::BunTest;
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, "\"bun_test\"");
let parsed: CompilationKind = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CompilationKind::BunTest);
let kind = CompilationKind::BunTypecheck;
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, "\"bun_typecheck\"");
let parsed: CompilationKind = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CompilationKind::BunTypecheck);
}
#[test]
fn test_bun_kinds_are_distinct() {
let _guard = test_guard!();
assert_ne!(CompilationKind::BunTest, CompilationKind::BunTypecheck);
assert_ne!(CompilationKind::BunTest, CompilationKind::CargoTest);
}
#[test]
fn test_wrapped_command_classification_repro() {
let _guard = test_guard!();
let result = classify_command("time cargo build");
assert!(
result.is_compilation,
"Should classify 'time cargo build' as compilation"
);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
let result = classify_command("sudo cargo check");
assert!(
result.is_compilation,
"Should classify 'sudo cargo check' as compilation"
);
let result = classify_command("env RUST_BACKTRACE=1 cargo test");
assert!(
result.is_compilation,
"Should classify env-wrapped cargo test as compilation"
);
let result = classify_command("env 'CARGO_TARGET_DIR=/data/tmp/rch-target' cargo build");
assert!(
result.is_compilation,
"Should classify shell-quoted env assignment before cargo build as compilation"
);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
}
#[test]
fn test_bun_piped_commands_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun test | grep error");
assert!(!result.is_compilation);
assert!(
result.reason.contains("piped"),
"Should be rejected as piped command"
);
let result = classify_command("bun test 2>&1 | tee output.log");
assert!(!result.is_compilation);
assert!(result.reason.contains("piped"));
let result = classify_command("bun typecheck | head -20");
assert!(!result.is_compilation);
assert!(result.reason.contains("piped"));
}
#[test]
fn test_bunx_tsc_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun x tsc --noEmit");
assert!(!result.is_compilation);
assert!(result.reason.contains("bun x"));
let result = classify_command("bun x eslint .");
assert!(!result.is_compilation);
let result = classify_command("bun x prettier --write .");
assert!(!result.is_compilation);
let result = classify_command("bun x vitest run");
assert!(!result.is_compilation);
}
#[test]
fn test_bun_redirected_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun test > results.txt");
assert!(!result.is_compilation);
assert!(result.reason.contains("redirect"));
let result = classify_command("bun typecheck > errors.log");
assert!(!result.is_compilation);
}
#[test]
fn test_bun_backgrounded_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun test &");
assert!(!result.is_compilation);
assert!(result.reason.contains("background"));
}
#[test]
fn test_bun_chained_commands_classified() {
let _guard = test_guard!();
let result = classify_command("bun test && echo done");
assert!(
!result.is_compilation,
"chained commands should be rejected"
);
assert!(result.reason.contains("chained"));
let result = classify_command("bun typecheck; bun test");
assert!(
!result.is_compilation,
"chained commands should be rejected"
);
assert!(result.reason.contains("chained"));
}
#[test]
fn test_bun_subshell_capture_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("bun test $(echo src/)");
assert!(!result.is_compilation);
assert!(result.reason.contains("subshell"));
}
#[test]
fn test_bun_wrapped_commands() {
let _guard = test_guard!();
let result = classify_command("time bun test");
if result.is_compilation {
assert_eq!(result.kind, Some(CompilationKind::BunTest));
}
let result = classify_command("env DEBUG=1 bun test");
if result.is_compilation {
assert_eq!(result.kind, Some(CompilationKind::BunTest));
}
}
#[test]
fn test_classify_command_detailed_matches_basic() {
let _guard = test_guard!();
let commands = [
"cargo build",
"cargo test --release",
"bun typecheck",
"gcc -c main.c -o main.o",
"ls -la",
];
for cmd in commands {
let basic = classify_command(cmd);
let detailed = classify_command_detailed(cmd);
assert_eq!(basic, detailed.classification);
}
}
#[test]
fn test_classify_command_detailed_rejects_piped() {
let _guard = test_guard!();
let detailed = classify_command_detailed("cargo build | tee log.txt");
assert!(!detailed.classification.is_compilation);
let tier1 = detailed.tiers.iter().find(|t| t.tier == 1).unwrap();
assert_eq!(tier1.decision, TierDecision::Reject);
assert!(tier1.reason.contains("piped"));
}
#[test]
fn test_classify_command_detailed_normalizes_wrappers() {
let _guard = test_guard!();
let detailed = classify_command_detailed("sudo cargo check");
assert_eq!(detailed.normalized, "cargo check");
assert!(detailed.classification.is_compilation);
}
#[test]
fn test_cargo_test_release() {
let _guard = test_guard!();
let result = classify_command("cargo test --release");
assert!(
result.is_compilation,
"cargo test --release should be classified as compilation"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
assert!(result.confidence >= 0.90);
}
#[test]
fn test_cargo_test_specific_test_name() {
let _guard = test_guard!();
let result = classify_command("cargo test my_test_function");
assert!(
result.is_compilation,
"cargo test with specific test name should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_with_nocapture() {
let _guard = test_guard!();
let result = classify_command("cargo test -- --nocapture");
assert!(
result.is_compilation,
"cargo test -- --nocapture should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_workspace() {
let _guard = test_guard!();
let result = classify_command("cargo test --workspace");
assert!(
result.is_compilation,
"cargo test --workspace should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_package() {
let _guard = test_guard!();
let result = classify_command("cargo test -p rch-common");
assert!(result.is_compilation, "cargo test -p should be classified");
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_short_alias() {
let _guard = test_guard!();
let result = classify_command("cargo t");
assert!(
result.is_compilation,
"cargo t (short alias) should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_all_features() {
let _guard = test_guard!();
let result = classify_command("cargo test --all-features");
assert!(
result.is_compilation,
"cargo test --all-features should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_no_default_features() {
let _guard = test_guard!();
let result = classify_command("cargo test --no-default-features");
assert!(
result.is_compilation,
"cargo test --no-default-features should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_with_env_var() {
let _guard = test_guard!();
let result = classify_command("RUST_BACKTRACE=1 cargo test");
assert!(
result.is_compilation,
"cargo test with env var should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_with_multiple_flags() {
let _guard = test_guard!();
let result = classify_command("cargo test --release --workspace -p rch -- --nocapture");
assert!(
result.is_compilation,
"cargo test with multiple flags should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_with_jobs() {
let _guard = test_guard!();
let result = classify_command("cargo test -j 8");
assert!(result.is_compilation, "cargo test -j should be classified");
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_target() {
let _guard = test_guard!();
let result = classify_command("cargo test --target x86_64-unknown-linux-gnu");
assert!(
result.is_compilation,
"cargo test --target should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_lib() {
let _guard = test_guard!();
let result = classify_command("cargo test --lib");
assert!(
result.is_compilation,
"cargo test --lib should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_bins() {
let _guard = test_guard!();
let result = classify_command("cargo test --bins");
assert!(
result.is_compilation,
"cargo test --bins should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_doc() {
let _guard = test_guard!();
let result = classify_command("cargo test --doc");
assert!(
result.is_compilation,
"cargo test --doc should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_filter_pattern() {
let _guard = test_guard!();
let result = classify_command("cargo test test_classification");
assert!(
result.is_compilation,
"cargo test with filter pattern should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_test_exact() {
let _guard = test_guard!();
let result = classify_command("cargo test --exact my_test");
assert!(
result.is_compilation,
"cargo test --exact should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_nextest_keyword_detected() {
let _guard = test_guard!();
assert!(contains_compilation_keyword("cargo nextest run"));
assert!(contains_compilation_keyword("cargo nextest list"));
}
#[test]
fn test_cargo_nextest_run_classification() {
let _guard = test_guard!();
let result = classify_command("cargo nextest run");
assert!(
result.is_compilation,
"cargo nextest run should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
assert!((result.confidence - 0.95).abs() < 0.001);
}
#[test]
fn test_cargo_nextest_run_with_flags() {
let _guard = test_guard!();
let result = classify_command("cargo nextest run --release");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("cargo nextest run --cargo-profile ci");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("cargo nextest run --workspace");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("cargo nextest run -p rch-common");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("cargo nextest run test_classification");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("cargo nextest run --release --no-fail-fast -j 8");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
}
#[test]
fn test_cargo_nextest_run_short_alias() {
let _guard = test_guard!();
let result = classify_command("cargo nextest r");
assert!(
result.is_compilation,
"cargo nextest r should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
}
#[test]
fn test_cargo_nextest_list_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest list");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_cargo_nextest_archive_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest archive");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_cargo_nextest_show_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest show");
assert!(!result.is_compilation);
assert!(result.reason.contains("never-intercept"));
}
#[test]
fn test_bare_cargo_nextest_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest");
assert!(!result.is_compilation);
assert!(result.reason.contains("without subcommand"));
}
#[test]
fn test_cargo_nextest_wrapped_commands() {
let _guard = test_guard!();
let result = classify_command("time cargo nextest run");
assert!(
result.is_compilation,
"time cargo nextest run should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
let result = classify_command("RUST_BACKTRACE=1 cargo nextest run");
assert!(
result.is_compilation,
"env-wrapped cargo nextest run should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoNextest));
}
#[test]
fn test_cargo_nextest_compilation_kind_serde() {
let _guard = test_guard!();
let kind = CompilationKind::CargoNextest;
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, "\"cargo_nextest\"");
let parsed: CompilationKind = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CompilationKind::CargoNextest);
}
#[test]
fn test_cargo_nextest_vs_cargo_test_distinct() {
let _guard = test_guard!();
assert_ne!(CompilationKind::CargoNextest, CompilationKind::CargoTest);
let nextest_result = classify_command("cargo nextest run");
let test_result = classify_command("cargo test");
assert!(nextest_result.is_compilation);
assert!(test_result.is_compilation);
assert_eq!(nextest_result.kind, Some(CompilationKind::CargoNextest));
assert_eq!(test_result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_nextest_piped_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest run | grep FAIL");
assert!(!result.is_compilation);
assert!(result.reason.contains("piped"));
}
#[test]
fn test_cargo_nextest_redirected_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest run > results.txt");
assert!(!result.is_compilation);
assert!(result.reason.contains("redirect"));
}
#[test]
fn test_cargo_nextest_backgrounded_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo nextest run &");
assert!(!result.is_compilation);
assert!(result.reason.contains("background"));
}
#[test]
fn test_cargo_bench_classification() {
let _guard = test_guard!();
let result = classify_command("cargo bench");
assert!(result.is_compilation, "cargo bench should be classified");
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
assert!((result.confidence - 0.90).abs() < 0.001);
}
#[test]
fn test_cargo_bench_with_filter() {
let _guard = test_guard!();
let result = classify_command("cargo bench my_bench");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
}
#[test]
fn test_cargo_bench_with_flags() {
let _guard = test_guard!();
let result = classify_command("cargo bench --release");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
let result = classify_command("cargo bench -p rch-common");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
let result = classify_command("cargo bench --features benchmarks");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
let result = classify_command("cargo bench --bench criterion_bench");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
}
#[test]
fn test_cargo_bench_wrapped() {
let _guard = test_guard!();
let result = classify_command("time cargo bench");
assert!(
result.is_compilation,
"time cargo bench should be classified"
);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
let result = classify_command("CARGO_INCREMENTAL=0 cargo bench");
assert!(result.is_compilation);
assert_eq!(result.kind, Some(CompilationKind::CargoBench));
}
#[test]
fn test_cargo_bench_serde() {
let _guard = test_guard!();
let kind = CompilationKind::CargoBench;
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, "\"cargo_bench\"");
let parsed: CompilationKind = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, CompilationKind::CargoBench);
}
#[test]
fn test_cargo_bench_distinct_from_test() {
let _guard = test_guard!();
assert_ne!(CompilationKind::CargoBench, CompilationKind::CargoTest);
let bench_result = classify_command("cargo bench");
let test_result = classify_command("cargo test");
assert!(bench_result.is_compilation);
assert!(test_result.is_compilation);
assert_eq!(bench_result.kind, Some(CompilationKind::CargoBench));
assert_eq!(test_result.kind, Some(CompilationKind::CargoTest));
}
#[test]
fn test_cargo_bench_piped_not_intercepted() {
let _guard = test_guard!();
let result = classify_command("cargo bench | tee output.txt");
assert!(!result.is_compilation);
assert!(result.reason.contains("piped"));
}
#[test]
fn test_normalize_path_and_wrapper_bug_repro() {
let _guard = test_guard!();
let normalized = normalize_command("/usr/bin/time cargo build");
assert_eq!(normalized, "cargo build");
let result = classify_command("/usr/bin/time cargo build");
assert!(result.is_compilation);
}
#[test]
fn test_command_base_rust() {
let _guard = test_guard!();
assert_eq!(CompilationKind::CargoBuild.command_base(), "cargo");
assert_eq!(CompilationKind::CargoTest.command_base(), "cargo");
assert_eq!(CompilationKind::CargoCheck.command_base(), "cargo");
assert_eq!(CompilationKind::CargoClippy.command_base(), "cargo");
assert_eq!(CompilationKind::CargoDoc.command_base(), "cargo");
assert_eq!(CompilationKind::CargoBench.command_base(), "cargo");
assert_eq!(CompilationKind::CargoNextest.command_base(), "cargo");
assert_eq!(CompilationKind::Rustc.command_base(), "rustc");
}
#[test]
fn test_command_base_c_cpp() {
let _guard = test_guard!();
assert_eq!(CompilationKind::Gcc.command_base(), "gcc");
assert_eq!(CompilationKind::Gpp.command_base(), "g++");
assert_eq!(CompilationKind::Clang.command_base(), "clang");
assert_eq!(CompilationKind::Clangpp.command_base(), "clang++");
}
#[test]
fn test_command_base_build_systems() {
let _guard = test_guard!();
assert_eq!(CompilationKind::Make.command_base(), "make");
assert_eq!(CompilationKind::CmakeBuild.command_base(), "cmake");
assert_eq!(CompilationKind::Ninja.command_base(), "ninja");
assert_eq!(CompilationKind::Meson.command_base(), "meson");
}
#[test]
fn test_command_base_bun() {
let _guard = test_guard!();
assert_eq!(CompilationKind::BunTest.command_base(), "bun");
assert_eq!(CompilationKind::BunTypecheck.command_base(), "bun");
}
mod proptest_classification {
use super::*;
use proptest::prelude::*;
fn arbitrary_string() -> impl Strategy<Value = String> {
prop::string::string_regex(".{0,500}").unwrap()
}
fn command_like_string() -> impl Strategy<Value = String> {
let prefixes = prop::sample::select(vec![
"cargo", "rustc", "gcc", "g++", "clang", "make", "cmake", "ninja", "bun", "ls",
"cd", "echo", "cat", "grep", "find", "rm", "mv", "cp", "mkdir",
]);
(prefixes, "[ a-zA-Z0-9_.-]{0,200}")
.prop_map(|(prefix, suffix)| format!("{}{}", prefix, suffix))
}
fn known_command_with_flags() -> impl Strategy<Value = String> {
let base_commands = prop::sample::select(vec![
"cargo build",
"cargo test",
"cargo check",
"cargo clippy",
"cargo run",
"rustc",
"gcc",
"g++",
"make",
"bun test",
"bun typecheck",
]);
let flags = prop::collection::vec(
prop::sample::select(vec![
"--release",
"--verbose",
"-j8",
"-p",
"--all",
"--workspace",
"--lib",
"--bin",
"-o",
"-c",
]),
0..5,
);
(base_commands, flags).prop_map(|(cmd, flags)| {
if flags.is_empty() {
cmd.to_string()
} else {
format!("{} {}", cmd, flags.join(" "))
}
})
}
fn unicode_and_control_chars() -> impl Strategy<Value = String> {
prop::string::string_regex(
r"[\x00-\x1f\x80-\xff\u{100}-\u{10FFFF}a-zA-Z0-9 |>&;$()]{0,100}",
)
.unwrap()
}
fn shell_special_commands() -> impl Strategy<Value = String> {
let components = prop::sample::select(vec![
"cargo build",
"ls -la",
"echo test",
"cat file.txt",
"grep pattern",
]);
let operators = prop::sample::select(vec![
" | ", " && ", " || ", " ; ", " > ", " < ", " & ", " 2>&1 ", " $(", " `",
]);
prop::collection::vec((components, operators), 1..4).prop_map(|pairs| {
let mut result = String::new();
for (i, (comp, op)) in pairs.iter().enumerate() {
if i > 0 {
result.push_str(op);
}
result.push_str(comp);
}
result
})
}
fn wrapped_commands() -> impl Strategy<Value = String> {
let wrappers = prop::sample::select(vec![
"sudo ",
"env ",
"time ",
"nice ",
"/usr/bin/time ",
"RUST_BACKTRACE=1 ",
"CC=clang ",
"",
]);
let commands = prop::sample::select(vec![
"cargo build",
"cargo test",
"make",
"gcc -c main.c",
"ls -la",
]);
(wrappers, commands).prop_map(|(wrapper, cmd)| format!("{}{}", wrapper, cmd))
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn test_classify_arbitrary_no_panic(s in arbitrary_string()) {
let _ = classify_command(&s);
}
#[test]
fn test_classify_command_like_no_panic(s in command_like_string()) {
let _ = classify_command(&s);
}
#[test]
fn test_classify_known_commands_valid(s in known_command_with_flags()) {
let result = classify_command(&s);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0,
"Confidence {} out of range for command: {}", result.confidence, s);
if result.is_compilation {
prop_assert!(result.kind.is_some(),
"is_compilation=true but kind=None for: {}", s);
}
}
#[test]
fn test_classify_unicode_no_panic(s in unicode_and_control_chars()) {
let _ = classify_command(&s);
}
#[test]
fn test_classify_shell_special(s in shell_special_commands()) {
let result = classify_command(&s);
if s.contains(" | ") || s.contains(" > ") || s.contains(" < ") || s.contains(" & ") {
let _ = result; }
}
#[test]
fn test_classify_wrapped_commands(s in wrapped_commands()) {
let result = classify_command(&s);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0);
}
#[test]
fn test_classify_deterministic(s in arbitrary_string()) {
let result1 = classify_command(&s);
let result2 = classify_command(&s);
prop_assert_eq!(result1, result2,
"Non-deterministic classification for: {}", s);
}
#[test]
fn test_classify_whitespace_variants(s in "[ \t\n\r]{0,20}") {
let _guard = test_guard!();
let result = classify_command(&s);
prop_assert!(!result.is_compilation,
"Whitespace-only command should not be classified as compilation: {:?}", s);
prop_assert!(result.reason.contains("empty") || result.reason.contains("keyword"),
"Unexpected reason for whitespace: {}", result.reason);
}
#[test]
fn test_classify_long_commands(
prefix in "cargo (build|test|check)",
suffix in "[a-zA-Z0-9_ -]{0,10000}"
) {
let long_cmd = format!("{} {}", prefix, suffix);
let result = classify_command(&long_cmd);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0);
}
#[test]
fn test_classify_special_bytes(s in prop::collection::vec(any::<u8>(), 0..200)) {
if let Ok(valid_str) = String::from_utf8(s.clone()) {
let _ = classify_command(&valid_str);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn test_cargo_subcommand_robustness(
subcommand in "(build|test|check|clippy|fmt|clean|run|doc|bench|nextest)",
suffix in "[a-zA-Z0-9_ -]{0,50}"
) {
let cmd = format!("cargo {} {}", subcommand, suffix);
let result = classify_command(&cmd);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0);
if subcommand == "fmt" || subcommand == "clean" {
prop_assert!(!result.is_compilation,
"cargo {} should not be compilation: {}", subcommand, cmd);
}
}
#[test]
fn test_bun_command_robustness(
subcommand in "(test|typecheck|install|add|remove|run|build|dev)",
suffix in "[a-zA-Z0-9_ -]{0,30}"
) {
let cmd = format!("bun {} {}", subcommand, suffix);
let result = classify_command(&cmd);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0);
if matches!(subcommand.as_str(), "install" | "add" | "remove" | "run" | "build" | "dev") {
prop_assert!(!result.is_compilation,
"bun {} should not be compilation", subcommand);
}
}
#[test]
fn test_c_compiler_robustness(
compiler in "(gcc|g\\+\\+|clang|clang\\+\\+)",
flags in "(-[cCoOgW][a-z0-9]* )*",
file in "[a-zA-Z_][a-zA-Z0-9_]*\\.(c|cpp|cc|h|hpp)"
) {
let cmd = format!("{} {}{}", compiler, flags, file);
let result = classify_command(&cmd);
prop_assert!(result.confidence >= 0.0 && result.confidence <= 1.0);
}
#[test]
fn test_quoted_args_handling(
base in "(cargo build|cargo test|gcc|make)",
quoted_content in "[a-zA-Z0-9 _-]{0,30}"
) {
let cmd_single = format!("{} '{}'", base, quoted_content);
let _ = classify_command(&cmd_single);
let cmd_double = format!("{} \"{}\"", base, quoted_content);
let _ = classify_command(&cmd_double);
}
}
#[test]
fn test_known_edge_cases() {
let _guard = test_guard!();
let edge_cases = [
"",
" ",
"\t",
"\n",
"cargo",
"cargo ",
"cargo ",
"cargo\tbuild",
"cargo\nbuild",
"cargo\rbuild",
"cargo+nightly",
"cargo +nightly",
"cargo +nightly build",
" cargo build ",
"CARGO_TARGET_DIR=/tmp cargo build",
"/usr/local/bin/cargo build",
"~/.cargo/bin/cargo build",
"./cargo build",
"../cargo build",
"cargo build 'test file.rs'",
"cargo build \"test file.rs\"",
"cargo build test\\ file.rs",
"cargo build -- --test-threads=1",
"cargo build --features \"feat1 feat2\"",
"cargo build 2>&1",
"cargo build 2>/dev/null",
"gcc -DFOO=\"bar baz\" main.c",
"make -j$(nproc)",
"ninja -C build",
"cmake --build . --target all",
"bun test --timeout 5000 src/",
"env -i PATH=/usr/bin cargo build",
];
for cmd in edge_cases {
let result = classify_command(cmd);
assert!(
result.confidence >= 0.0 && result.confidence <= 1.0,
"Invalid confidence for: {:?}",
cmd
);
}
}
#[test]
fn test_detailed_matches_simple_proptest() {
let _guard = test_guard!();
let test_cases = [
"cargo build --release",
"cargo test",
"make -j8",
"ls -la",
"echo hello",
];
for cmd in test_cases {
let simple = classify_command(cmd);
let detailed = classify_command_detailed(cmd);
assert_eq!(simple, detailed.classification, "Mismatch for: {}", cmd);
}
}
#[test]
fn test_split_single_command() {
let _guard = test_guard!();
assert_eq!(split_shell_commands("cargo build"), vec!["cargo build"]);
}
#[test]
fn test_split_and_operator() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("cargo fmt && cargo build"),
vec!["cargo fmt", "cargo build"]
);
}
#[test]
fn test_split_quoted_operator() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("echo '&&' && cargo build"),
vec!["echo '&&'", "cargo build"]
);
}
#[test]
fn test_split_mixed_operators() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("cd /tmp && make -j4 || echo fail"),
vec!["cd /tmp", "make -j4", "echo fail"]
);
}
#[test]
fn test_split_semicolons() {
let _guard = test_guard!();
assert_eq!(split_shell_commands("a ; b ; c"), vec!["a", "b", "c"]);
}
#[test]
fn test_split_quoted_semicolon() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("echo 'hello;world' && make"),
vec!["echo 'hello;world'", "make"]
);
}
#[test]
fn test_split_pipe_preserved() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("cargo build 2>&1 | tee log"),
vec!["cargo build 2>&1 | tee log"]
);
}
#[test]
fn test_split_double_quoted_operator() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands(r#"echo "&&" && cargo build"#),
vec![r#"echo "&&""#, "cargo build"]
);
}
#[test]
fn test_split_nested_quotes() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("echo \"he said 'hello && bye'\" && cargo test"),
vec!["echo \"he said 'hello && bye'\"", "cargo test"]
);
}
#[test]
fn test_split_escaped_quote() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands(r"echo it\'s && cargo build"),
vec![r"echo it\'s", "cargo build"]
);
}
#[test]
fn test_split_empty_string() {
let _guard = test_guard!();
assert!(split_shell_commands("").is_empty());
}
#[test]
fn test_split_only_whitespace() {
let _guard = test_guard!();
assert!(split_shell_commands(" ").is_empty());
}
#[test]
fn test_split_backtick_quoting() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("echo `echo && fail` && cargo build"),
vec!["echo `echo && fail`", "cargo build"]
);
}
#[test]
fn test_split_trailing_operator() {
let _guard = test_guard!();
assert_eq!(split_shell_commands("cargo build &&"), vec!["cargo build"]);
}
#[test]
fn test_split_unclosed_single_quote() {
let _guard = test_guard!();
let result = split_shell_commands("echo 'hello && cargo build");
assert_eq!(result, vec!["echo 'hello && cargo build"]);
}
#[test]
fn test_split_unclosed_double_quote() {
let _guard = test_guard!();
let result = split_shell_commands("echo \"hello && cargo build");
assert_eq!(result, vec!["echo \"hello && cargo build"]);
}
#[test]
fn test_split_unclosed_backtick() {
let _guard = test_guard!();
let result = split_shell_commands("echo `hello && cargo build");
assert_eq!(result, vec!["echo `hello && cargo build"]);
}
#[test]
fn test_split_embedded_nulls() {
let _guard = test_guard!();
let input = "cargo build\0 && echo done";
let result = split_shell_commands(input);
assert_eq!(result.len(), 2);
}
#[test]
fn test_split_unicode_input() {
let _guard = test_guard!();
let result = split_shell_commands("echo 'こんにちは' && cargo build");
assert_eq!(result, vec!["echo 'こんにちは'", "cargo build"]);
}
#[test]
fn test_split_extremely_long_input() {
let _guard = test_guard!();
let long_cmd = format!("echo {} && cargo build", "x".repeat(20_000));
let result = split_shell_commands(&long_cmd);
assert_eq!(result.len(), 2);
}
#[test]
fn test_classify_long_input_skips_splitting() {
let _guard = test_guard!();
let long_cmd = format!("cargo build && echo {}", "x".repeat(11_000));
let result = classify_command(&long_cmd);
assert!(!result.is_compilation);
assert!(
result.reason.to_string().contains("chained"),
"long input should be rejected by check_structure, not split"
);
}
#[test]
fn test_split_only_operators() {
let _guard = test_guard!();
let result = split_shell_commands("&& || ;");
assert!(
result.is_empty(),
"only operators should yield empty result"
);
}
#[test]
fn test_split_consecutive_operators() {
let _guard = test_guard!();
let result = split_shell_commands("cargo build && && echo done");
assert_eq!(result, vec!["cargo build", "echo done"]);
}
#[test]
fn test_classify_empty_after_split() {
let _guard = test_guard!();
let result = classify_command("echo hello && ls -la || pwd");
assert!(!result.is_compilation);
}
#[test]
fn test_split_three_segment_chain() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("cd /proj && cmake .. && make"),
vec!["cd /proj", "cmake ..", "make"]
);
}
#[test]
fn test_split_single_with_flags() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("cargo build --release"),
vec!["cargo build --release"]
);
}
#[test]
fn test_split_nested_quotes_semicolon() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("echo \"it's && done\" && make"),
vec!["echo \"it's && done\"", "make"]
);
}
#[test]
fn test_split_pipe_then_and() {
let _guard = test_guard!();
assert_eq!(
split_shell_commands("make 2>&1 | grep error && echo done"),
vec!["make 2>&1 | grep error", "echo done"]
);
}
#[test]
fn test_split_leading_operator() {
let _guard = test_guard!();
assert_eq!(split_shell_commands("&& cargo build"), vec!["cargo build"]);
}
#[test]
fn test_classify_cargo_fmt_and_build() {
let _guard = test_guard!();
let result = classify_command("cargo fmt && cargo build");
assert!(
result.is_compilation,
"compound command with compilation suffix should be accepted"
);
assert!(result.reason.contains("compound"));
}
#[test]
fn test_classify_cd_and_make() {
let _guard = test_guard!();
let result = classify_command("cd /project && make -j8");
assert!(
result.is_compilation,
"compound command with compilation suffix should be accepted"
);
assert!(result.reason.contains("compound"));
}
#[test]
fn test_classify_export_and_cargo_build() {
let _guard = test_guard!();
let result =
classify_command("export RUSTFLAGS='-C opt-level=3' && cargo build --release");
assert!(
result.is_compilation,
"compound command with compilation suffix should be accepted"
);
assert!(result.reason.contains("compound"));
}
#[test]
fn test_classify_mkdir_cmake_chain() {
let _guard = test_guard!();
let result =
classify_command("mkdir -p build && cmake -B build && cmake --build build");
assert!(
result.is_compilation,
"compound command with compilation suffix should be accepted"
);
assert!(result.reason.contains("compound"));
}
#[test]
fn test_classify_echo_and_cargo_test() {
let _guard = test_guard!();
let result = classify_command("echo 'Starting...' && cargo test");
assert!(
result.is_compilation,
"compound command with compilation suffix should be accepted"
);
assert!(result.reason.contains("compound"));
}
#[test]
fn test_classify_semicolon_chain_with_compilation() {
let _guard = test_guard!();
let result = classify_command("cargo fmt; cargo build; cargo test");
assert!(
!result.is_compilation,
"semicolon chained commands should be rejected"
);
assert!(result.reason.contains("chained"));
}
#[test]
fn test_classify_echo_chain_non_compilation() {
let _guard = test_guard!();
let result = classify_command("echo hello && echo world");
assert!(!result.is_compilation);
}
#[test]
fn test_classify_ls_cat_non_compilation() {
let _guard = test_guard!();
let result = classify_command("ls -la && cat file.txt");
assert!(!result.is_compilation);
}
#[test]
fn test_classify_git_chain_non_compilation() {
let _guard = test_guard!();
let result = classify_command("git status && git log");
assert!(!result.is_compilation);
}
#[test]
fn test_classify_multi_command_performance() {
let _guard = test_guard!();
let start = std::time::Instant::now();
for _ in 0..100 {
let _ = classify_command("cargo fmt && cargo build && cargo test");
}
let elapsed = start.elapsed();
let per_call_us = elapsed.as_micros() / 100;
assert!(
per_call_us < 5_000,
"classify_command took {}us per call, should be <5000us",
per_call_us
);
eprintln!(
"[perf] classify_command multi-command: {}us avg per call",
per_call_us
);
}
#[test]
fn test_classify_pipe_preserved_in_segment() {
let _guard = test_guard!();
let result = classify_command("cargo build 2>&1 | tee log");
assert!(!result.is_compilation, "piped command should be rejected");
}
#[test]
fn test_classify_pipe_segment_and_operator() {
let _guard = test_guard!();
let result = classify_command("make 2>&1 | grep error && cargo build");
assert!(
!result.is_compilation,
"chained commands should be rejected"
);
assert!(result.reason.contains("piped") || result.reason.contains("chained"));
}
}
#[allow(clippy::ptr_arg)]
fn assert_cow_borrowed(cow: &Cow<'static, str>, context: &str) {
assert!(
matches!(cow, Cow::Borrowed(_)),
"{context}: expected Cow::Borrowed, got Cow::Owned({:?})",
cow,
);
}
#[test]
fn test_cow_reject_empty_command_correctness() {
let _guard = test_guard!();
let result = classify_command("");
assert!(!result.is_compilation);
assert_eq!(result.confidence, 0.0);
assert_eq!(result.kind, None);
assert!(result.reason.contains("empty"), "reason: {}", result.reason);
}
#[test]
fn test_cow_reject_non_compilation_commands() {
let _guard = test_guard!();
let non_compilation = [
"ls -la",
"cat file.txt",
"echo hello",
"git status",
"pwd",
"cd /tmp",
];
for cmd in non_compilation {
let result = classify_command(cmd);
assert!(
!result.is_compilation,
"'{cmd}' should NOT be classified as compilation"
);
assert_eq!(result.confidence, 0.0, "'{cmd}' should have 0.0 confidence");
assert_eq!(result.kind, None, "'{cmd}' should have no CompilationKind");
assert!(!result.reason.is_empty(), "'{cmd}' should have a reason");
}
}
#[test]
fn test_cow_accept_compilation_commands() {
let _guard = test_guard!();
let cases: &[(&str, CompilationKind)] = &[
("cargo build", CompilationKind::CargoBuild),
("cargo test", CompilationKind::CargoTest),
("cargo check", CompilationKind::CargoCheck),
("cargo clippy", CompilationKind::CargoClippy),
("gcc -o hello hello.c", CompilationKind::Gcc),
("make", CompilationKind::Make),
("bun test", CompilationKind::BunTest),
];
for &(cmd, expected_kind) in cases {
let result = classify_command(cmd);
assert!(
result.is_compilation,
"'{cmd}' should be classified as compilation"
);
assert_eq!(
result.kind,
Some(expected_kind),
"'{cmd}' should have kind {expected_kind:?}"
);
assert!(
result.confidence > 0.0,
"'{cmd}' should have positive confidence"
);
assert!(!result.reason.is_empty(), "'{cmd}' should have a reason");
}
}
#[test]
fn test_cow_borrowed_on_tier0_reject() {
let _guard = test_guard!();
let result = classify_command("");
assert_cow_borrowed(&result.reason, "Tier 0 empty command reason");
}
#[test]
fn test_cow_borrowed_on_tier1_reject() {
let _guard = test_guard!();
let result = classify_command("cargo build | tee log");
assert!(!result.is_compilation);
assert_cow_borrowed(&result.reason, "Tier 1 piped command reason");
let result = classify_command("cargo build &");
assert!(!result.is_compilation);
assert_cow_borrowed(&result.reason, "Tier 1 backgrounded command reason");
let result = classify_command("cargo build > log.txt");
assert!(!result.is_compilation);
assert_cow_borrowed(&result.reason, "Tier 1 redirected command reason");
}
#[test]
fn test_cow_borrowed_on_tier2_reject() {
let _guard = test_guard!();
let non_keyword_commands = [
"ls -la",
"cat file.txt",
"echo hello world",
"git status",
"pwd",
"cd /tmp",
"grep pattern file",
"find . -name '*.rs'",
"cp src dst",
"rm file.txt",
];
for cmd in non_keyword_commands {
let result = classify_command(cmd);
assert!(!result.is_compilation, "'{cmd}' should be rejected");
assert_cow_borrowed(&result.reason, &format!("Tier 2 reject for '{cmd}'"));
}
}
#[test]
fn test_cow_borrowed_on_tier3_reject() {
let _guard = test_guard!();
let never_intercept = ["cargo fmt", "cargo install ripgrep", "cargo clean"];
for cmd in never_intercept {
let result = classify_command(cmd);
assert!(!result.is_compilation, "'{cmd}' should be rejected");
assert!(
result.reason.contains("never-intercept"),
"'{cmd}' reason should mention never-intercept: {}",
result.reason
);
assert_cow_borrowed(&result.reason, &format!("Tier 3 reject for '{cmd}'"));
}
}
#[test]
fn test_detailed_tiers_use_borrowed_names() {
let _guard = test_guard!();
let details = classify_command_detailed("ls -la");
for tier in &details.tiers {
assert_cow_borrowed(&tier.name, &format!("Tier {} name", tier.tier));
}
}
#[test]
fn test_detailed_tier_reasons_borrowed_on_reject() {
let _guard = test_guard!();
let details = classify_command_detailed("ls -la");
assert!(!details.classification.is_compilation);
for tier in &details.tiers {
assert_cow_borrowed(
&tier.reason,
&format!("Tier {} reason for 'ls -la'", tier.tier),
);
}
}
#[test]
fn test_detailed_compilation_tier_names_borrowed() {
let _guard = test_guard!();
let details = classify_command_detailed("cargo build");
assert!(details.classification.is_compilation);
for tier in &details.tiers {
assert_cow_borrowed(
&tier.name,
&format!("Tier {} name for 'cargo build'", tier.tier),
);
}
}
#[test]
fn test_classification_serde_roundtrip_borrowed() {
let _guard = test_guard!();
let original = Classification::not_compilation("no compilation keyword");
assert_cow_borrowed(&original.reason, "before serialize");
let json = serde_json::to_string(&original).expect("serialize");
let deserialized: Classification = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.is_compilation, original.is_compilation);
assert_eq!(deserialized.confidence, original.confidence);
assert_eq!(deserialized.kind, original.kind);
assert_eq!(deserialized.reason, original.reason);
}
#[test]
fn test_classification_serde_roundtrip_compilation() {
let _guard = test_guard!();
let original =
Classification::compilation(CompilationKind::CargoBuild, 0.95, "cargo build detected");
let json = serde_json::to_string(&original).expect("serialize");
let deserialized: Classification = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.is_compilation, original.is_compilation);
assert_eq!(deserialized.confidence, original.confidence);
assert_eq!(deserialized.kind, original.kind);
assert_eq!(deserialized.reason.as_ref(), original.reason.as_ref());
}
#[test]
fn test_classification_tier_serde_roundtrip() {
let _guard = test_guard!();
let tier = ClassificationTier {
tier: 2,
name: Cow::Borrowed(TIER_KEYWORD_FILTER),
decision: TierDecision::Reject,
reason: Cow::Borrowed("no compilation keyword"),
};
assert_cow_borrowed(&tier.name, "tier name before serialize");
assert_cow_borrowed(&tier.reason, "tier reason before serialize");
let json = serde_json::to_string(&tier).expect("serialize");
let deserialized: ClassificationTier = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.tier, tier.tier);
assert_eq!(deserialized.name.as_ref(), tier.name.as_ref());
assert_eq!(deserialized.decision, tier.decision);
assert_eq!(deserialized.reason.as_ref(), tier.reason.as_ref());
}
#[test]
fn test_classification_details_serde_roundtrip() {
let _guard = test_guard!();
let details = classify_command_detailed("cargo build");
assert!(details.classification.is_compilation);
let json = serde_json::to_string(&details).expect("serialize");
let deserialized: ClassificationDetails = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.original, details.original);
assert_eq!(deserialized.normalized, details.normalized);
assert_eq!(deserialized.tiers.len(), details.tiers.len());
assert_eq!(
deserialized.classification.is_compilation,
details.classification.is_compilation
);
assert_eq!(
deserialized.classification.kind,
details.classification.kind
);
}
#[test]
fn test_cow_reason_display() {
let _guard = test_guard!();
let result = classify_command("ls");
let displayed = format!("{}", result.reason);
assert!(!displayed.is_empty());
let debugged = format!("{:?}", result.reason);
assert!(!debugged.is_empty());
}
#[test]
fn test_cow_reason_comparison() {
let _guard = test_guard!();
let borrowed: Cow<'static, str> = Cow::Borrowed("no compilation keyword");
let owned: Cow<'static, str> = Cow::Owned("no compilation keyword".to_string());
assert_eq!(borrowed, owned);
let r1 = classify_command("ls");
let r2 = classify_command("pwd");
assert_eq!(r1.reason, r2.reason);
}
}
#[cfg(test)]
mod tests_bun_whitespace {
use super::*;
use crate::test_guard;
#[test]
fn test_bun_whitespace_resilience() {
let _guard = test_guard!();
let result = classify_command("bun test");
assert!(result.is_compilation, "bun test failed");
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun test");
assert!(result.is_compilation, "bun test failed");
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun\ttest");
assert!(result.is_compilation, "bun\\ttest failed");
assert_eq!(result.kind, Some(CompilationKind::BunTest));
let result = classify_command("bun typecheck src/");
assert!(result.is_compilation, "bun typecheck with spaces failed");
assert_eq!(result.kind, Some(CompilationKind::BunTypecheck));
}
#[test]
fn test_bun_watch_with_whitespace() {
let _guard = test_guard!();
let result = classify_command("bun test --watch");
assert!(
!result.is_compilation,
"bun test --watch should be rejected"
);
assert!(result.reason.contains("interactive"));
let result = classify_command("bun\ttypecheck\t-w");
assert!(
!result.is_compilation,
"bun typecheck -w should be rejected"
);
assert!(result.reason.contains("interactive"));
}
#[test]
fn test_bun_x_whitespace() {
let _guard = test_guard!();
let result = classify_command("bun x vitest");
assert!(!result.is_compilation, "bun x should be rejected");
assert!(result.reason.contains("bun x"));
}
}
#[cfg(test)]
mod tests_normalize_whitespace {
use super::*;
use crate::test_guard;
#[test]
fn test_wrapper_whitespace_resilience() {
let _guard = test_guard!();
assert_eq!(normalize_command("sudo cargo"), "cargo");
assert_eq!(normalize_command("sudo\tcargo"), "cargo");
assert_eq!(normalize_command("time\tsudo cargo"), "cargo");
assert_eq!(normalize_command("sudocargo"), "sudocargo");
assert_eq!(normalize_command("sudo"), "");
}
#[test]
fn test_env_var_whitespace() {
let _guard = test_guard!();
assert_eq!(normalize_command("env RUST_BACKTRACE=1 cargo"), "cargo");
assert_eq!(normalize_command("env\tRUST_BACKTRACE=1\tcargo"), "cargo");
}
}
#[cfg(test)]
mod regression_classification {
use super::*;
use crate::test_guard;
struct Case {
cmd: &'static str,
expect_compilation: bool,
expected_kind: Option<CompilationKind>,
reason_contains: &'static str,
min_confidence: f64,
}
fn run_cases(cases: &[Case]) {
for (i, c) in cases.iter().enumerate() {
let result = classify_command(c.cmd);
assert_eq!(
result.is_compilation, c.expect_compilation,
"Case {i} ({:?}): expected is_compilation={}, got={}. reason={:?}",
c.cmd, c.expect_compilation, result.is_compilation, result.reason
);
if c.expect_compilation {
assert_eq!(
result.kind, c.expected_kind,
"Case {i} ({:?}): expected kind={:?}, got={:?}",
c.cmd, c.expected_kind, result.kind
);
assert!(
result.confidence >= c.min_confidence,
"Case {i} ({:?}): expected confidence >= {}, got={}",
c.cmd,
c.min_confidence,
result.confidence
);
}
if !c.reason_contains.is_empty() {
assert!(
result.reason.contains(c.reason_contains),
"Case {i} ({:?}): expected reason containing {:?}, got {:?}",
c.cmd,
c.reason_contains,
result.reason
);
}
}
}
#[test]
fn regression_cargo_compilation_positive() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "cargo build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo build --release",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo build --workspace",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo build -p my-crate",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo b",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test --release",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test my_test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test -- --nocapture",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test --workspace",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test -p rch-common",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo t",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test --lib",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test --bins",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test --doc",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo test -- --test-threads=4",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo check",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoCheck),
reason_contains: "cargo check",
min_confidence: 0.85,
},
Case {
cmd: "cargo check --workspace --all-targets",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoCheck),
reason_contains: "cargo check",
min_confidence: 0.85,
},
Case {
cmd: "cargo c",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoCheck),
reason_contains: "cargo check",
min_confidence: 0.85,
},
Case {
cmd: "cargo clippy",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoClippy),
reason_contains: "cargo clippy",
min_confidence: 0.85,
},
Case {
cmd: "cargo clippy --workspace --all-targets -- -D warnings",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoClippy),
reason_contains: "cargo clippy",
min_confidence: 0.85,
},
Case {
cmd: "cargo doc",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoDoc),
reason_contains: "cargo doc",
min_confidence: 0.80,
},
Case {
cmd: "cargo doc --no-deps",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoDoc),
reason_contains: "cargo doc",
min_confidence: 0.80,
},
Case {
cmd: "cargo run",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo run",
min_confidence: 0.80,
},
Case {
cmd: "cargo run --release",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo run",
min_confidence: 0.80,
},
Case {
cmd: "cargo r",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo run",
min_confidence: 0.80,
},
Case {
cmd: "cargo bench",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBench),
reason_contains: "cargo bench",
min_confidence: 0.85,
},
Case {
cmd: "cargo bench --bench my_bench",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBench),
reason_contains: "cargo bench",
min_confidence: 0.85,
},
Case {
cmd: "cargo nextest run",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoNextest),
reason_contains: "cargo nextest run",
min_confidence: 0.90,
},
Case {
cmd: "cargo nextest run --workspace",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoNextest),
reason_contains: "cargo nextest run",
min_confidence: 0.90,
},
Case {
cmd: "cargo nextest r",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoNextest),
reason_contains: "cargo nextest run",
min_confidence: 0.90,
},
Case {
cmd: "cargo +nightly build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "cargo +1.80.0 test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "cargo +nightly nextest run",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoNextest),
reason_contains: "cargo nextest run",
min_confidence: 0.90,
},
];
run_cases(&cases);
}
#[test]
fn regression_cargo_never_intercept() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "cargo install ripgrep",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo publish",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo login",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo fmt",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo fmt --check",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo fix",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo clean",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo new my_project",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo init",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo add serde",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo remove serde",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo update",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo generate-lockfile",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo watch -x test",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo -V",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo nextest list",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo nextest archive",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo nextest show",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cargo",
expect_compilation: false,
expected_kind: None,
reason_contains: "bare cargo",
min_confidence: 0.0,
},
Case {
cmd: "cargo nextest",
expect_compilation: false,
expected_kind: None,
reason_contains: "bare cargo nextest",
min_confidence: 0.0,
},
Case {
cmd: "cargo tree",
expect_compilation: false,
expected_kind: None,
reason_contains: "not interceptable",
min_confidence: 0.0,
},
Case {
cmd: "cargo metadata",
expect_compilation: false,
expected_kind: None,
reason_contains: "not interceptable",
min_confidence: 0.0,
},
Case {
cmd: "cargo search serde",
expect_compilation: false,
expected_kind: None,
reason_contains: "not interceptable",
min_confidence: 0.0,
},
Case {
cmd: "cargo vendor",
expect_compilation: false,
expected_kind: None,
reason_contains: "not interceptable",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_bun_compilation_positive() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "bun test",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTest),
reason_contains: "bun test",
min_confidence: 0.90,
},
Case {
cmd: "bun test src/",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTest),
reason_contains: "bun test",
min_confidence: 0.90,
},
Case {
cmd: "bun test --bail",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTest),
reason_contains: "bun test",
min_confidence: 0.90,
},
Case {
cmd: "bun test --timeout 5000",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTest),
reason_contains: "bun test",
min_confidence: 0.90,
},
Case {
cmd: "bun typecheck",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTypecheck),
reason_contains: "bun typecheck",
min_confidence: 0.90,
},
Case {
cmd: "bun typecheck src/",
expect_compilation: true,
expected_kind: Some(CompilationKind::BunTypecheck),
reason_contains: "bun typecheck",
min_confidence: 0.90,
},
];
run_cases(&cases);
}
#[test]
fn regression_bun_never_intercept() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "bun install",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun add react",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun remove react",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun link",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun unlink",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun pm ls",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun init",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun create my-app",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun upgrade",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun run dev",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun build src/index.ts",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun dev",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun repl",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun x vitest",
expect_compilation: false,
expected_kind: None,
reason_contains: "bun x",
min_confidence: 0.0,
},
Case {
cmd: "bun x tsc --noEmit",
expect_compilation: false,
expected_kind: None,
reason_contains: "bun x",
min_confidence: 0.0,
},
Case {
cmd: "bun test --watch",
expect_compilation: false,
expected_kind: None,
reason_contains: "interactive",
min_confidence: 0.0,
},
Case {
cmd: "bun test -w",
expect_compilation: false,
expected_kind: None,
reason_contains: "interactive",
min_confidence: 0.0,
},
Case {
cmd: "bun typecheck --watch",
expect_compilation: false,
expected_kind: None,
reason_contains: "interactive",
min_confidence: 0.0,
},
Case {
cmd: "bun typecheck -w",
expect_compilation: false,
expected_kind: None,
reason_contains: "interactive",
min_confidence: 0.0,
},
Case {
cmd: "bun --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun -v",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun --help",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun -h",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "bun completions",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_c_cpp_compilation_positive() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "gcc -c main.c -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gcc),
reason_contains: "gcc",
min_confidence: 0.85,
},
Case {
cmd: "gcc main.c -o main",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gcc),
reason_contains: "gcc",
min_confidence: 0.85,
},
Case {
cmd: "g++ -c main.cpp -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gpp),
reason_contains: "g++",
min_confidence: 0.85,
},
Case {
cmd: "g++ main.cc -o main",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gpp),
reason_contains: "g++",
min_confidence: 0.85,
},
Case {
cmd: "clang -c main.c -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Clang),
reason_contains: "clang",
min_confidence: 0.85,
},
Case {
cmd: "clang++ -c main.cpp -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Clangpp),
reason_contains: "clang++",
min_confidence: 0.85,
},
Case {
cmd: "clang++ main.cc -o main",
expect_compilation: true,
expected_kind: Some(CompilationKind::Clangpp),
reason_contains: "clang++",
min_confidence: 0.85,
},
Case {
cmd: "cc -c main.c -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gcc),
reason_contains: "cc",
min_confidence: 0.80,
},
Case {
cmd: "c++ -c main.cpp -o main.o",
expect_compilation: true,
expected_kind: Some(CompilationKind::Gpp),
reason_contains: "c++",
min_confidence: 0.80,
},
];
run_cases(&cases);
}
#[test]
fn regression_c_cpp_version_checks_not_intercepted() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "rustc --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "rustc -V",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "gcc --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "gcc -v",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "clang --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "clang -v",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "make --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "make -v",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
Case {
cmd: "cmake --version",
expect_compilation: false,
expected_kind: None,
reason_contains: "never-intercept",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_build_systems_positive() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "make",
expect_compilation: true,
expected_kind: Some(CompilationKind::Make),
reason_contains: "make build",
min_confidence: 0.80,
},
Case {
cmd: "make -j8",
expect_compilation: true,
expected_kind: Some(CompilationKind::Make),
reason_contains: "make build",
min_confidence: 0.80,
},
Case {
cmd: "make all",
expect_compilation: true,
expected_kind: Some(CompilationKind::Make),
reason_contains: "make build",
min_confidence: 0.80,
},
Case {
cmd: "cmake --build build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CmakeBuild),
reason_contains: "cmake --build",
min_confidence: 0.85,
},
Case {
cmd: "cmake --build=build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CmakeBuild),
reason_contains: "cmake --build",
min_confidence: 0.85,
},
Case {
cmd: "ninja",
expect_compilation: true,
expected_kind: Some(CompilationKind::Ninja),
reason_contains: "ninja build",
min_confidence: 0.85,
},
Case {
cmd: "ninja -j4",
expect_compilation: true,
expected_kind: Some(CompilationKind::Ninja),
reason_contains: "ninja build",
min_confidence: 0.85,
},
Case {
cmd: "meson compile",
expect_compilation: true,
expected_kind: Some(CompilationKind::Meson),
reason_contains: "meson compile",
min_confidence: 0.80,
},
];
run_cases(&cases);
}
#[test]
fn regression_build_systems_not_intercepted() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "make clean",
expect_compilation: false,
expected_kind: None,
reason_contains: "make maintenance",
min_confidence: 0.0,
},
Case {
cmd: "make install",
expect_compilation: false,
expected_kind: None,
reason_contains: "make maintenance",
min_confidence: 0.0,
},
Case {
cmd: "make distclean",
expect_compilation: false,
expected_kind: None,
reason_contains: "make maintenance",
min_confidence: 0.0,
},
Case {
cmd: "ninja -t clean",
expect_compilation: false,
expected_kind: None,
reason_contains: "ninja clean",
min_confidence: 0.0,
},
Case {
cmd: "ninja clean",
expect_compilation: false,
expected_kind: None,
reason_contains: "ninja clean",
min_confidence: 0.0,
},
Case {
cmd: "meson setup build",
expect_compilation: false,
expected_kind: None,
reason_contains: "no matching pattern",
min_confidence: 0.0,
},
Case {
cmd: "meson configure",
expect_compilation: false,
expected_kind: None,
reason_contains: "no matching pattern",
min_confidence: 0.0,
},
Case {
cmd: "cmake .",
expect_compilation: false,
expected_kind: None,
reason_contains: "no matching pattern",
min_confidence: 0.0,
},
Case {
cmd: "cmake -DCMAKE_BUILD_TYPE=Release ..",
expect_compilation: false,
expected_kind: None,
reason_contains: "no matching pattern",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_rustc_positive() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "rustc main.rs",
expect_compilation: true,
expected_kind: Some(CompilationKind::Rustc),
reason_contains: "rustc",
min_confidence: 0.90,
},
Case {
cmd: "rustc main.rs -o main",
expect_compilation: true,
expected_kind: Some(CompilationKind::Rustc),
reason_contains: "rustc",
min_confidence: 0.90,
},
Case {
cmd: "rustc --edition 2021 main.rs",
expect_compilation: true,
expected_kind: Some(CompilationKind::Rustc),
reason_contains: "rustc",
min_confidence: 0.90,
},
];
run_cases(&cases);
}
#[test]
fn regression_shell_structure_exclusions() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "cargo build 2>&1 | grep error",
expect_compilation: false,
expected_kind: None,
reason_contains: "piped",
min_confidence: 0.0,
},
Case {
cmd: "cargo test | tee output.log",
expect_compilation: false,
expected_kind: None,
reason_contains: "piped",
min_confidence: 0.0,
},
Case {
cmd: "cargo build &",
expect_compilation: false,
expected_kind: None,
reason_contains: "background",
min_confidence: 0.0,
},
Case {
cmd: "cargo build > log.txt",
expect_compilation: false,
expected_kind: None,
reason_contains: "redirect",
min_confidence: 0.0,
},
Case {
cmd: "cargo build >> log.txt",
expect_compilation: false,
expected_kind: None,
reason_contains: "redirect",
min_confidence: 0.0,
},
Case {
cmd: "cargo build < input.txt",
expect_compilation: false,
expected_kind: None,
reason_contains: "input redirect",
min_confidence: 0.0,
},
Case {
cmd: "(cargo build)",
expect_compilation: false,
expected_kind: None,
reason_contains: "subshell",
min_confidence: 0.0,
},
Case {
cmd: "$(cargo build)",
expect_compilation: false,
expected_kind: None,
reason_contains: "subshell",
min_confidence: 0.0,
},
Case {
cmd: "`cargo build`",
expect_compilation: false,
expected_kind: None,
reason_contains: "subshell capture",
min_confidence: 0.0,
},
Case {
cmd: "cargo build --config <(echo ...)",
expect_compilation: false,
expected_kind: None,
reason_contains: "subshell",
min_confidence: 0.0,
},
Case {
cmd: "cargo build; cargo test",
expect_compilation: false,
expected_kind: None,
reason_contains: "chained",
min_confidence: 0.0,
},
Case {
cmd: "cargo build || echo failed",
expect_compilation: false,
expected_kind: None,
reason_contains: "chained",
min_confidence: 0.0,
},
Case {
cmd: "cargo build\nrm -rf /",
expect_compilation: false,
expected_kind: None,
reason_contains: "newline",
min_confidence: 0.0,
},
Case {
cmd: "cargo build\r\nevil",
expect_compilation: false,
expected_kind: None,
reason_contains: "newline",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_fd_redirect_safe() {
let _guard = test_guard!();
let result = classify_command("cargo build 2>&1");
assert!(
result.is_compilation,
"cargo build 2>&1 should be classified as compilation (fd redirect is safe), got: {:?}",
result.reason
);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
}
#[test]
fn regression_compound_commands() {
let _guard = test_guard!();
let result = classify_command("cd /path && cargo build");
assert!(
result.is_compilation,
"cd && cargo build should compile, got: {:?}",
result.reason
);
assert_eq!(result.kind, Some(CompilationKind::CargoBuild));
assert!(result.command_prefix.is_some(), "should have prefix");
assert!(
result.extracted_command.is_some(),
"should have extracted command"
);
let result = classify_command("cd /path && cargo test --workspace");
assert!(
result.is_compilation,
"cd && cargo test should compile, got: {:?}",
result.reason
);
assert_eq!(result.kind, Some(CompilationKind::CargoTest));
let result = classify_command("cd /path && cargo fmt");
assert!(!result.is_compilation, "cd && cargo fmt should NOT compile");
let result = classify_command("cd /path && ls -la");
assert!(!result.is_compilation, "cd && ls should NOT compile");
}
#[test]
fn regression_wrapper_normalization() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "sudo cargo build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "time cargo test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "env RUST_BACKTRACE=1 cargo test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "RUST_BACKTRACE=1 cargo test",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoTest),
reason_contains: "cargo test",
min_confidence: 0.90,
},
Case {
cmd: "nice cargo build --release",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "/usr/bin/cargo build",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoBuild),
reason_contains: "cargo build",
min_confidence: 0.90,
},
Case {
cmd: "sudo env RUSTFLAGS=-Dwarnings cargo clippy",
expect_compilation: true,
expected_kind: Some(CompilationKind::CargoClippy),
reason_contains: "cargo clippy",
min_confidence: 0.85,
},
];
run_cases(&cases);
}
#[test]
fn regression_non_compilation_passthrough() {
let _guard = test_guard!();
let cases = [
Case {
cmd: "ls -la",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "pwd",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "echo hello",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "cat Cargo.toml",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "git status",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "git commit -m 'fix'",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "npm install",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "yarn build",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "python main.py",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "go build .",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "docker build -t myapp .",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "pip install -r requirements.txt",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "curl https://example.com",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "mkdir -p build",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "rm -rf target/",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "cp -r src/ backup/",
expect_compilation: false,
expected_kind: None,
reason_contains: "no compilation keyword",
min_confidence: 0.0,
},
Case {
cmd: "",
expect_compilation: false,
expected_kind: None,
reason_contains: "empty command",
min_confidence: 0.0,
},
Case {
cmd: " ",
expect_compilation: false,
expected_kind: None,
reason_contains: "empty command",
min_confidence: 0.0,
},
];
run_cases(&cases);
}
#[test]
fn regression_classification_details_fields() {
let _guard = test_guard!();
let details = classify_command_detailed("cargo build --release");
assert_eq!(details.original, "cargo build --release");
assert!(details.classification.is_compilation);
assert_eq!(
details.classification.kind,
Some(CompilationKind::CargoBuild)
);
assert!(details.classification.confidence >= 0.90);
assert_eq!(details.tiers.len(), 5, "Should have 5 tier decisions");
for tier in &details.tiers[..4] {
assert_eq!(tier.decision, TierDecision::Pass);
}
assert_eq!(details.tiers[4].decision, TierDecision::Pass);
assert_eq!(details.tiers[4].tier, 4);
let details = classify_command_detailed("ls -la");
assert!(!details.classification.is_compilation);
assert!(
details.tiers.len() >= 3,
"Should have at least 3 tiers for keyword rejection"
);
assert_eq!(details.tiers.last().unwrap().decision, TierDecision::Reject);
let details = classify_command_detailed("cargo fmt --check");
assert!(!details.classification.is_compilation);
assert!(
details.tiers.len() >= 4,
"Should have at least 4 tiers for never-intercept rejection"
);
let tier3 = details.tiers.iter().find(|t| t.tier == 3).unwrap();
assert_eq!(tier3.decision, TierDecision::Reject);
assert!(
tier3.reason.contains("never-intercept"),
"Tier 3 reason should mention never-intercept, got: {:?}",
tier3.reason
);
let details = classify_command_detailed("cargo build | grep error");
assert!(!details.classification.is_compilation);
let tier1 = details.tiers.iter().find(|t| t.tier == 1).unwrap();
assert_eq!(tier1.decision, TierDecision::Reject);
assert!(tier1.reason.contains("piped"));
let details = classify_command_detailed("");
assert!(!details.classification.is_compilation);
assert_eq!(details.tiers[0].tier, 0);
assert_eq!(details.tiers[0].decision, TierDecision::Reject);
}
#[test]
fn regression_every_compilation_has_reason_and_confidence() {
let _guard = test_guard!();
let compilation_cmds = [
"cargo build",
"cargo test",
"cargo check",
"cargo clippy",
"cargo doc",
"cargo run",
"cargo bench",
"cargo nextest run",
"rustc main.rs",
"gcc -c main.c -o main.o",
"g++ -c main.cpp -o main.o",
"clang -c main.c -o main.o",
"clang++ -c main.cpp -o main.o",
"make",
"cmake --build build",
"ninja",
"meson compile",
"bun test",
"bun typecheck",
];
for cmd in compilation_cmds {
let result = classify_command(cmd);
assert!(result.is_compilation, "{cmd:?} should be compilation");
assert!(
result.kind.is_some(),
"{cmd:?} should have a CompilationKind"
);
assert!(
result.confidence > 0.0,
"{cmd:?} should have non-zero confidence"
);
assert!(
!result.reason.is_empty(),
"{cmd:?} should have a non-empty reason"
);
}
}
#[test]
fn regression_every_rejection_has_reason() {
let _guard = test_guard!();
let non_compilation_cmds = [
"",
"ls",
"pwd",
"git status",
"cargo fmt",
"cargo install ripgrep",
"cargo clean",
"bun install",
"bun add react",
"bun run dev",
"bun dev",
"bun repl",
"bun x vitest",
"bun test --watch",
"bun typecheck -w",
"make clean",
"ninja clean",
"gcc --version",
"rustc --version",
"cargo build | grep error",
"cargo build &",
"cargo build > log.txt",
"(cargo build)",
];
for cmd in non_compilation_cmds {
let result = classify_command(cmd);
assert!(
!result.is_compilation,
"{cmd:?} should NOT be compilation, got kind={:?}",
result.kind
);
assert!(
!result.reason.is_empty(),
"{cmd:?} should have a non-empty rejection reason"
);
assert_eq!(result.kind, None, "{cmd:?} should have no CompilationKind");
assert_eq!(
result.confidence, 0.0,
"{cmd:?} should have zero confidence"
);
}
}
#[test]
fn regression_real_world_agent_workflows() {
let _guard = test_guard!();
let cd_build = classify_command(
"cd /data/projects/remote_compilation_helper && cargo build --workspace --all-targets",
);
assert!(
cd_build.is_compilation,
"agent cd+build workflow should be intercepted"
);
let test_ws = classify_command("cargo test --workspace -- --nocapture");
assert!(test_ws.is_compilation);
assert_eq!(test_ws.kind, Some(CompilationKind::CargoTest));
let clippy = classify_command("cargo clippy --workspace --all-targets -- -D warnings");
assert!(clippy.is_compilation);
assert_eq!(clippy.kind, Some(CompilationKind::CargoClippy));
let fmt = classify_command("cargo fmt --check");
assert!(!fmt.is_compilation);
let cat = classify_command("cat Cargo.toml");
assert!(!cat.is_compilation);
let gs = classify_command("git status");
assert!(!gs.is_compilation);
let install = classify_command("cargo install cargo-nextest");
assert!(!install.is_compilation);
let pkg_test = classify_command("cargo test -p rch-common");
assert!(pkg_test.is_compilation);
assert_eq!(pkg_test.kind, Some(CompilationKind::CargoTest));
let env_test = classify_command("RUST_BACKTRACE=1 cargo test --workspace");
assert!(env_test.is_compilation);
assert_eq!(env_test.kind, Some(CompilationKind::CargoTest));
let nxt = classify_command("cargo nextest run --workspace --no-fail-fast");
assert!(nxt.is_compilation);
assert_eq!(nxt.kind, Some(CompilationKind::CargoNextest));
}
#[test]
fn regression_keyword_in_non_compilation_context() {
let _guard = test_guard!();
let result = classify_command("echo cargo build");
assert!(
!result.is_compilation,
"echo cargo should not compile, got: {:?}",
result.reason
);
let result = classify_command("grep make Makefile");
assert!(
!result.is_compilation,
"grep make should not compile, got: {:?}",
result.reason
);
let result = classify_command("cat gcc_output.log");
assert!(
!result.is_compilation,
"cat gcc_output should not compile, got: {:?}",
result.reason
);
}
#[test]
fn regression_compilation_kind_properties() {
let _guard = test_guard!();
assert!(CompilationKind::CargoTest.is_test_command());
assert!(CompilationKind::CargoNextest.is_test_command());
assert!(CompilationKind::CargoBench.is_test_command());
assert!(CompilationKind::BunTest.is_test_command());
assert!(!CompilationKind::CargoBuild.is_test_command());
assert!(!CompilationKind::CargoCheck.is_test_command());
assert!(!CompilationKind::CargoClippy.is_test_command());
assert!(!CompilationKind::CargoDoc.is_test_command());
assert!(!CompilationKind::BunTypecheck.is_test_command());
assert!(!CompilationKind::Gcc.is_test_command());
assert!(!CompilationKind::Gpp.is_test_command());
assert!(!CompilationKind::Clang.is_test_command());
assert!(!CompilationKind::Clangpp.is_test_command());
assert!(!CompilationKind::Make.is_test_command());
assert!(!CompilationKind::CmakeBuild.is_test_command());
assert!(!CompilationKind::Ninja.is_test_command());
assert!(!CompilationKind::Meson.is_test_command());
assert!(!CompilationKind::Rustc.is_test_command());
assert_eq!(CompilationKind::CargoBuild.command_base(), "cargo");
assert_eq!(CompilationKind::CargoTest.command_base(), "cargo");
assert_eq!(CompilationKind::CargoNextest.command_base(), "cargo");
assert_eq!(CompilationKind::Rustc.command_base(), "rustc");
assert_eq!(CompilationKind::Gcc.command_base(), "gcc");
assert_eq!(CompilationKind::Gpp.command_base(), "g++");
assert_eq!(CompilationKind::Clang.command_base(), "clang");
assert_eq!(CompilationKind::Clangpp.command_base(), "clang++");
assert_eq!(CompilationKind::Make.command_base(), "make");
assert_eq!(CompilationKind::CmakeBuild.command_base(), "cmake");
assert_eq!(CompilationKind::Ninja.command_base(), "ninja");
assert_eq!(CompilationKind::Meson.command_base(), "meson");
assert_eq!(CompilationKind::BunTest.command_base(), "bun");
assert_eq!(CompilationKind::BunTypecheck.command_base(), "bun");
}
}