TL;DR
Если задачу агенту описали словами, а проверяют заранее написанными тестами, тесты часто требуют того, чего в описании нет: точное имя константы, путь к файлу, дословный текст сообщения. Автор называет это разрывом между спецификацией и проверкой (specification–oracle gap). Агент может реализовать поведение верно, назвать константу по-своему и получить «ноль». Тест тогда проверяет не «умеет ли агент сделать задачу», а «угадал ли агент названия из чужого кода».
Боль в том, что провал выглядит как «агент не справился», хотя он справился. В Go, Rust и Java тесты пакета компилируются вместе. Одно неугаданное имя валит весь пакет. В статье такие «заблокированные» единицы падали в 80,6% прогонов, сопоставимые без скрытых имён — в 48,4%. Агенты угадывали только 45% скрытых имён. Выводимые по соглашениям проекта (например, Allow → AllowCtx) они воспроизводили в 79% случаев.
Метод — аудит по формуле: собрать всё, что тесты используют (имена, пути, дословные строки), вычесть то, что уже есть в кодовой базе, названо в ТЗ или выводится из соглашений проекта. Остаток и есть разрыв. Автор делает это скриптом, а читатель может применить ту же логику вручную или через LLM. Вывод для практики: всё, от чего зависят тесты, должно быть в ТЗ или в кодовой базе.
Схема метода
ШАГ 1: Выписать всё, на что ссылаются тесты → идентификаторы, пути импорта, дословные строки
ШАГ 2: Вычесть то, что «зафиксировано» →
• уже есть в кодовой базе
• названо в ТЗ дословно
• выводится из соглашений проекта (префикс/суффикс, место файла, сигнатура интерфейса)
ШАГ 3: Остаток = разрыв → G(тест) = Зависимости(тест) \ Зафиксированное
ШАГ 4: Оценить радиус поражения →
• имя или путь: падает весь пакет (всё компилируется вместе)
• дословная строка: падает только один тест
ШАГ 5: Закрыть разрыв → дописать имена/пути/строки в ТЗ (или убрать зависимость из теста)
Шаги 1–3 в статье автоматизирует статический скрипт на пяти языках. Модель при этом не вызывается.
Пример применения
Задача: Вы фрилансер и делаете для интернет-магазина на FastAPI уведомления в Telegram о оплаченных заказах (оплата через ЮKassa). У заказчика уже лежит папка tests/ с приёмочными тестами. Вы отдаёте задачу Claude Code. Тесты импортируют notifications.telegram.TelegramNotifier, константу ORDER_PAID_TEMPLATE и сравнивают текст «Заказ №{id} оплачен: {сумма} ₽». В вашем ТЗ про это ни слова. Пока вы не проверили, агент может сделать работающий код, который провалит приёмку.
Промпт:
Ты проверяешь техническое задание перед передачей агенту.
<ТЗ>
При получении вебхука ЮKassa со статусом payment.succeeded отправлять
менеджеру сообщение в Telegram: номер заказа и сумму. Токен бота брать
из переменных окружения. Если Telegram недоступен — не ронять обработку
вебхука, логировать ошибку.
ТЗ>
<Тесты>
{вставь содержимое tests/test_notifications.py и tests/conftest.py}
Тесты>
<Структура_репозитория>
{вставь вывод tree -L 3 и содержимое app/config.py}
Структура_репозитория>
Шаг 1. Выпиши ВСЁ, на что ссылаются тесты: импорты и пути модулей,
имена классов, функций, констант, полей, а также строки, которые
сравниваются с выводом дословно.
Шаг 2. Для каждого пункта определи:
(а) уже есть в репозитории,
(б) названо в ТЗ дословно или теми же словами по порядку,
(в) однозначно выводится из соглашений репозитория (укажи, из каких),
(г) нигде не зафиксировано.
Шаг 3. Выдай таблицу только по пунктам (г): что именно, в каком тесте,
что случится, если агент назовёт иначе (упадёт один тест или весь модуль).
Шаг 4. Предложи дополнение к ТЗ — блок «Обязательные имена и тексты»,
который закрывает все пункты (г).
Результат: Модель выдаст четыре блока: список зависимостей тестов, их классификацию по (а)–(г), таблицу «скрытых» пунктов с радиусом поражения и готовый блок для вставки в ТЗ. По вашему примеру ожидайте в таблице путь модуля, имя класса, константу шаблона и дословный текст сообщения. Конкретные формулировки зависят от ваших тестов.
Почему это работает
Слабость. ТЗ описывает что должно происходить, а тесты проверяют как это названо и где лежит. Всё, что в ТЗ не сказано, агент выбирает свободно, и это нормально. Тест же считает свободный выбор ошибкой. Агент не видит, что тесты зависят от конкретного имени: «правильного» ответа у него нет, только догадка. В скомпилированных языках догадка ещё и срабатывает на весь пакет сразу.
Сильная сторона. Агенты хорошо следуют явным указаниям и соглашениям, которые видны в кодовой базе. Выводимые имена они воспроизводили в 79% случаев. Если имя написано в ТЗ, оно перестаёт быть угадайкой: названные в ТЗ имена не ухудшали результат.
Как метод это использует. Он переводит зависимости тестов из «скрытых» в «явные»: сначала находит остаток, потом закрывает его текстом. Не нужно просить агента «быть внимательнее». Нужно убрать место для угадывания.
Рычаги: - Что считать «зафиксированным» → расширь (соглашения проекта) или сузь (только дословные упоминания в ТЗ), если хочешь строже или мягче аудит. - Радиус поражения → в Go/Rust/Java/TypeScript сначала закрывай пункты, которые валят целый модуль. Дословные строки — потом. - Сообщения об ошибках → если агент видит и может исправить падения при импорте, цена разрыва ниже (так в SWE-Chain). - Порядок задач в цепочке → если задача опирается на имена из предыдущей, повторяй их в её ТЗ.
Шаблон промпта
Ты — аудитор разрыва между ТЗ и тестами.
<Определения>
Зависимость теста — всё, что решение должно предоставить, чтобы тест прошёл:
идентификаторы (функции, классы, константы, методы, поля),
пути модулей и файлов, которые импортируются или подменяются,
строки, которые сравниваются с выводом дословно.
Зафиксировано — то, что (1) уже есть в исходной кодовой базе,
(2) названо в ТЗ дословно или теми же словами по порядку (для файла — только
если назван файл), (3) однозначно выводится из соглашений, уже
используемых в кодовой базе.
Разрыв = Зависимости \ Зафиксировано.
Определения>
<ТЗ>{текст_тз}ТЗ>
<Тесты>{код_тестов}Тесты>
<Кодовая_база>{структура_и_ключевые_файлы}Кодовая_база>
<Предыдущие_задачи>{ТЗ_предыдущих_задач_или_«нет»}Предыдущие_задачи>
<Процедура>
1. Выпиши все зависимости каждого теста.
2. Для каждой определи класс:
ЗАФИКСИРОВАНО_БАЗА / ЗАФИКСИРОВАНО_ТЗ / ВЫВОДИМО (укажи соглашение) / РАЗРЫВ.
3. Для каждого РАЗРЫВА определи радиус поражения:
имя или путь → падает вся единица, которая собирается вместе ({единица_сборки});
только строка → падает один тест.
4. Отдельно отметь «скрытые в названии теста» случаи: часть имени теста
строится из строки, которую знает только эталонное решение.
Процедура>
<Вывод>
Таблица: зависимость | тест | класс | радиус поражения.
Затем блок «Обязательные имена, пути и тексты» для вставки в ТЗ — только по РАЗРЫВАМ.
Не включай в блок то, что выводимо или уже зафиксировано.
Вывод>
Подставьте: {текст_тз} — задание агенту, {код_тестов} — файлы тестов, {структура_и_ключевые_файлы} — вывод tree и 2–3 файла, по которым видны соглашения проекта, {единица_сборки} — пакет, модуль или тестовый файл, который у вас собирается целиком.
🚀 Быстрый старт — вставь в чат:
Вот шаблон аудита разрыва между ТЗ и тестами. Адаптируй под мою задачу: [твоя задача].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит про язык и систему сборки, где лежат тесты и что проверяют, какие в проекте соглашения об именах и есть ли предыдущие задачи в цепочке. Это нужно, чтобы правильно определить «выводимое» и посчитать радиус поражения: на Go и Python он разный.
Ограничения
⚠️ Вывод для практики переносится, а не проверен: Статья анализирует бенчмарки. Что добавление имён в ТЗ повышает долю успешных задач у вас, авторы как вмешательство не измеряли. Они показали, что скрытое имя ведёт к провалу, а в нескольких прогонах указанное имя помогало пройти тесты.
⚠️ Основной анализ — на одном бенчмарке: Глубокие выводы сделаны на семи репозиториях. Сравнение с четырьмя другими бенчмарками авторы называют предварительным: единицы проверки и размеры разные.
⚠️ Зависит от того, как устроены тесты: Если тесты проверяют поведение через публичный интерфейс (scikit-learn, ripgrep), разрыва почти нет. Если тесты лезут во внутренности пакета (go-zero) — он большой.
⚠️ Зависит от того, что видит агент: Если среда показывает агенту ошибки импорта и он может их исправить, цена разрыва падает.
⚠️ Аудит несовершенен: Скрипт даёт ложные срабатывания (около 11%) и пропуски (около 7%). Ручной или LLM-аудит нужно проверять глазами.
⚠️ Рейтинг моделей почти не меняется: Если убрать затронутые тесты, порядок моделей остаётся прежним. Разрыв важен для разборов отдельных прогонов и для отдельных репозиториев, не для «кто лучше» в целом.
⚠️ Часть работы недоступна в тексте: Раздел с рекомендациями для создателей бенчмарков в материале обрывается.
Как исследовали
Автор взял SWE-Milestone — бенчмарк, где агент в одном репозитории проходит цепочку зависимых изменений (98 этапов в семи проектах на пяти языках). Проверяют заранее написанными тестами из исходного проекта, а описание этапа генерирует LLM. Он написал статический скрипт, который для каждого теста находит все имена, пути и дословные строки и вычитает то, что есть в исходном коде, в ТЗ или выводится по соглашениям. Скрипт проверили на выборках, размеченных вручную: точность 89,2%, полнота 92,9%.
Затем взяли 35 опубликованных прогонов (Claude Code, Codex, Gemini CLI, OpenHands, 27 моделей). В 25 из 98 этапов нашёлся тест со скрытым неустранимым именем или путём. Единицы сборки с такой зависимостью падали в 80,6% случаев, сопоставимые без неё — в 48,4%. Отношение 1,67 осталось после поправки на размер и репозиторий. Названные в ТЗ имена на падения не влияли (1,08).
Дальше — контрфактуал без агентов. В эталонном решении переименовали одну скрытую константу, и тесты не компилировались в 126 из 128 случаев. Это подтверждает механизм: виновато именно имя, а не «сложность задачи».
Удивило то, что рейтинг почти не меняется, если убрать затронутые тесты. Зато в go-zero средняя оценка прогонов вырастает с 38,33 до 42,08.
Ещё одна находка: от способа построения ТЗ зависит многое. Где ТЗ — заметки о релизе или краткое изложение от LLM, треть задач и больше затронута. Где задачи отобраны людьми (SWE-bench Verified) или написаны под тесты (DeepSWE), затронуто почти ничего. Правда, у редких случаев цена высокая: в SWE-bench Verified две задачи никто из 78 сильных систем не решил.
Адаптации и экстраполяции
🔧 Техника: добавить блок «Контракт имён» в ТЗ → закрыть разрыв до запуска
Перед запуском агента вставляйте в задачу такой блок (это перенос идеи статьи, а не её шаблон):
<Контракт_имён>
Обязательные имена и пути (не переименовывать):
- модуль: {путь}
- класс/функция/константа: {имя}
Тексты, которые сравниваются дословно:
- {строка}
Свободный выбор: всё остальное — называй по соглашениям проекта.
Контракт_имён>
🔧 Техника: правило в CLAUDE.md / AGENTS.md → агент сам не угадывает
Если в тестах (или в примерах использования) есть имя, путь или дословная
строка, которых нет в ТЗ и в кодовой базе, НЕ придумывай их.
Остановись и перечисли такие пункты вопросами.
Эффект: агент превращает «скрытое» в вопрос вместо молчаливой догадки. Это моя вариация, в статье не проверялась.
Экстраполяция: аудит в цепочке задач. Если вы даёте агенту несколько задач подряд, добавьте в промпт аудита блок <Предыдущие_задачи>. Большинство «жёстких» скрытых имён в статье были класса A2: имена, которые ввела предыдущая задача, не упомянув в своём ТЗ, а последующие тесты на них полагаются.
Ресурсы
- What the Specification Did Not Say: Specification–Oracle Gaps in Test-Graded Coding-Agent Benchmarks — Fei Ding, Alibaba Group
- SWE-Milestone (Deng et al., 2026), SWE-bench Verified (OpenAI, 2024), SWE-EVO (Le et al., 2025), SWE-Chain (Lam et al., 2026), DeepSWE 1.1 (Huang et al., 2026)
- Авторы обещают публикацию аудита, исправленных списков фильтров и дополнений к спецификациям
