Часть 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
Видимость системынет✓ (по контрактам)
Сложность внедренияминимальнаясредняясредняя
Зрелость инструментастабильноbetabeta (references)

Рекомендация для «Синтеза» — и, вероятно, для вас

Вариант В. Практическое правило одно: спека живёт там, где живёт ревью её кода; контракт живёт там, где его читают все.

  1. В каждом сервисном репо — свой openspec/ (часть 4 покажет цикл).
  2. Один store syn-specs — для контрактов между сервисами и требований к библиотекам.
  3. Для NuGet-библиотек: реализация пакета специфицируется в репозитории библиотек (это код Насти и её ревью), а публичный контракт пакета — какие типы и гарантии обещаны потребителям — в store, потому что его потребители живут в шести других репозиториях.

Если Stores для вас слишком «beta» — вариант Б и В имеют деградацию без магии: store — это просто git-репозиторий со спеками в согласованном формате. Клонируйте его рядом с кодом и дайте агенту путь к нему в контексте; references лишь автоматизирует то же самое.

Кейс «Синтез». Первым решением Даши был чистый вариант Б — «одно место, красиво». Через две недели споткнулись: чтобы поменять текст ошибки в одном сервисе, нужно было два PR в два репозитория. Локальные спеки вернули в сервисы, store оставили контрактам — и трение исчезло. Урок: центральный репозиторий — для того, что пересекает границы, а не для всего подряд.

Что дальше

Решение принято, каркас понятен. В части 4 — полный прогон одной фичи в одном сервисе: от /opsx-propose до /opsx-archive, с настоящими артефактами.