Skip to main content

aft/compress/
mod.rs

1//! Output compression for hoisted bash. Phase 0 stub; Phase 1 Tracks A and E fill in.
2
3pub mod bun;
4pub mod cargo;
5pub mod generic;
6pub mod git;
7pub mod npm;
8pub mod pnpm;
9pub mod pytest;
10pub mod tsc;
11
12use crate::context::AppContext;
13use bun::BunCompressor;
14use cargo::CargoCompressor;
15use generic::{strip_ansi, GenericCompressor};
16use git::GitCompressor;
17use npm::NpmCompressor;
18use pnpm::PnpmCompressor;
19use pytest::PytestCompressor;
20use tsc::TscCompressor;
21
22/// A `Compressor` knows how to reduce one specific command's output to fewer
23/// tokens while preserving the information the agent needs.
24pub trait Compressor {
25    /// Returns true if this compressor handles the given command head + args.
26    /// Called after generic detection (ANSI strip, dedup) so this is per-command logic only.
27    fn matches(&self, command: &str) -> bool;
28
29    /// Compress the output. Original is left untouched if compression fails.
30    fn compress(&self, command: &str, output: &str) -> String;
31}
32
33/// Top-level dispatch: try each registered compressor; fall back to generic.
34pub fn compress(command: &str, output: String, ctx: &AppContext) -> String {
35    if !ctx.config().experimental_bash_compress {
36        return output;
37    }
38
39    let stripped = strip_ansi(&output);
40    let compressors: [&dyn Compressor; 7] = [
41        &GitCompressor,
42        &CargoCompressor,
43        &TscCompressor,
44        &NpmCompressor,
45        &BunCompressor,
46        &PnpmCompressor,
47        &PytestCompressor,
48    ];
49    for compressor in compressors {
50        if compressor.matches(command) {
51            return compressor.compress(command, &stripped);
52        }
53    }
54
55    GenericCompressor.compress(command, &stripped)
56}