#!/usr/bin/env bash
# Canonical verification for a completed worktree branch.
# Mirrors CLAUDE.md § Verification. Live-agent marks are excluded (they need a
# real provider session and are not appropriate for an automated landing gate).
#
# `live_daemon` is excluded for a different reason and is worth stating so nobody
# "fixes" it back in: it needs no provider and would run fine here, but it starts
# a daemon and spawns real shells, and landing is meant to be cheap. CI runs it
# on ubuntu-latest and windows-latest on every push, which is where a host-shaped
# regression has to be caught anyway. Run it by hand with
# `uv run pytest tests/test_live_daemon.py -m live_daemon`.
#
# This is safe to run in several worktrees at once. Measured 2026-07-29, three
# concurrent runs took 58/58/62s versus about 60s solo, with 996 passing in each.
# CLAUDE.md records why the suite is parallel-safe.
set -euo pipefail

step() { printf '\n=== %s ===\n' "$*" >&2; }

# The gate saturates every core (`-n auto` pytest, tsc, the node suites), and
# several worktrees legitimately run it at once - so it yields the CPU to the
# interactive fleet instead of competing with it. The whole run drops to
# below-normal priority here, before anything is spawned, and every child
# inherits it: Windows passes the priority class down only from a BelowNormal or
# Idle parent, which is exactly the class chosen, and POSIX children inherit
# niceness unconditionally. Measured 2026-08-30: three concurrent gates plus a
# renderer suite held all 32 logical CPUs at ~85% and delayed typed input across
# every live session; a below-normal batch run costs the gate almost nothing on
# an otherwise-idle host and everything it was stealing on a busy one.
# `MUX_KEEP_PRIORITY=1` opts out (timing the gate itself is the one reason to).
# Lowering is best-effort and never fails the gate, but always says what it did.
if [[ -n "${MUX_KEEP_PRIORITY:-}" ]]; then
  printf 'priority: kept (MUX_KEEP_PRIORITY is set)\n' >&2
else
  case "$(uname -s)" in
    MINGW*|MSYS*|CYGWIN*)
      # $$ is the MSYS pid; the Windows one lives in /proc/<pid>/winpid.
      winpid=$(cat "/proc/$$/winpid" 2>/dev/null || true)
      if [[ -n "$winpid" ]] && powershell.exe -NoProfile -Command "(Get-Process -Id $winpid).PriorityClass = 'BelowNormal'" >/dev/null 2>&1; then
        printf 'priority: below normal (winpid %s)\n' "$winpid" >&2
      else
        printf 'priority: unchanged (could not lower winpid=%s)\n' "${winpid:-unknown}" >&2
      fi
      ;;
    *)
      # Use the absolute-priority form shared by BSD/macOS and util-linux.
      # macOS defines `-n` as an increment, which makes `renice -n 10` a
      # different contract from the exact niceness reported on the next line.
      if renice 10 -p $$ >/dev/null 2>&1; then
        current_nice=$(ps -o nice= -p $$ 2>/dev/null | tr -d '[:space:]' || true)
      else
        current_nice=""
      fi
      if [[ "$current_nice" == "10" ]]; then
        printf 'priority: niceness 10 (pid %s)\n' "$$" >&2
      else
        printf 'priority: unchanged (renice refused or was not applied)\n' >&2
      fi
      ;;
  esac
fi

step "pytest"
# Distributed across the host's physical cores (`-n auto`). The suite is
# function-scoped throughout, keeps its state in per-test tmp dirs, and binds no
# ports, so workers do not interfere; CLAUDE.md § Verification records what was
# measured and why it holds.
#
# `--dist loadgroup` rather than `load` or `worksteal`: it is the only mode that
# honours `xdist_group`, and the real-console files (`test_conpty_integration.py`,
# `test_pty_supervisor.py`) carry that mark so each stays on a single worker
# instead of running its wall-clock-sensitive pseudoconsole tests concurrently
# with itself. Tests without the mark are distributed exactly as in `load`.
#
# `--durations=25` stays on so a test that grows slow is visible in the gate's
# own output rather than only in a run someone thought to ask for.
uv run pytest tests -q -n auto --dist loadgroup --durations=25 -m "not live_agent and not live_subagent and not live_telemetry and not live_quota and not live_automations and not live_mcp and not live_edge_tts and not live_daemon and not live_model_flag"

step "ruff"
uv run ruff check src/swe_mux tests packaging

step "mypy"
uv run mypy

step "mypy (per-platform implementations)"
# Each host's PTY, process-ownership, and secret-store implementation is
# typechecked under its own platform, on every host. Without this, half the
# platform code is invisible to the type checker on whichever machine happens to
# be running the gate, and only breaks when someone builds on the other one.
uv run mypy --config-file mypy-platform.toml --platform linux \
  src/swe_mux/pty_backend_posix.py \
  src/swe_mux/posix_process_group.py \
  src/swe_mux/posix_guardian.py
uv run mypy --config-file mypy-platform.toml --platform win32 \
  src/swe_mux/pty_backend_windows.py \
  src/swe_mux/win_jobobj.py \
  src/swe_mux/secret_cipher_windows.py

step "frontend tsc"
( cd frontend && npx tsc --noEmit )

step "frontend test tsc"
# The base tsconfig only includes src, so this typechecks everything under test/ - the
# Node unit suites and the Playwright renderer harnesses - against the real components
# and types. It catches a harness prop that has drifted from the component it mounts
# before it can rot into a runtime-only Playwright failure (as pane-layout.spec.ts did),
# and a hand-built unit-test fixture that no longer matches the type it fakes.
( cd frontend && npx tsc --noEmit -p tsconfig.test.json )

step "frontend tests"
( cd frontend && npm test )

printf '\nverification passed\n' >&2
