Почему легаси ломает greenfield-допущения
В прошлом уроке мы провели фичу CSV-экспорта через полный цикл на чистом проекте. Там всё шло гладко: контракты задавали мы сами, архитектура была открытой, clarify снял неоднозначности ещё до плана.
В легаси всё устроено иначе. Три ключевых отличия:
- Поведение уже закреплено. Пользователи зависят от того, как система работает сейчас — включая баги, которые давно стали «фичами».
- Контракты не задокументированы. Форматы токенов, побочные эффекты, порядок вызовов — всё живёт в коде и в головах нескольких людей.
- Писать спеку на весь монолит нереалистично. Спека описывает только изменение — дельту.
Claude Code читает кодовую базу перед генерацией спеки, и это сильная сторона. Но если кодовая база большая и запутанная, агент получит лишь частичную картину. Спека окажется оторванной от реальности ещё до первого plan.md. Выход — приём «сначала research doc».
Шаг 0+: research doc как «сжатый вид» кодовой базы
Прежде чем запускать /speckit.specify, просим Claude Code исследовать релевантную часть системы и зафиксировать выводы:
Исследуй модуль авторизации: точки входа, middleware,
формат токенов, способы инвалидации сессий.
Запиши в specs/research/auth-module.md.Результат — research.md, который агент потом автоматически подхватит как контекст для спеки:
# Research: Auth Module
::widget{id="rc-2"}
## Entry Points
- POST /auth/login → AuthController.login()
- POST /auth/refresh → AuthController.refresh()
## Token Format
JWT, RS256, TTL: 15 мин (access), 7 дней (refresh)
Claims: userId, roles[], sessionId
## Session Invalidation
- Logout: добавляет sessionId в Redis blacklist
- Password change: инвалидирует все сессии пользователя
- ВАЖНО: refresh-токен не инвалидируется при обычном logoutПоследняя строчка — именно то, что нигде не было задокументировано. Без research doc этот нюанс всплыл бы в code review или уже в проде. С ним — spec.md знает о раздельной инвалидации токенов не потому что угадал, а потому что прочитал.
Спека описывает только дельту
В легаси-проекте spec.md фиксирует что именно меняется, а не всю систему. Явная секция Out of Scope — обязательная часть:
::widget{id="rc-3"}
## Scope
Добавить эндпоинт «выход со всех устройств».
Существующий flow логина/рефреша — без изменений.
## Out of Scope
- Изменение формата токенов
- Миграция хранилища сессий
- Изменения в эндпоинтах login/refreshOut of Scope — не формализм. Он страхует от классического «а давайте заодно…» и не даёт plan.md неожиданно разрастись на три спринта. В greenfield архитектуру только создают, границы гибкие. В легаси scope creep — реальная угроза на каждой итерации: смежный код всегда манит к себе.
/speckit.converge: код против спеки
После реализации — или в середине, если задачи выполнялись частями — запускаем:
/speckit.convergeКоманда сравнивает реальный код с spec.md и tasks.md и выдаёт отчёт:
✓ AC-1 (logout endpoint) → реализован
✓ AC-2 (access token invalidation) → реализован
⚠ AC-3 (refresh token invalidation) → не найдено в коде
→ Предлагаю T5: invalidate refresh tokens on logout-all
✗ Найдено в коде: email-уведомление при logout-all
→ Не было в spec.md. Задуманное поведение?В легаси расхождения случаются чаще, чем в greenfield: разработчики нередко добавляют «очевидные» вещи прямо в процессе, не возвращаясь к tasks.md. converge ловит это системно, а не через случайный взгляд ревьюера.
Инкрементальное наращивание охвата
Не нужно покрывать спекой весь проект сразу — это и нереалистично, и контрпродуктивно. Рабочий ритм выглядит так:
1. Новая фича → research doc → полный цикл
2. Следующая фича в том же модуле → research doc уже есть, обновляем только дельту
3. Постепенно specs/research/ превращается в живую документацию реальной системы
Важный момент: research doc эволюционирует вместе с кодом. Изменили формат токена — обновили auth-module.md. Иначе следующая спека опирается на устаревший контекст и история повторяется.
flowchart TD
A["Новая фича в легаси"] --> B["Research Doc\n(исследуем релевантные модули)"]
B --> C["/speckit.specify\n(спека только на дельту)"]
C --> D["/speckit.clarify"]
D --> E["/speckit.plan"]
E --> F["/speckit.tasks"]
F --> G["Реализация\n(git worktrees при [P]-задачах)"]
G --> H["/speckit.converge"]
H --> I{"Расхождения?"}
I -->|"да"| J["Уточняем задачи / спеку"]
J --> G
I -->|"нет"| K["Обновляем Research Doc\n(если изменились контракты)"]
K --> L["Готово ✓"]Параллельная работа через git worktrees
Когда несколько задач из tasks.md помечены [P] и их реализуют одновременно, конфликты файлов — типичная проблема. Решение — git worktrees:
# Создаём отдельное рабочее дерево для каждой параллельной задачи
git worktree add ../feature-t2 -b feature/logout-all-t2
git worktree add ../feature-t3 -b feature/logout-all-t3Каждый агент (или разработчик) работает в изолированном дереве без конфликтов. После завершения — стандартный PR и merge. Для небольшой фичи это обычно лишнее, но при крупных параллельных изменениях такой подход экономит нервы.
Упражнение: спланировать ввод SDD в легаси-репозиторий
Представьте: монолитный Rails-проект, ~80 000 строк, без документации, тесты частично сломаны. Нужно добавить «экспорт истории транзакций в PDF».
Перед тем как разобраться с артефактами ниже — попробуйте самостоятельно ответить: какие модули стоит исследовать первыми? (Подсказка: транзакции, генерация отчётов, права доступа.)
Теперь распределите артефакты по нужным местам:
Главное
- Research doc даёт агенту точный контекст до того, как он начнёт генерировать. Без него спека оторвана от реальности.
- Спека в легаси — это дельта.
Out of Scopeзащищает от scope creep на каждой итерации. /speckit.convergeловит расхождения между кодом и спекой. Запускайте минимум раз в середине реализации, не только в конце.- Охват наращивают инкрементально:
specs/research/со временем становится живой документацией проекта.
SDD не требует останавливать команду и переписывать всё с нуля. Достаточно правильно делать каждую следующую фичу — и через несколько месяцев легаси перестаёт быть terra incognita.