diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06a8a97..1e2afcb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,10 @@ jobs: run: python -m ruff check --select F401,F811 src scripts tests - name: Complexity proving test run: bash scripts/test-check-complexity.sh + - name: Vulture proving test + run: bash scripts/test-check-vulture.sh + - name: Vulture dead code + run: bash scripts/check-vulture.sh src scripts tests - name: Unit tests run: python -m unittest discover -s tests -v env: diff --git a/pyproject.toml b/pyproject.toml index d380502..fbad0f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.10" dependencies = ["mcp>=1.9.0,<2"] [project.optional-dependencies] -dev = ["ruff>=0.11", "lizard==1.24.0"] +dev = ["ruff>=0.11", "lizard==1.24.0", "vulture>=2.14"] [project.scripts] local-coding-slm = "local_coding_slm.server:main" diff --git a/scripts/check-vulture.sh b/scripts/check-vulture.sh new file mode 100755 index 0000000..788dcb2 --- /dev/null +++ b/scripts/check-vulture.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Fail on dead code vulture reports outside scripts/vulture-whitelist.py. +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WHITELIST="${VULTURE_WHITELIST:-$ROOT/scripts/vulture-whitelist.py}" +MIN="${VULTURE_MIN_CONFIDENCE:-60}" +if [[ $# -eq 0 ]]; then + echo "usage: check-vulture.sh ..." >&2 + exit 2 +fi +exec python3 -m vulture "$@" "$WHITELIST" --min-confidence "$MIN" diff --git a/scripts/test-check-vulture.sh b/scripts/test-check-vulture.sh new file mode 100755 index 0000000..d151fc4 --- /dev/null +++ b/scripts/test-check-vulture.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Proving test: an allowlisted unused name stays green; a new unused function fails. +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CHECK="${SCRIPT_DIR}/check-vulture.sh" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +cat > "$TMP/known_dead.py" <<'PY' +def known_dead(): + return 1 +PY +cat > "$TMP/whitelist.py" <<'PY' +known_dead # unused function (vulture whitelist) +PY +if ! VULTURE_WHITELIST="$TMP/whitelist.py" bash "$CHECK" "$TMP/known_dead.py"; then + echo "expected PASS on allowlisted unused name" >&2 + exit 1 +fi +cat > "$TMP/new_dead.py" <<'PY' +def brand_new_unused(): + return 2 +PY +if VULTURE_WHITELIST="$TMP/whitelist.py" bash "$CHECK" "$TMP/known_dead.py" "$TMP/new_dead.py"; then + echo "expected FAIL on a new unused function" >&2 + exit 1 +fi +echo "test-check-vulture: PASS" diff --git a/scripts/vulture-whitelist.py b/scripts/vulture-whitelist.py new file mode 100644 index 0000000..d7c40bd --- /dev/null +++ b/scripts/vulture-whitelist.py @@ -0,0 +1,16 @@ +# First-bake vulture allowlist. Add unused names here; do not delete this file. +job_by_id # unused function (src/local_coding_slm/eval/jobs.py:114) +response_chars # unused variable (src/local_coding_slm/eval/record.py:22) +exhausted # unused variable (src/local_coding_slm/eval/record.py:34) +_.last_packet # unused attribute (src/local_coding_slm/eval/review.py:69) +_.last_packet # unused attribute (src/local_coding_slm/eval/review.py:77) +_.log_message # unused method (src/local_coding_slm/eval/stub_ollama.py:82) +format # unused variable (src/local_coding_slm/eval/stub_ollama.py:82) +_.do_GET # unused method (src/local_coding_slm/eval/stub_ollama.py:93) +_.do_POST # unused method (src/local_coding_slm/eval/stub_ollama.py:108) +local_status # unused function (src/local_coding_slm/server.py:85) +local_code # unused function (src/local_coding_slm/server.py:91) +local_refactor # unused function (src/local_coding_slm/server.py:104) +local_generate_tests # unused function (src/local_coding_slm/server.py:117) +local_explain # unused function (src/local_coding_slm/server.py:132) +local_review # unused function (src/local_coding_slm/server.py:145)