В первой статье я рассказал, почему полностью перешёл на Spec-Driven Development, во второй — как изобретал roadmap-слой поверх Spec Kit. Обе статьи заканчивались одним и тем же вопросом из комментариев и личных сообщений: «Хорошо, а что делать с существующим проектом?» И это честный вопрос, потому что оба моих примера были удобными: либо новая фича, либо новый проект. А у большинства из нас не новый проект. У большинства — brownfield: несколько лет истории, продакшен, пользователи, и ни одной спеки.

В прошлой статье я писал, что применение SDD к существующим проектам — «проблемный момент». Пришло время закрыть этот долг. Это рассказ о том, как я за один длинный день (правда, длинный — с трёх дня до полдевятого вечера, в несколько параллельных сессий агента) прогнал через Spec Kit живой продакшен-проект и что из этого вышло.

Пациент

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

  • бэкенд с API, фоновыми задачами и веб-сокетами;

  • веб-фронтенд;

  • мобильное приложение (три магазина приложений, десятки языков);

  • отдельный микросервис отдачи файлов;

  • отчёты и BI;

  • BPM-контур для интеграционных процессов;

  • инфраструктура и деплой.

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

Классический brownfield. Поехали.

Проблема: Spec Kit думает, что кода ещё нет

Ванильный Spec Kit устроен просто: constitutionspecifyclarifyplantasksimplement. Весь конвейер предполагает, что спека появляется до кода. На brownfield это ломается в двух местах сразу.

Во-первых, constitution. По канону это неизменяемые принципы проекта, которые вы пишете в начале. Но на живом проекте принципы уже есть — они размазаны по инструкциям для агентов, pre-commit-хукам, CI-пайплайнам и негласным договорённостям. Написать constitution «из головы» — значит создать второй источник истины, который немедленно начнёт расходиться с первым.

Во-вторых, specify. Новая фича на brownfield не живёт в вакууме — она встраивается в существующее. А существующее нигде не описано. Агент, которому вы говорите «добавь настройку уведомлений», не знает, что система уведомлений уже есть, как она устроена и где у неё границы. Он узнаёт это, читая код, — каждый раз заново, в каждой сессии, тратя контекстное окно на археологию вместо работы.

Отсюда развилка с тремя вариантами:

  1. Использовать SDD только для нового. Спеки — вперёд, старый код — как есть. Дёшево, но каждая новая спека будет писаться вслепую относительно существующего.

  2. Реверс-инжинирить всё руками. Сесть и написать спеки на существующие подсистемы. Дорого настолько, что никто этого никогда не сделает. Собственно, ровно поэтому мы все и оказались там, где оказались.

  3. Реверс-инжинирить агентом. Пусть агент, который и так умеет читать код, напишет спеки задним числом, а я буду ревьюить.

Я пошёл по третьему пути. И тут выяснилось приятное: идти уже есть по чему.

Выбор расширения: не пишите своё, сначала поищите

У Spec Kit есть механизм расширений — сторонние наборы команд, которые подключаются к конвейеру и могут вешать хуки на его этапы. Экосистема пока крошечная, но запрос на brownfield в ней оказался самым громким: в апстриме Spec Kit висит issue про поддержку существующих проектов с десятками реакций, и именно из него выросло расширение spec-kit-brownfield — стороннее, MIT, версии 1.0.0.

Как я его выбирал — честно, без магии:

  1. Сформулировал, чего не хватает ванильному конвейеру: команды «посмотри на существующий код и сделай из него артефакты SDD». Это два разных действия: выжать из кода правила (constitution, шаблоны) и выжать из кода спеки (реверс-инжиниринг фич).

  2. Пошёл в issues апстрима — не в поиск по каталогам расширений, а именно в issues. Логика простая: если боль настоящая, вокруг неё уже есть тред, а в треде — ссылки на то, что люди наделали.

  3. Прочитал исходники расширения перед установкой. Тут важный момент, который снимает большую часть страха перед «сторонним расширением версии 1.0.0 от незнакомого автора»: команды Spec Kit — это не код, это markdown-промпты. Их можно (и нужно) прочитать глазами целиком, как читаешь чужой PR. Я прочитал. Никакой магии там нет — есть аккуратно структурированные инструкции агенту.

  4. Проверил, что первый шаг — read-only. Расширение начинает со сканирования, которое ничего не пишет. Это дало возможность посмотреть на результат до того, как что-то в репозитории изменится.

