Спецификация вместо промпта: как писать ТЗ для AI-агента-разработчика

Почему вайбкодинг ломается на втором спринте

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

Вайбкодинг хорош ровно до того момента, пока код не накопил историю. Когда история есть, агент начинает с ней конфликтовать.

Вот что происходит на практике. Ты просишь добавить авторизацию к существующему API. Агент пишет middleware, тесты проходят. Но внутри он тихо замокал UserService, потому что полная интеграция потребовала бы понять пять файлов, разбросанных по репозиторию. Он не сказал тебе об этом. Тест зеленый, задача выглядит решённой. Через неделю ты обнаруживаешь это в продакшене.

Второй сценарий: агент меняет архитектуру под текущую задачу. Репозиторий использует event-driven подход, но промпт сформулирован так, что агент решает проблему синхронным вызовом. Это работает локально. Это рвёт всё при нагрузке.

Третий: агент забывает конвенции. В репозитории принято логировать через logger.info с трейс-идентификатором. Агент пишет console.log. Ни один линтер это не поймает, но операционная команда потеряет эти логи в Grafana.

Цифра, которую часто цитируют про Claude Code, это 80.8% на SWE-bench Verified. Но этот результат получен на задачах с чётко очерченным контекстом и явными критериями приёмки. На размытых промптах с неполным контекстом модель работает принципиально иначе. SWE-bench не тестирует "опиши фичу в двух предложениях", он тестирует "вот репозиторий, вот issue с конкретным описанием поведения". Разница огромная.

Корень проблемы в том, что промпт оптимизирован под одно сообщение. Ты формулируешь запрос, агент отвечает. Это хорошо работает для скрипта на 50 строк, который напишешь сегодня и больше не откроешь. Для фичи, которая проживёт в репозитории шесть месяцев и будет трогаться восемь раз, промпта недостаточно.

Спецификация решает другую задачу. Она описывает не "сделай X", а жизненный цикл: что такое X, как X взаимодействует с Y и Z, какие инварианты нельзя нарушать, как выглядит готовая работа. Агент с хорошей спекой воспроизводит решение стабильно через итерации, потому что каждый раз получает полный контекст, а не кусок пазла.

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

Вайбкодинг не сломан. Но у него есть срок годности внутри одного проекта, и этот срок короче, чем кажется в начале.

График расхождения vibe coding и spec-driven разработки по итерациям

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

Что такое spec-driven development в применении к агентам

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

Это сильно отличается от того, как большинство команд работают с документацией. Обычно пишут ТЗ, кладут в Confluence, и через две недели оно врёт. С агентом такая схема не работает вообще. Агент не держит контекст между сессиями, не помнит, что вы обсуждали в пятницу, и не умеет «догадаться по ситуации». Ему нужен живой источник правды, который актуален прямо сейчас.

Корни у SDD в BDD. Джон Фергюсон Смарт популяризировал идею: сначала описываешь поведение на языке, который читают и человек, и инструмент, а потом уже пишешь код. Gherkin делал это через Given/When/Then. Адаптация под LLM добавляет ещё один слой: спека должна быть machine-readable и human-readable одновременно без компромисса ни в ту, ни в другую сторону. Markdown с жёсткой схемой секций справляется лучше, чем XML или YAML, потому что его можно читать без рендеринга и при этом парсить программно.

К маю 2026 сложился набор инструментов, который реально используют. GitHub Spec-Kit набрал значительное число звёзд и используется рядом команд, работающих с Copilot Workspace. BMAD-Method идёт дальше и задаёт ролевую структуру: кто из агентов за какую секцию отвечает. Kiro от AWS сделал ставку на интеграцию со спеками прямо в IDE, с автоматическим diff-ом при каждом коммите. Augment Code Context Engine решает чуть другую задачу: он индексирует кодовую базу и синхронизирует спеку с тем, что реально написано, а не с тем, что планировалось.

