bctx-weave 0.1.25

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

// stylelint default formatter: "path/to/file.css" header lines
static FILE_HEADER_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[^\s].*\.(css|scss|sass|less|styl|vue|svelte)[^\n]*$").unwrap());
// " line:col  ✕/⚠  message  rule-name" diagnostic lines
static DIAG_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^\s+(\d+:\d+)\s+([✕⚠×!]|error|warning)\s+(.+?)\s{2,}(\S+)\s*$").unwrap()
});

// ── compress stylelint output ─────────────────────────────────────────────────

pub fn compress_stylelint(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);

    // Group diagnostics by rule, counting occurrences
    let mut file_sections: Vec<String> = Vec::new();
    let mut current_file: Option<String> = None;
    let mut current_diags: Vec<String> = Vec::new();
    let mut error_count = 0usize;
    let mut warning_count = 0usize;

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

        if FILE_HEADER_RE.is_match(t) && !t.starts_with(' ') {
            // Flush previous file section
            if let Some(file) = current_file.take() {
                if !current_diags.is_empty() {
                    file_sections.push(file);
                    file_sections.append(&mut current_diags);
                }
            }
            current_file = Some(line.to_string());
        } else if let Some(caps) = DIAG_RE.captures(line) {
            let loc = caps.get(1).map(|m| m.as_str()).unwrap_or("?");
            let severity = caps.get(2).map(|m| m.as_str()).unwrap_or("?");
            let msg = caps.get(3).map(|m| m.as_str()).unwrap_or("?").trim();
            let rule = caps.get(4).map(|m| m.as_str()).unwrap_or("?");
            let is_error =
                severity == "" || severity == "×" || severity == "!" || severity == "error";
            if is_error {
                error_count += 1;
            } else {
                warning_count += 1;
            }
            current_diags.push(format!("  {loc}  {severity}  {msg}  {rule}"));
        } else if t.contains("problem") || t.contains("error") || t.contains("warning") {
            // Summary line
            if let Some(file) = current_file.take() {
                if !current_diags.is_empty() {
                    file_sections.push(file);
                    file_sections.append(&mut current_diags);
                }
            }
            file_sections.push(line.to_string());
        }
    }

    // Flush last file
    if let Some(file) = current_file {
        if !current_diags.is_empty() {
            file_sections.push(file);
            file_sections.extend(current_diags);
        }
    }

    if file_sections.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }

    let mut result = file_sections.join("\n");
    if error_count > 0 || warning_count > 0 {
        result.push_str(&format!(
            "\n\nSummary: {} errors, {} warnings",
            error_count, warning_count
        ));
    }
    result
}

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

    #[test]
    fn groups_errors_by_file() {
        let raw = "src/styles/main.css\n  1:5  ✕  Expected indentation of 2 spaces  indentation\n  2:1  ⚠  Unexpected empty line before closing brace  block-closing-brace-empty-line-before\n\nsrc/styles/button.css\n  5:3  ✕  Unexpected invalid hex color \"#gggggg\"  color-no-invalid-hex\n\n3 problems (2 errors, 1 warning)\n";
        let out = compress_stylelint(raw);
        assert!(out.contains("main.css"), "{out}");
        assert!(out.contains("button.css"), "{out}");
        assert!(out.contains("indentation"), "{out}");
        assert!(out.contains("color-no-invalid-hex"), "{out}");
    }

    #[test]
    fn passthrough_on_no_issues() {
        let raw = "No issues found\n";
        let out = compress_stylelint(raw);
        assert!(out.contains("No issues found") || out.is_empty(), "{out}");
    }

    #[test]
    fn handles_scss_files() {
        let raw = "src/components/_button.scss\n  10:5  ✕  Unexpected vendor-prefix \"-webkit-\"  vendor-prefix\n\n1 problem (1 error, 0 warnings)\n";
        let out = compress_stylelint(raw);
        assert!(out.contains("_button.scss"), "{out}");
        assert!(out.contains("vendor-prefix"), "{out}");
    }
}