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;
use std::collections::HashMap;

// golangci-lint offense: "path/to/file.go:10:5: message (linter-name)"
static OFFENSE_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^([^:]+:\d+:\d+): (.+) \(([a-zA-Z0-9_-]+)\)$").unwrap());
// "level [linter-name] message" format (text reporter)
static LEVEL_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"^(WARN|ERROR|INFO)\s+\[([a-zA-Z0-9_-]+)\]").unwrap());
// golangci-lint progress/info lines: "level=info ..." and "time=..." structured logs
static PROGRESS_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^(level=info|time=)[^\n]*\n?").unwrap());

pub fn compress_golangci(raw: &str, exit_code: i32) -> String {
    let cleaned = compactor::normalise(raw);
    let s = PROGRESS_RE.replace_all(&cleaned, "");

    if exit_code == 0 {
        return "golangci-lint: no issues found".to_string();
    }

    // Group offenses by linter name
    let mut linter_counts: HashMap<String, usize> = HashMap::new();
    let mut error_lines: Vec<String> = Vec::new();

    for line in s.lines() {
        let t = line.trim();
        if t.is_empty() {
            continue;
        }

        if let Some(caps) = OFFENSE_RE.captures(t) {
            let linter = caps[3].to_string();
            *linter_counts.entry(linter).or_insert(0) += 1;
            continue;
        }
        if let Some(caps) = LEVEL_RE.captures(t) {
            let linter = caps[2].to_string();
            *linter_counts.entry(linter).or_insert(0) += 1;
            continue;
        }
        // Non-offense lines (build errors, fatal, summary)
        if t.starts_with("FAIL")
            || t.contains("issue")
            || t.contains("error")
            || t.starts_with("level=error")
        {
            error_lines.push(line.to_string());
        }
    }

    let mut grouped: Vec<String> = Vec::new();
    for (linter, count) in &linter_counts {
        if *count > 1 {
            grouped.push(format!("{linter}{count})"));
        } else {
            grouped.push(linter.clone());
        }
    }
    grouped.sort();

    let mut result = grouped;
    result.extend(error_lines);
    compactor::collapse_blanks(&result.join("\n"))
}

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

    #[test]
    fn success_returns_clean_message() {
        let raw =
            "level=info msg=\"Running linters...\"\nlevel=info msg=\"Finished linters in 2.1s\"\n";
        let out = compress_golangci(raw, 0);
        assert!(
            out.contains("no issues") || out.is_empty() || !out.contains("Running"),
            "{out}"
        );
    }

    #[test]
    fn groups_repeated_linter_offenses() {
        let raw =
            (0..5)
                .map(|i| format!("pkg/foo.go:{i}:1: use of unsafe.Pointer (govet)\n"))
                .chain((0..3).map(|i| {
                    format!("pkg/bar.go:{i}:1: exported function lacks comment (golint)\n")
                }))
                .collect::<String>();
        let out = compress_golangci(&raw, 1);
        assert!(out.contains("×5") || out.contains("(×5)"), "govet: {out}");
        assert!(out.contains("×3") || out.contains("(×3)"), "golint: {out}");
    }

    #[test]
    fn strips_progress_info_lines() {
        let raw = "level=info msg=\"Running linters...\"\ntime=\"12:00\" level=info msg=\"Starting analysis\"\npkg/a.go:1:1: something wrong (staticcheck)\n";
        let out = compress_golangci(raw, 1);
        assert!(!out.contains("Running linters"), "{out}");
        assert!(!out.contains("Starting analysis"), "{out}");
    }
}