SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

Навык Old Coder: как проверять EVIDENCE вместо кода

Метод Old Coder заменяет чтение diff проверкой SPEC и отчетов EVIDENCE. Узнайте, почему мутационное тестирование эффективнее покрытия кода и как проверять отчеты агента.

Что на самом деле меняет навык Old Coder

Навык Old Coder заменяет проверку кода проверкой документации. Ваш агент по написанию кода составляет SPEC до того, как написать хоть одну строку кода; вы утверждаете этот документ, и только после этого агент приступает к реализации. Затем он прогоняет свою работу через фиксированный набор автоматических проверок, называемый gauntlet, и предоставляет вам отчет EVIDENCE, содержащий точные команды и фактические показатели. Вы читаете два документа. Вы никогда не читаете diff.

Этот обмен работает только в том случае, если оба документа вызывают такое же доверие, какое раньше вызывал diff. SPEC вызывает доверие, потому что вы утвердили его до появления кода, поэтому он не мог быть подогнан под уже написанный агентом код. Отчет EVIDENCE вызывает доверие, потому что каждое число в нем получено в результате выполнения команды, которую вы можете запустить самостоятельно и увидеть, что она выдает тот же результат. Если любая из этих частей становится недостаточно строгой, вы меняете проверку на краткое изложение проверки, что хуже, чем чтение diff, так как создает ложное ощущение завершенности.

Этот навык представляет собой обычный markdown, поэтому он работает с любым агентом, который следует письменным инструкциям: Claude Code, Codex CLI, Cursor или вашим собственным циклом. Он относится к тому же семейству, что и навык персонажа ленивого старшего разработчика Ponytail. Если формат файла для вас в новинку, в том, что такое навык агента и как агент его загружает описаны технические аспекты.

SPEC — это единственное решение, которое вы принимаете самостоятельно

SPEC — это план тестирования, составляемый до написания кода. Файл спецификации должен содержать четыре элемента.

  • Конкретные сценарии: входные данные, ожидаемые результаты, граничные случаи, ошибки. divide(1, 0) raises ZeroDivisionError with message X, а не «обрабатывает некорректный ввод».
  • Негативные ограничения: что не должно измениться, например, существующие тесты и сигнатуры публичных API.
  • План настройки: каждый инструмент и каждая новая зависимость с кратким пояснением, зачем они нужны.
  • Абсолютный путь к файлу, чтобы его можно было открыть без поиска.

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

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

Это единственное решение «да» или «нет», которое остаётся за вами; в этом заключается его суть и риск. Относитесь к этому как к внесению изменений в production, потому что это именно то, чем оно является. Если вы уже используете явный шлюз подтверждения перед действиями агента, утверждение спецификации встраивается в то же самое место вашего рабочего процесса.

Отчет EVIDENCE: цифры с приложенными командами

Отчет EVIDENCE — это то, что вы читаете в конце. Данный навык требует, чтобы каждое поведение по спецификации было сопоставлено с тестом, который его проверяет, каждый уровень проверки был представлен вместе с выполненной командой и её фактическим результатом, каждое число было взято из одного свежего запуска после последнего изменения кода, а каждый пропущенный уровень был указан с пояснением причины. Прилагательные не являются доказательством. «Все 50 тестов пройдены, покрытие 49/49 операторов» — это результат. «Хорошо протестировано» — нет.

Демонстрационный отчет в репозитории, demo-rate-limiter/evidence.md, определяет свое исходное состояние как коммит плюс sha256 хэш дерева. Эта строка важнее, чем кажется, поскольку она указывает, какие именно байты привели к этим цифрам. Без неё отчет может молча описывать рабочее дерево, которое больше не существует. Демонстрация показывает этот дрейф. На коммите acc5a89 отчет указывал исходное состояние 8b88bda и 41 пройденный тест. На main по состоянию на 12 октября 2026 года он указывает коммит 42528d9, хэш дерева 5aa96ec5487c957c и 50 пройденных тестов, при этом покрытие осталось прежним: 49/49 операторов и 20/20 ветвлений.

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

Установка навыка и фиксация версии коммита

