Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors

Враждебная перепроверка кода ИИ-агента: хук Stop и субагент

Linux и DevOps

Враждебная перепроверка кода ИИ-агента: хук Stop и субагент
Как встроить обязательную перепроверку правок ИИ-агента: субагент-критик, хук Stop, приращение diff и бюджет разбора. Полный промт и готовые сценарии.
Claude Code, хуки, субагенты, автоматизация разработки, code review, DevOps, ИИ-агенты, CLAUDE.md, git, качество кода, инфраструктура, промт для агента, оптимизация токенов

Содержание
  1. Враждебная перепроверка кода ИИ-агента: субагент-критик и хук Stop
  2. Содержание
  3. Почему самопроверка агента не работает без механики
  4. Из чего состоит конструкция
  5. Как устроено событие Stop
  6. Где размещать конфигурацию
  7. Почему stop_hook_active не годится как предохранитель
  8. Субагент-критик: полный системный запрос
  9. Ограничение записи только тестами
  10. Удержание хода через отпечаток изменений
  11. Расчёт отпечатка
  12. Хук удержания
  13. Приращение вместо полного diff
  14. Бюджет разбора: потолок ходов и проверок
  15. Жёсткий потолок во фронтматтере
  16. Мягкий бюджет в системном запросе
  17. Адресность вместо равномерного налога
  18. Передний план вместо фона: как не потерять ходы на опрос
  19. Выборка разделов CLAUDE.md вместо дублирования
  20. Правила в CLAUDE.md: порядок ритуалов
  21. Ручной запуск
  22. Приёмка: что проверяется исполнением
  23. Полный промт: готовое задание для агента
  24. Ограничения и неинтерактивные прогоны
  25. Альтернативы: PostToolUse, prompt-хук, agent-хук
  26. Частые вопросы
  27. Разбор идёт дольше, чем разработка. Что смотреть первым?
  28. Почему хук не блокирует, хотя сценарий возвращает ошибку?
  29. Что произойдёт при ошибке в логике счётчика?
  30. Зачем ставить маркер через SubagentStart, если есть счётчик попыток?
  31. Нужно ли дублировать правила проекта в файл субагента?
  32. Обязательна ли разведка перед сборкой?
  33. Работает ли конструкция без git?
  34. Как отключить конструкцию временно?
  35. Заключение

Враждебная перепроверка кода ИИ-агента: субагент-критик и хук Stop

Перепроверка правок, сделанных агентом, в типовой конфигурации держится на одном условии: человек в нужный момент напишет «перепроверь логику». Условие ненадёжное — именно на длинных ходах, где дефектов больше всего, просьба забывается.

Результат воспроизводится в любом проекте: код запускается, тесты зелёные, гипотеза, на которой строилась правка, не проверялась ни разу. Дефект уезжает в production-ветку.

Ниже — конструкция, переводящая перепроверку из просьбы в механику: субагент-критик с собственным окном контекста, хук на событии Stop, удерживающий ход до прохождения разбора, и правила в CLAUDE.md, фиксирующие порядок ритуалов. Отдельно разобрана главная проблема наивной реализации — разбор, который идёт дольше разработки. В конце — полный текст задания, которое передаётся агенту целиком.

Содержание

Почему самопроверка агента не работает без механики

Запрос «перепроверь свой код» отправляется в то же окно контекста, где код был только что написан. Модель видит собственную цепочку рассуждений, считает её обоснованной и подтверждает результат. Это не дефект конкретной модели, а следствие того, что проверяющий и проверяемый работают на одном наборе допущений.

Второй фактор — необязательность. Любая инструкция вида «перед завершением хода выполни разбор» соблюдается неравномерно: на коротких правках чаще, на длинных, где накопилось десять файлов изменений, реже.

Механика снимает оба фактора. Разбор выполняется в изолированном контексте, а завершение хода блокируется на уровне процесса, а не на уровне вежливой просьбы.

Механика не отменяет правила в CLAUDE.md. Хук отвечает за то, что разбор нельзя пропустить, правило — за то, в каком порядке он выполняется относительно остальных ритуалов завершения хода.

Из чего состоит конструкция

Три независимые части, каждая решает свою задачу:

  • Субагент-критик.claude/agents/adversarial-reviewer.md. Разбирает незакоммиченные изменения в отдельном окне контекста, не наследуя историю основной сессии.
  • Хук на событии Stop — сравнивает отпечаток текущих изменений с отпечатком, зафиксированным после успешного разбора. Не совпал — ход не отпускается.
  • Правила в CLAUDE.md — фиксируют порядок: разбор → правки по его итогам → запись в память проекта → ответ.

Дополнительно потребуются четыре вспомогательных сценария: расчёт отпечатка, определение области разбора, фиксация результата и выборка разделов CLAUDE.md.

Итоговое дерево:

.claude/
├── agents/
│   └── adversarial-reviewer.md
├── commands/
│   └── selfcheck.md
├── hooks/
│   └── require-review.sh
├── scripts/
│   ├── diff-fingerprint.sh
│   ├── review-scope.sh
│   ├── mark-reviewed.sh
│   └── rules-extract.sh
├── state/            # в .gitignore
└── settings.json

Как устроено событие Stop

Событие Stop срабатывает, когда агент заканчивает ход. Это единственная точка жизненного цикла, где можно перехватить завершение и вернуть управление обратно в цикл.

Ключевые свойства события, определяющие конфигурацию:

  • matcher не поддерживается — хук срабатывает на каждом завершении хода. Поле matcher в конфигурации молча игнорируется.
  • Код возврата 2 блокирует завершение и передаёт содержимое stderr обратно агенту как инструкцию.
  • Код возврата 1 и любой другой ненулевой трактуется как неблокирующая ошибка: в расшифровке появляется уведомление, ход отпускается.
  • При коде возврата 0 разбирается stdout на предмет JSON. При коде 2 JSON игнорируется — совмещать оба способа нельзя.
  • На вход через stdin приходит JSON с полями session_id, transcript_path, cwd, permission_mode, stop_hook_active и last_assistant_message.

Самая частая ошибка при написании такого хука — завершение блокирующей ветки через exit 1. Привычный для Unix код неуспеха здесь не блокирует ничего: ход отпускается, а в расшифровке остаётся только уведомление об ошибке хука. Блокирует исключительно exit 2.

Где размещать конфигурацию

