bctx-weave 0.1.11

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

// "Starting supabase_db_..." container spin-up lines
static CONTAINER_START_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^[^\n]*(Starting|Stopping) supabase_[^\n]*\n?").unwrap());
// Docker pull layer progress lines
static DOCKER_LAYER_RE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"(?m)^[a-f0-9]{12}: (?:Pulling|Pull complete|Already exists|Waiting|Verifying|Download)[^\n]*\n?").unwrap()
});
// "Digest: sha256:..." after docker pulls
static DIGEST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^Digest: sha256:[^\n]*\n?").unwrap());
// "Status: Downloaded newer image..." docker pull result (useful but verbose if repeated)
static PULL_STATUS_RE: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"(?m)^Status: Downloaded newer image[^\n]*\n?").unwrap());

// ── supabase db push / migration ──────────────────────────────────────────────

pub fn compress_db(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = DOCKER_LAYER_RE.replace_all(&cleaned, "");
    let s = DIGEST_RE.replace_all(&s, "");
    let s = PULL_STATUS_RE.replace_all(&s, "");
    // Note: APPLYING_RE is not stripped here — migration filenames are useful signal

    // Keep migration names, errors, warnings, success confirmations
    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains(".sql")
                    || t.contains("migration")
                    || t.contains("Migration")
                    || t.contains("schema")
                    || t.contains("Applied")
                    || t.contains("Skipped")
                    || t.contains("success")
                    || t.contains("Success")
                    || t.contains("error")
                    || t.contains("Error")
                    || t.contains("warning")
                    || t.contains("Warning"))
        })
        .collect();

    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── supabase functions deploy ─────────────────────────────────────────────────

pub fn compress_functions(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let useful: Vec<&str> = cleaned
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("Deploying")
                    || t.contains("Deployed")
                    || t.contains("Function")
                    || t.contains("function")
                    || t.contains("URL:")
                    || t.contains("url:")
                    || t.contains("supabase.co")
                    || t.contains("error")
                    || t.contains("Error")
                    || t.contains("")
                    || t.contains("Done"))
        })
        .collect();
    if useful.is_empty() {
        return compactor::collapse_blanks(&cleaned);
    }
    useful.join("\n")
}

// ── supabase start / stop ─────────────────────────────────────────────────────

pub fn compress_start(raw: &str) -> String {
    let cleaned = compactor::normalise(raw);
    let s = CONTAINER_START_RE.replace_all(&cleaned, "");
    let s = DOCKER_LAYER_RE.replace_all(&s, "");
    let s = DIGEST_RE.replace_all(&s, "");

    // Keep the final service URL table and errors
    let useful: Vec<&str> = s
        .lines()
        .filter(|l| {
            let t = l.trim();
            !t.is_empty()
                && (t.contains("API URL")
                    || t.contains("GraphQL")
                    || t.contains("DB URL")
                    || t.contains("Studio URL")
                    || t.contains("Inbucket")
                    || t.contains("JWT")
                    || t.contains("anon key")
                    || t.contains("service_role")
                    || t.contains("supabase started")
                    || t.starts_with("         ")
                    || t.contains("error")
                    || t.contains("Error"))
        })
        .collect();

    if useful.is_empty() {
        return compactor::collapse_blanks(&s);
    }
    useful.join("\n")
}

// ── supabase gen types ────────────────────────────────────────────────────────

pub fn compress_gen(raw: &str) -> String {
    // Type generation produces full TypeScript — passthrough but truncate
    let cleaned = compactor::normalise(raw);
    let lines: Vec<&str> = cleaned.lines().collect();
    if lines.len() > 60 {
        return format!(
            "{}\n… [{} more lines — redirect to file with > types.ts] …",
            lines[..60].join("\n"),
            lines.len() - 60
        );
    }
    cleaned
}

// ── supabase status ───────────────────────────────────────────────────────────

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

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

pub fn compress_supabase(subcmd: &str, raw: &str) -> String {
    let sub = subcmd.trim();
    if sub.starts_with("db") || sub.starts_with("migration") {
        return compress_db(raw);
    }
    if sub.starts_with("functions") || sub.starts_with("fn") {
        return compress_functions(raw);
    }
    if sub.starts_with("start") {
        return compress_start(raw);
    }
    if sub.starts_with("stop") {
        let cleaned = compactor::normalise(raw);
        let s = CONTAINER_START_RE.replace_all(&cleaned, "");
        return compactor::collapse_blanks(&s);
    }
    if sub.starts_with("gen") {
        return compress_gen(raw);
    }
    if sub.starts_with("status") {
        return compress_status(raw);
    }
    // link, unlink, projects, orgs, etc.
    compactor::normalise(raw)
}

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

    #[test]
    fn db_push_keeps_migration_names() {
        let raw = "Connecting to remote database...\nApplying migration 20240101000000_add_users.sql...\nApplying migration 20240102000000_add_posts.sql...\nMigration applied successfully\n";
        let out = compress_db(raw);
        assert!(
            out.contains("add_users.sql") || out.contains("migration"),
            "{out}"
        );
        assert!(out.contains("success"), "{out}");
    }

    #[test]
    fn db_strips_docker_pull_noise() {
        let raw = "a1b2c3d4e5f6: Pulling from library/postgres\na1b2c3d4e5f6: Pull complete\nDigest: sha256:abc123\nApplying migration 20240101_init.sql...\nApplied 1 migration\n";
        let out = compress_db(raw);
        assert!(!out.contains("Pull complete"), "{out}");
        assert!(!out.contains("Digest:"), "{out}");
        assert!(out.contains("Applied"), "{out}");
    }

    #[test]
    fn start_strips_container_spin_up() {
        let raw = "Starting supabase_db_myapp...\nStarting supabase_auth_myapp...\nStarting supabase_rest_myapp...\nAPI URL: http://localhost:54321\nDB URL: postgresql://postgres:postgres@localhost:54322/postgres\nstudio started\n";
        let out = compress_start(raw);
        assert!(!out.contains("Starting supabase_"), "{out}");
        assert!(out.contains("API URL"), "{out}");
        assert!(out.contains("DB URL"), "{out}");
    }

    #[test]
    fn gen_truncates_large_type_output() {
        let lines: Vec<String> = (0..80).map(|i| format!("  type{i}: string;")).collect();
        let out = compress_gen(&lines.join("\n"));
        assert!(out.contains("more lines"), "{out}");
    }
}