#!/usr/bin/env bash # The fast lint lanes on the uncommitted files only — ruff (check + format) on Python, eslint on the # frontend's code and prettier on all of the frontend — for a check after every edit instead of a # full gate run. It is a convenience, never the gate: scripts/check.sh stays what a merge or a # release requires. # # bash scripts/lint-changed.sh lint what `git status` shows as changed or new # bash scripts/lint-changed.sh --hook the same, as a Claude Code Stop hook: silent and exit 0 when # clean; otherwise the findings on stderr and exit 2, which # hands them back to the agent before it ends its turn # # Measured 2026-10-04: one cold eslint run (type-aware) takes 3.4–4.7 s, so this runs once per turn # (Stop), not after every edit — a warm daemon (eslint_d) would add a dependency and a stray process. set -u cd "$(dirname "$0")/.." || exit 2 hook=0 [ "${1:-}" = --hook ] && hook=1 if [ "$hook" = 1 ]; then # A Stop hook that blocked once is told so on the next stop; let that one through, or an unfixable # finding would keep the agent in a loop. grep -q '"stop_hook_active": *true' && exit 0 fi mapfile -t changed < <(git status --porcelain --untracked-files=all | sed -E 's/^.. //; s/.* -> //' | while read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done) # eslint reads code; prettier reads every file the gate's `prettier --check .` does (a locale JSON, # a stylesheet), and `--ignore-unknown` passes over the ones it has no parser for. py=() fe=() pretty=() for f in "${changed[@]}"; do case "$f" in backend/*.py | scripts/*.py) py+=("$f") ;; esac case "$f" in frontend/*.ts | frontend/*.tsx | frontend/*.js | frontend/*.mjs) fe+=("${f#frontend/}") ;; esac case "$f" in frontend/*) pretty+=("${f#frontend/}") ;; esac done report="$( if [ ${#py[@]} -gt 0 ]; then python -m ruff check --quiet --config backend/ruff.toml "${py[@]}" 2>&1 python -m ruff format --check --quiet --config backend/ruff.toml "${py[@]}" 2>&1 fi if [ ${#fe[@]} -gt 0 ]; then (cd frontend && npx eslint --max-warnings 0 "${fe[@]}" 2>&1) fi if [ ${#pretty[@]} -gt 0 ]; then (cd frontend && npx prettier --check --ignore-unknown --log-level warn "${pretty[@]}" 2>&1) fi )" if [ -z "$report" ]; then [ "$hook" = 1 ] || echo "lint-changed: clean (${#py[@]} py, ${#pretty[@]} frontend)" exit 0 fi if [ "$hook" = 1 ]; then printf 'lint-changed found problems in the uncommitted files:\n%s\n' "$report" >&2 exit 2 fi printf '%s\n' "$report" exit 1