Критерий «прочитал промпты — понял, что они сделают» победил альтернативу «напишу свои команды». Свои я бы писал неделю и получил бы примерно то же самое, только без обкатки на чужих проектах.

Расширение добавляет четыре команды:

Команда

Пишет в репо?

Что делает

brownfield-scan

нет

Обходит проект, определяет стек, фреймворки, архитектурные паттерны, структуру модулей и конвенции. Результат — «профиль проекта»

brownfield-bootstrap

да

По профилю генерирует constitution и кастомизирует шаблоны спек под реальную архитектуру — вместо генериковых заглушек ванильного Spec Kit

brownfield-migrate

да

Реверс-инжинирит spec.md / plan.md / tasks.md для фич, построенных до Spec Kit

brownfield-validate

нет

Проверяет, что сгенерированные constitution и шаблоны всё ещё соответствуют реальной структуре проекта, и репортит расхождения

Плюс у расширения есть хук after_init: сразу после инициализации Spec Kit автоматически запускается скан. Мелочь, но правильная — профиль проекта появляется раньше, чем вы успеваете сделать что-то на его основе вручную.

Constitution на brownfield — это археология, а не законотворчество

Первый содержательный результат — constitution, сгенерированная bootstrap’ом по профилю проекта. И здесь главный инсайт всего дня:

На brownfield constitution не устанавливает правила. Она их раскапывает.

У проекта уже были законы: зеро-варнинг линтеры на всех трёх платформах с блокировкой в CI, правило «каждая пользовательская строка переведена на все 47 локалей», обязательные чек-листы безопасности для каждого эндпоинта, дисциплина аналитических событий, конвенции именования. Всё это жило в инструкциях для агентов, обязательных скиллах и pre-commit-хуках. Bootstrap не придумал ни одного нового правила — он собрал существующие в шесть принципов и честно прописал в governance-секции: первоисточник — существующие инструкции проекта; при расхождении прав первоисточник, а constitution лишь резюмирует его для конвейера Spec Kit.

Это ровно то, чего я хотел добиться: второго источника истины не появилось. Появился индекс первого.

Отдельно советую не пропускать brownfield-validate после bootstrap: он сверяет сгенерированное с реальной структурой проекта. У меня он поймал пару мест, где скан слишком оптимистично обобщил конвенции («все модули устроены так») по трём примерам из пяти.

Миграция: по одной подсистеме за проход

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

chore: initialize Spec Kit with Brownfield extension
chore: project-aware constitution and templates via bootstrap
docs: migrate <подсистема 1> into spec-kit artifacts
docs: draft specs for the gap features of <подсистема 1>
docs: migrate <подсистема 2> into spec-kit artifacts
docs: draft specs for the gap features of <подсистема 2>
...

Пары «migrate → draft gaps» я гонял в параллельных сессиях — подсистемы почти не пересекаются по файлам, поэтому две-три миграции спокойно идут одновременно, пока я ревьюю уже готовую. Собственно, поэтому восемь подсистем и уложились в один день: это не скорость агента, это конвейер.

Сразу пришлось решить вопрос нумерации, которого нет в ванильном Spec Kit: мигрированные спеки и будущие фичи живут в одном каталоге specs/, и их нельзя путать. Я завёл конвенцию:

specs/
├── 000-001-<подсистема>/   # мигрированные: спека описывает «как построено»
│   ├── spec.md
│   ├── plan.md
│   └── tasks.md
├── 000-002-.../
│   ...
├── 001-<фича>/             # свежие: обычный конвейер Spec Kit
├── 002-<фича>/
│   ...

