vallum 0.8.5

Security boundary between AI coding agents and your shell — redacts secrets, neutralizes prompt injection, sanitizes untrusted terminal output, audits every command.
Documentation
use super::CommandOptimizer;

pub struct PytestOptimizer;

impl CommandOptimizer for PytestOptimizer {
    fn name(&self) -> &'static str {
        "pytest"
    }

    fn matches(&self, cmd: &str, args: &[String]) -> bool {
        if cmd == "pytest" {
            return true;
        }

        matches!(cmd, "python" | "python3")
            && args.len() >= 2
            && args[0] == "-m"
            && args[1] == "pytest"
    }

    fn optimize(&self, input: &str) -> Option<String> {
        if input.lines().count() < 10 {
            return None;
        }

        let mut progress_hidden = 0usize;
        let mut preserved = Vec::new();

        for line in input.lines() {
            let trimmed = line.trim();

            if is_progress_line(trimmed) {
                progress_hidden += 1;
                continue;
            }

            if should_preserve(trimmed) {
                preserved.push(line.to_string());
            }
        }

        if progress_hidden == 0 {
            return None;
        }

        let mut out = String::new();
        out.push_str(&format!("Progress: {} lines hidden\n", progress_hidden));

        if !preserved.is_empty() {
            out.push('\n');
            out.push_str(&preserved.join("\n"));
            out.push('\n');
        }

        out.push_str("[summarized by vallum]\n");
        Some(out)
    }
}

fn is_progress_line(trimmed: &str) -> bool {
    if !(trimmed.contains(".py") || trimmed.contains("::")) {
        return false;
    }

    let Some(last) = trimmed.split_whitespace().last() else {
        return false;
    };

    !last.is_empty()
        && last
            .chars()
            .all(|ch| matches!(ch, '.' | 'F' | 'E' | 's' | 'x' | 'X'))
}

fn should_preserve(trimmed: &str) -> bool {
    trimmed.is_empty()
        || trimmed.starts_with("================")
        || trimmed.starts_with("platform ")
        || trimmed.starts_with("rootdir:")
        || trimmed.starts_with("configfile:")
        || trimmed.starts_with("plugins:")
        || trimmed.starts_with("collected ")
        || trimmed.starts_with("FAILED ")
        || trimmed.starts_with("ERROR ")
        || trimmed.starts_with("E   ")
        || trimmed.starts_with(">")
        || trimmed.starts_with("________________")
}

#[cfg(test)]
mod tests {
    use super::*;

    fn args(items: &[&str]) -> Vec<String> {
        items.iter().map(|s| s.to_string()).collect()
    }

    #[test]
    fn matches_pytest_binary() {
        let opt = PytestOptimizer;
        assert!(opt.matches("pytest", &[]));
    }

    #[test]
    fn matches_python_module_pytest() {
        let opt = PytestOptimizer;
        assert!(opt.matches("python3", &args(&["-m", "pytest"])));
    }

    #[test]
    fn summarizes_progress_lines() {
        let opt = PytestOptimizer;
        let input = concat!(
            "============================= test session starts =============================\n",
            "platform darwin -- Python 3.11.0, pytest-8.0.0, pluggy-1.0.0\n",
            "rootdir: /tmp/app\n",
            "plugins: anyio-4.0.0\n",
            "collected 12 items\n",
            "\n",
            "tests/test_alpha.py ........\n",
            "tests/test_beta.py ...F.\n",
            "\n",
            "=================================== FAILURES ===================================\n",
            "______________________________ test_example ___________________________________\n",
            "E   assert 1 == 2\n",
            "\n",
            "=========================== short test summary info ============================\n",
            "FAILED tests/test_beta.py::test_example - assert 1 == 2\n",
            "========================= 1 failed, 11 passed in 0.50s =========================\n"
        );

        let result = opt.optimize(input).unwrap();
        assert!(result.contains("Progress: 2 lines hidden"));
        assert!(result.contains("collected 12 items"));
        assert!(result.contains("1 failed, 11 passed"));
        assert!(result.contains("[summarized by vallum]"));
    }
}