batpak 0.2.0

Event sourcing with causal graphs and policy gates. Sync API, zero async.
Documentation
#!/usr/bin/env bash
# coverage-feedback: like `mix test --cover` — runs tests with coverage, pings
# back WHAT wasn't covered. Not just a number. Uncovered functions, uncovered
# lines, grouped by module. Exits non-zero if below threshold.
#
# Usage:
#   ./scripts/coverage-feedback              # human report
#   ./scripts/coverage-feedback --ci         # fail if below 70% line coverage
#   ./scripts/coverage-feedback --json       # raw llvm-cov JSON to stdout
#   ./scripts/coverage-feedback --threshold 80  # custom threshold
#
# Requires: cargo-llvm-cov (auto-installs if missing)
# [SPEC:scripts/coverage-feedback]

set -euo pipefail

THRESHOLD="${THRESHOLD:-70}"
MODE="human"

while [[ $# -gt 0 ]]; do
    case "$1" in
        --ci)        MODE="ci"; shift ;;
        --json)      MODE="json"; shift ;;
        --threshold) THRESHOLD="$2"; shift 2 ;;
        *)           echo "Unknown arg: $1"; exit 1 ;;
    esac
done

# Auto-install cargo-llvm-cov if missing
if ! command -v cargo-llvm-cov &>/dev/null; then
    echo "cargo-llvm-cov not found. Attempting install..."
    if ! cargo install cargo-llvm-cov --locked 2>&1; then
        echo ""
        echo "ERROR: Failed to install cargo-llvm-cov."
        echo ""
        echo "Install manually with one of:"
        echo "  cargo install cargo-llvm-cov --locked"
        echo "  cargo binstall cargo-llvm-cov"
        echo "  brew install cargo-llvm-cov    (macOS)"
        echo ""
        echo "Or in CI, use: taiki-e/install-action@cargo-llvm-cov"
        exit 2
    fi
fi

EXPORT_DIR="$(mktemp -d)"
trap 'rm -rf "$EXPORT_DIR"' EXIT

echo "Running tests with coverage instrumentation..."
echo ""

# Run coverage, capture JSON export for analysis + text for display.
cargo llvm-cov nextest \
    --all-features \
    --json \
    --output-path "$EXPORT_DIR/coverage.json" \
    2>&1 | tail -1

if [[ "$MODE" == "json" ]]; then
    cat "$EXPORT_DIR/coverage.json"
    exit 0
fi

# Also generate the human-readable text report
cargo llvm-cov report \
    --all-features \
    --text \
    2>/dev/null > "$EXPORT_DIR/text-report.txt" || true

# ---- Parse JSON and ping back what's NOT covered ----
# Uses only bash + awk (no jq dependency).

echo ""
echo "================================================================"
echo "  COVERAGE FEEDBACK"
echo "================================================================"
echo ""

# Extract totals from the JSON summary.
# llvm-cov export JSON has: data[0].totals.{lines,functions,regions}
# Each has: count, covered, percent
LINES_TOTAL=$(grep -o '"lines":{[^}]*}' "$EXPORT_DIR/coverage.json" | tail -1 | grep -o '"count":[0-9]*' | head -1 | cut -d: -f2)
LINES_COVERED=$(grep -o '"lines":{[^}]*}' "$EXPORT_DIR/coverage.json" | tail -1 | grep -o '"covered":[0-9]*' | head -1 | cut -d: -f2)
FUNCS_TOTAL=$(grep -o '"functions":{[^}]*}' "$EXPORT_DIR/coverage.json" | tail -1 | grep -o '"count":[0-9]*' | head -1 | cut -d: -f2)
FUNCS_COVERED=$(grep -o '"functions":{[^}]*}' "$EXPORT_DIR/coverage.json" | tail -1 | grep -o '"covered":[0-9]*' | head -1 | cut -d: -f2)

if [[ -z "$LINES_TOTAL" || "$LINES_TOTAL" == "0" ]]; then
    echo "WARNING: Could not parse coverage JSON. Raw text report:"
    echo ""
    cat "$EXPORT_DIR/text-report.txt"
    exit 0
fi

LINE_PCT=$((LINES_COVERED * 100 / LINES_TOTAL))
FUNC_PCT=$((FUNCS_COVERED * 100 / FUNCS_TOTAL))

echo "  Lines:     $LINES_COVERED / $LINES_TOTAL ($LINE_PCT%)"
echo "  Functions: $FUNCS_COVERED / $FUNCS_TOTAL ($FUNC_PCT%)"
echo ""

# ---- Ping back: what's NOT covered ----
# Parse the text report for files with < 100% coverage and show uncovered lines.

echo "----------------------------------------------------------------"
echo "  UNCOVERED (the ping-back)"
echo "----------------------------------------------------------------"
echo ""

# The text report from llvm-cov has format:
#   filename    regions    miss    cover    lines    miss    cover ...
# Extract files with missed lines > 0

if [[ -f "$EXPORT_DIR/text-report.txt" ]]; then
    # Show per-file summary, filtering to files with missed coverage
    awk '
    /^-/ { next }
    /^Filename/ { next }
    /^TOTAL/ { next }
    /^$/ { next }
    {
        # Fields vary but file is $1, look for miss counts
        if (NF >= 7) {
            file = $1
            region_miss = $3
            line_miss = $6
            if (region_miss + 0 > 0 || line_miss + 0 > 0) {
                printf "  %-50s  regions_miss=%-4s  lines_miss=%-4s\n", file, region_miss, line_miss
            }
        }
    }
    ' "$EXPORT_DIR/text-report.txt"
fi

echo ""
echo "----------------------------------------------------------------"
echo "  UNCOVERED FUNCTIONS"
echo "----------------------------------------------------------------"
echo ""

# Extract function-level coverage from JSON.
# Functions with execution_count == 0 are completely untested.
# This is the real "ping back" — it tells you WHICH functions to write tests for.
python3 -c "
import json, sys

with open('$EXPORT_DIR/coverage.json') as f:
    data = json.load(f)

uncovered = []
for file_data in data.get('data', [{}])[0].get('functions', []):
    name = file_data.get('name', '')
    count = file_data.get('count', 0)
    filenames = file_data.get('filenames', [])
    regions = file_data.get('regions', [])
    if count == 0 and 'test' not in name.lower():
        loc = filenames[0] if filenames else '?'
        # Clean up the function name (demangle)
        short = name.split('::')[-1] if '::' in name else name
        mod_path = '::'.join(name.split('::')[:-1])
        uncovered.append((loc, mod_path, short))

uncovered.sort()
if not uncovered:
    print('  All functions covered!')
else:
    current_file = ''
    for loc, mod_path, fn_name in uncovered:
        if loc != current_file:
            current_file = loc
            print(f'  {loc}:')
        print(f'    -> {mod_path}::{fn_name}()')
    print()
    print(f'  Total uncovered functions: {len(uncovered)}')
" 2>/dev/null || echo "  (install python3 for function-level detail, or use --json)"

echo ""
echo "================================================================"

# ---- Threshold check ----
if [[ "$MODE" == "ci" ]]; then
    if [[ "$LINE_PCT" -lt "$THRESHOLD" ]]; then
        echo ""
        echo "FAIL: Line coverage $LINE_PCT% < threshold $THRESHOLD%"
        echo "Fix the uncovered functions listed above."
        exit 1
    else
        echo ""
        echo "PASS: Line coverage $LINE_PCT% >= threshold $THRESHOLD%"
    fi
fi