use crate::{
providers::ToolCall,
tools::{
ToolCapability, ToolOutputCompressionSettings, ToolResult, contract::metadata_key as meta,
},
};
use serde_json::Value;
const STATUS_LINE_CAP: usize = 12;
const DIFF_FILE_CAP: usize = 20;
const GIT_LOG_LINE_CAP: usize = 15;
const DIAGNOSTIC_LINE_CAP: usize = 30;
const FAILURE_LINE_CAP: usize = 40;
pub(crate) fn provider_visible_tool_output(
call: &ToolCall,
result: &ToolResult,
settings: &ToolOutputCompressionSettings,
) -> String {
if let Some(output) = failed_tool_diagnostic_output(call, result) {
return output;
}
if !settings.enabled || !is_bash_dispatch_name(&call.name) {
return result.content.clone();
}
let Some(command) = command_text(call) else {
return result.content.clone();
};
let Some(rule) = classify_bash_command(command) else {
return result.content.clone();
};
let framed_streams = result
.content
.strip_prefix("stdout:\n")
.and_then(|body| body.split_once("\nstderr:\n"));
let Some(stdout) = metadata_string(&result.metadata, meta::STDOUT)
.or_else(|| framed_streams.map(|(stdout, _)| stdout))
else {
return result.content.clone();
};
let Some(stderr) = metadata_string(&result.metadata, meta::STDERR)
.or_else(|| framed_streams.map(|(_, stderr)| stderr))
else {
return result.content.clone();
};
if stdout.is_empty() && stderr.is_empty() && !result.success {
return result.content.clone();
}
if matches!(
rule,
BashCompressionRule::GitStatus | BashCompressionRule::GitDiff | BashCompressionRule::GitLog
) && !stderr.is_empty()
{
return result.content.clone();
}
let summary = match rule {
BashCompressionRule::GitStatus => compress_git_status(stdout),
BashCompressionRule::GitDiff => compress_git_diff(stdout),
BashCompressionRule::GitLog => compress_git_log(stdout),
BashCompressionRule::CargoCheck => compress_cargo_check(result, stdout, stderr),
BashCompressionRule::CargoTest => compress_cargo_test(result, stdout, stderr),
};
if summary.trim().is_empty() {
return result.content.clone();
}
let candidate = format!(
"[tool_output_compression]\ntool: {}\nrule: {}\ncommand: {}\nsuccess: {}\nexit_code: {}\nraw_stdout_bytes: {}\nraw_stderr_bytes: {}\nstdout_truncated: {}\nstderr_truncated: {}\ncompression: curated\n\n{}",
call.name,
rule.id(),
command,
result.success,
exit_code_text(&result.metadata),
stdout.len(),
stderr.len(),
metadata_bool(&result.metadata, meta::STDOUT_TRUNCATED),
metadata_bool(&result.metadata, meta::STDERR_TRUNCATED),
summary
);
if has_net_token_savings(&result.content, &candidate) {
candidate
} else {
result.content.clone()
}
}
fn classify_bash_command(command: &str) -> Option<BashCompressionRule> {
if command.contains('|')
|| contains_unsafe_operator(command)
|| contains_uncertain_shell_syntax(command)
{
return None;
}
let tokens = command
.split_whitespace()
.filter(|token| !is_allowed_redirect(token))
.collect::<Vec<_>>();
classify_bash_tokens(&tokens)
}
fn classify_bash_tokens(tokens: &[&str]) -> Option<BashCompressionRule> {
match tokens {
["git", "status", rest @ ..] | ["git", "--no-pager", "status", rest @ ..]
if git_status_args_supported(rest) =>
{
Some(BashCompressionRule::GitStatus)
}
["git", "diff", rest @ ..] | ["git", "--no-pager", "diff", rest @ ..]
if git_diff_args_supported(rest) =>
{
Some(BashCompressionRule::GitDiff)
}
["git", "-C", path, "diff", rest @ ..]
| ["git", "-C", path, "--no-pager", "diff", rest @ ..]
| ["git", "--no-pager", "-C", path, "diff", rest @ ..]
if is_git_cwd_path_supported(path) && git_cwd_diff_args_supported(rest) =>
{
Some(BashCompressionRule::GitDiff)
}
["git", "log", rest @ ..] | ["git", "--no-pager", "log", rest @ ..]
if git_log_args_supported(rest) =>
{
Some(BashCompressionRule::GitLog)
}
["cargo", "check", rest @ ..] if cargo_args_supported(rest, false) => {
Some(BashCompressionRule::CargoCheck)
}
["cargo", "test", rest @ ..] if cargo_args_supported(rest, true) => {
Some(BashCompressionRule::CargoTest)
}
["cargo", toolchain, "check", rest @ ..]
if is_cargo_toolchain(toolchain) && cargo_qualified_args_supported(rest, false) =>
{
Some(BashCompressionRule::CargoCheck)
}
["cargo", toolchain, "test", rest @ ..]
if is_cargo_toolchain(toolchain) && cargo_qualified_args_supported(rest, true) =>
{
Some(BashCompressionRule::CargoTest)
}
_ => None,
}
}
fn contains_unsafe_operator(command: &str) -> bool {
if ["&&", "||", ";", "$(", "`", "\n", "\r", "<", ">>"]
.iter()
.any(|operator| command.contains(operator))
{
return true;
}
command.split_whitespace().any(|token| {
token.contains('>') && !is_allowed_redirect(token)
|| token.contains('&') && !is_allowed_redirect(token)
})
}
fn contains_uncertain_shell_syntax(command: &str) -> bool {
command.chars().any(|character| {
matches!(
character,
'\'' | '"'
| '\\'
| '$'
| '{'
| '}'
| '*'
| '?'
| '['
| ']'
| '~'
| '('
| ')'
| '!'
| '#'
)
})
}
fn is_allowed_redirect(token: &str) -> bool {
matches!(token, "2>&1" | "1>&2")
}
fn is_bash_dispatch_name(name: &str) -> bool {
ToolCapability::from_dispatch_name(name) == Some(ToolCapability::Bash)
}
fn git_status_args_supported(args: &[&str]) -> bool {
let mut requested_short_form = false;
for arg in args {
match *arg {
"--short" | "--porcelain" | "--porcelain=v1" | "-sb" => {
requested_short_form = true;
}
"--branch" => {}
_ => return false,
}
}
requested_short_form
}
fn git_diff_args_supported(args: &[&str]) -> bool {
git_args_before_pathspec_delimiter(args).all(|arg| {
!is_git_diff_incompatible_output_flag(arg) && !is_git_global_option_after_diff(arg)
})
}
fn git_cwd_diff_args_supported(args: &[&str]) -> bool {
git_diff_args_supported(args)
}
fn git_args_before_pathspec_delimiter<'a, 'b>(
args: &'a [&'b str],
) -> impl Iterator<Item = &'b str> + 'a {
args.iter().copied().take_while(|arg| *arg != "--")
}
fn is_git_global_option_after_diff(arg: &str) -> bool {
matches!(
arg,
"-P" | "-c"
| "--paginate"
| "--no-pager"
| "--pager"
| "--bare"
| "--no-replace-objects"
| "--no-lazy-fetch"
| "--no-optional-locks"
| "--no-advice"
| "--exec-path"
| "--html-path"
| "--man-path"
| "--info-path"
| "--git-dir"
| "--work-tree"
| "--namespace"
| "--config"
| "--config-env"
| "--super-prefix"
| "--literal-pathspecs"
| "--glob-pathspecs"
| "--noglob-pathspecs"
| "--icase-pathspecs"
| "--list-cmds"
| "--attr-source"
) || arg.starts_with("-C")
|| arg.starts_with("-c")
|| arg.starts_with("--exec-path=")
|| arg.starts_with("--git-dir=")
|| arg.starts_with("--work-tree=")
|| arg.starts_with("--namespace=")
|| arg.starts_with("--config=")
|| arg.starts_with("--config-env=")
|| arg.starts_with("--super-prefix=")
|| arg.starts_with("--list-cmds=")
|| arg.starts_with("--attr-source=")
|| arg.starts_with("--no-pager=")
}
fn is_git_cwd_path_supported(path: &str) -> bool {
!path.is_empty()
&& !path.starts_with('-')
&& path
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'.' | b'_' | b'-'))
}
fn is_cargo_toolchain(token: &str) -> bool {
let Some(suffix) = token.strip_prefix('+') else {
return false;
};
!suffix.is_empty()
&& suffix
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
}
fn is_git_diff_incompatible_output_flag(arg: &str) -> bool {
matches!(
arg,
"-o" | "-q"
| "-s"
| "--check"
| "--color-words"
| "--compact-summary"
| "--dirstat"
| "--ext-diff"
| "--name-only"
| "--name-status"
| "--no-patch"
| "--numstat"
| "--output"
| "--quiet"
| "--raw"
| "--shortstat"
| "--stat"
| "--summary"
| "--textconv"
| "--word-diff"
| "--binary"
| "--full-index"
| "--abbrev"
| "--no-abbrev"
| "--no-prefix"
| "--color"
| "--no-color"
| "--function-context"
| "--inter-hunk-context"
| "--line-prefix"
| "--src-prefix"
| "--dst-prefix"
| "--unified"
| "--output-indicator-new"
| "--output-indicator-old"
| "--output-indicator-context"
| "--color-moved"
| "--color-moved-ws"
| "--ws-error-highlight"
| "--submodule"
| "--word-diff-regex"
) || arg.starts_with("--color-words=")
|| arg.starts_with("--dirstat=")
|| arg.starts_with("--name-only=")
|| arg.starts_with("--name-status=")
|| arg.starts_with("--numstat=")
|| arg.starts_with("--output=")
|| arg.starts_with("--raw=")
|| arg.starts_with("--shortstat=")
|| arg.starts_with("--stat")
|| arg.starts_with("--word-diff=")
|| arg.starts_with("--word-diff-regex=")
|| arg.starts_with("--color=")
|| arg.starts_with("--abbrev=")
|| arg.starts_with("--line-prefix=")
|| arg.starts_with("--src-prefix=")
|| arg.starts_with("--dst-prefix=")
|| arg.starts_with("--unified=")
|| arg.starts_with("--inter-hunk-context=")
|| arg.starts_with("--output-indicator-new=")
|| arg.starts_with("--output-indicator-old=")
|| arg.starts_with("--output-indicator-context=")
|| arg.starts_with("--color-moved=")
|| arg.starts_with("--color-moved-ws=")
|| arg.starts_with("--ws-error-highlight=")
|| arg.starts_with("--submodule=")
|| arg.starts_with("-U")
}
fn git_log_args_supported(args: &[&str]) -> bool {
args.contains(&"--oneline")
&& !args
.iter()
.any(|arg| is_git_log_incompatible_output_flag(arg))
}
fn is_git_log_incompatible_output_flag(arg: &str) -> bool {
matches!(
arg,
"-p" | "--boundary"
| "--color"
| "--graph"
| "--left-right"
| "--name-only"
| "--name-status"
| "--no-notes"
| "--no-patch"
| "--notes"
| "--numstat"
| "--patch"
| "--patch-with-stat"
| "--raw"
| "--shortstat"
| "--show-linear-break"
| "--show-notes"
| "--show-signature"
| "--source"
| "--stat"
| "--summary"
| "--parents"
| "--children"
| "--cherry-mark"
) || arg.starts_with("--color=")
|| arg.starts_with("--format")
|| arg.starts_with("--name-only=")
|| arg.starts_with("--name-status=")
|| arg.starts_with("--pretty")
|| arg.starts_with("--stat")
|| arg == "--decorate"
|| arg.starts_with("--decorate=")
|| arg.starts_with("--decorate-refs")
}
fn cargo_qualified_args_supported(args: &[&str], is_test: bool) -> bool {
cargo_args_supported(args, is_test)
&& !args.iter().any(|arg| {
arg.trim_matches(|character| matches!(character, '\'' | '"'))
.starts_with('+')
})
}
fn cargo_args_supported(args: &[&str], is_test: bool) -> bool {
let mut index = 0;
while index < args.len() {
let arg = args[index];
if arg == "--color" {
let Some(value) = args.get(index + 1) else {
return false;
};
if !matches!(*value, "auto" | "never") {
return false;
}
index += 2;
continue;
}
if arg
.strip_prefix("--color=")
.is_some_and(|value| !matches!(value, "auto" | "never"))
{
return false;
}
if is_cargo_passthrough_flag(arg, is_test) {
return false;
}
index += 1;
}
true
}
fn is_cargo_passthrough_flag(arg: &str, is_test: bool) -> bool {
arg == "-h"
|| arg == "--help"
|| arg == "--timings"
|| arg.starts_with("--timings=")
|| arg == "--future-incompat-report"
|| arg == "--color=always"
|| is_cargo_machine_format_flag(arg)
|| (is_test && is_cargo_test_output_shaping_flag(arg))
|| (is_test && is_cargo_test_passthrough_flag(arg))
}
fn is_cargo_test_output_shaping_flag(arg: &str) -> bool {
arg == "--list"
|| arg.starts_with("--list=")
|| arg == "--format"
|| arg.starts_with("--format=")
}
fn is_cargo_machine_format_flag(arg: &str) -> bool {
arg == "--message-format"
|| arg.starts_with("--message-format=")
|| arg == "--json"
|| arg.starts_with("--json=")
|| arg == "--format=json"
|| arg.starts_with("--format=json-")
}
fn is_cargo_test_passthrough_flag(arg: &str) -> bool {
matches!(arg, "--nocapture" | "--show-output" | "--no-capture")
|| arg.starts_with("--nocapture=")
|| arg.starts_with("--show-output=")
|| arg.starts_with("--no-capture=")
}
fn compress_git_status(stdout: &str) -> String {
let lines = non_empty_lines(stdout);
let branch = lines.iter().find(|line| line.starts_with("## ")).copied();
let status_lines = lines
.iter()
.copied()
.filter(|line| !line.starts_with("## "))
.collect::<Vec<_>>();
let untracked = status_lines
.iter()
.filter(|line| line.trim_start().starts_with("??"))
.count();
let mut out = vec![format!("changed_files: {}", status_lines.len())];
out.push(format!("untracked_files: {untracked}"));
if let Some(branch) = branch {
out.push(format!("branch: {branch}"));
}
out.push("status_lines:".to_string());
out.extend(capped_prefixed(&status_lines, STATUS_LINE_CAP));
out.join("\n")
}
fn compress_git_diff(stdout: &str) -> String {
let lines = stdout.lines().collect::<Vec<_>>();
if !lines.is_empty() && !lines.iter().any(|line| is_git_diff_header(line)) {
return String::new();
}
let files = lines
.iter()
.filter_map(|line| line.strip_prefix("diff --git "))
.filter_map(|line| line.split_whitespace().next())
.map(|path| path.strip_prefix("a/").unwrap_or(path))
.collect::<Vec<_>>();
let hunk_count = lines.iter().filter(|line| line.starts_with("@@")).count();
let insertions = lines
.iter()
.filter(|line| line.starts_with('+') && !line.starts_with("+++"))
.count();
let deletions = lines
.iter()
.filter(|line| line.starts_with('-') && !line.starts_with("---"))
.count();
let stat_lines = lines
.iter()
.copied()
.filter(|line| line.contains(" | ") || line.contains(" file changed"))
.take(DIFF_FILE_CAP)
.collect::<Vec<_>>();
let mut out = vec![
format!("files_touched: {}", files.len()),
format!("hunks: {hunk_count}"),
format!("insertions: {insertions}"),
format!("deletions: {deletions}"),
"files:".to_string(),
];
out.extend(capped_prefixed(&files, DIFF_FILE_CAP));
if !stat_lines.is_empty() {
out.push("stat_lines:".to_string());
out.extend(capped_prefixed(&stat_lines, DIFF_FILE_CAP));
}
out.join("\n")
}
fn is_git_diff_header(line: &str) -> bool {
line.strip_prefix("diff --git ")
.is_some_and(|rest| rest.split_whitespace().count() >= 2)
}
fn compress_git_log(stdout: &str) -> String {
let lines = non_empty_lines(stdout);
if lines.is_empty() || !lines.iter().all(|line| is_git_oneline_commit_line(line)) {
return String::new();
}
let mut out = vec![format!("commits: {}", lines.len()), "subjects:".to_string()];
out.extend(capped_prefixed(&lines, GIT_LOG_LINE_CAP));
out.join("\n")
}
fn is_git_oneline_commit_line(line: &str) -> bool {
if line
.bytes()
.next()
.is_some_and(|byte| byte.is_ascii_whitespace())
{
return false;
}
let hash = line.split(' ').next().unwrap_or_default();
(4..=64).contains(&hash.len()) && hash.bytes().all(|byte| byte.is_ascii_hexdigit())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BashCompressionRule {
GitStatus,
GitDiff,
GitLog,
CargoCheck,
CargoTest,
}
impl BashCompressionRule {
fn id(self) -> &'static str {
match self {
Self::GitStatus => "bash.git_status",
Self::GitDiff => "bash.git_diff",
Self::GitLog => "bash.git_log",
Self::CargoCheck => "bash.cargo_check",
Self::CargoTest => "bash.cargo_test",
}
}
}
fn command_text(call: &ToolCall) -> Option<&str> {
call.arguments
.get("command")
.or_else(|| call.arguments.get("cmd"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|command| !command.is_empty())
}
fn compress_cargo_check(result: &ToolResult, stdout: &str, stderr: &str) -> String {
let combined = combined_lines(stdout, stderr);
let final_status = combined
.iter()
.rev()
.find(|line| {
line.contains("Finished ")
|| line.contains("could not compile")
|| line.contains("error:")
})
.copied();
let mut out = cargo_header(result, &combined);
if let Some(final_status) = final_status {
out.push(format!("final_status: {}", final_status.trim()));
}
out.push("diagnostics:".to_string());
out.extend(first_interesting_lines(&combined, DIAGNOSTIC_LINE_CAP));
out.join("\n")
}
fn compress_cargo_test(result: &ToolResult, stdout: &str, stderr: &str) -> String {
let combined = combined_lines(stdout, stderr);
let test_results = combined
.iter()
.copied()
.filter(|line| line.contains("test result:"))
.collect::<Vec<_>>();
let failing = combined
.iter()
.filter_map(|line| line.trim().strip_prefix("test "))
.filter_map(|line| line.strip_suffix(" ... FAILED"))
.collect::<Vec<_>>();
let mut out = cargo_header(result, &combined);
out.push("test_results:".to_string());
out.extend(capped_prefixed(&test_results, STATUS_LINE_CAP));
out.push("failing_tests:".to_string());
out.extend(capped_prefixed(&failing, STATUS_LINE_CAP));
out.push("failure_context:".to_string());
out.extend(first_interesting_lines(&combined, FAILURE_LINE_CAP));
out.join("\n")
}
fn cargo_header(result: &ToolResult, lines: &[&str]) -> Vec<String> {
let warnings = lines
.iter()
.filter(|line| line.trim_start().starts_with("warning"))
.count();
let errors = lines
.iter()
.filter(|line| line.trim_start().starts_with("error"))
.count();
let mut codes = lines
.iter()
.filter_map(|line| line.split('[').nth(1))
.filter_map(|rest| rest.split(']').next())
.filter(|code| code.starts_with('E') && code[1..].chars().all(|c| c.is_ascii_digit()))
.collect::<Vec<_>>();
codes.sort_unstable();
codes.dedup();
vec![
format!("exit_code: {}", exit_code_text(&result.metadata)),
format!("warnings: {warnings}"),
format!("errors: {errors}"),
format!("compiler_error_codes: {}", codes.join(", ")),
]
}
fn non_empty_lines(text: &str) -> Vec<&str> {
text.lines()
.filter(|line| !line.trim().is_empty())
.collect()
}
fn combined_lines<'a>(stdout: &'a str, stderr: &'a str) -> Vec<&'a str> {
stdout
.lines()
.chain(stderr.lines())
.filter(|line| !line.trim().is_empty())
.collect()
}
fn capped_prefixed<T: AsRef<str>>(lines: &[T], cap: usize) -> Vec<String> {
lines
.iter()
.take(cap)
.map(|line| format!("- {}", line.as_ref()))
.collect()
}
fn first_interesting_lines(lines: &[&str], cap: usize) -> Vec<String> {
lines
.iter()
.copied()
.filter(|line| {
let trimmed = line.trim_start();
trimmed.starts_with("error")
|| trimmed.starts_with("warning")
|| trimmed.starts_with("note")
|| trimmed.starts_with("help")
|| trimmed.starts_with("thread '")
|| trimmed.starts_with("failures:")
|| trimmed.starts_with("---- ")
|| trimmed.contains("panicked at")
|| trimmed.contains("FAILED")
|| trimmed.contains("could not compile")
})
.take(cap)
.map(|line| format!("- {}", line.trim_end()))
.collect()
}
fn failed_tool_diagnostic_output(call: &ToolCall, result: &ToolResult) -> Option<String> {
if result.success
|| !result.content.trim().is_empty()
|| ToolCapability::from_dispatch_name(&call.name) != Some(ToolCapability::Grep)
{
return None;
}
metadata_string(&result.metadata, meta::STDERR)
.filter(|stderr| !stderr.trim().is_empty())
.or_else(|| {
metadata_string(&result.metadata, meta::STDOUT)
.filter(|stdout| !stdout.trim().is_empty())
})
.map(str::to_string)
}
fn metadata_string<'a>(metadata: &'a Value, key: &str) -> Option<&'a str> {
metadata.get(key).and_then(Value::as_str)
}
fn metadata_bool(metadata: &Value, key: &str) -> bool {
metadata.get(key).and_then(Value::as_bool).unwrap_or(false)
}
fn exit_code_text(metadata: &Value) -> String {
metadata
.get(meta::EXIT_CODE)
.and_then(Value::as_i64)
.map(|code| code.to_string())
.unwrap_or_else(|| "null".to_string())
}
fn has_net_token_savings(raw: &str, candidate: &str) -> bool {
["gpt-4o", "gpt-4"].into_iter().all(|model| {
let count = |text| {
crate::context::project_text_tokens(
crate::providers::OPENAI_CODEX_PROVIDER,
model,
text,
)
.tokens
};
count(candidate) < count(raw)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::{ToolResultDisplay, ToolSettings};
use serde_json::json;
fn bash_call(command: &str) -> ToolCall {
ToolCall {
id: "call_1".to_string(),
name: "bash".to_string(),
arguments: json!({"command": command}),
}
}
fn grep_call(name: &str, pattern: &str) -> ToolCall {
ToolCall {
id: "call_1".to_string(),
name: name.to_string(),
arguments: json!({"patterns": [pattern], "path": "."}),
}
}
fn bash_result(
stdout: &str,
stderr: &str,
success: bool,
exit_code: Option<i32>,
) -> ToolResult {
ToolResult {
tool_name: "bash".to_string(),
success,
content: format!("stdout:\n{stdout}\nstderr:\n{stderr}"),
metadata: json!({
"stdout": stdout,
"stderr": stderr,
"exit_code": exit_code,
"stdout_truncated": false,
"stderr_truncated": false
}),
display: ToolResultDisplay::default(),
}
}
fn enabled() -> ToolOutputCompressionSettings {
ToolOutputCompressionSettings { enabled: true }
}
#[test]
fn dispatch_errors_without_complete_streams_pass_through() {
for tool_name in ["bash", "shell"] {
for command in [
"git status --short",
"git diff",
"git log --oneline",
"cargo check",
"cargo test",
] {
for metadata in [
json!({}),
json!({"stdout": ""}),
json!({"stderr": ""}),
json!({"stdout": null, "stderr": false}),
json!({"stdout": "", "stderr": ""}),
] {
let result = ToolResult {
content: "failed to spawn command: permission denied\n".repeat(100),
metadata,
..bash_result("", "", false, None)
};
assert_passthrough_result_for_tool(tool_name, command, &result);
}
}
}
}
#[test]
fn short_results_pass_through_including_header_cost() {
for (command, stdout, stderr) in [
("git status --short", " M src/lib.rs\n", ""),
("git diff", "", ""),
("git log --oneline", "abc123 subject\n", ""),
("cargo check", "", "Finished `dev` profile\n"),
("cargo test", "test result: ok. 1 passed\n", ""),
] {
let result = bash_result(stdout, stderr, true, Some(0));
for tool_name in ["bash", "shell"] {
assert_passthrough_result_for_tool(tool_name, command, &result);
}
}
}
#[test]
fn long_command_header_can_outweigh_patch_savings() {
let result = bash_result(&large_patch(), "", true, Some(0));
assert!(
provider_visible_tool_output(&bash_call("git diff"), &result, &enabled())
.starts_with("[tool_output_compression]")
);
let command = format!("git diff -- {}", "src/file.rs ".repeat(1000));
assert_passthrough_result_for_tool("bash", &command, &result);
}
#[test]
fn incomplete_content_framing_passes_through() {
let patch = large_patch();
for content in [
format!("stdout:\n{patch}"),
format!("stderr:\n{patch}"),
format!("dispatch error\nstdout:\n{patch}\nstderr:\n"),
] {
let result = ToolResult {
content,
metadata: json!({}),
..bash_result("", "", false, None)
};
assert_passthrough_result_for_tool("bash", "git diff", &result);
}
}
#[test]
fn token_gate_rejects_ties_and_byte_only_savings() {
assert!(!has_net_token_savings("same text", "same text"));
let raw = " ".repeat(100);
let candidate = "x!".repeat(100);
assert!(candidate.len() < raw.len());
assert!(!has_net_token_savings(&raw, &candidate));
}
#[test]
fn framed_stream_fallback_matches_metadata_with_net_token_savings() {
let stdout = large_patch();
let result = bash_result(&stdout, "", false, Some(1));
let mut fallback = result.clone();
fallback.metadata.as_object_mut().unwrap().remove("stdout");
fallback.metadata.as_object_mut().unwrap().remove("stderr");
let call = bash_call("git diff --exit-code");
let output = provider_visible_tool_output(&call, &result, &enabled());
assert!(output.starts_with("[tool_output_compression]"));
assert_eq!(
output,
provider_visible_tool_output(&call, &fallback, &enabled())
);
assert!(output.contains(&format!("raw_stdout_bytes: {}", stdout.len())));
assert!(output.contains("raw_stderr_bytes: 0"));
assert!(output.contains("success: false\nexit_code: 1"));
for model in ["gpt-4o", "gpt-4"] {
let count = |text| {
crate::context::project_text_tokens(
crate::providers::OPENAI_CODEX_PROVIDER,
model,
text,
)
.tokens
};
assert!(count(&output) < count(&result.content));
}
}
fn large_patch() -> String {
format!(
"diff --git a/src/lib.rs b/src/lib.rs\n@@ -1,100 +1,100 @@\n{}",
"-old value\n+new value\n".repeat(100)
)
}
fn large_status() -> String {
(0..100)
.map(|index| format!(" M src/file_{index}.rs\n"))
.collect()
}
#[test]
fn disabled_passthrough_returns_exact_content() {
let result = bash_result(&large_status(), "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("git status --short"),
&result,
&ToolSettings::default().output_compression,
);
assert_eq!(output, result.content);
}
#[test]
fn compressed_provider_output_does_not_change_local_structured_detail() {
let call = bash_call("git status --short");
let result = bash_result(&large_status(), "", true, Some(0));
let provider_output = provider_visible_tool_output(&call, &result, &enabled());
let detail = crate::tool_display::tool_activity_detail(
&call,
&result,
"bash git status --short",
crate::output::ActivityStatus::Success,
);
assert!(provider_output.starts_with("[tool_output_compression]"));
assert_ne!(provider_output, result.content);
assert_eq!(detail.output.as_ref(), result.content);
assert!(!detail.output.contains("[tool_output_compression]"));
}
#[test]
fn invalid_grep_regex_provider_output_includes_diagnostic() {
let result = ToolResult {
tool_name: "grep".to_string(),
success: false,
content: String::new(),
metadata: json!({
"stdout": "",
"stderr": "regex parse error:\n [unterminated character class\n",
}),
display: ToolResultDisplay::default(),
};
let output = provider_visible_tool_output(&grep_call("grep", "["), &result, &enabled());
assert!(output.contains("regex parse error"));
assert_eq!(result.content, "");
}
#[test]
fn invalid_legacy_grep_regex_provider_output_includes_diagnostic() {
let result = ToolResult {
tool_name: "ffgrep".to_string(),
success: false,
content: String::new(),
metadata: json!({
"stdout": "",
"stderr": "regex parse error:\n [unterminated character class\n",
}),
display: ToolResultDisplay::default(),
};
let output = provider_visible_tool_output(&grep_call("ffgrep", "["), &result, &enabled());
assert!(output.contains("regex parse error"));
assert_eq!(result.content, "");
}
#[test]
fn non_bash_and_unrecognized_passthrough() {
let mut call = bash_call("echo hi");
let result = bash_result("hi\n", "", true, Some(0));
assert_eq!(
provider_visible_tool_output(&call, &result, &enabled()),
result.content
);
call.name = "read".to_string();
assert_eq!(
provider_visible_tool_output(&call, &result, &enabled()),
result.content
);
}
#[test]
fn shell_alias_uses_same_compression_rules() {
let mut call = bash_call("git status --short");
call.name = "shell".to_string();
let output = provider_visible_tool_output(
&call,
&bash_result(&large_status(), "", true, Some(0)),
&enabled(),
);
assert!(output.contains("tool: shell"));
assert!(output.contains("rule: bash.git_status"));
}
#[test]
fn git_status_short_summary_counts_files() {
let result = bash_result(
&format!(
"## main...origin/main [ahead 1]\n?? notes.md\n{}",
large_status()
),
"",
true,
Some(0),
);
let output = provider_visible_tool_output(
&bash_call("git status --short --branch"),
&result,
&enabled(),
);
assert!(output.contains("rule: bash.git_status"));
assert!(output.contains("changed_files: 101"));
assert!(output.contains("untracked_files: 1"));
assert!(output.contains("branch: ## main...origin/main [ahead 1]"));
}
#[test]
fn git_status_long_forms_passthrough_exactly() {
let raw_stdout = "On branch main\nnothing to commit, working tree clean\n";
for command in [
"git status",
"git status --branch",
"git --no-pager status",
"git --no-pager status --branch",
] {
let result = bash_result(raw_stdout, "", true, Some(0));
let output = provider_visible_tool_output(&bash_call(command), &result, &enabled());
assert_eq!(output, result.content, "{command}");
}
}
#[test]
fn git_status_short_forms_are_compressed() {
for command in [
"git status --short",
"git status --porcelain",
"git status --porcelain=v1",
"git status -sb",
] {
let output = assert_rule(command, &large_status(), "", "bash.git_status");
assert!(output.contains("changed_files: 100"), "{command}: {output}");
}
}
#[test]
fn git_stderr_diagnostics_passthrough_exactly_for_bash_and_shell() {
for tool_name in ["bash", "shell"] {
for (command, stderr) in [
(
"git status --short",
"fatal: not a git repository (or any of the parent directories): .git\n",
),
(
"git diff --bad-option",
"error: unknown option `--bad-option'\n",
),
(
"git log --oneline",
"fatal: not a git repository (or any of the parent directories): .git\n",
),
] {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result("", stderr, false, Some(128));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
#[test]
fn git_diff_nonzero_exit_with_validated_patch_still_compresses() {
let stdout = large_patch();
let result = bash_result(&stdout, "", false, Some(1));
let output =
provider_visible_tool_output(&bash_call("git diff --exit-code"), &result, &enabled());
assert_ne!(output, result.content);
assert!(output.contains("rule: bash.git_diff"));
assert!(output.contains("files_touched: 1"));
assert!(output.contains("success: false"));
assert!(output.contains("exit_code: 1"));
}
#[test]
fn git_diff_summary_counts_files_hunks_and_lines() {
let stdout = large_patch();
let output = provider_visible_tool_output(
&bash_call("git diff -- src/lib.rs"),
&bash_result(&stdout, "", true, Some(0)),
&enabled(),
);
assert!(output.contains("rule: bash.git_diff"));
assert!(output.contains("files_touched: 1"));
assert!(output.contains("hunks: 1"));
assert!(output.contains("insertions: 100"));
assert!(output.contains("deletions: 100"));
}
#[test]
fn git_diff_unsupported_output_shapes_passthrough_exactly() {
for command in [
"git diff --name-only",
"git diff --name-status",
"git diff --raw",
"git diff --stat",
"git diff --numstat",
"git diff --output-indicator-new=+",
"git diff --output-indicator-old=-",
"git diff --output-indicator-context=.",
"git diff --color=always",
"git diff --word-diff-regex=.",
] {
let result = bash_result("src/lib.rs\n", "", true, Some(0));
let output = provider_visible_tool_output(&bash_call(command), &result, &enabled());
assert_eq!(output, result.content, "{command}");
}
let empty_result = bash_result("", "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("git diff --name-only"),
&empty_result,
&enabled(),
);
assert_eq!(output, empty_result.content);
}
#[test]
fn git_diff_non_patch_output_falls_back_to_raw_content() {
let result = bash_result("only-a-file-name\n", "", true, Some(0));
let output = provider_visible_tool_output(&bash_call("git diff"), &result, &enabled());
assert_eq!(output, result.content);
}
#[test]
fn git_diff_empty_supported_output_passes_through() {
let result = bash_result("", "", true, Some(0));
assert_passthrough_result_for_tool("bash", "git diff", &result);
}
#[test]
fn cargo_check_summary_includes_diagnostics() {
let stderr = "error[E0425]: cannot find value `x` in this scope\nwarning: unused import: `Foo`\nerror: could not compile `demo`\n";
let stderr = format!("{}{stderr}", " Checking dependency v0.1.0\n".repeat(100));
let output = provider_visible_tool_output(
&bash_call("cargo check --all-targets"),
&bash_result("", &stderr, false, Some(101)),
&enabled(),
);
assert!(output.contains("rule: bash.cargo_check"));
assert!(output.contains("exit_code: 101"));
assert!(output.contains("warnings: 1"));
assert!(output.contains("errors: 2"));
assert!(output.contains("compiler_error_codes: E0425"));
}
#[test]
fn cargo_test_summary_includes_failures() {
let stdout = "test tests::fails ... FAILED\ntest result: FAILED. 0 passed; 1 failed\nfailures:\n---- tests::fails stdout ----\nthread 'tests::fails' panicked at src/lib.rs:1:1\n";
let stdout = format!("{}{stdout}", "test tests::passes ... ok\n".repeat(100));
let output = provider_visible_tool_output(
&bash_call("cargo test tool_output_compression"),
&bash_result(&stdout, "", false, Some(101)),
&enabled(),
);
assert!(output.contains("rule: bash.cargo_test"));
assert!(output.contains("- tests::fails"));
assert!(output.contains("test result: FAILED"));
assert!(output.contains("panicked at"));
}
#[test]
fn cargo_passthrough_flags_are_exact_for_bash_and_shell() {
let commands = [
"cargo check -h",
"cargo check --help",
"cargo check -- --help",
"cargo check --timings",
"cargo check --timings=html",
"cargo check --timings=json",
"cargo check --future-incompat-report",
"cargo check --color=always",
"cargo check --color always",
"cargo check --message-format=json",
"cargo check --message-format json",
"cargo check --message-format=json-diagnostic-rendered-ansi",
"cargo test -h",
"cargo test --help",
"cargo test -- --help",
"cargo test --timings",
"cargo test --timings=html",
"cargo test --future-incompat-report",
"cargo test --color=always",
"cargo test --color always",
"cargo test --message-format=json",
"cargo test --message-format json",
"cargo test --nocapture",
"cargo test --nocapture=true",
"cargo test --nocapture true",
"cargo test --show-output",
"cargo test --show-output=true",
"cargo test --show-output true",
"cargo test --no-capture",
"cargo test --no-capture=true",
"cargo test --no-capture true",
"cargo test -- --nocapture",
"cargo test -- --show-output",
"cargo test -- --no-capture",
"cargo test -- --no-capture=true",
"cargo test -- --no-capture true",
"cargo test --list",
"cargo test -- --list",
"cargo test --list=true",
"cargo test -- --list=true",
"cargo test --format=junit",
"cargo test -- --format=junit",
"cargo test --format junit",
"cargo test -- --format junit",
"cargo test --format",
"cargo test -- --format",
"cargo +stable check -h",
"cargo +stable check -- --help",
"cargo +stable check --timings=html",
"cargo +stable check --future-incompat-report",
"cargo +stable check --color always",
"cargo +stable check --message-format json",
"cargo +stable test -- --help",
"cargo +stable test -- --no-capture true",
"cargo +stable test --format junit",
"cargo +stable test -- --format=junit",
"cargo +stable test --nocapture=true",
"cargo +stable test --show-output",
"cargo +stable check --help",
"cargo +stable check -- --timings",
"cargo +stable check -- --timings=html",
"cargo +stable check --color=always",
"cargo +stable check -- --color=always",
"cargo +stable check -- --color always",
"cargo +stable check --message-format=json-diagnostic-rendered-ansi",
"cargo +stable test -h",
"cargo +stable test --timings",
"cargo +stable test --timings=html",
"cargo +stable test --future-incompat-report",
"cargo +stable test --color=always",
"cargo +stable test -- --color always",
"cargo +stable test --message-format=json",
"cargo +stable test --message-format json",
"cargo +stable test --nocapture",
"cargo +stable test --nocapture true",
"cargo +stable test -- --nocapture=true",
"cargo +stable test --show-output=true",
"cargo +stable test -- --show-output",
"cargo +stable test --no-capture",
"cargo +stable test --no-capture=true",
"cargo +stable test -- --no-capture",
"cargo +stable test -- --list",
"cargo +stable test --list=true",
"cargo +stable test -- --list=true",
"cargo +stable test --format=junit",
"cargo +stable test -- --format",
"cargo +stable test -- --format junit",
];
for tool_name in ["bash", "shell"] {
for command in commands {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result("{\"raw\":true}\n", "", true, Some(0));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
#[test]
fn cargo_color_auto_and_never_remain_compressible() {
for command in [
"cargo check --color=auto",
"cargo check --color auto",
"cargo check --color=never",
"cargo check --color never",
"cargo +stable check --color=auto",
"cargo +stable test --color never",
] {
let rule = if command.contains(" test ") {
"bash.cargo_test"
} else {
"bash.cargo_check"
};
assert_rule(
command,
&" Checking dependency v0.1.0\n".repeat(100),
"",
rule,
);
}
}
#[test]
fn cargo_toolchain_qualified_commands_compress_for_bash_and_shell() {
let cases = [
(
"cargo +stable check",
"bash.cargo_check",
"Finished `dev` profile\n",
"",
),
(
"cargo +nightly-2025-01-01 check --all-targets",
"bash.cargo_check",
"Finished `dev` profile\n",
"",
),
(
"cargo +2025 check",
"bash.cargo_check",
"Finished `dev` profile\n",
"",
),
(
"cargo +1.88.0 check",
"bash.cargo_check",
"Finished `dev` profile\n",
"",
),
(
"cargo +x86_64-unknown-linux-gnu test --lib",
"bash.cargo_test",
"test result: ok. 1 passed\n",
"",
),
(
"cargo +custom_toolchain-2 test",
"bash.cargo_test",
"test result: ok. 1 passed\n",
"",
),
];
for tool_name in ["bash", "shell"] {
for (command, rule, stdout, stderr) in cases {
let stdout = format!(
"{}{stdout}",
" Compiling dependency v0.1.0\n".repeat(100)
);
assert_rule_for_tool(tool_name, command, &stdout, stderr, rule);
}
}
}
#[test]
fn cargo_invalid_toolchain_qualifiers_passthrough_exactly() {
let commands = [
"cargo + check",
"cargo ++ check",
"cargo +stable+ check",
"cargo +stable/foo check",
"cargo +stable\\foo check",
"cargo +stable$HOME check",
"cargo +stable* check",
"cargo +stable? check",
"cargo +stable{foo} check",
"cargo +stable:foo check",
"cargo '+stable' check",
"cargo \"+stable\" check",
"cargo +stable +nightly check",
"cargo +stable + check",
"cargo +stable check +nightly",
"cargo +stable check '+nightly'",
"cargo +stable test \"+nightly\"",
"cargo +stable check +",
"cargo +stable",
"cargo +stable build",
"cargo +stable clippy",
"cargo -stable check",
];
for tool_name in ["bash", "shell"] {
for command in commands {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result("raw qualified output\n", "", true, Some(0));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
fn assert_rule_for_tool(
tool_name: &str,
command: &str,
stdout: &str,
stderr: &str,
rule: &str,
) -> String {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = provider_visible_tool_output(
&call,
&bash_result(stdout, stderr, true, Some(0)),
&enabled(),
);
assert!(
result.contains(&format!("rule: {rule}")),
"{tool_name}: {result}"
);
assert!(
result.contains(&format!("command: {command}")),
"{tool_name}: {result}"
);
result
}
fn assert_rule(command: &str, stdout: &str, stderr: &str, rule: &str) -> String {
assert_rule_for_tool("bash", command, stdout, stderr, rule)
}
fn assert_passthrough(command: &str) {
assert_passthrough_for_tool("bash", command);
}
fn assert_passthrough_for_tool(tool_name: &str, command: &str) {
let result = bash_result("raw output\n", "", true, Some(0));
assert_passthrough_result_for_tool(tool_name, command, &result);
}
fn assert_passthrough_result_for_tool(tool_name: &str, command: &str, result: &ToolResult) {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let output = provider_visible_tool_output(&call, result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
#[test]
fn shell_syntax_that_can_mask_output_flags_passes_through_exactly() {
let commands = [
r#"cargo check "--help""#,
"cargo check '--help'",
r"cargo check \--help",
"cargo check $CARGO_HELP",
"cargo check ${CARGO_HELP}",
"cargo check ~",
"cargo check {--help}",
"cargo check --hel*",
r#"cargo check "--message-format=json""#,
"cargo check '--message-format=json'",
r"cargo check \--message-format=json",
"cargo check $CARGO_MESSAGE_FORMAT",
"cargo check --message-format=$CARGO_FORMAT",
r#"cargo test "--nocapture""#,
"cargo test '--show-output'",
r"cargo test \--no-capture",
"cargo test $CARGO_TEST_FLAGS",
"cargo test ~",
r#"cargo test "--list""#,
"cargo test '--format=junit'",
r"cargo test \--format=junit",
"cargo test $CARGO_TEST_FORMAT",
"cargo test {--nocapture}",
"cargo test --no*",
r#"git diff "--name-only""#,
"git diff '--stat'",
r"git diff \--output=patch",
"git diff $GIT_DIFF_FLAG",
"git diff ~",
"git -C ./repo diff '--name-only'",
];
let result = bash_result("", "", true, Some(0));
for tool_name in ["bash", "shell"] {
for command in commands {
assert_passthrough_result_for_tool(tool_name, command, &result);
}
}
}
#[test]
fn git_cwd_diff_prefix_orders_are_supported_for_bash_and_shell() {
let patch = large_patch();
for tool_name in ["bash", "shell"] {
for command in [
"git -C ./repo diff",
"git -C ./repo --no-pager diff",
"git --no-pager -C ./repo diff",
"git -C /tmp/project_1/repo-2.0 diff",
"git -C ./repo_2 diff --exit-code",
"git -C ./repo diff -p",
"git -C ./repo diff --patch",
] {
assert_rule_for_tool(tool_name, command, &patch, "", "bash.git_diff");
}
}
}
#[test]
fn git_cwd_diff_incompatible_flags_passthrough_exactly() {
for prefix in [
"git -C ./repo diff",
"git -C ./repo --no-pager diff",
"git --no-pager -C ./repo diff",
] {
for flag in ["--name-only", "--stat", "--output=patch", "--word-diff"] {
let command = format!("{prefix} {flag}");
for tool_name in ["bash", "shell"] {
let mut call = bash_call(&command);
call.name = tool_name.to_string();
let result = bash_result("raw git output\n", "", true, Some(0));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
}
#[test]
fn git_diff_pathspec_delimiter_scopes_option_validation_for_bash_and_shell() {
let patch = large_patch();
let compressible = [
"git diff -- --name-only",
"git diff -- --no-pager",
"git diff -- -C./other",
"git diff -- --output=patch",
"git -C ./repo diff -- --name-only",
"git -C ./repo diff -- --no-pager",
"git -C ./repo diff -- -C./other",
"git -C ./repo diff -- --git-dir=./other",
"git -C ./repo --no-pager diff -- --name-status",
];
let passthrough = [
"git diff --name-only --",
"git diff --no-pager --",
"git diff -C./other --",
"git -C ./repo diff --name-only --",
"git -C ./repo diff --no-pager --",
"git -C ./repo diff -C./other --",
"git -C ./repo diff --git-dir=./other --",
];
for tool_name in ["bash", "shell"] {
for command in compressible {
assert_rule_for_tool(tool_name, command, &patch, "", "bash.git_diff");
}
for command in passthrough {
assert_passthrough_for_tool(tool_name, command);
}
}
}
#[test]
fn git_cwd_diff_stderr_passthrough_is_exact_for_bash_and_shell() {
let stderr = "fatal: not a git repository\n";
for command in [
"git -C ./repo diff",
"git -C ./repo --no-pager diff",
"git --no-pager -C ./repo diff",
] {
for tool_name in ["bash", "shell"] {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result("", stderr, false, Some(128));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
#[test]
fn git_cwd_diff_nonpatch_output_falls_back_to_raw_content() {
for command in [
"git -C ./repo diff",
"git -C ./repo --no-pager diff",
"git --no-pager -C ./repo diff",
] {
let result = bash_result("only-a-file-name\n", "", true, Some(0));
let output = provider_visible_tool_output(&bash_call(command), &result, &enabled());
assert_eq!(output, result.content, "{command}");
}
}
#[test]
fn git_cwd_diff_empty_output_passes_through() {
for tool_name in ["bash", "shell"] {
for command in [
"git -C ./repo diff",
"git -C ./repo --no-pager diff",
"git --no-pager -C ./repo diff",
] {
let result = bash_result("", "", true, Some(0));
assert_passthrough_result_for_tool(tool_name, command, &result);
}
}
}
#[test]
fn git_cwd_diff_nonzero_exit_with_patch_still_compresses() {
for tool_name in ["bash", "shell"] {
let mut call = bash_call("git -C ./repo diff --exit-code");
call.name = tool_name.to_string();
let result = bash_result(&large_patch(), "", false, Some(1));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert!(
output.contains("rule: bash.git_diff"),
"{tool_name}: {output}"
);
assert!(output.contains("exit_code: 1"), "{tool_name}: {output}");
}
}
#[test]
fn git_cwd_diff_rejected_paths_and_prefixes_passthrough_exactly() {
let commands = [
"git -Cpath diff",
"git -C diff",
"git -C --bad-path diff",
"git -C - diff",
"git -C '' diff",
"git -C \"./repo\" diff",
"git -C './repo' diff",
"git -C ./my\\ repo diff",
"git -C ./repo$PWD diff",
"git -C ~/repo diff",
"git -C ./répo_2 diff",
"git -C ./repo* diff",
"git -C ./repo? diff",
"git -C ./repo[1] diff",
"git -C ./repo{one} diff",
"git -C ./repo;other diff",
"git -C ./repo:other diff",
"git -C ./repo -C ./other diff",
"git -C ./repo diff -C ./other",
"git -C ./repo diff -C./other",
"git --no-pager -C./repo diff",
"git --no-pager -C ./repo -C ./other diff",
"git --git-dir ./repo -C ./repo diff",
"git -C ./repo --git-dir ./repo diff",
"git -C ./repo --pager diff",
"git -C ./repo --no-pager --git-dir ./repo diff",
"git -C ./repo diff --no-pager",
"git -C ./repo diff --pager",
"git -C ./repo diff --paginate",
"git -C ./repo diff --git-dir ./other",
"git -C ./repo diff --git-dir=./other",
"git -C ./repo diff --work-tree ./other",
"git -C ./repo diff --work-tree=./other",
"git -C ./repo diff -c core.foo=bar",
"git -C ./repo diff -cfoo=bar",
"git -C ./repo diff --config-env core.foo=ENV",
"git -C ./repo diff --no-replace-objects",
];
for tool_name in ["bash", "shell"] {
for command in commands {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result("raw rejected git output\n", "", true, Some(0));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
#[test]
fn git_cwd_diff_preserves_pipeline_operator_and_redirect_rules() {
let patch = large_patch();
for tool_name in ["bash", "shell"] {
assert_rule_for_tool(
tool_name,
"git -C ./repo diff 2>&1",
&patch,
"",
"bash.git_diff",
);
for command in [
"git -C ./repo diff | cat",
"git -C ./repo diff && cat patch",
"git -C ./repo diff; cat patch",
"git -C ./repo diff > patch",
"git -C ./repo diff 2>err",
"git -C ./repo diff < input",
"git -C ./repo diff $(cat args)",
"git -C ./repo diff `cat args`",
] {
let mut call = bash_call(command);
call.name = tool_name.to_string();
let result = bash_result(&patch, "", true, Some(0));
let output = provider_visible_tool_output(&call, &result, &enabled());
assert_eq!(output, result.content, "{tool_name}: {command}");
}
}
}
#[test]
fn git_no_pager_status_and_diff_classify_like_base_commands() {
assert_rule("git --no-pager diff", &large_patch(), "", "bash.git_diff");
assert_rule(
"git --no-pager status --short",
&large_status(),
"",
"bash.git_status",
);
assert_passthrough("git --no-pager status --bad");
}
#[test]
fn git_log_oneline_summary_counts_and_caps_subjects() {
let stdout = (1..=100)
.map(|index| format!("abc{index:02} subject {index}"))
.collect::<Vec<_>>()
.join("\n");
let output = assert_rule("git log --oneline -100", &stdout, "", "bash.git_log");
assert!(output.contains("commits: 100"));
assert!(output.contains("subjects:"));
assert!(output.contains("- abc01 subject 1"));
assert!(output.contains("- abc15 subject 15"));
assert!(!output.contains("- abc16 subject 16"));
assert_passthrough("git log --pretty=oneline");
assert_passthrough("git log -5");
assert_rule(
"git --no-pager log --oneline -100",
&stdout,
"",
"bash.git_log",
);
}
#[test]
fn git_log_output_shape_overrides_passthrough_exactly() {
let raw_stdout = "abc123 subject\n file.txt | 2 ++\n";
for command in [
"git log --oneline --stat",
"git log --oneline --patch",
"git log --oneline -p",
"git log --oneline --format=%H",
"git log --oneline --pretty=medium",
"git log --oneline --decorate",
"git log --oneline --decorate=full",
"git log --oneline --parents",
"git log --oneline --children",
] {
let result = bash_result(raw_stdout, "", true, Some(0));
let output = provider_visible_tool_output(&bash_call(command), &result, &enabled());
assert_eq!(output, result.content, "{command}");
}
let result = bash_result(raw_stdout, "", true, Some(0));
let output =
provider_visible_tool_output(&bash_call("git log --oneline"), &result, &enabled());
assert_eq!(output, result.content);
}
#[test]
fn safe_fd_redirects_are_stripped_before_classification() {
assert_rule(
"cargo test --lib 2>&1",
&format!(
"{}test result: ok. 100 passed\n",
"test tests::passes ... ok\n".repeat(100)
),
"",
"bash.cargo_test",
);
assert_rule(
"cargo check 1>&2",
"",
&format!(
"{}Finished `dev` profile\n",
" Checking dependency v0.1.0\n".repeat(100)
),
"bash.cargo_check",
);
assert_passthrough("cargo test < input.txt");
assert_passthrough("cargo test > out.log");
assert_passthrough("cargo test 2>err.log");
assert_passthrough("cargo test >>out.log");
assert_passthrough("cargo test 2>&1foo");
assert_passthrough("cargo test 2 > & 1");
}
#[test]
fn literal_pipelines_always_passthrough_exactly() {
for (command, raw_stdout) in [
("git status --short | wc -l", "2\n"),
("cargo check 2>&1 | wc -l", "17\n"),
("git diff | cat", "raw diff through cat\n"),
("cargo test --lib 2>&1 | tail -25", "tail output\n"),
] {
let result = bash_result(raw_stdout, "", true, Some(0));
let output = provider_visible_tool_output(&bash_call(command), &result, &enabled());
assert_eq!(output, result.content, "{command}");
}
}
#[test]
fn compound_shell_command_rejected() {
let result = bash_result("M README.md\n", "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("git status --short && git diff"),
&result,
&enabled(),
);
assert_eq!(output, result.content);
}
#[test]
fn newline_compound_command_rejected() {
let result = bash_result("test output\n", "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("cargo test\ncat target.log"),
&result,
&enabled(),
);
assert_eq!(output, result.content);
}
#[test]
fn carriage_return_compound_command_rejected() {
let result = bash_result("check output\n", "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("cargo check\rcat target.log"),
&result,
&enabled(),
);
assert_eq!(output, result.content);
}
#[test]
fn single_ampersand_compound_command_rejected() {
let result = bash_result("diff output\n", "", true, Some(0));
let output = provider_visible_tool_output(
&bash_call("git diff & cat target.log"),
&result,
&enabled(),
);
assert_eq!(output, result.content);
}
}