Но инструмент инструментом, а структура важнее. Классическое ТЗ пишет "что делать". Спека для агента содержит три вещи, которых в ТЗ почти никогда нет. Первое: примеры кода прямо в теле документа, не в приложении. Второе: контр-примеры, то есть явный список того, что система не должна делать при похожих входных данных. Третье: ASSUMPTION-блоки, в которых зафиксированы допущения, принятые на момент написания. Агент, который наталкивается на ASSUMPTION, понимает: здесь граница знания, а не граница требования.

Принцип, который O'Reilly описывал применительно к системной архитектуре, здесь работает буквально. Верхний уровень спеки отвечает на "что" и "почему". Как именно это реализовать, агент узнаёт из подспек, которые генерируются по мере декомпозиции задачи. Это не иерархия ради иерархии. Это способ не перегружать контекстное окно: агент тянет только ту подспеку, которая нужна для текущего шага, а не весь документ целиком.

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

Анатомия рабочей спецификации: 7 обязательных блоков

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

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

1. Goal. Одна фраза. Отвечает на вопрос "что считаем успехом". Если не помещается в одну строку, значит у тебя две задачи, режь.

2. Context. Список файлов, модулей и ADR, которые агент обязан прочитать до того, как тронет клавиатуру. Без этого блока он начнёт изобретать паттерны, которых в репе уже три штуки. Я пишу пути явно, не "посмотри в src/", а конкретные файлы.

3. User stories с acceptance criteria. Формат Given-When-Then. Звучит занудно, но именно эта форма заставляет тебя самого додумать поведение до того, как оно станет багом. Given задаёт состояние, When действие, Then проверяемый результат.

4. Constraints. Что нельзя делать. Это важнее, чем кажется: агенту проще запретить, чем направить. Типичный набор: не трогать миграции, не менять публичный API, не добавлять зависимости без апрува, не переписывать соседние модули "заодно".

5. Assumptions. Явный список допущений с маркером [ASSUMPTION: ...]. Смысл маркера: ревьюер (человек или другой агент) видит, где ты додумал за продукта, и может оспорить до мерджа, а не после инцидента.

6. Examples. Примеры входов и выходов, edge cases, ссылка на gold standard файл с эталонным стилем. Если в репе уже есть похожий middleware или хендлер, я тыкаю на него пальцем: "пиши как тут".

7. Eval criteria. Как мы поймём, что агент справился. Не "тесты пройдут", а конкретно: какие тесты, сколько кейсов, какие метрики, что проверяем руками.

Вот живой пример из моего недавнего тикета:

# spec: add-rate-limiter.md


![Анатомия рабочей спецификации: 7 обязательных блоков](/uploads/25-inline-2-25121e8a.png)

*Семь блоков спеки покрывают контекст, границы, сценарии, ограничения и критерии готовности.*

## Goal
Ограничить /api/search до 10 RPS на пользователя, вернуть 429 с Retry-After.


![Сравнение антипаттерна и рабочего Gherkin-сценария](/uploads/25-inline-3-d612b483.png)

*Размытый Given/When/Then ломает авто-тесты, а конкретный сценарий позволяет запустить их сразу.*

## Context
- src/middleware/auth.ts (откуда берём userId)
- src/redis.ts (используем существующий клиент)
- ADR-014-rate-limiting.md


![Структура репозитория для контекст-инжиниринга агента](/uploads/25-inline-4-fad99bab.png)

*Правильная раскладка файлов позволяет агенту находить нужный контекст за один шаг поиска.*

## Acceptance criteria
Given аутентифицированный пользователь
When он делает 11-й запрос за секунду
Then API возвращает 429 и заголовок Retry-After: 1


![Иерархия декомпозиции фичи: верхняя спека и дочерние подспеки](/uploads/25-inline-5-a4f51743.png)

*Верхняя спека фиксирует цель и границы, а подспеки описывают конкретные сценарии без дублирования.*

## Constraints
- НЕ добавлять express-rate-limit, использовать наш RedisClient
- НЕ менять сигнатуру auth middleware

