Add quick gate and independent review pipeline

Introduce tools/quick_gate.py: a one-second check that parses every file
in protocol, tests and tools, imports each protocol module, and runs the
functional tests of the labs that need no experiment data.

Wire it to a PostToolUse hook so edits under protocol or tests are checked
automatically, and add two read-only review agents with slash commands that
drive them: verifier for independent re-derivation of results, adversary for
unsafe-state hunting in control, failsafe, session and reset code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
LittleSam129
2026-08-07 17:33:46 +03:00
parent 7d122b53ed
commit 28227e4228
7 changed files with 456 additions and 0 deletions

145
tools/quick_gate.py Normal file
View File

@@ -0,0 +1,145 @@
"""
Быстрый шлюз проверки SDR Rover Link.
Проверяет за одну-две секунды то, что ломается чаще всего:
1. Синтаксис всех файлов в protocol, tests и tools.
2. Импорт каждого модуля protocol.
3. Функциональные проверки тех лаб, которым не нужны данные эксперимента.
Полный прогон лабы шлюз не заменяет.
Он отвечает на один вопрос: не сломана ли кодовая база прямо сейчас.
Запуск:
python tools/quick_gate.py
"""
from __future__ import annotations
import ast
import importlib
import inspect
from pathlib import Path
import sys
import traceback
PROJECT_ROOT = Path(__file__).resolve().parent.parent
CHECKED_DIRECTORIES = ("protocol", "tests", "tools")
def check_syntax() -> list[str]:
"""Разобрать все файлы Python и вернуть список сообщений об ошибках."""
failures: list[str] = []
for directory in CHECKED_DIRECTORIES:
for path in sorted((PROJECT_ROOT / directory).glob("*.py")):
try:
ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
except SyntaxError as error:
relative = path.relative_to(PROJECT_ROOT)
failures.append(f"{relative}:{error.lineno}: синтаксис: {error.msg}")
return failures
def check_protocol_imports() -> list[str]:
"""Импортировать каждый модуль protocol и вернуть список сообщений об ошибках."""
failures: list[str] = []
for path in sorted((PROJECT_ROOT / "protocol").glob("*.py")):
if path.name == "__init__.py":
continue
module_name = f"protocol.{path.stem}"
try:
importlib.import_module(module_name)
except Exception:
failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}")
return failures
def check_functional_tests() -> tuple[list[str], int, int]:
"""
Выполнить функциональные проверки лаб, не требующие данных эксперимента.
Возвращает:
failures:
Список сообщений об ошибках.
executed:
Количество выполненных лаб.
skipped:
Количество пропущенных лаб.
"""
failures: list[str] = []
executed = 0
skipped = 0
for path in sorted((PROJECT_ROOT / "tests").glob("lab*.py")):
source = path.read_text(encoding="utf-8")
if "def run_functional_tests" not in source:
continue
module_name = f"tests.{path.stem}"
try:
module = importlib.import_module(module_name)
except Exception:
failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}")
continue
run_functional_tests = module.run_functional_tests
parameters = inspect.signature(run_functional_tests).parameters
required = [name for name, parameter in parameters.items() if parameter.default is parameter.empty]
if required:
skipped += 1
continue
try:
run_functional_tests()
except Exception:
failures.append(f"{module_name}: проверки: {traceback.format_exc(limit=3).strip()}")
continue
executed += 1
return failures, executed, skipped
def main() -> int:
"""Выполнить шлюз и вернуть код возврата процесса."""
sys.path.insert(0, str(PROJECT_ROOT))
syntax_failures = check_syntax()
if syntax_failures:
print("Шлюз не пройден: ошибки синтаксиса")
for failure in syntax_failures:
print(f" {failure}")
return 1
import_failures = check_protocol_imports()
test_failures, executed, skipped = check_functional_tests()
failures = import_failures + test_failures
if failures:
print("Шлюз не пройден")
for failure in failures:
print(f" {failure}")
return 1
print(f"Шлюз пройден: синтаксис в порядке, protocol импортируется, функциональных проверок выполнено {executed}, пропущено {skipped}")
return 0
if __name__ == "__main__":
raise SystemExit(main())