Область действия определяется файлом настроек:

  • .claude/settings.json — проектная область, попадает в репозиторий, включается у всех участников;
  • .claude/settings.local.json — только текущая рабочая копия, по умолчанию не индексируется git;
  • ~/.claude/settings.json — все проекты пользователя.

Записи хуков из разных уровней объединяются, а не замещают друг друга. Если settings.json уже существует, блок hooks вливается в имеющуюся структуру, файл не перезаписывается целиком.

Итоговая конфигурация со всеми доработками из разделов ниже:

{
  "env": {
    "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS": "1"
  },
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-review.sh",
            "args": [],
            "timeout": 30,
            "statusMessage": "Проверка отпечатка разбора"
          }
        ]
      }
    ],
    "SubagentStart": [
      {
        "matcher": "^adversarial-reviewer$",
        "hooks": [
          {
            "type": "command",
            "command": "mkdir -p ${CLAUDE_PROJECT_DIR}/.claude/state && touch ${CLAUDE_PROJECT_DIR}/.claude/state/review-running"
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "matcher": "^adversarial-reviewer$",
        "hooks": [
          {
            "type": "command",
            "command": "rm -f ${CLAUDE_PROJECT_DIR}/.claude/state/review-running"
          }
        ]
      }
    ]
  }
}

Подстановка ${CLAUDE_PROJECT_DIR} раскрывается в корень проекта независимо от текущего рабочего каталога. При наличии args команда запускается напрямую, без оболочки — экранирование путей с пробелами не требуется. Якоря ^…$ в matcher обязательны на версиях до 2.1.195: там дефис в имени уводит значение на путь регулярного выражения без привязки к границам.

Почему stop_hook_active не годится как предохранитель

Во входном JSON присутствует флаг stop_hook_active: он равен true, если предыдущий хук уже удержал ход. Типовая рекомендация — выходить с кодом 0 при взведённом флаге, чтобы не зациклиться.

Для рассматриваемой задачи такой предохранитель непригоден. Он превращает шлюз в одноразовое напоминание: первая попытка завершения блокируется, вторая проходит вне зависимости от того, был разбор выполнен или нет.

Корректный вариант — счётчик попыток в пределах сессии с явным пределом. Три попытки достаточно: одна на запуск разбора, одна на закрытие найденных дефектов, одна запасная. При достижении предела ход отпускается с предупреждением пользователю, а не зависает.

Встроенный предохранитель существует: после восьми блокировок подряд сессия завершается с предупреждением. Порог меняется переменной CLAUDE_CODE_STOP_HOOK_BLOCK_CAP, значение 0 отключает его полностью. Полагаться на него не следует — он срабатывает поздно и грубо, уже после того, как восемь ходов потрачены впустую.

Субагент-критик: полный системный запрос

Субагент — файл Markdown с фронтматтером YAML. Обязательны только name и description; остальные поля управляют инструментами, моделью, режимом разрешений, потолком ходов и хуками.

Что попадает в начальный контекст субагента: собственный системный запрос, задание от основной сессии, полная иерархия файлов CLAUDE.md, снимок состояния git и содержимое навыков, перечисленных в поле skills. История основной сессии, ранее прочитанные файлы и результаты прежних вызовов инструментов — не попадают. Изоляция входа и есть причина, по которой субагент не выгораживает только что написанный код.

Файлы CLAUDE.md загружаются в контекст пользовательских субагентов автоматически. Пропускают их только встроенные агенты Explore и Plan. Копировать инварианты проекта в файл субагента не нужно и вредно: копия создаёт второй источник истины, расходящийся с первым при первой же дописанной строке.

Полный файл .claude/agents/adversarial-reviewer.md:

---
name: adversarial-reviewer
description: Враждебный разбор приращения изменений ветки. Ищет дефекты, доказывает их воспроизведением и краснеющим тестом, возвращает отчёт основной сессии. Запускается перед завершением хода после правки кода, а также по команде /selfcheck.
tools: Read, Grep, Glob, Bash, Write
model: sonnet
effort: medium
maxTurns: 25
color: red
---

Роль — враждебный рецензент изменений.
Изменения написаны другим агентом. Презумпция корректности не действует.
Задача не в том, чтобы подтвердить работоспособность, а в том, чтобы
найти условия, при которых код делает не то, что заявлено.

## Область разбора

Область задана приращением, которое основная сессия передала
в задании текстом. Добывать его повторно не требуется.
Если приращение в задании отсутствует — получить его командой
`.claude/scripts/review-scope.sh` и не более.

Разбираются только файлы из приращения и их прямые зависимости.
Обход проекта поиском по всей кодовой базе запрещён.
Обращение к файлам вне приращения — не более трёх за разбор,
каждое обосновывается в отчёте.

## Бюджет

- Не более 12 проверок запуском за разбор.
- Не более 3 файлов вне изменённых.
- По исчерпании бюджета — немедленно составить отчёт по достигнутому,
  остальное перечислить в разделе «Непроверенное» с пометкой
  «бюджет исчерпан».

Бюджет мягкий и срабатывает раньше жёсткого потолка maxTurns.
Упор в maxTurns означает, что бюджет проигнорирован: это дефект
разбора, а не признак тщательности.

## Порядок работы

1. Прочитать приращение из задания.

2. Получить действующие правила проекта на месте, не по памяти:
   `.claude/scripts/rules-extract.sh "Сквозные инварианты" \
      "Границы модулей" "Определение готовности" "Уроки"`
   Пустой вывод по любому разделу — остановиться и сообщить:
   разбор без правил бессмыслен.

3. Отобрать гипотезы. Из раздела с уроками выбрать только строки,
   применимые к затронутым модулям. Остальные — одной строкой
   в «Непроверенное», без прогона. Отбор выполняется чтением,
   а не запуском.

4. Сверить приращение со сквозными инвариантами и границами модулей.
   Нарушение инварианта — дефект независимо от того, зелёные ли тесты.

5. Проверять запуском только отобранные гипотезы, в пределах бюджета.
   Гипотеза отсеивается чтением; запуск тратится на подтверждение,
   а не на перебор.

6. Для каждой подтверждённой дыры написать регрессионный тест.

7. По завершении разбора выполнить `.claude/scripts/mark-reviewed.sh`.

## Доказательство дефекта

Требование краснеющего теста относится к подтверждённой находке,
а не к каждой гипотезе.

