bctx-weave 0.1.4

bctx-weave — FilterMesh lens pipeline, CLI interception, domain compression
Documentation
use forge::signal::compactor;
use once_cell::sync::Lazy;
use regex::Regex;

#[allow(dead_code)]
static CF_RESOURCE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\d{4}-\d{2}-\d{2}T[^\s]+ [A-Z_]+ [A-Z_]+ [^\n]+\n?").unwrap());
static PROGRESS_BAR_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^\s*[-=]{3,}[^\n]*\n?").unwrap());

// ── aws cloudformation ────────────────────────────────────────────────────────

pub fn compress_cloudformation(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Keep only FAILED/ROLLBACK/COMPLETE status lines, strip IN_PROGRESS noise
    let out: Vec<&str> = cleaned
        .lines()
        .filter(|l| !l.contains("_IN_PROGRESS") || l.contains("FAILED") || l.contains("ROLLBACK"))
        .collect();
    compactor::collapse_blanks(&out.join("\n"))
}

// ── aws s3 cp / sync ──────────────────────────────────────────────────────────

pub fn compress_s3(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PROGRESS_BAR_RE.replace_all(&cleaned, "");
    // Keep "upload:" / "download:" / "copy:" / "delete:" lines + errors
    let out: Vec<&str> = s
        .lines()
        .filter(|l| {
            l.trim_start().starts_with("upload:")
                || l.trim_start().starts_with("download:")
                || l.trim_start().starts_with("copy:")
                || l.trim_start().starts_with("delete:")
                || l.contains("error")
                || l.contains("Error")
                || l.contains("fatal")
        })
        .collect();
    if out.is_empty() {
        s.into_owned()
    } else {
        out.join("\n")
    }
}

// ── aws ec2 / generic json ────────────────────────────────────────────────────

pub fn compress_generic_aws(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Detect large JSON responses and truncate
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() > 40 {
        return format!(
            "{}\n... [{} more lines — use --query to filter] ...",
            lines[..40].join("\n"),
            lines.len() - 40
        );
    }
    cleaned
}

// ── aws logs (CloudWatch) ─────────────────────────────────────────────────────

pub fn compress_logs(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    super::super::sys::log_dedup(&cleaned)
}

// ── aws lambda ────────────────────────────────────────────────────────────────

pub fn compress_lambda(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip verbose fields rarely useful in task context
    let noisy: &[&str] = &[
        "\"FunctionArn\"",
        "\"Role\"",
        "\"CodeSha256\"",
        "\"RevisionId\"",
        "\"LastModified\"",
        "\"CodeSize\"",
        "\"Layers\"",
        "\"VpcConfig\"",
        "\"TracingConfig\"",
    ];
    let out: Vec<&str> = cleaned
        .lines()
        .filter(|l| noisy.iter().all(|n| !l.contains(n)))
        .collect();
    compactor::collapse_blanks(&out.join("\n"))
}

// ── aws ecs ───────────────────────────────────────────────────────────────────

pub fn compress_ecs(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().filter(|l| !l.trim().is_empty()).collect();
    if lines.len() <= 30 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more lines] ...",
        lines[..30].join("\n"),
        lines.len() - 30
    )
}

// ── aws iam ───────────────────────────────────────────────────────────────────

pub fn compress_iam(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    // Strip verbose inline-policy JSON blobs and base64 — keep names/ARNs/dates
    let noisy: &[&str] = &[
        "\"PolicyDocument\":",
        "\"AssumeRolePolicyDocument\":",
        "\"Statement\":",
        "\"Action\":",
        "\"Effect\":",
        "\"Resource\":",
        "\"Condition\":",
        "\"Principal\":",
    ];
    let out: Vec<&str> = cleaned
        .lines()
        .filter(|l| noisy.iter().all(|n| !l.trim().starts_with(n)))
        .collect();
    let lines = out.as_slice();
    if lines.len() <= 40 {
        return lines.join("\n");
    }
    format!(
        "{}\n... [{} more lines — use --query to filter] ...",
        lines[..40].join("\n"),
        lines.len() - 40
    )
}

// ── aws sts (caller identity / assume-role) ───────────────────────────────────

pub fn compress_sts(raw: &str) -> String {
    // sts get-caller-identity is tiny — normalise only
    compactor::normalise(raw)
}

// ── top-level dispatcher ──────────────────────────────────────────────────────

pub fn compress_aws(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("cloudformation") {
        return compress_cloudformation(raw);
    }
    if sub.starts_with("s3") {
        return compress_s3(raw);
    }
    if sub.starts_with("logs") {
        return compress_logs(raw);
    }
    if sub.starts_with("lambda") {
        return compress_lambda(raw);
    }
    if sub.starts_with("ecs") {
        return compress_ecs(raw);
    }
    if sub.starts_with("iam") {
        return compress_iam(raw);
    }
    if sub.starts_with("sts") {
        return compress_sts(raw);
    }
    compress_generic_aws(raw)
}

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

    #[test]
    fn cloudformation_strips_in_progress() {
        let raw = "MyBucket AWS::S3::Bucket CREATE_IN_PROGRESS\nMyBucket AWS::S3::Bucket CREATE_COMPLETE\n";
        let out = compress_cloudformation(raw);
        assert!(!out.contains("CREATE_IN_PROGRESS"), "{out}");
        assert!(out.contains("CREATE_COMPLETE"));
    }

    #[test]
    fn s3_keeps_upload_lines() {
        let raw =
            "upload: ./foo.txt to s3://bucket/foo.txt\nCompleted 1.0 MiB/1.0 MiB (500 KiB/s)\n";
        let out = compress_s3(raw);
        assert!(out.contains("upload:"));
    }

    #[test]
    fn generic_truncates_large_json() {
        let raw = (0..50)
            .map(|i| format!("  \"key{i}\": \"value{i}\","))
            .collect::<Vec<_>>()
            .join("\n");
        let out = compress_generic_aws(&raw);
        assert!(out.contains("more lines"), "{out}");
    }

    #[test]
    fn lambda_strips_noisy_fields() {
        let raw = "{\n  \"FunctionName\": \"my-fn\",\n  \"FunctionArn\": \"arn:aws:lambda:us-east-1:123:function:my-fn\",\n  \"Runtime\": \"nodejs18.x\",\n  \"Role\": \"arn:aws:iam::123:role/role\",\n  \"CodeSha256\": \"abc123\"\n}\n";
        let out = compress_lambda(raw);
        assert!(!out.contains("FunctionArn"), "{out}");
        assert!(!out.contains("CodeSha256"), "{out}");
        assert!(out.contains("FunctionName"), "{out}");
        assert!(out.contains("Runtime"), "{out}");
    }

    #[test]
    fn logs_deduplicates_repeated_entries() {
        let raw = "[ERROR] timeout\n[ERROR] timeout\n[ERROR] timeout\n[INFO] recovered\n";
        let out = compress_logs(raw);
        assert!(
            out.contains("repeated") || out.contains("[ERROR] timeout"),
            "{out}"
        );
        assert!(out.contains("[INFO] recovered"), "{out}");
    }
}