В первой статье я рассказал, почему полностью перешёл на Spec-Driven Development, во второй — как изобретал roadmap-слой поверх Spec Kit. Обе статьи заканчивались одним и тем же вопросом из комментариев и личных сообщений: «Хорошо, а что делать с существующим проектом?» И это честный вопрос, потому что оба моих примера были удобными: либо новая фича, либо новый проект. А у большинства из нас не новый проект. У большинства — brownfield: несколько лет истории, продакшен, пользователи, и ни одной спеки.
В прошлой статье я писал, что применение SDD к существующим проектам — «проблемный момент». Пришло время закрыть этот долг. Это рассказ о том, как я за один длинный день (правда, длинный — с трёх дня до полдевятого вечера, в несколько параллельных сессий агента) прогнал через Spec Kit живой продакшен-проект и что из этого вышло.
Пациент
Не буду рассказывать, что делает продукт — для статьи это неважно. Важно, из чего он состоит, потому что именно состав определяет сложность миграции:
бэкенд с API, фоновыми задачами и веб-сокетами;
веб-фронтенд;
мобильное приложение (три магазина приложений, десятки языков);
отдельный микросервис отдачи файлов;
отчёты и BI;
BPM-контур для интеграционных процессов;
инфраструктура и деплой.
Восемь продуктовых подсистем — от форм и чек-листов до чата с RAG, поиска и уведомлений. Каждая подсистема размазана по всем слоям: у неё есть модели и API на бэке, страницы на вебе, экраны в мобилке, фоновые задачи, события в аналитике. Ни одна не описана нигде, кроме кода и головы.
Классический brownfield. Поехали.
Проблема: Spec Kit думает, что кода ещё нет
Ванильный Spec Kit устроен просто: constitution → specify → clarify → plan → tasks → implement. Весь конвейер предполагает, что спека появляется до кода. На brownfield это ломается в двух местах сразу.
Во-первых, constitution. По канону это неизменяемые принципы проекта, которые вы пишете в начале. Но на живом проекте принципы уже есть — они размазаны по инструкциям для агентов, pre-commit-хукам, CI-пайплайнам и негласным договорённостям. Написать constitution «из головы» — значит создать второй источник истины, который немедленно начнёт расходиться с первым.
Во-вторых, specify. Новая фича на brownfield не живёт в вакууме — она встраивается в существующее. А существующее нигде не описано. Агент, которому вы говорите «добавь настройку уведомлений», не знает, что система уведомлений уже есть, как она устроена и где у неё границы. Он узнаёт это, читая код, — каждый раз заново, в каждой сессии, тратя контекстное окно на археологию вместо работы.
Отсюда развилка с тремя вариантами:
Использовать SDD только для нового. Спеки — вперёд, старый код — как есть. Дёшево, но каждая новая спека будет писаться вслепую относительно существующего.
Реверс-инжинирить всё руками. Сесть и написать спеки на существующие подсистемы. Дорого настолько, что никто этого никогда не сделает. Собственно, ровно поэтому мы все и оказались там, где оказались.
Реверс-инжинирить агентом. Пусть агент, который и так умеет читать код, напишет спеки задним числом, а я буду ревьюить.
Я пошёл по третьему пути. И тут выяснилось приятное: идти уже есть по чему.
Выбор расширения: не пишите своё, сначала поищите
У Spec Kit есть механизм расширений — сторонние наборы команд, которые подключаются к конвейеру и могут вешать хуки на его этапы. Экосистема пока крошечная, но запрос на brownfield в ней оказался самым громким: в апстриме Spec Kit висит issue про поддержку существующих проектов с десятками реакций, и именно из него выросло расширение spec-kit-brownfield — стороннее, MIT, версии 1.0.0.
Как я его выбирал — честно, без магии:
Сформулировал, чего не хватает ванильному конвейеру: команды «посмотри на существующий код и сделай из него артефакты SDD». Это два разных действия: выжать из кода правила (constitution, шаблоны) и выжать из кода спеки (реверс-инжиниринг фич).
Пошёл в issues апстрима — не в поиск по каталогам расширений, а именно в issues. Логика простая: если боль настоящая, вокруг неё уже есть тред, а в треде — ссылки на то, что люди наделали.
Прочитал исходники расширения перед установкой. Тут важный момент, который снимает большую часть страха перед «сторонним расширением версии 1.0.0 от незнакомого автора»: команды Spec Kit — это не код, это markdown-промпты. Их можно (и нужно) прочитать глазами целиком, как читаешь чужой PR. Я прочитал. Никакой магии там нет — есть аккуратно структурированные инструкции агенту.
Проверил, что первый шаг — read-only. Расширение начинает со сканирования, которое ничего не пишет. Это дало возможность посмотреть на результат до того, как что-то в репозитории изменится.
Критерий «прочитал промпты — понял, что они сделают» победил альтернативу «напишу свои команды». Свои я бы писал неделю и получил бы примерно то же самое, только без обкатки на чужих проектах.
Расширение добавляет четыре команды:
Команда | Пишет в репо? | Что делает |
|---|---|---|
| нет | Обходит проект, определяет стек, фреймворки, архитектурные паттерны, структуру модулей и конвенции. Результат — «профиль проекта» |
| да | По профилю генерирует constitution и кастомизирует шаблоны спек под реальную архитектуру — вместо генериковых заглушек ванильного Spec Kit |
| да | Реверс-инжинирит spec.md / plan.md / tasks.md для фич, построенных до Spec Kit |
| нет | Проверяет, что сгенерированные 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, только не придуманный, а выкопанный. С приоритетами, которые не надо изобретать: «сломанный механизм» очевидно важнее «долга по тестам».
Дальше конвейер становится каноническим, по одной спеке за раз:
clarifyпо черновику — да, даже по черновику, который написан по гэпу, который найден в моём собственном коде. Опыт первой статьи никуда не делся: недоспецификация — основная причина, по которой агент уезжает не туда. Черновик, сгенерированный из гэпа, недоспецифицирован всегда — он знает, что сломано, но не знает, чего я хочу вместо этого.plan→tasks→implement— стандартно.Одна спека = одна единица приёмки и отката. Правило из первой статьи, и на brownfield оно ещё важнее: когда чинишь механизм, размазанный по трём платформам, возможность откатить весь фикс одним движением — не роскошь.
Отдельно про два инструмента ванильного Spec Kit, которые на brownfield заиграли по-новому. converge сверяет кодовую базу со спекой и дописывает в tasks.md то, что осталось недостроенным, — для мигрированных подсистем это способ периодически пересверять as-built-базлайн с реальностью (код-то продолжает меняться). А tasks-to-issues конвертирует задачи в issues с учётом зависимостей — мостик из мира спек в мир трекера, для тех членов команды, которые в репозиторий за спеками не пойдут. Это, кстати, частичный ответ на вопрос из конца второй статьи про интеграцию SDD с командой — но только частичный, полный тянет на отдельную статью.
План на ближайшие месяцы выглядит так: сначала — черновики, закрывающие сломанные механизмы (clarify → implement, по одному), затем — долги по тестам и легаси, и только потом, на выправленном базлайне, — новые фичи через обычный specify. Соблазн начать с новых фич велик, но он означает строить на фундаменте, про который ты только что документально узнал, что он с дырами.
Что бы я сказал себе неделю назад
Не пишите свои brownfield-команды, пока не прочитали чужие. Команды Spec Kit — это markdown-промпты; аудит стороннего расширения занимает полчаса чтения. Это несравнимо дешевле недели написания своего.
Constitution на brownfield — индекс, а не закон. Если у проекта уже есть свод правил — инструкции для агентов, хуки, CI — constitution должна явно объявить его первоисточником. Второй источник истины умрёт от рассинхрона за месяц.
Мигрируйте по одной подсистеме за проход, коммитьте парой «migrate + gaps». Это те же «резкие границы срезов» из второй статьи. Заодно пары отлично параллелятся по сессиям.
Разделите нумерацию as-built и go-forward сразу. Я допёр до префикса на третьей подсистеме; лучше договориться до первой.
Гэпы — главный продукт. Если после миграции у вас нет списка дыр со стабильными ID и трассировкой в черновые спеки — вы получили красивую документацию, а не рабочий инструмент. Документацию никто не откроет; спеку с гэпом G401 откроет
clarifyна следующей неделе.Ретроспективный Constitution Check — бесплатный аудит. Прогнать существующую подсистему через чек-лист принципов — самый дешёвый способ узнать, где проект расходится сам с собой.
Во второй статье я формулировал открытый вопрос про распределение ролей PO — разработчик — QA при внедрении SDD в команде. Он открыт и после brownfield-миграции, но теперь у меня есть 30 черновых спек, на которых ответ придётся искать на практике: кто гоняет clarify, кто принимает срезы, как черновик из гэпа встречается с бэклогом из трекера. Об этом — когда набью шишек. А если вы уже прогоняли существующий проект через SDD — расскажите в комментариях, где у вас разошлось с моей картиной: экосистема расширений маленькая, и сейчас ровно тот момент, когда чужие грабли ещё успевают стать чьими-то несобранными.
Подписывайтесь на канал ТехДир Подсекин, ставьте лайк, вам не сложно, мне - приятно.