Тест обязан краснеть на коде ДО правки и зеленеть после.
Зелёный в обоих случаях доказательством не является и переписывается:
такой тест проверяет не дефект, а собственную формулировку.

Проверка краснения выполняется явно — временным откатом правки,
`git stash`, либо запуском теста на предыдущем состоянии файла.
Прогон точечный: только модули, затронутые приращением.
Список модулей вычисляется из путей приращения, весь набор
тестов не запускается ни при каких условиях.

## Что запрещено

- Править код. Единственное исключение — регрессионный тест,
  доказывающий дефект. Всё остальное передаётся основной сессии
  описанием: файл, строка, характер правки.
- Предлагать возврат функциональности, сознательно исключённой
  из модели предметной области. Исключённое — решение владельца,
  а не упущение разработчика.
- Трогать стилистику, именование, форматирование и предпочтения.
- Использовать формулировки «выглядит корректно», «изменения
  безопасны», «логика верна». Либо проверено запуском и попадает
  в раздел проверенного, либо попадает в раздел непроверенного
  с указанием причины. Третьего состояния нет.
- Создавать фоновые оболочки ожидания и опрашивать собственное
  состояние.

## Формат итога

Ровно три раздела, в этом порядке.

### 1. Подтверждённые дефекты

Для каждого:
- файл и номер строки;
- нарушенный инвариант или пункт «Уроков» — дословно;
- сценарий воспроизведения, который был выполнен;
- путь к написанному регрессионному тесту;
- подтверждение, что тест краснеет на коде до правки.

### 2. Проверенные и опровергнутые гипотезы

Для каждой: формулировка гипотезы, способ проверки, результат.
Раздел обязателен даже когда он длиннее первого.

### 3. Непроверенное

Для каждого пункта: что не проверено и по какой причине —
неприменимо к затронутым модулям, исчерпан бюджет, отсутствует
окружение, требуется внешний сервис. Пустой раздел допустим
только если проверено действительно всё.

В конце отчёта — израсходованный бюджет: число проверок запуском
и число обращений к файлам вне приращения.

Если ни одного дефекта не найдено, первый раздел остаётся пустым,
а второй и третий заполняются полностью. Пустой отчёт целиком
означает, что разбор не выполнялся.

Ограничение записи только тестами

Поле tools не различает пути. Чтобы запретить субагенту править что-либо кроме каталога тестов, добавляется хук PreToolUse прямо во фронтматтер:

hooks:
  PreToolUse:
    - matcher: "Write|Edit"
      hooks:
        - type: command
          command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/tests-only.sh"
          args: []

Сценарий читает tool_input.file_path из stdin и возвращает 2, если путь не начинается с каталога тестов. Хуки во фронтматтере проектного субагента запускаются после подтверждения доверия к рабочему каталогу.

Удержание хода через отпечаток изменений

Механика опирается на сравнение двух значений: отпечатка текущего состояния рабочей копии и отпечатка, зафиксированного после успешного разбора. Совпали — ход отпускается молча. Разошлись — значит после разбора код правился, разбор устарел.

Расчёт отпечатка

Файл .claude/scripts/diff-fingerprint.sh:

#!/usr/bin/env bash
# Отпечаток незакоммиченных изменений рабочей копии
set -uo pipefail

cd "${CLAUDE_PROJECT_DIR:-$PWD}" || exit 1

if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  echo "no-git"
  exit 0
fi

payload=$( { git diff HEAD 2>/dev/null; git status --porcelain 2>/dev/null; } )

if [ -z "$payload" ]; then
  echo "empty"
  exit 0
fi

if command -v sha256sum >/dev/null 2>&1; then
  printf '%s' "$payload" | sha256sum | cut -d' ' -f1
else
  printf '%s' "$payload" | shasum -a 256 | cut -d' ' -f1
fi

Команда git status --porcelain добавлена намеренно: git diff HEAD не показывает содержимое неотслеживаемых файлов, но их появление меняет вывод status и, следовательно, отпечаток. Проверка на sha256sum с запасным вариантом shasum обеспечивает работу и в GNU-окружении, и в BSD-окружении macOS.

Хук удержания

Файл .claude/hooks/require-review.sh:

#!/usr/bin/env bash
# Stop-хук: не отпускает ход, пока разбор не зафиксирован
set -uo pipefail

INPUT=$(cat)
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
STATE_DIR="$PROJECT_DIR/.claude/state"
MAX_ATTEMPTS=3

# Неинтерактивные прогоны не блокируются
[ -n "${CI:-}" ] && exit 0

# Разбор в работе — не мешать и не считать попытку
[ -f "$STATE_DIR/review-running" ] && exit 0

session=$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')
mode=$(printf '%s' "$INPUT" | jq -r '.permission_mode // "default"')
[ "$mode" = "plan" ] && exit 0

current=$("$PROJECT_DIR/.claude/scripts/diff-fingerprint.sh")
case "$current" in
  empty|no-git) exit 0 ;;
esac

mkdir -p "$STATE_DIR"
reviewed_file="$STATE_DIR/reviewed"
attempts_file="$STATE_DIR/attempts-$session"

if [ -f "$reviewed_file" ] && [ "$(cat "$reviewed_file")" = "$current" ]; then
  rm -f "$attempts_file"
  exit 0
fi

attempts=$(cat "$attempts_file" 2>/dev/null || echo 0)
attempts=$((attempts + 1))
printf '%s' "$attempts" > "$attempts_file"

if [ "$attempts" -gt "$MAX_ATTEMPTS" ]; then
  rm -f "$attempts_file"
  printf '{"systemMessage":"Разбор не пройден за %s попытки. Ход отпущен, проверка остаётся за человеком."}' "$MAX_ATTEMPTS"
  exit 0
fi

cat >&2 <<'MSG'
Ход не отпущен: изменения в рабочей копии не прошли враждебный разбор.
Порядок действий:
1. Получить область разбора: .claude/scripts/review-scope.sh
2. Передать её текстом субагенту adversarial-reviewer, дождаться отчёта.
   Не создавать фоновых оболочек ожидания и не опрашивать состояние.
3. Закрыть подтверждённые дефекты.
4. Выполнить .claude/scripts/mark-reviewed.sh для фиксации.
MSG
exit 2

