#!/bin/bash # # CloserApp — automated WIRING / dead-feature scanner (Pass N + discovery pre-check) # # Catches the "silent dead feature" class that QA found live (N-001 Bucket List, N-002 # Date Builder): a feature looks like an empty/initial state but is actually non-functional # because a required id is never wired, a setter is never called, or saved data is never read. # # ⛔ CLAUDE: This is a LIVING tool — IMPROVE IT whenever you discover a new dead-wiring / orphan # failure mode. Add the new grep, keep the script self-contained + runnable from the project # root, write findings to stdout + SCAN_OUTPUT, and update this header with what you added. # Do not remove an existing check unless it is provably wrong (note why in the header). # # Usage: # ./scripts/wiring-scan.sh > /tmp/claude-wiring-scan-$(date +%Y%m%d).md # cat /tmp/claude-wiring-scan-$(date +%Y%m%d).md # # Tiers: # 🔴 CRITICAL — a `fun setX(...)` in a *ViewModel.kt with ZERO callers. The screen/nav never # pushes the value, so any op gated on it silently no-ops (N-001 class). # 🟠 MAJOR — a repository/data-source READ method (`fun observe*/get*/load*`) with no caller # in `ui/`. Data the app can write but never displays → orphan feature (N-002 class). # 🟡 REVIEW — a `if (x.isEmpty()/== null) return` / `?: return` bail-guard inside a *ViewModel. # Legitimate, BUT confirm something actually provides `x` — an un-provided guard is # exactly how a feature goes silently dead. Verify each by persisting real data and # reading it back from Firestore (admin), not by trusting the empty-state render. # # Findings are HINTS, not proofs — a flagged item can be intentional (e.g. a setter used only in a # @Preview, or a read method genuinely pending UI). Confirm each against live behavior + ground truth. # set -uo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC_DIR="$PROJECT_ROOT/app/src/main/java/app/closer" UI_DIR="$SRC_DIR/ui" SCAN_OUTPUT="${1:-/tmp/claude-wiring-scan-$(date +%Y%m%d).md}" : > "$SCAN_OUTPUT" log() { echo "$1" | tee -a "$SCAN_OUTPUT"; } crit=0; major=0; review=0 log "# Wiring / dead-feature scan — $(date '+%Y-%m-%d %H:%M')" log "" log "> Hints, not proofs. Confirm each against live behavior + a Firestore admin read." log "" # ── Tier 1: dead setters (CRITICAL) ─────────────────────────────────────────── log "## 🔴 CRITICAL — ViewModel \`setX(...)\` with no caller (N-001 class)" log "" while IFS= read -r decl; do file="${decl%%:*}" name="$(echo "$decl" | grep -oE 'fun set[A-Z][A-Za-z0-9]*' | head -1 | sed 's/^fun //')" [ -z "$name" ] && continue # callers = references to `name(` or `::name` anywhere under SRC, minus the declaration itself callers="$(grep -rEn "(\.|::| )${name}\(|::${name}\b" "$SRC_DIR" 2>/dev/null | grep -v "fun ${name}(" | grep -cv '^$')" if [ "${callers:-0}" -eq 0 ]; then log "- 🔴 \`${name}()\` — **no callers** — ${file#$PROJECT_ROOT/}" crit=$((crit+1)) fi done < <(grep -rEn 'fun set[A-Z][A-Za-z0-9]*\(' "$SRC_DIR" --include=*ViewModel.kt 2>/dev/null) [ "$crit" -eq 0 ] && log "- none ✅" log "" # ── Tier 2: orphan readers (MAJOR) ──────────────────────────────────────────── log "## 🟠 MAJOR — repository/data-source read method never called from \`ui/\` (N-002 class)" log "" # Scan REPOSITORY INTERFACES only (*Repository.kt, which the glob excludes *RepositoryImpl.kt # + *DataSource.kt) — those are the read entry points a VM/screen would call. A repo read method # with no ui/ caller means the app can fetch the data but no screen ever shows it (N-002). while IFS= read -r decl; do file="${decl%%:*}" name="$(echo "$decl" | grep -oE 'fun (observe|get|load)[A-Z][A-Za-z0-9]*' | head -1 | sed 's/^fun //')" [ -z "$name" ] && continue ui_callers="$(grep -rEn "(\.|::| )${name}\(|::${name}\b" "$UI_DIR" 2>/dev/null | grep -cv '^$')" if [ "${ui_callers:-0}" -eq 0 ]; then log "- 🟠 \`${name}()\` — **no \`ui/\` caller** (written data may never be displayed) — ${file#$PROJECT_ROOT/}" major=$((major+1)) fi done < <(grep -rEn 'fun (observe|get|load)[A-Z][A-Za-z0-9]*\(' "$SRC_DIR" --include=*Repository.kt 2>/dev/null) [ "$major" -eq 0 ] && log "- none ✅" log "" # ── Tier 3: silent bail-guards in ViewModels (REVIEW) ───────────────────────── log "## 🟡 REVIEW — \`if (x.isEmpty()/==null) return\` / \`?: return\` bail-guards in ViewModels" log "" log "Each is a point where the feature silently does nothing if state was never initialized." log "Confirm something provides the value (persist real data → read it back via admin)." log "Auth-presence guards (\`currentUserId ?: return\`) are filtered out — they're never the wiring gap;" log "the risky ones gate on a STATE field a \`setX\`/resolver is supposed to populate (e.g. coupleId)." log "" while IFS= read -r hit; do log "- 🟡 ${hit#$PROJECT_ROOT/}" review=$((review+1)) done < <(grep -rEn 'if \([a-zA-Z0-9_.]+\.(isEmpty\(\)|isBlank\(\))\) return|[a-zA-Z0-9_.]+ \?: return\b' "$SRC_DIR" --include=*ViewModel.kt 2>/dev/null \ | grep -viE 'currentUser|currentUserId|authRepository|FirebaseAuth|firstOrNull|\.find ?\{|getOrNull') [ "$review" -eq 0 ] && log "- none" log "" log "## Summary" log "" log "| Tier | Count |" log "|---|---|" log "| 🔴 CRITICAL (dead setters) | $crit |" log "| 🟠 MAJOR (orphan readers) | $major |" log "| 🟡 REVIEW (bail-guards) | $review |" log "" log "_Record these counts in ClaudeQACoverage.md under Pass N before driving the interactive features._" # Exit non-zero only on CRITICAL so CI/automation can gate on it. [ "$crit" -gt 0 ] && exit 1 || exit 0