diff --git a/CLAUDE.md b/CLAUDE.md index f27f9a8..aaf08d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,16 +58,27 @@ make build-web # single daemon (pure-Go ones: web/waked/poll/caldav build w make test # go test -race across ./internal/... ./cmd/... with CGO env set ``` -Run a single test (must carry the CGO env for packages that touch STT/TTS/voice): +Run one package or one test with `make t`. **Do not hand-write the CGO preamble.** +Past sessions pasted it about 390 times. That is where the shell-quoting failures +came from. This box runs zsh, so an unquoted `-run Test*` or `--include=*.go` +dies on "no matches found" before `go` is ever reached. ```sh -CGO_CFLAGS="-I$(pwd)/deps/include -I$(pwd)/deps/whisper.cpp/ggml/include" \ -CGO_LDFLAGS="-L$(pwd)/deps/lib -Wl,-rpath,$(pwd)/deps/lib" \ -LD_LIBRARY_PATH="$(pwd)/deps/lib" \ -deps/go/go/bin/go test -run TestName ./internal/router/ +make t PKG=./internal/router/ +make t PKG=./cmd/mavend/ RUN=TestSimulator +make t PKG=./internal/router/eval/ RUN='TestONNX' V=1 # V=1 for -v, RACE=0 to drop -race ``` -Pure-Go packages (`router`, `memory`, `mavweb`, …) run under a plain `go test ./pkg/`. +`t` carries `-race`, so a green `make t` cannot turn red under `make test`. It carries +`-count=1`, so a cached PASS from before your edit is never mistaken for a result. + +It also sets `MAVEN_ONNX_LIB`, which the hand-written recipe did not. The four +`TestONNX*` measurements self-skip when that variable is unset. The run still prints +`ok`. So every targeted eval done the old way reported the hash ratchet while reading +as a real embedder score. + +Pure-Go packages (`router`, `memory`, `mavweb`, …) also run under a plain `go test ./pkg/`, +but `make t` works everywhere and is one thing to remember. ## The daemons (`cmd/`) diff --git a/Makefile b/Makefile index 7abe3b4..c6ac993 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ PIPER_BIN := $(shell pwd)/deps/piper/piper PIPER_MODEL := $(shell pwd)/models/tts/ru_RU-irina-medium.onnx PIPER_ESPEAK := $(shell pwd)/deps/piper/espeak-ng-data -.PHONY: simulate stt-fixtures test-stt-golden all build build-stt build-tts build-daemon build-client build-waked build-web build-poll build-caldav clean test fmt-check vet run-stt run-tts run-web download-embedder deps-go deps-sentinel tidy eval-router eval-reach eval-recall eval-phrasing eval-models build-gpud +.PHONY: t audit simulate stt-fixtures test-stt-golden all build build-stt build-tts build-daemon build-client build-waked build-web build-poll build-caldav clean test fmt-check vet run-stt run-tts run-web download-embedder deps-go deps-sentinel tidy eval-router eval-reach eval-recall eval-phrasing eval-models build-gpud all: build @@ -128,6 +128,35 @@ test: fmt-check vet CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" LD_LIBRARY_PATH="$(shell pwd)/deps/lib" \ $(GO) test -race -coverprofile=coverage.out ./internal/... ./cmd/... +# t — run ONE package or ONE test with the toolchain env already wired. This is +# the iteration target; `test` is the gate. Reach for it instead of pasting the +# CGO_CFLAGS/CGO_LDFLAGS/LD_LIBRARY_PATH preamble by hand, which is how it was +# done ~390 times across past sessions and is where the shell-quoting failures +# came from -- the interactive shell here is zsh, and an unquoted `-run Test*` +# or `--include=*.go` dies on "no matches found" before go ever starts. +# +# make t # whole tree (same scope as `test`) +# make t PKG=./internal/router/ +# make t PKG=./cmd/mavend/ RUN=TestSimulator +# make t PKG=./internal/router/eval/ RUN='TestONNX' V=1 +# make t PKG=./internal/store/ RACE=0 # drop -race when iterating hot +# +# -race is on by default so a green `make t` cannot turn red under `make test`. +# -count=1 because a cached PASS from before your edit is worse than no answer. +# MAVEN_ONNX_LIB is set for the same reason: the four TestONNX* measurements +# self-skip when it is unset, so a targeted eval run would otherwise report the +# deterministic hash ratchet and look like it scored the real embedder. +PKG ?= ./internal/... ./cmd/... +RUN ?= +V ?= +RACE ?= 1 + +t: + CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" LD_LIBRARY_PATH="$(shell pwd)/deps/lib" \ + MAVEN_ONNX_LIB="$(MAVEN_ONNX_LIB)" \ + $(GO) test $(if $(V),-v,) $(if $(filter-out 0,$(RACE)),-race,) -count=1 \ + $(if $(RUN),-run '$(RUN)',) $(PKG) + # eval-router — score the held-out RU routing fixture (internal/router/eval). # Verbose so the report tables land in the terminal. MAVEN_ONNX_LIB points the # prod-representative baseline at the vendored runtime; override it or set it @@ -189,6 +218,16 @@ eval-models: # scores the fixtures against ggml-small and self-skips when the model is # absent, and TestGoldenFixturesAreCanonical, which checks the committed audio # and the manifest with no model at all. +# audit — the repo inventory: LOC per package, open TODOs, real stubs, living-doc +# staleness, test shape, packages with no test. Read-only, prints, writes nothing. +# Run it instead of rebuilding the same greps by hand; past sessions spent 93 of +# them on this before their first edit. SECTION=loc|todo|stubs|docs|tests|gaps +# narrows it. +SECTION ?= all + +audit: + @SECTION="$(SECTION)" ./scripts/audit.sh + stt-fixtures: ./scripts/gen-stt-fixtures.sh diff --git a/scripts/audit.sh b/scripts/audit.sh new file mode 100755 index 0000000..3be6550 --- /dev/null +++ b/scripts/audit.sh @@ -0,0 +1,143 @@ +#!/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