## Assumptions
[ASSUMPTION: лимит общий для всех эндпоинтов /api/search/*]
[ASSUMPTION: для анонимов считаем по IP]

## Examples
См. src/middleware/cache.ts как образец стиля middleware.


![Swim-lane диаграмма жизненного цикла спеки в спринте](/uploads/25-inline-8-d7b426a0.png)

*Спека проходит через четыре роли за спринт: аналитик пишет, разработчик уточняет, агент выполняет, QA закрывает.*

## Eval
- unit: tests/middleware/rateLimiter.test.ts (≥6 кейсов)
- load: k6 скрипт в scripts/load/rate-limit.js, p95 < 50ms

Спека на 30 строк. По моему опыту, агент с ней справляется заметно лучше, чем без неё: количество итераций с правками в чате сокращается. Главный профит даже не в скорости генерации, а в том, что пока я её пишу, я сам успеваю заметить дыры в задаче. Половина моих [ASSUMPTION: ...] после написания превращалась в вопрос продакту, и хорошо, что до кода.

Формат Given-When-Then: почему агенты понимают Gherkin лучше прозы

Когда я переключился с обычных user stories на Gherkin для тасков, которые гоняю через Claude и Cursor, попадание в требование с первого прохода заметно выросло. Дело не в магии. LLM видели миллионы .feature файлов из публичных репозиториев Cucumber, SpecFlow, Behave, и синтаксис Given/When/Then для них работает как якорь: модель переключается в режим структурного мышления, где каждый шаг это предусловие, действие или проверяемое следствие.

Побочный эффект приятный. Любой сценарий, написанный по форме, агент может сразу превратить в тест, на pytest, на Playwright, на что угодно. Я часто пишу спеку, а потом одной командой прошу сгенерировать e2e-тесты по тем же сценариям. Конверсия близкая к единице, потому что структура уже изоморфна тесту.

Главный антипаттерн, на котором горят почти все:

Then система должна корректно обрабатывать ошибки

Слово "корректно" агент додумает сам. Иногда вернёт 400, иногда 422, иногда залоггирует и тихо проглотит. И формально будет прав, потому что ты сам не сказал, что значит корректно. Я видел, как одна и та же расплывчатая Then-строка в разных запусках Claude давала три разных контракта API.

Рабочий вариант жёстче. В Then должен быть наблюдаемый артефакт: HTTP-код, конкретный текст ошибки, поле в JSON, состояние строки в БД, событие в очереди. Если ты не можешь это проверить ассертом, это не Then, это пожелание.

Вот так выглядит сценарий, который агент реализует одинаково в десяти запусках подряд:

Scenario: повторная отправка платежа с тем же idempotency-key
  Given в БД есть платёж с key="abc" и status="completed"
  When клиент шлёт POST /payments с тем же key
  Then ответ 200 (не 201)
  And тело ответа идентично первому ответу
  And в БД ровно одна запись с key="abc"

Обрати внимание на 200 (не 201). Явное противопоставление режет неоднозначность: модель не будет колебаться между "создал заново" и "вернул существующий". И ровно одна запись это проверяемое состояние, а не "не должно быть дубликатов".

Про объём. Я предпочитаю держать 5-9 сценариев на одну спеку. По наблюдениям практикующих команд, длинные списки сценариев создают риск, что агент начинает путать предусловия между ними или тихо игнорировать часть. Если набирается больше, я дроблю фичу на подспеки по доменной оси: отдельно идемпотентность, отдельно валидация, отдельно работа с просроченными токенами. Каждая подспека идёт в свой запуск и в свой PR.

Контекст-инжиниринг: что положить в репозиторий, чтобы агент не гадал

Агент работает ровно настолько хорошо, насколько хорошо документирован проект. Это звучит банально, но за последний год я переписал контекст в трёх своих репах с нуля и каждый раз качество PR от Claude и Codex росло скачком. Не от смены модели. От файлов в корне.

Минимум, который должен лежать в любом репозитории, куда вы пускаете агента:

AGENTS.md или CLAUDE.md в корне. Туда я кладу: стек (Node 22, pnpm, Postgres 16, Drizzle), команды (pnpm dev, pnpm test, pnpm check), правила коммитов, табу (никогда не править миграции задним числом, не трогать legacy/), и список людей, к которым эскалировать в комментарии PR. Файл должен читаться за две минуты. Если у вас там 800 строк, агент утонет в нём так же, как джун.

Эталонный модуль. Я называю его gold standard, в репе это обычно src/features/billing/ или подобное. Один модуль, в котором собраны все конвенции сразу: как мы пишем сервисы, как разносим ошибки, как тестируем, как делаем миграции, как логируем. В AGENTS.md одна строка: "при создании новой фичи копируй структуру из src/features/billing и адаптируй". Это работает на порядок лучше, чем десять страниц "правил стиля". По сути это интеграционный тест для конвенций: если вы что-то поменяли в подходе, gold standard должен обновиться первым.

ADR в /docs/adr/. Короткие записи по шаблону Михаэля Нюгарда: контекст, решение, последствия. Почему мы выбрали Drizzle, а не Prisma. Почему очередь на SQS, а не на Kafka. Почему фронт без Redux. В спеке задачи я прямо пишу агенту: "см. ADR-0014, не предлагай вернуть Redux". Без этого агент будет на каждом ревью предлагать мейнстримное решение, потому что в его обучающей выборке его больше.

Скрипты-проверки одной командой. У меня это pnpm check, под капотом lint + typecheck + unit + быстрый интеграционный прогон. В AGENTS.md написано: "перед тем как сказать done, запусти pnpm check и приложи вывод". Агенты, которые умеют в bash (а это сейчас все рабочие), делают это сами. Если у вас три отдельные команды и порядок важен, забудьте, агент перепутает на втором запуске.

MCP-серверы. К маю 2026 это уже не экзотика, а базовая обвязка наравне с REST. У меня подключены: Jira (агент сам читает тикет, на который ссылается ветка), Figma (вытаскивает токены и размеры из макета вместо того, чтобы выдумывать), Postgres read-only (проверяет схему перед написанием запроса), Sentry (смотрит реальные стектрейсы по багу). Anthropic зафиксировал спецификацию MCP ещё в конце 2024, за полтора года экосистема устаканилась, серверы есть почти под всё. Если вы всё ещё копипастите тикет в промпт руками, вы теряете часа полтора в день.

И антипаттерн, на который я напоролся сам и видел у двух команд клиентов. Называю его EndRoom: в репе есть негласный порядок сборки, который знают только два человека. Сначала собрать packages/shared, потом сгенерировать типы, потом миграции, потом всё остальное. Нигде не записано. Агент делает PR, CI падает на первом же шаге, агент пытается чинить, ломает ещё больше, тратит ваш бюджет на токены и ваше время на ревью мусора. Лечится одним Makefile или скриптом bootstrap.sh, который делает всё в правильном порядке. Полчаса работы, окупается на первом же агентском PR.

Правило, к которому я пришёл: если новый человек не может поднять проект и прогнать тесты за 15 минут по README, агент тоже не сможет. Контекст-инжиниринг это не магия для LLM, это нормальная инженерная гигиена, которую мы все откладывали годами. Агенты просто сделали цену небрежности видимой.

Декомпозиция большой фичи: спека верхнего уровня и подспеки

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

Сверху лежит overview. Это документ для человека и для агента одновременно: зачем мы вообще это делаем, какие метрики двигаем, что считается успехом, где риски. Никакого кода, никаких сигнатур функций. Только цели и ссылки на 3-7 подспек, каждая из которых закрывается отдельной задачей и проходит отдельное ревью.

Вот как у меня лежит реальная фича переписывания чекаута:

specs/
  checkout-v2/
    00-overview.md         # цели, метрики, риски
    01-db-schema.md        # миграция, новые таблицы
    02-domain-model.md     # типы, инварианты
    03-api-contracts.md    # OpenAPI
    04-payment-adapter.md  # интеграция со Stripe
    05-frontend-flow.md    # экраны, состояния
    06-observability.md    # логи, метрики, алерты

Порядок номеров не косметика. Сначала контракты (01, 02, 03), потом реализация (04, 05), потом наблюдаемость (06). Логика такая: если схема БД и типы домена не зафиксированы, агент при работе над платёжным адаптером начнёт сам додумывать поля, и через два PR ты получишь два разных представления одного заказа. Я это ловил трижды, теперь не экономлю на контрактах.

Что можно параллелить, а что нельзя. 01, 02, 03 идут строго последовательно, один агент, один за другим, мердж в main между шагами. А вот 04, 05, 06 уже можно раздать трём агентам параллельно: payment-adapter дёргает API из 03, фронт дёргает то же API из 03, observability вешается на готовые контракты. Они физически не могут пересечься по файлам, если контракты заморожены.

Если же запараллелить 02 и 03 (типы и API одновременно), агенты разойдутся по неймингу полей. У одного customerId, у другого userId, и потом ты руками сводишь два PR. Не делай так.

Подспека сама по себе короткая, страница-полторы. В ней: что входит в скоуп, что явно вне скоупа, какие файлы трогаем, какие тесты обязательны, на какие места в overview и соседних подспеках она опирается. Последний пункт критичен. Без явных ссылок агент не подтянет нужный контекст и начнёт фантазировать.

Я держу для себя одну метрику качества декомпозиции: если ревьюер при чтении PR ни разу не открыл соседнюю подспеку чтобы понять "а что тут вообще происходит", декомпозиция удалась.

Evals: как проверить что агент понял спеку, а не угадал

Anthropic в своём гайде по агентной разработке рекомендует писать evals до того, как агент написал хоть строчку кода. Не после факапа, когда команда уже разгребает сломанный прод и спорит, кто виноват, человек или Claude. Логика очевидна, если подумать секунду: без evals у тебя нет способа отличить агента, который понял задачу, от агента, который угадал по форме промпта и подогнал тесты под свою реализацию.

Я разложил это на четыре уровня, каждый ловит свой тип факапа.

Уровень 1. Базовая гигиена. Код компилируется, линтер молчит, существующий тестовый набор зелёный. Это даже не проверка понимания спеки, это проверка что агент не сломал то, что работало вчера. Звучит тривиально, но часть PR от агента валится именно тут, особенно когда задача затрагивает общий модуль.

Уровень 2. Acceptance tests, написанные человеком до запуска агента. Берёшь тикет, выписываешь acceptance criteria в виде тестов, коммитишь их в ветку, и только потом скармливаешь задачу агенту. Агент не видит, как именно ты проверяешь его работу, он видит только спеку текстом. Это единственный способ убедиться что тесты не подогнаны под решение. Если агент пишет тесты сам, они почти всегда проходят, потому что он пишет их под код, который только что родил.

Уровень 3. Property-based и mutation testing. Вот тут начинается интересное. Property-based (Hypothesis в Python, fast-check в TS) генерирует входы, которые ты не предусмотрел. Mutation testing (mutmut, Stryker) ломает код агента в случайных местах и смотрит, падают ли тесты. Если агент написал тест assert result == 42 под захардкоженное return 42, mutation testing это вскроет: мутация не меняет поведение с точки зрения теста, значит тест бесполезен. Этот подход хорошо зарекомендовал себя именно для агентного кода, потому что агенты склонны писать тесты под свою реализацию, и тавтологические случаи встречаются чаще, чем при ручном написании тестов.

Уровень 4. Ручное ревью на constraints. Тесты не ловят нарушения правил вида "не добавлять новые зависимости", "не использовать requests, у нас httpx", "не лезть в legacy-модуль". Constraints обычно живут в голове техлида или в полузабытом ADR, и агент про них узнаёт только если ты явно положил их в контекст. Ревью на этом уровне это глазами по diff: что появилось в pyproject.toml, какие импорты новые, не вылез ли агент за границы модуля.

Метрика, которую я считаю по команде с февраля: процент PR от агента, прошедших ревью без переделок (squash, force-push, "перепиши вот это" не считаются). Цель выше 70%. Если ниже, проблема почти всегда не в агенте, а в спеке: люди отдают задачи в формате "почини баг с корзиной", агент додумывает, ревьюер потом возвращает на доработку. После того как мы подняли качество тикетов и добавили acceptance criteria обязательным полем, метрика у нас заметно выросла.

Типичные ошибки в спеках и как их чинить

За последний год я переписал, наверное, сорок спек для агентных пайплайнов и собрал коллекцию граблей, на которые наступают все. Дел