#!/usr/bin/env bash # audit.sh — the repo inventory, in one command. # # Every section here was reconstructed by hand, from scratch, in session after # session: 93 grep sweeps across the four longest ones before a single edit was # made. The answers move slowly and the sweeps did not, so they are written down # once here instead. # # It prints and never writes. A committed inventory file goes stale silently and # then lies; a report you regenerate cannot. # # Read-only. Safe to run at any point, including mid-conflict. # # make audit # everything # make audit SECTION=todo # one section: loc, todo, stubs, docs, tests, gaps set -uo pipefail cd "$(dirname "$0")/.." || exit 1 SECTION="${SECTION:-all}" want() { [ "$SECTION" = all ] || [ "$SECTION" = "$1" ]; } rule() { printf '\n=== %s %s\n' "$1" "$(printf '%.0s=' $(seq 1 $((66 - ${#1}))))"; } # git grep over tracked files only. deps/ and models/ are gitignored and huge, # and a plain grep -r walks into both. g() { git grep -nI "$@" 2>/dev/null; } printf 'Maven repo inventory @ %s (%s)\n' \ "$(git rev-parse --short HEAD 2>/dev/null || echo '?')" \ "$(git log -1 --format=%cs 2>/dev/null || echo '?')" # --- loc ------------------------------------------------------------------- # Non-test Go lines per package. Size is the cheapest proxy for "where does the # complexity actually sit", and it is the first thing every audit asked for. if want loc; then rule "PACKAGES BY LOC (non-test)" for d in $(find ./cmd ./internal ./pkg -maxdepth 2 -type d 2>/dev/null | sort); do files=$(find "$d" -maxdepth 1 -name '*.go' ! -name '*_test.go' 2>/dev/null) [ -z "$files" ] && continue n=$(printf '%s\n' "$files" | wc -l) l=$(printf '%s\0' $files | xargs -0 cat 2>/dev/null | wc -l) printf '%7d %3d files %s\n' "$l" "$n" "$d" done | sort -rn fi # --- todo ------------------------------------------------------------------ if want todo; then rule "TODO / FIXME / XXX / HACK / BUG (non-test)" g -E '(^|[^a-zA-Z])(TODO|FIXME|XXX|HACK|BUG:)' -- 'cmd/**/*.go' 'internal/**/*.go' 'pkg/**/*.go' \ | grep -v '_test\.go:' | sed 's/^/ /' || echo " none" fi # --- stubs ----------------------------------------------------------------- # Code only, never doc comments. "not wired" is this repo's design vocabulary # for a nil dependency and appears in ~30 comments that describe working code, # so searching prose here reports the architecture back as a gap. Likewise # internal/ipc/unimplemented.go is skipped whole: the file IS the deliberate # Unimplemented*Server pattern, not 60 missing methods. "placeholder" is not a # term here either -- it names real identifiers (SQL placeholders, # Deck.RequirePlaceholder, tokenPlaceholder) and matched 16 working lines. if want stubs; then rule "STUBS / NOT IMPLEMENTED (code, non-test)" g -iE 'not (yet )?implemented|unimplemented|пока не умею|panic\("TODO' \ -- 'cmd/**/*.go' 'internal/**/*.go' 'pkg/**/*.go' \ | grep -v '_test\.go:' \ | grep -v '^internal/ipc/unimplemented\.go:' \ | grep -vE '^[^:]+:[0-9]+:[[:space:]]*//' \ | sed 's/^/ /' || echo " none" # CoreAPI stub parity. A stub missing from unimplemented.go breaks the build # via `var _ CoreAPI = UnimplementedCoreAPI{}`. A stub left behind after its # method leaves an interface compiles forever and is caught by nothing, so it # is counted here until V-652 turns it into a test. printf '\n CoreAPI stub parity:\n' python3 - <<'PY' 2>/dev/null | sed 's/^/ /' || echo " (skipped: python3 unavailable)" import re src = open('internal/ipc/coreapi.go').read() decl = set() for m in re.finditer(r'type (\w+API) interface \{(.*?)\n\}', src, re.S): decl |= set(re.findall(r'^\t([A-Z]\w*)\(', m.group(2), re.M)) stub = set(re.findall(r'func \(UnimplementedCoreAPI\) (\w+)\(', open('internal/ipc/unimplemented.go').read())) print(f"{len(decl)} declared, {len(stub)} stubbed") for name in sorted(stub - decl): print(f"STALE {name} (stubbed, on no interface)") for name in sorted(decl - stub): print(f"MISSING {name} (declared, no stub)") PY fi # --- docs ------------------------------------------------------------------ # Living tier only. docs/evals/ are dated measurements that are never edited # after the day, and docs/archive/ is dead by definition, so neither can be # stale. Age is against the recorded date, not against HEAD: every commit moves # HEAD, so a sha comparison would mark the whole tier stale every day. if want docs; then rule "LIVING DOCS — Last verified" today=$(date +%s) for f in docs/*.md; do [ -e "$f" ] || continue line=$(grep -m1 -o 'Last verified: *[0-9-]\{8,10\}[^ ]*\( *@ *[0-9a-f]\{7,\}\)\?' "$f" 2>/dev/null) if [ -z "$line" ]; then printf ' %-44s %s\n' "$(basename "$f")" "MISSING" continue fi d=$(printf '%s' "$line" | grep -o '[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' | head -1) age="" if [ -n "$d" ] && when=$(date -d "$d" +%s 2>/dev/null); then days=$(( (today - when) / 86400 )) age="${days}d" [ "$days" -gt 30 ] && age="${days}d <-- STALE" fi printf ' %-44s %-34s %s\n' "$(basename "$f")" "${line#Last verified: }" "$age" done fi # --- tests ----------------------------------------------------------------- if want tests; then rule "TEST SHAPE" printf ' benchmarks : %s\n' "$(g -c 'func Benchmark' -- '**/*_test.go' | awk -F: '{s+=$2} END{print s+0}')" printf ' fuzz : %s\n' "$(g -c 'func Fuzz' -- '**/*_test.go' | awk -F: '{s+=$2} END{print s+0}')" printf ' table t.Run: %s\n' "$(g -c 't.Run(' -- '**/*_test.go' | awk -F: '{s+=$2} END{print s+0}')" printf ' golden files: %s\n' "$(g -l 'golden' -- '**/*_test.go' | wc -l)" fi # --- gaps ------------------------------------------------------------------ # A package with production code and no test file at all. Not a verdict — some # are pure wiring — but it is the list worth looking at before adding more. if want gaps; then rule "PACKAGES WITH NO TEST FILE" found=0 for d in $(find ./cmd ./internal ./pkg -maxdepth 2 -type d 2>/dev/null | sort); do ls "$d"/*.go >/dev/null 2>&1 || continue ls "$d"/*_test.go >/dev/null 2>&1 && continue n=$(ls "$d"/*.go 2>/dev/null | wc -l) l=$(cat "$d"/*.go 2>/dev/null | wc -l) printf ' %6d lines %2d files %s\n' "$l" "$n" "$d" found=1 done [ "$found" = 0 ] && echo " none" fi exit 0