Префикс 000- — это «нулевой километр», as-built-базлайн. Обычная сквозная нумерация — go-forward-бэклог. Договорённость зафиксирована в коммите ровно в тот момент, когда я на третьей подсистеме понял, что без неё будет каша.

Что такое мигрированная спека

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

  • в шапке — Status: migrated и провенанс: из каких каталогов кода и какой документации спека выведена;

  • plan.md содержит ретроспективный Constitution Check: подсистема оценивается по принципам constitution, как будто её сдают сейчас. Пункты бывают трёх видов: [x] — соответствует, [~] — частично, и у каждого [~] есть ссылка на конкретный гэп (о них ниже);

  • tasks.md выглядит непривычно: вся реально существующая функциональность записана как выполненные задачи, по фазам — модель данных, API, фронтенд, мобилка, тесты. Все галочки проставлены. А в конце — единственная фаза без галочек.

Про эту фазу — отдельный раздел, потому что она оказалась самым ценным результатом всей затеи.

Ревью, или почему я доверяю спекам задним числом

Очевидный вопрос: а не нагаллюцинировал ли агент? Отвечаю, как я это контролировал. Во-первых, мигрированная спека обязана ссылаться на реальные файлы и реальную документацию — провенанс проверяется за минуту. Во-вторых, я ревьюил каждую спеку как PR — и это несравнимо дешевле, чем писать её: подсистему-то я знаю, мне нужно только сверить утверждения. В-третьих — и это самое интересное — враньё в такой спеке быстро вскрывается само: спека утверждает «система умеет X», гэп-анализ рядом утверждает «X сломан», и противоречие бросается в глаза. Формат с двумя направлениями взгляда самопроверяется.

Гэпы: самое ценное — не спеки, а дыры между ними

В первой статье я писал: «SDD подсвечивает, что вы сами не знаете, что строите». На brownfield эта формула становится жёстче:

Миграция подсвечивает, что ваш код делает то, чего никто не решал, и не делает того, что все считают сделанным.

Когда агент раскладывает подсистему в формат «user stories → требования → критерии успеха», в разложении остаются дыры. Requirement есть — реализации нет. Реализация есть — но только на одной платформе из трёх. Механизм построен целиком — и не подключён. Каждая такая дыра фиксируется как гэп — нумерованный пункт в финальной фазе tasks.md, со стабильным ID, у которого старший разряд кодирует подсистему: G0xx — первая, G1xx — вторая, и так далее.

Итог по восьми подсистемам: около 76 гэпов. Несколько реальных примеров (детали слегка обезличены), чтобы был понятен калибр:

  • Механизм, сломанный сквозь три слоя. Квоты на использование чата: стриминговый клиент репортит нулевое использование (токены не считаются → серверный счётчик не декрементится), мобилка молча игнорирует события «квота на исходе / исчерпана» (стейт выставляется, но не рендерится, отправка не блокируется), а веб честно рисует баннеры для событий, которые в реальности не могут произойти. Каждый слой по отдельности выглядит рабочим. Вместе — механизм не существует.

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

  • Готовая фича, до которой нельзя дотянуться. Настройки уведомлений: модель, API и типизированный клиент существуют, но ни веб, ни мобилка не показывают ни одного UI. Функциональность есть; пользователя к ней не пускают.

  • Подсистема без платформы. У одной из подсистем вообще нет мобильной поверхности — проверено по экранам, роутам, эндпоинтам и тестам. Считалось, что «наверное, есть».

  • Слепые зоны наблюдаемости. Ноль аналитических событий на всю подсистему уведомлений — их эффективность в принципе неизмерима.

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

Из гэпов — в спеки: замыкание цикла