Репозиторий находится по адресу AmazingAng/old-coder и распространяется по лицензии MIT. В его README приведена команда для установки в одну строку через CLI навыков. В команде указывается имя навыка, так как теперь в репозитории также поставляется второй навык, old-coder-api, предназначенный для проектирования HTTP и JSON API:

npx skills add https://github.com/amazingang/old-coder --skill old-coder

Эта команда устанавливает ту версию, которая содержится в main на момент запуска, при этом сам CLI является динамически обновляемым компонентом. Проверьте, куда были скопированы файлы, прежде чем считать, что навык активен:

ls ~/.claude/skills/old-coder/

Вы должны увидеть SKILL.md и каталог references/. Если их там нет, значит, навык находится не в той директории, где его ищет Claude Code. CLI навыков выполняет установку для каждого агента отдельно: глобальный путь Claude Code — ~/.claude/skills/, в то время как ~/.agents/skills/ относится к другим агентам, например Codex. Таким образом, навык, установленный только туда, существует на диске, но Claude Code его не загрузит. Запуск npx skills@latest add ... позволяет избежать использования устаревшей кэшированной копии CLI.

Отдавайте предпочтение ручной установке, так как это позволяет зафиксировать, что именно вы установили:

git clone https://github.com/AmazingAng/old-coder.git
cd old-coder
git checkout acc5a89
git rev-parse HEAD
mkdir -p ~/.claude/skills
cp -r skills/old-coder ~/.claude/skills/

acc5a89 был последним коммитом в main на 17 августа 2026 года. С тех пор main переместился на a0eb529 (18 августа 2026 года), который оставался актуальным на 12 октября 2026 года. Выберите свой вариант и запишите его. Репозиторий активно развивается, и эталонные тесты (gauntlet reference), шаблоны и протокол верификатора уже перемещались между файлами. Если в ваших отчетах EVIDENCE не указано, какая версия навыка использовалась для оценки, вы не сможете отличить изменения в вашем коде от изменений в правилах. Сохраняйте хэш коммита рядом с SPEC в том же репозитории, где находится управляемый им код.

Для агента, который не считывает ~/.claude/skills, добавьте skills/old-coder/SKILL.md и skills/old-coder/references/gauntlet.md в его системный промпт или файл правил. На этом интеграция завершена.

Что выполняется внутри gauntlet

Gauntlet — это набор уровней, запускаемых после того, как все проверки спецификаций пройдены успешно. Вот их перечень:

  • Полный набор тестов для поиска регрессий. Не допускается появление новых ошибок; все существующие ранее ошибки сначала фиксируются как базовый уровень.
  • Статическая типизация, линтинг и форматирование для предотвращения целых классов ошибок и контроля отклонений в коде.
  • Покрытие измененных строк кода; при несоблюдении порогового значения процесс должен завершиться с ненулевым кодом.
  • Мутационное тестирование для выявления тестов, которые фактически ничего не проверяют.
  • Тестирование на основе свойств (property-based testing) для поиска граничных случаев, которые невозможно предусмотреть.
  • Бюджет сложности, один реальный запуск программы, сканирование цепочки поставок и секретов, а также запуск набора тестов в случайном порядке.
  • Уровни предметной области, выбранные исходя из рисков задачи: стресс-тестирование конкурентности, проверка совместимости API, репетиция отката и бенчмарки задержек.

В демонстрационном примере эти проверки объединены в один скрипт demo-rate-limiter/tools/gauntlet.sh, работающий с зафиксированным набором инструментов в requirements-dev.txt: pytest, pytest-cov, coverage, hypothesis, mypy, ruff, pip-audit и pytest-randomly. Каждая утилита закреплена за конкретной версией (например, pytest 9.1.1, ruff 0.16.0 на октябрь 2026 года). Запустите его:

cd demo-rate-limiter
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
./tools/gauntlet.sh

Скрипт выводит баннер для каждого уровня, например === tests + coverage === и === mutation ===, и завершается выводом === gauntlet: all layers green ===. Он выполняется под set -e, поэтому первый же сломанный уровень останавливает выполнение, и финальный баннер не выводится. Появление финального баннера является индикатором успеха: это означает, что каждый уровень завершился с нулевым кодом. Именно так скрипт считывается в acc5a89. В текущей версии main каждый уровень проходит через вспомогательный скрипт run_layer, поэтому баннеры содержат идентификатор уровня, например === tests-coverage ===. Завершающий этап вызывает сбой, если какой-либо ожидаемый уровень не был выполнен, поэтому пропущенный уровень больше не может остаться незамеченным.

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

