← назад к разделу

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

Причина не в модели. Задача ставилась голосом и в чате — в форме, которая нигде не хранится и ни с чем не сверяется. Ответ на это придумали не вчера: записать, что должно получиться, до того, как писать код. Новое здесь то, что записанное читает не только человек, но и агент, — и потому от формы записи стало зависеть гораздо больше.

Подход, выросший вокруг этой идеи, называют spec-driven development. Разберём, из чего он состоит, что даёт и где ломается.

одно изменение — один каталог в репозитории Зачем proposal Что сценарии Как design Шаги tasks Архив после мержа следующее изменение приходит к описанной системе, а не к чистому листу агент читает эти файлы целиком — и код выводит из них, а не из переписки

Цикл изменения: зачем меняем → сценарии поведения → как делаем → чеклист шагов → архив после мержа.

Обязательно

Что это такое

Spec-driven development — способ вести работу, при котором артефактом договорённости становится документ в репозитории, а не сообщение в чате. Код появляется из этого документа, а не наоборот.

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

Цикл: от предложения до архива

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

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

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

### Требование: отмена заказа

Покупатель может отменить заказ, пока он не отправлен.

#### Сценарий: отмена оплаченного заказа
- ЕСЛИ заказ оплачен и ещё не отправлен
- КОГДА покупатель отменяет заказ
- ТОГДА заказ переходит в статус «отменён»
- И деньги возвращаются на карту в течение суток

#### Сценарий: отмена отправленного заказа
- ЕСЛИ заказ уже отправлен
- КОГДА покупатель отменяет заказ
- ТОГДА система отказывает и предлагает оформить возврат

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

Такой сценарий одинаково читается человеком, проверяется тестом и понимается агентом. Это его главное свойство — не красота, а то, что из него выводится и код, и проверка.

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

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

## Дизайн

### Что трогаем
- Агрегат Order: новое состояние CANCELLED, переход только из PAID и NEW
- Новая команда CancelOrder (порт in), реализация в OrderService
- Событие OrderCancelled в существующую очередь orders.events
- Таблица orders: колонка cancelled_at (nullable), без удаления старых

### Чего НЕ делаем
- Возврат денег автоматически — отдельное изменение, здесь только событие
- Отмену отправленных заказов (это возврат, другой процесс)
- Изменение публичного контракта GET /orders — поле status уже строковое

### Альтернативы
- Флаг is_cancelled вместо состояния: отвергнуто, статус уже есть и должен
  быть один источник правды о жизненном цикле
- Синхронный возврат денег в той же транзакции: отвергнуто, внешний вызов
  в транзакции; вместо этого событие и отдельный обработчик

### Совместимость
- Старые клиенты видят новый статус как неизвестную строку — проверено,
  в контракте статус не перечисление
- Миграция только добавляет колонку, откат безопасен

Три раздела здесь несут почти всю пользу. «Чего не делаем» — самый ценный: он и есть граница, которую потом проверяет приёмка, и именно его отсутствие даёт результат «агент сделал заодно». «Альтернативы» отвечают на вопрос «почему так» через полгода — это и есть то, чего не найти в истории коммитов. «Совместимость» — место, где ломающее изменение видно до того, как оно уехало в прод.

Задачи. Чеклист шагов, по которому идёт работа. Обычно с отметками: сделано, проверено. Агент берёт задачи по одной, человек видит, где он находится.

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

- [ ] Добавить поле cancelled_at
- [ ] Создать метод cancel()
- [ ] Добавить статус в enum
- [ ] Написать тесты

Все пункты можно закрыть и не получить работающую отмену заказа. А вот тот же объём работы, записанный от поведения:

- [ ] Оплаченный заказ отменяется, статус становится CANCELLED — тест
      insufficientOrder...cancelsPaidOrder зелёный
- [ ] Отправленный заказ не отменяется, приходит отказ с понятным кодом — тест зелёный
- [ ] При отмене публикуется OrderCancelled с id заказа и временем — тест зелёный
- [ ] Повторная отмена не создаёт второе событие — тест зелёный
- [ ] Миграция применяется на копии прода, откат проверен
- [ ] Старый клиент (предыдущая версия образа) работает с новой схемой — прогон тестов

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

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

Кто пишет спеку