Ветка предохранителя завершается кодом 0 и печатает JSON с полем systemMessage — так предупреждение попадает пользователю, а не агенту. Блокирующая ветка использует stderr и код 2: этот текст читает агент и выполняет как инструкцию. Проверка маркера review-running стоит до счётчика намеренно — иначе попытки будут расходоваться на ходы, где разбор ещё не закончился.

Приращение вместо полного diff

Фича собирается за пять-шесть ходов, а хук срабатывает на каждом. Первый разбор честный, остальные перепроверяют то же самое плюс дельту. Отпечаток здесь не помогает: он ловит только полное совпадение и одинаково реагирует на правку в одну строку и на переписанный модуль.

Решение — фиксировать не только хеш, но и само состояние, чтобы следующий разбор получал приращение. Команда git stash create собирает объект-коммит из рабочей копии, ничего в ней не меняя:

#!/usr/bin/env bash
# .claude/scripts/mark-reviewed.sh — фиксация результата разбора
set -uo pipefail

PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
STATE_DIR="$PROJECT_DIR/.claude/state"
cd "$PROJECT_DIR" || exit 1

mkdir -p "$STATE_DIR"

fingerprint=$("$PROJECT_DIR/.claude/scripts/diff-fingerprint.sh")
printf '%s' "$fingerprint" > "$STATE_DIR/reviewed"

# Снимок разобранного состояния для расчёта приращения
ref=$(git stash create 2>/dev/null)
if [ -n "$ref" ]; then
  git update-ref refs/reviewed "$ref"
else
  # Рабочая копия чиста — снимком служит HEAD
  git update-ref refs/reviewed HEAD
fi

echo "Разбор зафиксирован. Отпечаток: $fingerprint"

Вызов git update-ref обязателен: без ссылки объект, созданный stash create, попадёт под сборку мусора и приращение перестанет считаться.

Область следующего разбора:

#!/usr/bin/env bash
# .claude/scripts/review-scope.sh — что разбирать в этот раз
set -uo pipefail

cd "${CLAUDE_PROJECT_DIR:-$PWD}" || exit 1

if git rev-parse --verify -q refs/reviewed >/dev/null; then
  git diff refs/reviewed          # приращение с прошлого разбора
else
  git diff HEAD                   # первый разбор в ветке
fi

git status --porcelain

Основная сессия вызывает этот сценарий и кладёт вывод в задание субагенту текстом. Субагент получает готовое приращение и не тратит ходы на его добычу — Grep и Glob остаются для точечных уточнений, а не для восстановления картины с нуля.

Ссылка refs/reviewed живёт в рабочей копии и не попадает в удалённый репозиторий при обычном git push. Сбрасывается вручную командой git update-ref -d refs/reviewed — это заставит следующий разбор пройти по полному diff ветки.

Бюджет разбора: потолок ходов и проверок

Наивная реализация даёт характерную картину: приращение в +258/−28 строк, разбор идёт 20 минут, израсходовано 190 тысяч токенов и 82 вызова инструментов. Двести пятьдесят восемь добавленных строк столько не стоят.

Механика деградации известна. На 190 тысячах контекст субагента проходит автоматическое сжатие, часть найденного теряется, агент перечитывает те же файлы заново — отсюда и восемьдесят два вызова. Цикл самоподдерживающийся: чем дольше он идёт, тем больше поводов продолжать.

Причина не в модели, а в формулировках без потолка. «Пройти по каждой строке раздела с уроками» при сорока строках означает сорок расследований. «Проверить каждую гипотезу запуском» — сорок прогонов. Обе формулировки писались ради полноты и не содержат ограничения сверху.

Жёсткий потолок во фронтматтере

Поле maxTurns обрывает субагента по числу ходов. Поле effort задаёт уровень усилий, model снимает переплату на разборе diff:

model: sonnet
effort: medium
maxTurns: 25

Разбор приращения редко требует старшей модели, а стоимость складывается с каждым ходом основной сессии. Переменная CLAUDE_CODE_SUBAGENT_MODEL задаёт модель сразу для всех субагентов, если такое решение принимается на уровне проекта.

Мягкий бюджет в системном запросе

Жёсткий обрыв на maxTurns оставляет отчёт недописанным, поэтому мягкий предел должен срабатывать раньше: не более двенадцати проверок запуском, не более трёх файлов вне приращения, по исчерпании — немедленный отчёт по достигнутому. Формулировки приведены в файле субагента выше.

Три правила, дающие основной эффект:

  • Отбор перед проверкой. Из раздела с уроками сначала выбираются строки, применимые к затронутым модулям. Остальные уходят в «Непроверенное» одной строкой без прогона.
  • Краснеющий тест — для находки, не для гипотезы. Проверка через откат правки стоит двух прогонов. Применять её к каждой гипотезе — прямой путь к двадцатиминутному разбору.
  • Точечный прогон. Список модулей вычисляется из путей приращения и передаётся в задании. Без этого критик запускает весь набор «на всякий случай».

Ориентир для приращения на две-три сотни строк: 3–6 минут, 30–50 тысяч токенов, до 25 вызовов инструментов. Разбор, упирающийся в maxTurns, — не признак тщательности, а признак игнорируемого бюджета: смотреть следует расшифровку субагента, а не крутить настройки.

Адресность вместо равномерного налога

Сужать срабатывание по типам файлов сразу не стоит — фильтр вслепую вырезает ровно те случаи, ради которых конструкция создавалась. Но после недели наблюдений данные появляются, и тогда переход с равномерного налога на адресный оправдан.

Разумный порог: разбор обязателен, когда приращение задевает денежный контур, аутентификацию, миграции или конкурентный код. На остальном достаточно линтера и проверки типов.

Передний план вместо фона: как не потерять ходы на опрос

С версии 2.1.198 субагенты по умолчанию уходят в фон. Для разбора, результат которого нужен немедленно, это даёт паразитный цикл: основная сессия тратит ход на вопрос «как там разбор», получает ответ «идёт», тратит следующий. В расшифровке это видно как чередование строк ожидания и коротких реплик вида «Жду».

Хуже того, цикл усиливается шлюзом. Каждый ход опроса завершается, срабатывает Stop, отпечаток не совпадает, потому что разбор не закончен, возвращается код 2 — и добавляется ещё один ход ожидания.

Два независимых средства, применяются вместе:

Первое — снять фоновый режим. Переменная в блоке env проектных настроек:

{
  "env": {
    "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS": "1"
  }
}

Основная сессия блокируется один раз и получает отчёт. Ходов на ожидание — ноль. Если фоновый режим нужен для других задач, переменная не трогается, но в задании прописывается явное требование: дождаться результата субагента, не проверять его состояние отдельными командами, не поднимать оболочки ожидания.

Второе — маркер работающего разбора, чтобы шлюз молчал, пока критик работает. События SubagentStart и SubagentStop ставят и снимают файл-маркер (конфигурация приведена выше), а хук Stop проверяет его первой строкой после проверки CI.

Самодельные оболочки ожидания опасны не только расходом ходов. Оболочка, выбравшая свой предел, помечается как упавшая, а сессия продолжает ждать результат, которого не будет. Фоновый субагент, оборвавшийся на ошибке взаимодействия с интерфейсом, возвращает последний вывод и пометку об обрыве — но только при штатном ожидании, не через кустарный цикл опроса.

Выборка разделов CLAUDE.md вместо дублирования

Правила проекта попадают в контекст субагента целиком вместе с CLAUDE.md. Отдельный сценарий выборки нужен не для доставки, а для принуждения: раздел с накопленными уроками должен быть пройден отбором построчно, а не прочитан фоном.

Побочный эффект — живучесть. Дописанная в раздел строка начинает работать в тот же день, без правки файла субагента.

Файл .claude/scripts/rules-extract.sh:

#!/usr/bin/env bash
# Печатает указанные разделы CLAUDE.md целиком, вместе с подзаголовками
set -uo pipefail

FILE="${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md"
[ -f "$FILE" ] || { echo "CLAUDE.md не найден: $FILE" >&2; exit 1; }
[ "$#" -gt 0 ] || { echo "Не заданы заголовки разделов" >&2; exit 1; }