pytest -q --cov=ratelimiter --cov-report=term-missing --cov-fail-under=100

Без --cov-fail-under утилита pytest --cov выводит процент покрытия и завершается с кодом 0, независимо от того, насколько сильно упал уровень покрытия. Это пример «открытого при сбое» (fail-open) уровня внутри скрипта, который по условию должен останавливаться при первой же ошибке. Уровень gauntlet, который не может вызвать сбой — это лишь декорация.

Что мутационное тестирование дает сверх покрытия кода

Покрытие кода отвечает на один вопрос: выполнил ли набор тестов эту строку. Оно не отвечает на вопрос, который действительно важен: сработало бы хоть одно утверждение (assertion), если бы в этой строке была ошибка. Тест, который вызывает функцию, но ничего не проверяет, все равно показывает полное покрытие всех затронутых строк. Покрытие выявляет нетестируемый код. Оно не выявляет тесты, которые ничего не проверяют.

Мутационное тестирование напрямую отвечает на второй вопрос. Оно намеренно изменяет код, внося по одному небольшому исправлению за раз, и перезапускает набор тестов. Если тесты падают, мутант считается «убитым» — значит, какое-то утверждение отслеживало это поведение. Если тесты проходят успешно, мутант выжил: строка была выполнена, но результат никто не проверил.

Демонстрация выполняет это с помощью tools/mutants.py. Она вносит пронумерованные единичные изменения в src/ratelimiter/__init__.py, запускает pytest после каждого из них, а затем восстанавливает файл. Эти изменения имитируют ошибки, которые совершает уставший человек: >= превращается в > или удаляется возвращаемое значение.

Правило «убийства» в этом исполнителе — это то, в чем ошибается большинство самописных скриптов для мутационного тестирования. Только код завершения pytest, равный 1, считается «убийством», так как 1 означает, что тесты были запущены и хотя бы один из них провалился. Код завершения 0 означает, что мутант выжил. Любой другой код — ошибка сбора или отсутствие найденных тестов — означает, что ничего не было проверено, и это нельзя учитывать. Скрипт, который считает любой «ненулевой» код «убийством», засчитывает собственные сбои как успехи, и этот показатель будет только расти.

В том же файле есть еще две важные детали. Один мутант, M11, исключен из списка, так как он эквивалентен исходному коду: удаление одной истекшей записи вместо всех истекших записей дает тот же наблюдаемый результат при монотонных часах, поэтому ни один тест не может его «убить». Кроме того, исполнитель устанавливает PYTHONDONTWRITEBYTECODE=1, пока «перчатка» (gauntlet) предварительно удаляет все __pycache__, потому что два мутанта одинакового размера, созданные в одну и ту же секунду, могут использовать общий кэш .pyc, и тогда второй мутант унаследует вердикт первого.

Эта опасность — причина, по которой «перчатка» запускает негативный контроль перед основным проходом мутаций:

.venv/bin/python tools/mutants.py --negative-control
.venv/bin/python tools/mutants.py

Контроль запускает двух мутантов с зафиксированным временем модификации: одного, который должен быть убит, и одного, который строго эквивалентен и должен выжить. Если оба возвращаются как «убитые», значит, кэш байт-кода просочился между запусками, и каждый показатель «убийств» в отчете завышен. Это правило мастерства в отношении проверок, воплощенное в жизнь. pytest и mypy годами оттачивали свое поведение при сбоях. Скрипт, который вы написали на прошлой неделе, этого не делал, поэтому докажите, что он может выдавать ошибку, прежде чем доверять ему при успешном прохождении, и зафиксируйте это доказательство в EVIDENCE.

ГрафикMutants killed by each suite run alone (demo-rate-limiter evidence.md, August 2026)
Данные, на которых построен график
[
  {
    "label": "Scenario tests",
    "mutants_killed": 22,
    "mutants_run": 22
  },
  {
    "label": "Property tests",
    "mutants_killed": 3,
    "mutants_run": 22
  }
]

