Files
jellybit/scripts/gate.sh
T
avandClaude Opus 4.8 f21f2f9ca8 ревью: govulncheck в task setup и статус WARN в гейте
Инструмент ставится вместе с остальными (версия закреплена), гейт его больше не
пропускает.

Уязвимость почти всегда унаследована — это состояние зависимостей и тулчейна, а
не диффа, — поэтому шаг не краснит гейт, а получает статус WARN: виден в сводке,
уезжает в находки и в границы покрытия. Иначе красный гейт на каждом прогоне
перестают читать. Уязвимость, приехавшую с новой зависимостью change, агент
отличает по трассам вызовов и выводит как major.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 18:25:47 +03:00

158 lines
6.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Детерминированный гейт ревью: прогоняет всё, у чего есть объективный оракул,
# и печатает сводку. В отличие от `task test`/`task lint` не останавливается на
# первом отказе — ревьюверу нужна полная картина, а не первая упавшая команда.
#
# Использование: scripts/gate.sh [<base-rev>]
# base-rev — база для диффа (покрытие изменённых строк). По умолчанию
# определяется как merge-base с master; для самого master — HEAD~1.
#
# Коды возврата: 0 — все обязательные шаги зелёные, 1 — есть красные.
# Пропущенные шаги (нет инструмента) НЕ красят гейт, но попадают в сводку:
# молча пропущенная проверка — это ложное ощущение проверенности. То же с WARN:
# отказ, который почти всегда унаследован (уязвимость в зависимости или в
# стандартной библиотеке), не должен краснить каждый прогон ревью — иначе
# красный гейт перестают читать. Разбирает такой отказ агент.
set -u
OUT_DIR="tmp/gate"
mkdir -p "$OUT_DIR"
BASE="${1:-}"
if [ -z "$BASE" ]; then
if git rev-parse --verify -q master >/dev/null && \
[ "$(git rev-parse --abbrev-ref HEAD)" != "master" ]; then
BASE="$(git merge-base HEAD master)"
else
BASE="HEAD~1"
fi
fi
FAILED=0
SUMMARY=""
record() { # <status> <name> <hint>
SUMMARY="${SUMMARY}$1\t$2\t$3\n"
[ "$1" = "FAIL" ] && FAILED=1
return 0
}
step() { # <name> <hint-on-fail> <cmd...>
local name="$1" hint="$2"
shift 2
if "$@" >"$OUT_DIR/$name.log" 2>&1; then
record OK "$name" ""
else
record FAIL "$name" "$hint$OUT_DIR/$name.log"
fi
}
echo "== gate: база диффа $BASE =="
# --- Компиляция и статика ---
step build "не собирается" go build ./...
step vet "" go vet ./...
if command -v golangci-lint >/dev/null; then
step lint "" golangci-lint run
else
record SKIP lint "golangci-lint не установлен (task setup)"
fi
gofmt_out="$(gofmt -l . 2>/dev/null | grep -v '^tmp/' || true)"
if [ -n "$gofmt_out" ]; then
record FAIL gofmt "не отформатировано: $(echo "$gofmt_out" | tr '\n' ' ')"
else
record OK gofmt ""
fi
# --- Тесты ---
step test "" go test -count=1 ./...
# Флаки: повторный прогон тех же тестов. Расхождение результатов — находка не
# ниже major (тест, который иногда зелёный, не является оракулом ни для чего).
if [ -f "$OUT_DIR/test.log" ] && grep -q '^ok\|^---' "$OUT_DIR/test.log"; then
if go test -count=1 ./... >"$OUT_DIR/test-repeat.log" 2>&1; then
if grep -q "^FAIL" "$OUT_DIR/test.log"; then
record FAIL flaky "прогон 1 красный, прогон 2 зелёный — флаки-тест"
else
record OK flaky ""
fi
else
if grep -q "^FAIL" "$OUT_DIR/test.log"; then
record OK flaky "" # стабильно красный — это не флаки, отработает шаг test
else
record FAIL flaky "прогон 1 зелёный, прогон 2 красный — флаки-тест"
fi
fi
fi
# --- Гонки ---
if command -v gcc >/dev/null; then
step race "детектор гонок сработал" env CGO_ENABLED=1 go test -race -count=1 ./...
else
record SKIP race "нет gcc: -race требует cgo. Гонки НЕ проверены — в границы покрытия"
fi
# --- Покрытие изменённых строк ---
if go test -count=1 -coverprofile="$OUT_DIR/cover.out" ./... >"$OUT_DIR/cover.log" 2>&1; then
if python3 scripts/diff-coverage.py "$OUT_DIR/cover.out" "$BASE" \
>"$OUT_DIR/diff-coverage.log" 2>&1; then
record OK diff-coverage "$(head -1 "$OUT_DIR/diff-coverage.log")"
else
record SKIP diff-coverage "не удалось посчитать → $OUT_DIR/diff-coverage.log"
fi
else
record SKIP diff-coverage "прогон с профилем не собрался → $OUT_DIR/cover.log"
fi
# --- Миграции на чистой схеме ---
step migrations "миграции не накатываются с нуля" \
go test -count=1 -run Migration ./internal/store/...
# --- ER-схема синхронна с миграциями ---
# docs/conventions/database.md: структуру меняем — обновляем ER-схему в том же
# change. Проверка по диффу, поэтому живёт здесь, а не в internal/archrules.
changed="$(git diff --name-only "$BASE"...HEAD 2>/dev/null || true)"
if echo "$changed" | grep -q '^internal/store/migrations/'; then
if echo "$changed" | grep -q '^docs/specs/database.md$'; then
record OK er-schema ""
else
record FAIL er-schema "миграция изменена, а docs/specs/database.md — нет"
fi
fi
# --- Секреты ---
if command -v gitleaks >/dev/null; then
step gitleaks "возможен секрет в истории/индексе" gitleaks git --no-banner
else
record SKIP gitleaks "gitleaks не установлен"
fi
# --- Уязвимости зависимостей ---
# Не блокирует: находка тут — состояние зависимостей и тулчейна, а не диффа.
# Исключение (уязвимость приехала с новой зависимостью в этом change) разбирает
# агент по списку вызовов.
if command -v govulncheck >/dev/null; then
if govulncheck ./... >"$OUT_DIR/govulncheck.log" 2>&1; then
record OK govulncheck ""
else
record WARN govulncheck "достижимо из кода уязвимостей: $(grep -c '^Vulnerability #' "$OUT_DIR/govulncheck.log")$OUT_DIR/govulncheck.log"
fi
else
record SKIP govulncheck "govulncheck не установлен (task setup)"
fi
echo
echo "== сводка =="
printf "%b" "$SUMMARY" | while IFS=$'\t' read -r status name hint; do
printf "%-5s %-14s %s\n" "$status" "$name" "$hint"
done
echo
if [ "$FAILED" -eq 1 ]; then
echo "ГЕЙТ КРАСНЫЙ — опиниативные проходы не запускаются"
exit 1
fi
echo "гейт зелёный (шаги WARN и SKIP см. в сводке — они идут в находки и в границы покрытия)"