Первый практический вопрос, который возникает у всех: человек пишет сам, агент пишет черновик, или пополам? Ответ — пополам, но с жёстким разделением, кто за что отвечает.

Зачем и границы — человек. Раздел «предложение» и строки «чего не делаем» — это решения, а не текст. Агент не знает, что нужно продукту и чего команда сознательно не делает в этот заход; он напишет правдоподобное и полное, то есть заведомо более широкое, чем нужно. Эти два места пишут своей рукой, даже если всё остальное черновиком.

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

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

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

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

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

Итог в одну строку: человек владеет «зачем» и границами, агент помогает с формой и полнотой. Обратное распределение — когда агент пишет спеку целиком, а человек её просматривает — даёт документ, с которым все согласны и который никто не решал.

Что это даёт на самом деле

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

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

У изменения появляется след. Через полгода вопрос «почему здесь так?» получает ответ из архива изменений, а не из легенд.

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

И обратная сторона, о которой стоит знать заранее: спека занимает контекст, то есть стоит денег и внимания модели. Файл изменения на четыре раздела — это обычно 1,5–3 тысячи токенов; если агент читает его в начале каждой сессии (а он должен), это добавляется к каждому обращению вместе с правилами проекта.

Отсюда практические выводы, которые заодно делают спеку лучше как документ:

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

Соотношение простое: спека размером в страницу окупается многократно, спека в десять страниц начинает конкурировать за контекст с самим кодом.

Где этот подход ломается

Честный разбор важнее рекламы, поэтому — четыре типичных провала.

Спека расходится с кодом. Самый частый. Изменение сделали, документ поправить забыли; через три итерации документ описывает систему, которой нет. Дальше он вреден: агент читает его и уверенно делает не то. Лечится единственным способом — правка документа входит в то же изменение, что и правка кода, и ревьюер это проверяет.

Чеклист выполнен, а система не та. Задачи закрыты все до одной, а пользователь по-прежнему не может отменить заказ. Так бывает, когда задачи писали от реализации («добавить поле», «создать метод»), а не от поведения. Признак здорового чеклиста: по нему можно проверить результат, не открывая код.

Сценарии описывают поведение, но не модель. Из строк «КОГДА — ТОГДА» не видно, что «заказ» — это единое целое с правилом «сумма позиций равна итогу», что «отменён» — конечное состояние, из которого нет выхода. Когда таких правил много, они расползаются по сценариям и начинают противоречить друг другу. Это не довод против сценариев — это граница их применимости: они описывают изменение, а не систему.

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

Спека не попала в контекст, хотя лежит в репозитории. Самое частое и самое банальное: агент не читает файлы, о которых не знает. Лечится тем, что путь к файлу изменения называют в задании явно («работай по openspec/changes/cancel-order/»), а общее правило «перед работой прочитай файл изменения» кладут в файл правил проекта, который агент читает всегда.

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

Требование сформулировано как пожелание, а не как условие. «Обработка ошибок должна быть аккуратной» невыполнимо и непроверяемо, поэтому агент сделает своё. Условие из сценария («ТОГДА возвращается 409 и заказ не меняется») выполнимо и проверяемо. Если пункт нельзя превратить в проверку, он будет проигнорирован — не из непослушания, а потому что в нём нет инструкции.

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

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

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

Как начать завтра

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

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

Дополнительно: при первом чтении можно пропустить

Глубже: инструментырасширенное

Три заметных инструмента реализуют одну схему по-разному.

OpenSpec — самый лёгкий: несколько markdown-шаблонов, каталог openspec/ в репозитории и соглашение о том, как оформлять изменение. Не привязан ни к какой среде разработки и работает с любым агентом, потому что ничего не требует, кроме файлов.

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

Kiro от AWS — тот же цикл, встроенный в собственную среду разработки: спецификация, задачи и агент живут в одном окне. Удобно, пока вы внутри этой среды.

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

Глубже: сценарий превращается в тестрасширенное

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

Сценарий из раздела выше:

ЕСЛИ заказ оплачен и ещё не отправлен
КОГДА покупатель отменяет заказ
ТОГДА заказ переходит в статус «отменён»
И деньги возвращаются на карту в течение суток

Тот же сценарий тестом, построчно:

@Test
void paidNotShippedOrder_isCancelledAndRefundRequested() {
    var order = orders.save(anOrder().status(PAID).shipped(false).build());   // ЕСЛИ

    api.cancel(order.id());                                                   // КОГДА

    assertThat(orders.byId(order.id()).status()).isEqualTo(CANCELLED);        // ТОГДА
    assertThat(events.published()).contains(new OrderCancelled(order.id()));  // И
}
func TestPaidNotShippedOrder_IsCancelledAndRefundRequested(t *testing.T) {
	order := orders.Save(anOrder().Status(Paid).Shipped(false).Build())        // ЕСЛИ

	api.Cancel(order.ID)                                                        // КОГДА

	require.Equal(t, Cancelled, orders.ByID(order.ID).Status)                  // ТОГДА
	require.Contains(t, events.Published(), OrderCancelled{OrderID: order.ID}) // И
}
it('paid, not shipped order is cancelled and refund requested', async () => {
  const order = await orders.save(anOrder().status('PAID').shipped(false).build()); // ЕСЛИ

  await api.cancel(order.id);                                                       // КОГДА

  expect((await orders.byId(order.id)).status).toBe('CANCELLED');                  // ТОГДА
  expect(events.published()).toContainEqual(new OrderCancelled(order.id));         // И
});
def test_paid_not_shipped_order_is_cancelled_and_refund_requested():
    order = orders.save(an_order().status(PAID).shipped(False).build())   # ЕСЛИ

    api.cancel(order.id)                                                  # КОГДА

    assert orders.by_id(order.id).status == CANCELLED                     # ТОГДА
    assert OrderCancelled(order.id) in events.published()                 # И

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

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

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

Глубже: спецификация изменения и спецификация системырасширенное

Из последнего провала растёт различие, которое стоит держать в голове.

Описанные инструменты работают со спецификацией изменения: она живёт от предложения до архива. Есть и другой вид — спецификация системы: она описывает домен целиком (сущности, их жизненный цикл, правила, команды и события) и живёт столько же, сколько сервис. На ней стоит Use Case Pattern, и она отвечает на вопрос «как устроено», а не «что мы меняем в этот заход».

спецификация что меняем сейчас дельта, живёт до архива как всё устроено модель, живёт с сервисом

Два документа с одним названием: дельта отвечает «что меняем сейчас» и уходит в архив, модель системы отвечает «как устроено» и живёт с сервисом.

Они не конкуренты. Долгоживущая спецификация даёт агенту модель мира, дельта изменения — текущую задачу и её границы. Подробное сравнение — в статье Use Case Pattern и spec-driven инструменты.

Коротко

  • Spec-driven development — это про то, что договорённость живёт файлом в репозитории и проходит ревью как код, а не остаётся сообщением в чате. Пропуск первого шага обходится дороже всего: половина задач отваливается на вопросе «зачем», ещё до того, как дело дойдёт до кода.
  • Сценарии «ЕСЛИ — КОГДА — ТОГДА» одинаково читаются человеком, агентом и тестом — в этом их ценность; условие не пропускают.
  • Инструменты — OpenSpec (легко и где угодно), Spec Kit (жёсткий процесс), Kiro (всё в своей среде); механика у всех одна.
  • Главные провалы: документ разошёлся с кодом, чеклист выполнен «по реализации», сценарии не выражают модель, полный цикл ради однострочной правки.
  • Спецификация изменения отвечает «что меняем сейчас», спецификация системы — «как всё устроено»; вместе они дают агенту и задачу, и модель мира.
  • Человек владеет «зачем» и границами, агент помогает с формой и полнотой: сценарии он пишет по вашему рассказу, дизайн предлагает вариантами, решение и раздел «чего не делаем» остаются за вами.
  • Чеклист от реализации («добавить поле») можно закрыть и не получить работающую функцию; здоровый чеклист выводится из сценариев и проверяется без чтения кода.
  • Спека занимает контекст: страница окупается, десять страниц конкурируют с кодом за внимание модели; агенту дают нужный раздел, а архив не подмешивают.
  • «Агент прочитал и сделал по-своему» лечится четырьмя способами: назвать путь к файлу явно, попросить пересказ и противоречия, формулировать требования как проверяемые условия, повторять ограничения в длинной сессии.
  • Сценарий превращается в тест построчно: условие — подготовка, действие — вызов, каждое следствие — утверждение; порядок работы: сценарии, красные тесты, код, приёмка.

Что почитать дальше