Гэп в tasks.md — это диагноз, а не план лечения. Финальный шаг каждого прохода — сгруппировать гэпы подсистемы в черновые спеки обычного конвейера. Из ~76 гэпов получилось 30 черновиков: тестовые покрытия, выпиливание легаси, починка сломанных механизмов, недостающие платформы. Каждый черновик трассируется в обе стороны — в его шапке написано, какие гэпы из какого tasks.md он закрывает, а гэп в мигрированной спеке знает, каким черновиком он будет закрыт.

И вот тут смыкается всё, о чём были первые две статьи. Помните roadmap-слой из второй статьи — «мелкий планировочный документ, который называет и упорядочивает срезы, но не проектирует их»? На brownfield он сгенерировался сам. Тридцать черновых спек, выведенных из гэп-анализа, — это и есть roadmap, только не придуманный, а выкопанный. С приоритетами, которые не надо изобретать: «сломанный механизм» очевидно важнее «долга по тестам».

Дальше конвейер становится каноническим, по одной спеке за раз:

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

  2. plantasksimplement — стандартно.

  3. Одна спека = одна единица приёмки и отката. Правило из первой статьи, и на brownfield оно ещё важнее: когда чинишь механизм, размазанный по трём платформам, возможность откатить весь фикс одним движением — не роскошь.

Отдельно про два инструмента ванильного Spec Kit, которые на brownfield заиграли по-новому. converge сверяет кодовую базу со спекой и дописывает в tasks.md то, что осталось недостроенным, — для мигрированных подсистем это способ периодически пересверять as-built-базлайн с реальностью (код-то продолжает меняться). А tasks-to-issues конвертирует задачи в issues с учётом зависимостей — мостик из мира спек в мир трекера, для тех членов команды, которые в репозиторий за спеками не пойдут. Это, кстати, частичный ответ на вопрос из конца второй статьи про интеграцию SDD с командой — но только частичный, полный тянет на отдельную статью.

План на ближайшие месяцы выглядит так: сначала — черновики, закрывающие сломанные механизмы (clarify → implement, по одному), затем — долги по тестам и легаси, и только потом, на выправленном базлайне, — новые фичи через обычный specify. Соблазн начать с новых фич велик, но он означает строить на фундаменте, про который ты только что документально узнал, что он с дырами.

Что бы я сказал себе неделю назад

  1. Не пишите свои brownfield-команды, пока не прочитали чужие. Команды Spec Kit — это markdown-промпты; аудит стороннего расширения занимает полчаса чтения. Это несравнимо дешевле недели написания своего.

  2. Constitution на brownfield — индекс, а не закон. Если у проекта уже есть свод правил — инструкции для агентов, хуки, CI — constitution должна явно объявить его первоисточником. Второй источник истины умрёт от рассинхрона за месяц.

  3. Мигрируйте по одной подсистеме за проход, коммитьте парой «migrate + gaps». Это те же «резкие границы срезов» из второй статьи. Заодно пары отлично параллелятся по сессиям.

  4. Разделите нумерацию as-built и go-forward сразу. Я допёр до префикса на третьей подсистеме; лучше договориться до первой.

  5. Гэпы — главный продукт. Если после миграции у вас нет списка дыр со стабильными ID и трассировкой в черновые спеки — вы получили красивую документацию, а не рабочий инструмент. Документацию никто не откроет; спеку с гэпом G401 откроет clarify на следующей неделе.

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

Во второй статье я формулировал открытый вопрос про распределение ролей PO — разработчик — QA при внедрении SDD в команде. Он открыт и после brownfield-миграции, но теперь у меня есть 30 черновых спек, на которых ответ придётся искать на практике: кто гоняет clarify, кто принимает срезы, как черновик из гэпа встречается с бэклогом из трекера. Об этом — когда набью шишек. А если вы уже прогоняли существующий проект через SDD — расскажите в комментариях, где у вас разошлось с моей картиной: экосистема расширений маленькая, и сейчас ровно тот момент, когда чужие грабли ещё успевают стать чьими-то несобранными.


Подписывайтесь на канал ТехДир Подсекин, ставьте лайк, вам не сложно, мне - приятно.