Implement silent background cleanup of notes that have been in trash for more than 30 days, fulfilling the promise already shown in the Trash view UI. Safety model (all 5 checks must pass per file): - _trashed: true in frontmatter - _trashed_at present and parseable as date - Date strictly >30 days ago - File exists on disk - File path inside vault root Uses trash::delete (OS trash) with fs::remove_file fallback. Triggers on app launch and window focus (max once/hour). Audit log at .laputa/purge.log. Dry-run mode for testing. Also fixes pre-commit hook to read CodeScene thresholds from .codescene-thresholds instead of hardcoded values, matching the ratchet mechanism documented in CLAUDE.md. ADR-0042 documents the safety model. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
74 lines
3.3 KiB
Bash
Executable File
74 lines
3.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# Pre-commit: same checks as CI. Fix here, not in CI.
|
|
set -e
|
|
echo "🔍 Pre-commit checks..."
|
|
|
|
# Lint + types (only if TS files staged)
|
|
STAGED_TS=$(git diff --cached --name-only | grep -E '\.(ts|tsx)$' || true)
|
|
if [ -n "$STAGED_TS" ]; then
|
|
echo " → lint + tsc..."
|
|
pnpm lint --quiet
|
|
npx tsc --noEmit
|
|
fi
|
|
|
|
# Unit tests
|
|
echo " → tests..."
|
|
pnpm test --run --silent
|
|
|
|
echo "✅ Pre-commit passed"
|
|
|
|
# ── CodeScene Code Health gate ────────────────────────────────────────────
|
|
# Blocks commit if scores drop below thresholds in .codescene-thresholds.
|
|
# Thresholds are a ratchet — only go up, auto-updated after each successful push.
|
|
# Note: remote scores lag behind local changes (update after push + re-analysis).
|
|
# If check fails: extract hooks, split components, reduce complexity.
|
|
# Never use eslint-disable, #[allow(...)], or `as any`.
|
|
echo "🏥 CodeScene code health check..."
|
|
THRESHOLDS_FILE=".codescene-thresholds"
|
|
if [ -z "$CODESCENE_PAT" ] || [ -z "$CODESCENE_PROJECT_ID" ]; then
|
|
echo " ⚠️ CODESCENE_PAT or CODESCENE_PROJECT_ID not set — skipping (CI will enforce)"
|
|
elif [ ! -f "$THRESHOLDS_FILE" ]; then
|
|
echo " ⚠️ $THRESHOLDS_FILE not found — skipping (CI will enforce)"
|
|
else
|
|
# Read ratchet thresholds
|
|
HOTSPOT_THRESHOLD=$(grep '^HOTSPOT_THRESHOLD=' "$THRESHOLDS_FILE" | cut -d= -f2)
|
|
AVERAGE_THRESHOLD=$(grep '^AVERAGE_THRESHOLD=' "$THRESHOLDS_FILE" | cut -d= -f2)
|
|
if [ -z "$HOTSPOT_THRESHOLD" ] || [ -z "$AVERAGE_THRESHOLD" ]; then
|
|
echo " ⚠️ Could not parse thresholds from $THRESHOLDS_FILE — skipping"
|
|
else
|
|
API_RESPONSE=$(curl -sf \
|
|
-H "Authorization: Bearer $CODESCENE_PAT" \
|
|
-H "Accept: application/json" \
|
|
"https://api.codescene.io/v2/projects/$CODESCENE_PROJECT_ID" 2>/dev/null || echo "{}")
|
|
HOTSPOT_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['hotspot_code_health']['now'])" 2>/dev/null || echo "")
|
|
AVERAGE_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['code_health']['now'])" 2>/dev/null || echo "")
|
|
if [ -z "$HOTSPOT_SCORE" ] || [ -z "$AVERAGE_SCORE" ]; then
|
|
echo " ⚠️ Could not fetch CodeScene scores — skipping (CI will enforce)"
|
|
else
|
|
echo " Hotspot Code Health: $HOTSPOT_SCORE (threshold: $HOTSPOT_THRESHOLD)"
|
|
echo " Average Code Health: $AVERAGE_SCORE (threshold: $AVERAGE_THRESHOLD)"
|
|
python3 -c "
|
|
import sys
|
|
hotspot = float('$HOTSPOT_SCORE')
|
|
average = float('$AVERAGE_SCORE')
|
|
h_thresh = float('$HOTSPOT_THRESHOLD')
|
|
a_thresh = float('$AVERAGE_THRESHOLD')
|
|
failed = False
|
|
if hotspot < h_thresh:
|
|
print(f'FAIL: Hotspot Code Health {hotspot:.2f} < {h_thresh} — extract hooks, split components, reduce complexity')
|
|
failed = True
|
|
else:
|
|
print(f'OK: Hotspot {hotspot:.2f} >= {h_thresh}')
|
|
if average < a_thresh:
|
|
print(f'FAIL: Average Code Health {average:.2f} < {a_thresh} — recent changes introduced regressions in non-hotspot files')
|
|
print(' Review files changed in this task. Never use eslint-disable, #[allow(...)], or as any to bypass.')
|
|
failed = True
|
|
else:
|
|
print(f'OK: Average {average:.2f} >= {a_thresh}')
|
|
if failed:
|
|
sys.exit(1)
|
|
" || exit 1
|
|
fi
|
|
fi
|
|
fi
|