Синхронизация спецификаций, тестов и кода в разработке ИИ
Не позволяйте AI-агентам отклоняться от спецификаций, тестов и кода.
Агенты ИИ для написания кода быстро выпускают функции, но спецификации, тесты и код незаметно расходятся. Этот гид описывает модель прослеживаемости, маппинг спецификаций на тесты и код, а также проверки CI, которые выявляют расхождения перед слиянием.
Спецификация, которую никто не перепроверяет по отношению к работающей системе, хуже, чем отсутствие спецификации вовсе, потому что она создает ложное чувство уверенности. Рецензенты доверяют документу вместо diff-а, и агент ИИ, которому сказано «следовать существующему паттерну», охотно последует тому, что код на самом деле делает, даже если это противоречит требованию, которое он должен был удовлетворить.
Решение — не в большем количестве документации. Это маленькая, но обязательная связь между четырьмя вещами, которые уже существуют в большинстве репозиториев: требованием, решением по дизайну, лежащим в его основе, тестами, которые его доказывают, и коммитами или pull-запросами, которые его изменили.

Как только эта связь существует в виде данных, а не общего понимания, вы можете ее запрашивать. Вы можете спросить, какие требования не имеют тестового покрытия, какие тесты больше не соответствуют никакому требованию и какие файлы изменились в pull-запросе без соответствующего идентификатора требования. Этот запрос является фактическим результатом данной статьи, а остальная часть поста объясняет, как его создать с помощью инструментов, которые вы, вероятно, уже используете.
Проблема дрейфа: почему спецификации, тесты и код рассинхронизируются
Дрейф проявляется в четырех узнаваемых формах, и команды, использующие ИИ, сталкиваются со всеми четырьмя быстрее, чем команды, пишущие каждую строку вручную.
- Спецификация меняется, код — нет. Требование уточняется в последующем разговоре или ветке комментариев, но никто не регенерирует или не редактирует реализацию, чтобы она соответствовала.
- Код меняется, спецификация — нет. Агент или разработчик исправляет ошибку или рефакторит модуль, а спецификация продолжает описывать старое поведение, как будто оно все еще актуально.
- Тесты покрывают реализацию, а не намерение. Юнит-тесты утверждают то, что код делает в данный момент, что является циркулярным: они проходят по конструкции даже тогда, когда код удовлетворяет неправильному требованию.
- Pull-запросы не ссылаются на требования. Рецензенты одобряют diff на основе «выглядит разумно», потому что нет явного утверждения, с которым можно было бы его проверить.
Недавние исследования процессов в рамках фреймворков разработки ИИ выделяют дрейф спецификаций как повторяющийся риск именно потому, что агенты быстро и многократно регенерируют код, и каждая регенерация — это новая возможность для того, чтобы спецификация и реализация разошлись еще дальше. Дебаты о Spec-Driven Development vs Vibe Coding на самом деле являются спором об этом же режиме отказа: спецификация, которую никто не контролирует, деградирует до того же дрейфа, что и без нее, просто с лишними церемониями.
Современные рабочие процессы в стиле spec-kit все чаще описывают это как разложение спецификаций (specification rot): спецификация продолжает выглядеть авторитетной, но незаметно теряет связь с тем, что система на самом деле делает. Базовое определение spec-driven development рассматривает спецификацию как источник истины, но источник истины остается истинным только в том случае, если что-то постоянно проверяет его на соответствие реальности.
Модель прослеживаемости для разработки с помощью ИИ
Рабочая модель прослеживаемости нуждается в шести идентификаторах, которые связывают бизнес-требование вплоть до строк кода и pull-запроса, который его реализовал. У большинства команд уже есть три или четыре из них; недостающими обычно являются идентификатор решения по дизайну и явная связь обратно от тестов и коммитов.
| Идентификатор | Где хранится | Пример |
|---|---|---|
| ID требования | requirements.md или инструмент спецификаций |
REQ-014 |
| ID решения по дизайну | ADR / запись о решении | ADR-0032 |
| ID задачи | декомпозиция задач или трекер задач | TASK-014-3 |
| ID теста | файл теста или имя теста | test_req_014_password_reset |
| Ссылка на коммит / PR | История Git | PR #482 |
| Измененные файлы | Git diff | auth/reset.go, auth/reset_test.go |
Отношения между этими идентификаторами формируют граф, а не прямую линию, потому что одно требование может породить несколько задач, и один pull-запрос может затронуть несколько требований одновременно.
REQ-014"] --> ADR["Design Decision
ADR-0032"] ADR --> TASK["Task
TASK-014-3"] TASK --> CODE["Code Change
auth/reset.go"] TASK --> TEST["Test
test_req_014_password_reset"] CODE --> PR["Pull Request
#482"] TEST --> PR PR --> COMMIT["Commit history"]
Хранение этого графа в виде структурированных данных, а не прозы, позволяет вам запрашивать его позже. Экосистема Spec Kit от GitHub пошла именно в этом направлении: расширения, такие как spec-kit-trace, сканируют токены REQ-XXX, встроенные в файлы спецификаций и тестов, и генерируют детерминированную матрицу на основе этого буквального текстового совпадения, намеренно избегая нечеткого угадывания на основе имен, которое приводит к тихим ложным срабатываниям.
Маппинг спецификаций на тесты: превращение критериев приемки в тест-кейсы
Каждый критерий приемки в спецификации, по своей конструкции, является поведенческим утверждением: при данном состоянии, когда актер делает это, система должна отреагировать таким образом. Это уже форма тест-кейса, поэтому самые сильные рабочие процессы SDD генерируют тесты из тех же критериев приемки, которые генерируют код, вместо того чтобы просить агента, генерирующего код, придумывать свои собственные тесты постфактум.
Широко используемый формат для записи этих критериев — EARS (Easy Approach to Requirements Syntax), который заставляет каждое требование вписаться в недвусмысленный, тестируемый паттерн, такой как «Когда <триггер>, система должна <реакция>». Эта структура четко отображается на четыре категории тестов, которые должно иметь каждое требование:
- Позитивные тесты — «счастливый путь», который требование явно описывает.
- Негативные тесты — входы или состояния, которые требование говорит должны быть отклонены.
- Граничные тесты — края диапазонов, лимитов и порогов, упомянутых в критериях приемки.
- Тесты миграции — поведение для данных или состояния, предшествующих требованию, чтобы старая запись не тихо обходила новое правило.
| Тип требования | Категория теста для добавления | Частая ошибка |
|---|---|---|
| «Система должна отклонить X» | Негативные | Тестируется только путь принятия |
| «Лимит N элементов» | Граничные | N-1, N и N+1 не все покрыты |
| «Новое поле заменяет старое поле» | Миграция | Старые записи без нового поля тихо крашатся |
| «В пределах 60 секунд» | Граничные + тайминги | Тест проверяет логику, а не фактический временной бюджет |
Юнит-тесты, написанные таким образом, по-прежнему важны как быстрый, дешевый слой пирамиды; практические паттерны для их структурирования описаны в гайде по юнит-тестированию на Go и в гайде по юнит-тестированию на Python. То, что прослеживаемость добавляет сверху, — это буквальный, стабильный токен требования, встроенный в имя теста или комментарий к тесту, чтобы последующий запрос мог доказать — а не предполагать — что REQ-014 имеет покрытие.
Маппинг спецификаций на код: от планов дизайна к таблице прослеживаемости
Маппинг спецификаций на тесты доказывает поведение; маппинг спецификаций на код доказывает область охвата. Он отвечает на другой вопрос: какие файлы на самом деле должны были измениться для этого требования, и остался ли diff внутри этой границы или переполнился в несвязанные модули?
План дизайна, который заранее перечисляет затронутые файлы — даже грубый список, — дает вам что-то, с чем можно сравнить реальный pull-запрос позже. Комментарии в коде должны ссылаться на ID требования только тогда, когда это добавляет информацию, которую рецензент не может получить из самой спецификации; комментарий, дословно повторяющий текст требования, — это шум, но // enforces REQ-014 boundary: max 5 reset attempts per hour заслуживает своего места, потому что число иначе не видно в diff-e.
Сгенерированная таблица прослеживаемости превращает это в что-то, что можно просмотреть за секунды, а не что-то, что рецензент должен реконструировать, читая оба документа бок о бок:
| Требование | Решение по дизайну | Измененные файлы | Тесты | Статус |
|---|---|---|---|---|
| REQ-014 | ADR-0032 | auth/reset.go, auth/reset_test.go |
test_req_014_* (4) |
Покрыто |
| REQ-015 | ADR-0032 | auth/reset.go |
нет | Пробел |
| REQ-016 | — | auth/notify.go |
test_notify_basic |
Сиротская ссылка спецификации |
Эта одна таблица сразу выявляет две самые распространенные схемы отказа: REQ-015 изменил код с нулевыми соответствующими тестами, и тест, прикрепленный к REQ-016, на самом деле не ссылается на ID требования, что означает, что либо спецификация отсутствует, либо тест был неправильно классифицирован.
Рабочий процесс Pull Request: обзор спецификаций, кода и тестовых diff-ов вместе
Pull-запрос, построенный вокруг прослеживаемости, просматривает три diff-а бок о бок вместо одного: что изменилось в спецификации, что изменилось в коде и что изменилось в тестах. Вопрос рецензента перестает быть «выглядит ли это правильно?» и становится гораздо более конкретным: «какое требование удовлетворяет это изменение и доказывает ли доказательство это?»
Здесь лучше работает короткий, конкретный чек-лист рецензента, чем длинный, потому что рецензенты пропускают длинные чек-листы под давлением дедлайнов:
- Назначает ли описание PR ID требования(ий), которые он удовлетворяет?
- Появляется ли каждый измененный файл в списке затронутых файлов плана дизайна, или дополнительная область охвата объяснена?
- Ссылается ли хотя бы один новый или существующий тест на каждый ID требования, затронутый этим PR?
- Если спецификация изменилась, изменились ли код и тесты в том же PR, или есть отслеживаемое последующее действие?
Автоматизация прослеживаемости в CI
Ручной обзор улавливает дрейф только так часто, как рецензенты помнят о необходимости искать его, поэтому проверки выше должны находиться в CI, а не на странице wiki, которую никто не перечитывает. Те же паттерны GitHub Actions, которые вы уже используете для задач сборки и тестирования, применимы здесь напрямую — проверки прослеживаемости — это просто еще одна задача в том же конвейере.
Практические идеи автоматизации, примерно в порядке усилий:
- Проверки CI для файлов спецификаций — провалить сборку, если файл спецификации был отредактирован без соответствующего изменения кода или теста в том же PR, или наоборот.
- Требовать ID требований в заголовках или описаниях PR — легковесная проверка регулярным выражением (
REQ-\d+) блокирует слияния, которые не называют то, что они реализуют. - Суммарные отчеты прослеживаемости, сгенерированные агентом — пусть агент создает краткое резюме того, какие требования затрагивает PR, чтобы человек подтвердил, а не писал с нуля.
- Покрытие тестов по критерию приемки, а не только по строкам — покрытие по строкам говорит вам, что код запускался; покрытие по требованиям говорит вам, что утверждение было проверено.
- Предупреждения о устаревших спецификациях — отмечать спецификации, которые не трогались в течение N коммитов, затрагивающих их связанные файлы, поскольку долго молчащие спецификации — это те, которые с наибольшей вероятностью тихо разложились.
Расширения, построенные поверх Spec Kit от GitHub, уже механически реализуют несколько из них: одно сканирует буквальные токены REQ-XXX по файлам спецификаций и тестов, чтобы построить матрицу и отметить сиротские тесты, а более строгий пакет, ориентированный на V-модель, заходит еще дальше, генерируя парную спецификацию тестов для каждой спецификации разработки и производя несколько матриц прослеживаемости для команд, работающих в рамках нормативных фреймворков, таких как IEC 62304 или ISO 26262. Вам не нужен такой уровень церемонии для большинства проектов, но основная идея — детерминированная, сгенерированная скриптом матрица, а не поддерживаемая вручную таблица — масштабируется вниз так же хорошо, как и вверх.
Использование агентов ИИ для прослеживаемости, а не как оракула
Агенты ИИ хорошо подходят для механических частей прослеживаемости и плохо подходят для того, чтобы быть окончательным судьей того, было ли требование на самом деле удовлетворено. Три задачи напрямую соответствуют сильным сторонам агента:
- Сравнить спецификацию и diff — попросите агента перечислить каждое требование, упомянутое в файлах спецификаций, затронутых PR, и каждое, для которого он не нашел соответствующего кода.
- Найти непокрытые требования — попросите агента просканировать набор тестов на наличие токенов требований и сообщить, какие требования в спецификации не имеют их.
- Обнаружить код, не описанный спецификацией — попросите агента отметить измененные файлы или функции, которые затрагивают модули, несущие требования, но не соответствуют ни одному ID требования в diff-e.
Режим отказа, от которого нужно защищаться, — это доверие к резюме агента как к истине в последней инстанции, а не как к отправной точке для рецензента. Агент может неправильно прочитать комментарий, пропустить токен требования, разделенный между двумя файлами, или уверенно заявить о покрытии для теста, который лишь поверхностно проверяет путь кода. Относитесь к каждому сгенерированному агентом отчету о прослеживаемости так же, как вы относитесь к проверке со стороны младшего рецензента: полезно, быстро, но все еще требующему второго взгляда перед тем, как он заблокирует слияние. Это та же осторожность, которая применяется к записям решений для разработки, управляемой ИИ — запись остается надежной только в том случае, если что-то другое, кроме агента, который ее написал, в конечном итоге проверяет ее.
Минимальный шаблон прослеживаемости, который можно скопировать
Вам не нужна тяжеловесная фреймворк, чтобы начать. Шаблон из пяти файлов, закоммитированный в репозиторий рядом с кодом, который он описывает, охватывает основы:
docs/
requirements.md # REQ-IDs с критериями приемки в стиле EARS
design.md # ADR-IDs, затронутые файлы, архитектурные решения
tasks.md # TASK-IDs, маппинг на одно или несколько REQ-IDs
tests.md # какие файлы/функции тестов ссылаются на какие REQ-IDs
traceability.md # сгенерированная таблица: REQ -> ADR -> TASK -> файлы -> тесты -> PR
requirements.md, design.md и tasks.md пишутся или редактируются людьми и агентами вместе, так же как уже описано в рабочем процессе spec-driven development. tests.md и traceability.md должны быть сгенерированы, а не поддерживаться вручную, даже если генератором является короткий скрипт, который просто ищет REQ-\d+ по директории тестов и файлам спецификаций — поддерживаемые вручную таблицы прослеживаемости сами по себе являются формой риска дрейфа, потому что никто не обновляет таблицу под давлением дедлайна.
Заключение
Spec-driven development не заканчивается в момент, когда код выходит из агента; он полезен только тогда, когда код, тесты и спецификации со временем держат друг друга честными, через PR, рефакторинг и изменения требований, которые приходят с разрывом в месяцы. Модель прослеживаемости, построенная из шести простых идентификаторов, усиленная горсткой проверок CI и проверяемая с помощью короткого чек-листа PR, дает вам большую часть пользы без накладных расходов полного фреймворка соответствия. Начните с минимального шаблона, подключите самую дешевую проверку CI первой — ID требований в описаниях PR — и добавьте таблицу прослеживаемости и предупреждения об устаревших спецификациях, как только эта привычка закрепится.
Прослеживаемость — это часть более широкой дисциплины тестирования и документации, охватываемой в кластере App Architecture in Production, и она находится наряду с вопросами инструментов, исследованными в кластере AI developer tools для команд, выбирающих, какие рабочие процессы агентов стандартизировать.