status=0
for title in "$@"; do
  body=$(awk -v want="$title" '
    /^#+[ \t]/ {
      match($0, /^#+/)
      level = RLENGTH
      text = substr($0, level + 1)
      sub(/^[ \t]+/, "", text)
      if (inside && level <= want_level) inside = 0
      if (!inside && text == want) { inside = 1; want_level = level; print; next }
    }
    inside { print }
  ' "$FILE")

  if [ -z "$body" ]; then
    echo "Раздел отсутствует в CLAUDE.md: $title" >&2
    status=1
    continue
  fi
  printf '%s\n\n' "$body"
done

exit $status

Учёт уровня заголовка обязателен: без него выборка обрывается на первом же подзаголовке ### внутри искомого раздела. Сравнение заголовков посимвольное, поэтому имена разделов берутся из файла дословно, включая регистр и знаки препинания.

Правила в CLAUDE.md: порядок ритуалов

Хук отвечает за неотвратимость, правило — за очерёдность. Если в проекте уже есть автоматическая запись состояния в память перед итоговым ответом, порядок придётся пересмотреть: иначе в память уедет состояние до правок, сделанных по итогам разбора.

Раздел размещается перед разделом о памяти проекта:

## Враждебная перепроверка

Порядок завершения хода:
1. Получить область разбора: `.claude/scripts/review-scope.sh`.
2. Передать её текстом субагенту adversarial-reviewer вместе
   со списком затронутых модулей. Дождаться отчёта на переднем плане:
   оболочки ожидания не создавать, состояние субагента не опрашивать.
3. Правки по итогам разбора.
4. Фиксация: `.claude/scripts/mark-reviewed.sh`.
5. Запись в память проекта.
6. Итоговый ответ.

Перепроверка не выполняется в двух случаях: правка косметическая
(опечатка, форматирование, один импорт) либо файлы проекта не менялись.
«Тесты зелёные» и «правка тривиальная» основанием не являются.

Дополнительно правится триггер автозаписи (пройденная перепроверка становится частью условия) и определение готовности (закрытые дефекты входят в критерий).

Ручной запуск

Команда .claude/commands/selfcheck.md даёт возможность запросить разбор вне цикла завершения хода:

---
description: Враждебный разбор приращения изменений
---

Получи область разбора командой .claude/scripts/review-scope.sh
и передай её текстом субагенту adversarial-reviewer.
Дополнительная область от пользователя: $ARGUMENTS

Дождись отчёта на переднем плане. Не создавай фоновых оболочек
ожидания и не опрашивай состояние субагента.
После получения итога закрой подтверждённые дефекты и выполни
.claude/scripts/mark-reviewed.sh

Имя команды выбирается так, чтобы не пересечься со встроенными /verify и /code-review. Стиль файла согласуется с уже существующими командами проекта — единообразие важнее формальной корректности.

Приёмка: что проверяется исполнением

Конструкция состоит из сценариев, которые «выглядят правильно» ровно до первого прогона. Формулировка «сценарий написан корректно» без исполнения не засчитывается.

1. Выборка разделов. Сценарий печатает каждый заявленный раздел целиком — от первой до последней строки. Пустой вывод или обрезанная секция означают неверные заголовки либо ошибку в разборе уровней.

.claude/scripts/rules-extract.sh "Уроки" | head -40
.claude/scripts/rules-extract.sh "Уроки" | wc -l

2. Живучесть выборки. Временная строка, дописанная в конец раздела, обязана появиться в выводе без правки файла субагента. После проверки — откат.

3. Автономный прогон хука. Хук проверяется подачей JSON на stdin, без запуска агента:

# Изменений нет — код 0
echo '{"session_id":"t1","stop_hook_active":false,"permission_mode":"default"}' \
  | .claude/hooks/require-review.sh; echo "код возврата: $?"

# Внесена заведомая правка — код 2
echo "// проверка" >> src/example.js
echo '{"session_id":"t1","stop_hook_active":false,"permission_mode":"default"}' \
  | .claude/hooks/require-review.sh; echo "код возврата: $?"

# После фиксации — снова код 0
.claude/scripts/mark-reviewed.sh
echo '{"session_id":"t1","stop_hook_active":false,"permission_mode":"default"}' \
  | .claude/hooks/require-review.sh; echo "код возврата: $?"

4. Приращение. После фиксации внести правку в один файл и убедиться, что review-scope.sh печатает только её, а не весь diff ветки:

.claude/scripts/mark-reviewed.sh
echo "// вторая правка" >> src/other.js
.claude/scripts/review-scope.sh | grep -c '^+++'

5. Маркер работающего разбора. Ручное создание файла .claude/state/review-running обязано переводить хук в код 0; удаление — возвращать блокировку. Это доказывает, что шлюз не мешает разбору.

6. Предохранитель. Четыре подряд идущих вызова с одним session_id и неизменным приращением: первые три возвращают 2, четвёртый — 0 с сообщением. Зацикливания быть не должно.

7. Живая проверка удержания. Внести правку в реальной сессии, попробовать завершить ход — хук обязан не отпустить. Выполнить фиксацию, завершиться повторно — обязан отпустить.

8. Бюджет разбора. На приращении в две-три сотни строк засечь время, число токенов и число вызовов инструментов по панели фоновых задач. Выход за ориентир 3–6 минут и 50 тысяч токенов — повод открыть расшифровку субагента и найти, на чём он расходует ходы.

9. Видимость и чистота. Команда /hooks показывает хук на событии Stop как проектный. Каталог .claude/state/ не появляется в git status.

Автономный прогон хука подачей JSON на stdin — самый дешёвый способ отладки. Полный цикл проверки занимает секунды и не расходует контекст сессии.

Полный промт: готовое задание для агента

Всё описанное собирается в одно задание, которое передаётся агенту целиком. Ключевая особенность — два этапа с обязательной остановкой между ними: содержимое файлов собирается по фактическому состоянию репозитория, а не по образцам из обучающих данных.

Разведка перед сборкой не формальность. Заголовки разделов CLAUDE.md сверяются посимвольно, имена команд согласуются с уже существующими, событие Stop может быть занято другим хуком. Пропуск этапа 0 даёт правдоподобный, но нерабочий результат.

# Задание: встроить обязательную враждебную перепроверку

Задание из двух этапов. Первый — разведка по фактическому состоянию
репозитория с остановкой на подтверждение. Второй — сборка.
Не начинай второй этап, пока первый не подтверждён человеком.

## Зачем

Перепроверка правок сейчас держится на том, что человек в нужный момент
напишет «перепроверь логику». Задача переводит перепроверку из просьбы
в механику.

Три части: субагент-критик (разбирает изменения в своём окне контекста,
поэтому не выгораживает только что написанный код), хук Stop (не
отпускает ход, пока разбор не пройден), правила в CLAUDE.md (встраивают
разбор в ритуал завершения хода).

## Границы

Затрагиваются только CLAUDE.md, каталог .claude/ и .gitignore.
Код приложения, схема базы данных, миграции и техническое задание
не трогаются.

Ничего из принятой модели предметной области не пересматривается.
Если покажется, что для встраивания нужно поменять инвариант проекта
или ранее принятое решение владельца — остановись и сообщи: это признак
неверно понятой задачи.

Отдельная ветка, один читаемый PR.

---

# Этап 0. Разведка (выполнить и остановиться)

Ничего не создавай и не правь. Собери фактическое состояние
и отчитайся.

## Что выяснить

**Каталог .claude/.** Полное дерево. Что уже есть в agents/, commands/,
hooks/, skills/. Существуют ли settings.json и settings.local.json,
что в них лежит — особенно разделы hooks и env: какие события заняты,
какие переменные уже установлены. Как устроены уже существующие
команды: новая команда должна быть написана в том же стиле.

**CLAUDE.md на HEAD.** Точные заголовки разделов посимвольно — от них
зависит выборка. Точное расположение разделов про инварианты, границы
модулей, определение готовности, память проекта и накопленные уроки.
Текущее число строк в разделе с уроками — от него зависит, насколько
жёстким должен быть отбор гипотез.

**Инструментарий.** Есть ли в системе jq, sha256sum (или только shasum),
какая версия Claude Code — от версии зависит поведение matcher
с дефисом и режим запуска субагентов по умолчанию. Используется ли
где-то неинтерактивный прогон `claude -p` — поищи в .github/workflows/,
ops/, deploy.sh и подобных. На такие прогоны хук Stop тоже подействует.

**Структура тестов.** Как запускается точечный прогон одного модуля
и как из пути файла вычисляется имя модуля. Без этого критик будет
запускать весь набор.

**Память проекта.** Шаблон файла, в который пишется состояние по итогам
задачи, чтобы запись легла в принятом формате.

## Что отдать в отчёте

1. Дерево .claude/ и содержимое settings.json, если он есть.
2. Точный список заголовков разделов CLAUDE.md, которые попадут
   в выборку, — дословно, как в файле.
3. Число строк в разделе с уроками и оценка, сколько из них
   применимы к типичному приращению.
4. Команда точечного прогона тестов одного модуля.
5. Предлагаемые имена и пути новых файлов, согласованные с тем,
   что уже лежит рядом.
6. Конфликты: занятые имена, уже существующее событие Stop,
   уже установленные переменные окружения.
7. Найденные неинтерактивные прогоны, если есть.
8. Вопросы, где разведка не дала однозначного ответа.

**Остановись и жди подтверждения.** Не переходи к этапу 1 сам.

---

# Этап 1. Сборка (после подтверждения)

Содержимое файлов собирай по фактическому состоянию репозитория.
Ниже — решения, которые приняты и не пересматриваются.

## Принятые решения

**1. Порядок ритуалов: разбор → правки по его итогам → фиксация →
запись в память → ответ.** Если запись в память сейчас единолично
занимает момент перед итоговым ответом, туда уедет состояние до правок.
Порядок фиксируется в двух местах: в новом разделе и в триггере записи.

**2. Инварианты не дублируются в файл субагента.** Субагент получает
иерархию CLAUDE.md автоматически; копия внутри его файла создаёт второй
источник истины, который разойдётся с первым при первой же дописанной
строке. Вместо копии — вспомогательный сценарий, печатающий нужные
разделы выборкой по заголовкам (awk или аналог). Точные заголовки
взять из разведки.

**3. Раздел с уроками проходится отбором, а не перебором.** Субагент
получает саму секцию и выбирает из неё строки, применимые к затронутым
модулям. Остальные уходят в «Непроверенное» без прогона. Дописанный
урок начинает работать в тот же день.

**4. Механика удержания хода — отпечаток изменений.** Хук Stop считает
отпечаток (`git diff HEAD` плюс `git status --porcelain`) и сверяет
с отпечатком, записанным после успешного разбора. Не совпал — код
возврата 2, ход не отпускается, в stderr идёт инструкция. Совпал —
молча пропускает. Изменений нет — пропускает. Файл состояния —
под .claude/state/, каталог в .gitignore.

**5. Область разбора — приращение, а не полный diff.** Фиксация,
помимо отпечатка, создаёт снимок через `git stash create` и записывает
его в refs/reviewed через `git update-ref`. Отдельный сценарий печатает
`git diff refs/reviewed`, а при отсутствии ссылки — `git diff HEAD`.
Основная сессия передаёт вывод субагенту текстом; субагент не добывает
приращение самостоятельно.

**6. Предохранитель на попытках обязателен.** Счётчик попыток в пределах
одной сессии, предел 3, при достижении — ход отпускается с сообщением.
Флаг stop_hook_active как единственный предохранитель не годится:
он превращает шлюз в одноразовое напоминание.

**7. Шлюз молчит, пока разбор идёт.** События SubagentStart
и SubagentStop с якорным matcher по имени критика ставят и снимают
файл-маркер под .claude/state/. Хук Stop проверяет маркер до счётчика
попыток, иначе попытки расходуются на ходы ожидания.

**8. Разбор выполняется на переднем плане.** В блок env проектных
настроек добавляется CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1, а в правила
и в файл субагента — прямой запрет на фоновые оболочки ожидания
и опрос собственного состояния. Опрос стоит по ходу за реплику
и ничего не даёт.

**9. Бюджет разбора обязателен.** Во фронтматтере: model sonnet,
effort medium, maxTurns 25. В системном запросе — мягкий бюджет:
не более 12 проверок запуском, не более 3 файлов вне приращения,
по исчерпании немедленный отчёт по достигнутому. Ориентир для
приращения в 200–300 строк: 3–6 минут, 30–50 тысяч токенов,
до 25 вызовов инструментов.

**10. Доказательство дефекта — краснеющий тест, но только для
подтверждённой находки.** Тест обязан краснеть на коде до правки
и зеленеть после. Зелёный в обоих случаях доказательством не является.
Гипотеза отсеивается чтением; запуск тратится на подтверждение.
Прогон точечный — только модули из приращения.

**11. Субагент не правит код.** Он находит, доказывает воспроизведением
и передаёт основной сессии. Единственное исключение — регрессионный
тест, доказывающий дефект.

**12. Субагенту запрещено предлагать возврат исключённого из модели.**
Сознательно исключённая функциональность — решение владельца, а не
упущение. Стилистику, именование и предпочтения он не трогает вовсе.

**13. Формулировки «выглядит корректно» и «изменения безопасны»
запрещены.** Либо проверено запуском, либо в разделе «не проверено».
Итог разбора — три раздела: подтверждённые дефекты (файл, строка,
нарушенный инвариант или пункт уроков, сценарий воспроизведения,
написанный тест), проверенные и опровергнутые гипотезы, непроверенное
с указанием причины. В конце — израсходованный бюджет.

**14. Перепроверка не выполняется только при косметической правке**
(опечатка, форматирование, один импорт) или когда файлы проекта
не менялись. «Тесты зелёные» и «правка тривиальная» основанием
не являются.

## Что должно появиться

- Субагент-критик в .claude/agents/ с бюджетом и потолком ходов.
- Сценарий выборки разделов CLAUDE.md.
- Сценарий расчёта отпечатка.
- Сценарий области разбора (приращение).
- Сценарий фиксации: отпечаток плюс refs/reviewed.
- Хук Stop в настройках проекта плюс события SubagentStart
  и SubagentStop для маркера.
- Переменная CLAUDE_CODE_DISABLE_BACKGROUND_TASKS в блоке env.
- Команда ручного запуска разбора — в стиле уже существующих команд.
- Раздел «Враждебная перепроверка» в CLAUDE.md перед разделом
  о памяти проекта.
- Правка триггера записи в память и пункт в определении готовности.
- Строка в .gitignore.

Если разведка показала, что settings.json существует — влей блоки
hooks и env в имеющуюся структуру, не перезаписывая файл. Событие Stop
не поддерживает matcher. Имена в matcher для SubagentStart
и SubagentStop заякорить символами ^ и $.

## Приёмка (проверяется исполнением)

1. Сценарий выборки печатает все заявленные разделы целиком.
   Пустой вывод или обрезанная секция — задача не готова.
2. **Живая проверка удержания:** внеси заведомую правку, попробуй
   завершить ход — хук обязан не отпустить. Выполни фиксацию,
   завершись повторно — обязан отпустить. «Сценарий выглядит
   правильно» без прогона не засчитывается.
3. **Проверка приращения:** после фиксации внеси правку в один файл,
   убедись, что сценарий области печатает только её.
4. **Проверка маркера:** созданный вручную файл review-running
   переводит хук в код 0, удаление возвращает блокировку.
5. **Проверка предохранителя:** счётчик растёт, на четвёртой попытке
   ход отпускается с сообщением, а не зацикливается.
6. **Проверка живучести выборки:** допиши временную строку в конец
   раздела с уроками, убедись, что она появилась в выводе, откати.
7. **Проверка бюджета:** прогон на приращении в 200–300 строк
   укладывается в 6 минут и 50 тысяч токенов. Упор в maxTurns —
   задача не готова.
8. Хук виден в /hooks как проектный на событии Stop.
9. Каталог .claude/state/ не в индексе git.
10. Конвейер CI зелёный.

## Вопросы владельцу — задать в отчёте, не решать самому

- Класть хук в settings.json (включится у всех, кто работает через
  Claude Code) или в settings.local.json (только эта рабочая копия)?
- Отключать фоновый режим глобально переменной или ограничиться
  запретом в правилах, если фон нужен для других задач?
- Если разведка нашла неинтерактивные прогоны `claude -p` — нужно ли
  для них исключение?
- Сужать ли срабатывание по типу файлов (например, отпускать ход,
  когда в приращении только *.md)? По умолчанию не сужаем: сначала
  нужна неделя наблюдений за реальной частотой, фильтр вслепую
  вырежет ровно те случаи, ради которых всё затевалось.

## Завершение

Запись в память проекта в принятом формате. В описании PR — чем именно
проверено удержание хода, живучесть выборки и соблюдение бюджета
(пункты 2–7 приёмки), а не только состав файлов.

Ограничения и неинтерактивные прогоны

Событие Stop срабатывает и в неинтерактивном режиме. Хук, блокирующий завершение хода, в конвейере CI приведёт к зависанию до истечения предела блокировок. Отсюда проверка переменной CI в начале сценария; альтернативы — отдельный файл настроек через --settings либо параметр disableAllHooks для нужного окружения.

Прочие ограничения, влияющие на конструкцию:

  • Вывод хука ограничен 10 000 символов; превышение усекается или выносится в файл.
  • Хуки исполняются без управляющего терминала — прямая запись в /dev/tty недоступна. Сообщение пользователю передаётся через systemMessage.
  • Предел ожидания по умолчанию — 600 секунд для командных хуков; для быстрой проверки отпечатка разумно поставить 30.
  • jq должен присутствовать в PATH. При его отсутствии разбор JSON заменяется средствами python3 либо grep.
  • Ссылка refs/reviewed локальна для рабочей копии. При работе в нескольких копиях одной ветки приращение считается отдельно в каждой.
  • Хуки из файлов настроек действуют и внутри субагентов. Событие Stop, объявленное во фронтматтере субагента, преобразуется в SubagentStop.
  • При maxTurns субагент обрывается жёстко: отчёт может остаться недописанным. Мягкий бюджет в системном запросе обязан срабатывать раньше.

Альтернативы: PostToolUse, prompt-хук, agent-хук

Событие Stop — не единственная точка перехвата, но для перепроверки самая подходящая. Сравнение подходов:

  • Stop против PostToolUse. PostToolUse срабатывает после каждой правки файла — разбор запускался бы десятки раз за ход, на промежуточных состояниях кода. К тому же это событие не блокирует: инструмент уже отработал. Годится для линтера и форматирования, не годится для шлюза.
  • Командный хук против prompt-хука. Обработчик типа prompt отправляет запрос быстрой модели и получает решение да/нет. Проверка «был ли разбор» становится вероятностной и стоит токенов на каждом ходе. Сравнение хешей детерминировано, бесплатно и не ошибается.
  • Обработчик типа agent. Порождает субагента прямо из хука. Механизм экспериментальный, поведение может измениться; для конструкции, от которой зависит порядок завершения хода, ранние возможности — лишний риск.
  • Кодовый шлюз в git. Хук pre-commit проверяет то же самое, но срабатывает позже: агент к этому моменту уже отчитался о готовности, а человек уже прочитал ответ. Оба уровня дополняют друг друга и не заменяют.

Вместо кода возврата 2 доступен возврат JSON при коде 0: {"decision":"block","reason":"..."}. Функционально равнозначно, но в интерфейсе блокировка через код 2 помечается как ошибка хука, что вводит в заблуждение. Поле hookSpecificOutput.additionalContext для событий Stop и SubagentStop передаёт агенту обратную связь без метки ошибки и позволяет продолжить ход — предпочтительный вариант для проектов, где важна чистота расшифровки.

Частые вопросы

Разбор идёт дольше, чем разработка. Что смотреть первым?

Панель фоновых задач: время, токены и число вызовов инструментов. Двадцать минут и 190 тысяч токенов на приращении в 250 строк означают, что критик обходит проект вместо разбора приращения. Лечится передачей diff текстом, ограничением области, бюджетом проверок и потолком maxTurns. Если в расшифровке видны чередующиеся строки ожидания — проблема в фоновом режиме, а не в объёме работы.

Почему хук не блокирует, хотя сценарий возвращает ошибку?

В подавляющем большинстве случаев блокирующая ветка завершается через exit 1. Для события Stop блокирует только код 2; остальные ненулевые коды считаются неблокирующими ошибками. Проверяется одной строкой: echo '{}' | .claude/hooks/require-review.sh; echo $?.

Что произойдёт при ошибке в логике счётчика?

Сработает встроенный предел: после восьми блокировок подряд сессия завершается с предупреждением. Порог настраивается переменной CLAUDE_CODE_STOP_HOOK_BLOCK_CAP. Восемь потраченных ходов — заметная потеря, поэтому собственный предохранитель на трёх попытках остаётся обязательным.

Зачем ставить маркер через SubagentStart, если есть счётчик попыток?

Счётчик защищает от бесконечной блокировки, но не отличает ход ожидания от хода с правкой. Без маркера три попытки расходуются, пока критик ещё работает, и шлюз отпускает ход ровно тогда, когда отчёт наконец приходит.

Нужно ли дублировать правила проекта в файл субагента?

Нет. Пользовательские субагенты получают всю иерархию CLAUDE.md при запуске. Дублирование создаёт второй источник истины, расходящийся с первым при первой же правке.

Обязательна ли разведка перед сборкой?

Да. Выборка разделов сверяет заголовки посимвольно, событие Stop может быть уже занято, а команда точечного прогона тестов у каждого проекта своя. Без фактических данных агент соберёт правдоподобную, но неработающую конструкцию.

Работает ли конструкция без git?

Нет: и отпечаток, и приращение строятся на git. Вне репозитория сценарий возвращает no-git, и хук пропускает ход. Замена через хеширование дерева каталогов возможна, но обходится дороже и не даёт приращения.

Как отключить конструкцию временно?

Параметр "disableAllHooks": true в файле настроек отключает все хуки разом. Отключение отдельного хука с сохранением его в конфигурации не предусмотрено — запись удаляется из JSON.

Заключение

Перепроверка, оформленная как механика, отличается от перепроверки, оформленной как просьба, ровно одним свойством — её нельзя пропустить, когда ход длинный и внимание рассеяно. Но неотвратимость без ограничений превращается в собственную противоположность: разбор на двадцать минут и двести тысяч токенов при правке в двести строк отменяют сами себя, потому что первым делом отключается именно то, что мешает работать. Рабочая конфигурация складывается из четырёх решений: изолированный контекст субагента убирает предвзятость автора кода, приращение вместо полного diff не даёт перепроверять одно и то же на каждом ходе, бюджет и потолок ходов удерживают стоимость в разумных пределах, а передний план вместо фона возвращает ходы, которые иначе уходят на опрос. Стоимость внедрения — пять небольших сценариев, один блок в настройках и одно двухэтапное задание; стоимость невнедрения измеряется в разборах инцидентов на коде, который запускался, показывал зелёные тесты и делал не то.

Оцените статью
ctrllife.ru
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x