vojo/docs/known-tech-debt-lint/diff.sh

91 lines
3.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Compare current `npm run typecheck` output to the known tech-debt snapshot.
# Emits ONLY new errors introduced relative to the snapshot — does not dump
# the full output, so agents can read the result without burning context.
#
# Comparison is line/col-insensitive: each error's `(L,C):` location is masked
# to `(_,_):` before sorting + comm, so a pure line shift (e.g. one added/
# removed line above the error) doesn't trigger a phantom NEW + fixed pair.
# Re-run `npm run typecheck` to see real positions for any errors flagged here.
#
# Caveat: this checks the working tree, not the staged worktree. If you stage
# a fix but leave it unstaged, or vice versa, the diff reports the working-tree
# state. For a strict pre-commit gate, run from a clean stash-apply state.
#
# `npm run check:eslint` is now a normal green check (0 errors); no snapshot
# needed there. Run it directly if you want to see warnings.
#
# Usage:
# bash docs/known-tech-debt-lint/diff.sh
set -u
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BASELINE_DIR="$ROOT/docs/known-tech-debt-lint"
TC_BASE="$BASELINE_DIR/typecheck.snapshot.txt"
# Temp files registered for cleanup on any exit path (success, error, ^C).
TMP_FILES=()
cleanup() { [ "${#TMP_FILES[@]}" -gt 0 ] && rm -f "${TMP_FILES[@]}"; }
trap cleanup EXIT INT TERM
# Tracks whether any new errors were introduced. Set to 1 by run_typecheck_diff
# when delta > 0; script exits with this code at end so CI / runbooks can use
# the script as a gate (e.g. `bash diff.sh && echo OK`).
NEW_ERRORS=0
run_typecheck_diff() {
echo "=== typecheck diff vs known-tech-debt snapshot ==="
local now tc_rc
now="$(mktemp)"
TMP_FILES+=("$now")
( cd "$ROOT" && npm run typecheck ) >"$now" 2>&1
tc_rc=$?
# Sanity-check: distinguish "tsc ran and reported errors" (rc=2, expected on
# this baseline) from "tsc/npm/node failed to run at all" (rc=other, broken
# toolchain). Without this guard a rc=127 "tsc: not found" or rc=1 npm error
# could produce stdout with no "error TS" lines and the diff would falsely
# report "no new errors" / "incidentally fixed: 33".
if [ "$tc_rc" -ne 0 ] && [ "$tc_rc" -ne 2 ] && ! grep -q "error TS" "$now"; then
echo " ERROR: 'npm run typecheck' did not run cleanly (exit=$tc_rc):"
sed 's/^/ /' "$now"
NEW_ERRORS=2
return
fi
# Mask `(line,col):` so a pure line shift doesn't change an error's identity.
# We compare on the masked form; for NEW lines we display the masked form (the
# real position is reproducible by running `npm run typecheck` directly).
# `sort` (NOT `sort -u`): we want to preserve cardinality so that two identical
# masked errors in the same file aren't collapsed to one — a regression that
# adds a duplicate error would otherwise be hidden by the first occurrence.
local mask_re='s/\([0-9]+,[0-9]+\):/(_,_):/'
local now_masked base_masked
now_masked="$(mktemp)"
base_masked="$(mktemp)"
TMP_FILES+=("$now_masked" "$base_masked")
sed -E "$mask_re" "$now" | grep -E "error TS" | sort > "$now_masked"
sed -E "$mask_re" "$TC_BASE" | grep -E "error TS" | sort > "$base_masked"
local new
new="$(comm -23 "$now_masked" "$base_masked")"
if [ -z "$new" ]; then
echo " no new typecheck errors"
else
local count
count="$(printf '%s\n' "$new" | wc -l)"
echo " NEW errors: $count (line/col masked — run \`npm run typecheck\` for real positions)"
printf '%s\n' "$new" | sed 's/^/ /'
NEW_ERRORS=1
fi
local fixed
fixed="$(comm -13 "$now_masked" "$base_masked")"
if [ -n "$fixed" ]; then
local fcount
fcount="$(printf '%s\n' "$fixed" | wc -l)"
echo " (incidentally fixed: $fcount)"
fi
}
run_typecheck_diff
exit "$NEW_ERRORS"