Собственный отчет EVIDENCE в демонстрации показывает, почему единое агрегированное число все еще скрывает детали. Набор сценариев убил 22 из 22 мутантов. Тесты на основе свойств (property-based tests), запущенные отдельно против тех же мутантов, убили 3. «Убийство» приписывается тому тесту, который падает первым, поэтому идеальный общий результат подтверждает набор тестов в целом, но ничего не говорит о каждом отдельном уровне внутри него. Эти свойства все равно занимают свое место, так как они находят такие формы входных данных, которые никто не перечислил вручную. Они не несут основную нагрузку по проверке корректности, и вы узнаете об этом, только измеряя каждый уровень отдельно.

Запуск проверки на сервере, а не на ноутбуке

Отчет EVIDENCE — это утверждение о том, что определенные команды привели к определенным результатам. Это утверждение можно проверить только в том случае, если кто-то другой получит те же результаты, а ноутбук — худшее место для таких попыток. Ваш Python может быть другой версии, а ваш PATH содержит инструменты, которых не будет на другой машине. Мутационное тестирование усугубляет ситуацию, так как оно перезапускает весь набор тестов по одному разу для каждого мутанта, поэтому один только список из демонстрации означает 22 дополнительных запусков набора тестов.

Поместите всё в контейнер на VPS. Контейнер фиксирует операционную систему и интерпретатор, закрепленный requirements-dev.txt фиксирует инструменты, а VPS предоставляет машину, на которой не запущен ваш браузер.

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
      python3 python3-venv git ca-certificates \
 && rm -rf /var/lib/apt/lists/*
WORKDIR /work
docker build -t gauntlet:24.04 .
docker run --rm -v "$PWD:/work" -w /work/demo-rate-limiter gauntlet:24.04 \
  sh -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e . && ./tools/gauntlet.sh'

Успешный запуск завершается на === gauntlet: all layers green ===. Два этапа требуют исходящего сетевого соединения: установка закрепленных инструментов через pip и слой pip-audit, который проверяет ваши зависимости по базе уязвимостей. Автономный запуск не пропускает этот слой без предупреждения. Он завершается ошибкой, что и требуется от системы контроля качества.

Для запуска при каждом push-событии тот же скрипт становится одним шагом задания CI. Репозиторий выполняет собственную проверку в GitHub Actions на ubuntu-latest с использованием Python 3.12, запускаемую при push-событиях в main и при pull requests. Укажите runs-on на self-hosted runner, и задание выполнится на вашем VPS, при этом уберите триггер pull_request, когда будете это делать:

name: gauntlet
on:
  push:
    branches: [main]
jobs:
  gauntlet:
    runs-on: self-hosted
    defaults:
      run:
        working-directory: demo-rate-limiter
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
      - run: ./tools/gauntlet.sh

Оставьте триггер для push-событий в ветки, которые вы контролируете. Self-hosted runner, который также собирает pull requests из форков, запускает чужой код на вашем сервере с учетными данными вашего runner. Та же логика применима и к самому агенту: предоставьте ему одноразовую виртуальную машину, которую можно удалить, а не вашу рабочую станцию. Если вы хотите, чтобы результат проверки приходил туда, где обсуждаются изменения, подключите его к self-hosted агенту для проверки PR.

Где этот подход не работает

Первый сбой — структурный, и никакие инструменты его не устранят. Проверка превращает ограничения в вашей SPEC в исполняемые доказательства. Она не может сказать вам, была ли SPEC верной. Утвердите спецификацию, содержащую неверные требования, и вы получите безупречный отчет EVIDENCE для неверной программы: полное покрытие, каждый мутант убит, каждый слой отмечен зеленым, а программное обеспечение делает то, что вам не нужно. Каждый час, сэкономленный на чтении diff, должен быть потрачен на чтение спецификации.

Второй сбой — это инструменты проверки. Репозиторий честно сообщает об этом в своем собственном файле доказательств. Его протокол независимой верификации прошел шесть раундов, шестой вернул failed, а исправления, внесенные после этого раунда, так и не были повторно верифицированы, поэтому состояние, которое поставляется, не было верифицировано от начала до конца. Слой проверки shell-скриптов записан как недоступный, а не как пройденный. В ходе предыдущих раундов были обнаружены реальные дефекты поведения и ненадежный механизм выполнения мутантов, работавший для состояний, которые уже были помечены как успешные. Зеленый статус проверки не является самодостаточным подтверждением корректности.

Третий сбой — это область охвата. Написанный вручную список мутантов покрывает только те ошибки, которые кто-то догадался внедрить, и каждый эквивалентный мутант, удаленный из этого списка, — это суждение, которому вы доверяете. Готовые инструменты мутационного тестирования (mutmut, cosmic-ray, Stryker, PIT) генерируют мутантов систематически и являются лучшим выбором по умолчанию, если для вашего языка программирования они существуют.

Соотнесение усилий с уровнем риска

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

  • Уровень 1, тривиальный: опечатка, комментарий, значение конфигурации. Полный набор проверок плюс линтинг, новые тесты не требуются, необходимо лишь одно предложение с пояснением, почему они не нужны.
  • Уровень 2, обычный: исправление ошибки или небольшая функциональность. Полный цикл разработки; исправление ошибки должно начинаться с написания падающего теста, воспроизводящего проблему, чтобы вчерашняя ошибка стала завтрашним регрессионным тестом.
  • Уровень 3, критический: финансовые операции, аутентификация, потеря данных, конкурентность, публичный API. Начните с составления модели отказов, перечисляющей способы, которыми данные изменения могут нанести вред. Добавьте уровень защиты для каждого сценария, затем выполните полный цикл разработки, включая property-based тесты, мутационное тестирование и один этап целенаправленной атаки на реализацию с использованием некорректных входных данных.

Для уровня 3 также предусмотрен экспериментальный этап. Второй агент с «чистым» контекстом получает только контракт задачи, утвержденную SPEC и состояние исходного кода. Он пытается нарушить работоспособность готового решения до того, как будет подписан EVIDENCE. Он не вносит исправлений, а только составляет отчет, который затем оценивается человеком. Это снижает корреляцию, возникающую из-за общего контекста задачи. Это не снижает корреляцию, возникающую из-за использования одной и той же модели.

Если вы предпочитаете создать собственный навык, а не использовать этот, в разделе написание собственного навыка агента описана структура файлов и поле description, определяющее, когда агент загружает этот навык.

FAQ

Что обнаруживает мутационное тестирование, чего не видит покрытие кода?

Покрытие кода фиксирует факт выполнения строки. Оно не может определить, сработала бы какая-либо проверка (assertion), если бы эта строка содержала ошибку. Поэтому тест, который вызывает функцию, но ничего не проверяет, всё равно отчитывается о полном покрытии. Мутационное тестирование намеренно изменяет код, внося по одному исправлению за раз, и перезапускает набор тестов. Выживший мутант означает, что строка была выполнена, но результат её работы ничем не проверялся. Именно поэтому в навыках указано правило «никогда не гнаться за цифрами покрытия» как абсолютное, а мутационное тестирование названо уровнем, который выявляет манипуляции с метриками.

Нужно ли мне по-прежнему читать код, который пишет мой агент?

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

Могу ли я запускать набор тестов Old Coder в CI на собственном сервере?

Да, и это лучший вариант для него. Установите зафиксированную версию инструментария из requirements-dev.txt внутри контейнера и запускайте скрипт тестирования проекта как один из шагов сборки. В GitHub Actions установите runs-on: self-hosted и зарегистрируйте runner на вашем VPS. Ограничьте запуск триггером на push в ветки, которые вы контролируете, так как self-hosted runner, собирающий pull requests из форков, выполняет недоверенный код с правами доступа вашего runner.

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

Зафиксируйте одну версию. Репозиторий находится в стадии активной разработки, его справочные файлы уже были разделены и перемещены, поэтому отчёт, созданный в прошлом месяце, мог быть оценён по другим правилам. Клонируйте репозиторий, переключитесь на конкретный коммит, скопируйте skills/old-coder в ~/.claude/skills/ и запишите хеш этого коммита рядом с вашим SPEC. Тогда изменение в ваших доказательствах (evidence) будет означать изменение в вашем коде.