Часть 3 · ~12 мин
Монорепозиторий или мультирепо: где живут спеки
Вопрос, который решает всё
openspec init в одном репозитории — тривиален. Но у «Синтеза» одиннадцать репозиториев: семь сервисов, два фронтенда, репозиторий NuGet-библиотек и инфраструктурный. Прежде чем раскатывать SDD, нужно ответить: где живёт спецификация? Ответ зависит от того, как устроен код — поэтому сначала честно про монорепо и мультирепо.
Монорепозиторий и мультирепо: цена вопроса
| Монорепозиторий | Мультирепо | |
|---|---|---|
| Сквозное изменение | один PR, один ревьюер видит всё | N PR, согласование руками |
| Атомарность контрактов | код и контракт меняются вместе | версии пакетов, окно рассинхрона |
| Права и владение | сложнее разграничить | граница репо = граница команды |
| CI | тяжелеет, нужна селективная сборка | простые независимые пайплайны |
| Контекст для ИИ-агента | всё в одной рабочей копии | агент видит один репо, остальное — неявное знание |
| Переезд | дорогой разовый проект | вы уже здесь |
Последняя строка — главная. Переезд в монорепозиторий ради SDD — это лечение головной боли трепанацией: месяцы работы, чтобы решить проблему, у которой есть решение дешевле. Мультирепо — не ошибка, которую надо исправить, а контекст, в котором надо спроектировать размещение спецификаций. Если вы уже в монорепо — вам проще: один openspec/ в корне, части 4–6 применимы напрямую. Дальше — для остальных.
Три варианта размещения спецификаций
Вариант А: openspec/ в каждом репозитории
Каждый сервис владеет своими спеками; они лежат рядом с кодом, который описывают.
- Плюсы: спека и код в одном PR; агент видит спеки «своего» сервиса без настройки; владение очевидно — кто владеет сервисом, тот и спекой.
- Минусы: сквозная фича размазывается по репозиториям — общий контракт дублируется или теряется; требования к общим библиотекам живут в N местах; нет точки, где команда видит систему целиком.
Вариант Б: центральный репозиторий спецификаций (Store)
OpenSpec называет это Store — отдельный репозиторий, чья единственная работа — планирование. Та же структура openspec/ (specs + changes), плюс файл идентичности; шарится обычным git push/clone:
syn-specs (store: планирование в своём репо)
├── .openspec-store/store.yaml
└── openspec/
├── specs/ что истинно
└── changes/ что в работе
▲
┌─────────────┼─────────────┐
payments-svc orders-svc syn-libs
(код) (код) (код, NuGet)
- Плюсы: один источник истины для всей системы; сквозные фичи планируются в одном месте; можно планировать до того, как код существует; агент в любом репо умеет читать store.
- Минусы: спека и код меняются в разных PR — нужна дисциплина ссылок; ещё один репозиторий в голове у каждого; Stores — в beta (флаги и форматы могут меняться между релизами).
Вариант В: гибрид — локальные спеки + store для контрактов
Спеки, живущие внутри одного сервиса, — в его репозитории (вариант А). Всё, что пересекает границу репозитория, — контракты API, схемы событий, требования к общим библиотекам — в store (вариант Б). Код-репо декларирует связь одной строкой в openspec/config.yaml:
# payments-service/openspec/config.yaml
references:
- syn-specs
references — это read-only контекст: агент в payments-service видит индекс спек store с командой для получения каждой (openspec show <spec-id> --store syn-specs), но работа остаётся в локальном openspec/.
- Плюсы: локальное — локально (дёшево, атомарно), общее — централизовано (один источник истины); граница «что в store» совпадает с настоящей архитектурной границей — что пересекает репозиторий, то контракт.
- Минусы: нужно одно командное соглашение — куда что кладём; та же beta-оговорка по Stores.
Сравнение одним взглядом
| А: везде своя | Б: всё в store | В: гибрид | |
|---|---|---|---|
| Локальная фича | ✓ один PR | два репо на любую мелочь | ✓ один PR |
| Сквозная фича | размазана | ✓ одно место | ✓ контракт в store |
| NuGet-библиотеки | требования в N репо | ✓ | ✓ спека в репо либы, контракт в store |
| Видимость системы | нет | ✓ | ✓ (по контрактам) |
| Сложность внедрения | минимальная | средняя | средняя |
| Зрелость инструмента | стабильно | beta | beta (references) |
Рекомендация для «Синтеза» — и, вероятно, для вас
Вариант В. Практическое правило одно: спека живёт там, где живёт ревью её кода; контракт живёт там, где его читают все.
- В каждом сервисном репо — свой
openspec/(часть 4 покажет цикл). - Один store
syn-specs— для контрактов между сервисами и требований к библиотекам. - Для NuGet-библиотек: реализация пакета специфицируется в репозитории библиотек (это код Насти и её ревью), а публичный контракт пакета — какие типы и гарантии обещаны потребителям — в store, потому что его потребители живут в шести других репозиториях.
Если Stores для вас слишком «beta» — вариант Б и В имеют деградацию без магии: store — это просто git-репозиторий со спеками в согласованном формате. Клонируйте его рядом с кодом и дайте агенту путь к нему в контексте; references лишь автоматизирует то же самое.
Кейс «Синтез». Первым решением Даши был чистый вариант Б — «одно место, красиво». Через две недели споткнулись: чтобы поменять текст ошибки в одном сервисе, нужно было два PR в два репозитория. Локальные спеки вернули в сервисы, store оставили контрактам — и трение исчезло. Урок: центральный репозиторий — для того, что пересекает границы, а не для всего подряд.
Что дальше
Решение принято, каркас понятен. В части 4 — полный прогон одной фичи в одном сервисе: от /opsx-propose до /opsx-archive, с настоящими артефактами.