Зачем это нужно
Всё, что разобрано в уроках 15.2-15.11, работает одинаково и для соло-разработчика, и для команды из двадцати человек. В команде меняются вопросы по краям:
- где живут спецификации;
- как коллеги ревьюят план до кода;
- как это встраивается в существующий pull request-процесс;
- что делать, когда планирование перестает помещаться в один репозиторий - например, одна фича затрагивает API-сервер, веб-приложение и общую библиотеку.
Первые три вопроса закрывает обычный командный workflow OpenSpec. Четвертый - beta-функциональность Stores: репозиторий, единственная задача которого - планирование.
Основная идея
Изменение - это просто файлы, и OpenSpec никогда не трогает git. Поэтому OpenSpec встраивается в существующий командный процесс вместо того, чтобы заменять его: change отображается на branch и pull request, ревью плана происходит до ревью кода, а архивация синхронизирует общую «истину» только с тем, что реально доехало до main.
Когда планирование вырастает из одного репозитория, план переезжает в Store - отдельный git-репозиторий той же формы openspec/, который кодовые репозитории подключают по имени. Обмен - обычным git push, без скрытой синхронизации.
Часть 1. OpenSpec в команде
Одно правило: OpenSpec не трогает git
OpenSpec читает и пишет обычный Markdown в openspec/. Он никогда не делает commit, branch, push или pull в вашем проекте - и никогда сам не клонирует и не синхронизирует Store. Из этого следует:
openspec/коммитится как любой исходник. Спецификации, активные изменения и архив - часть истории проекта. Да, коммитить нужно весь каталог - включая архив выполненных изменений.- Изменение - это папка, которую версионируют как код.
openspec/changes/add-dark-mode/- просто файлы на ветке. - Все договоренности ниже - конвенции, а не принуждение. OpenSpec не заставляет работать именно так; этот способ просто ложится в git без трения.
Повседневный цикл: change, branch, pull request
Workflow, который хорошо работает, отображает изменение на ветку и pull request:
git switch -c add-dark-mode завести ветку, как обычно
│
/opsx:propose add-dark-mode черновик плана (proposal + specs + tasks)
│
РЕВЬЮ ПЛАНА вы читаете его до кода
│
/opsx:apply реализация; артефакты и код меняются вместе
│
git commit && открыть PR PR содержит и spec delta, и код
│
коллега ревьюит, мержит
│
/opsx:archive дельта вливается в specs/, change уходит в archive/
Тот же цикл в виде схемы:
План и код живут рядом на одной ветке, поэтому коллеги ревьюят их вместе, а через полгода архивированная спецификация все еще объясняет, почему код выглядит именно так.
Ревью спецификаций в pull request
Именно здесь команда чувствует отдачу. Когда PR включает дельта-спецификацию изменения, ревьюер получает то, чего сырой diff никогда не даст: формулировку на естественном языке, что изменение должно делать - до чтения первой строки кода.
Хороший порядок ревью:
- Прочитать
proposal.md- та ли это проблема и тот ли scope. - Прочитать дельты в
specs/- правильно ли определено «готово» (двухминутный pass из урока 15.10, теперь внутри PR). - Потом читать diff кода - доставляет ли он именно эти требования.
Ревьюер, не согласный с подходом, может возразить против proposal дешево - вместо того чтобы пересуживать подход по 300 строкам кода. Поместите дельта-спецификацию в начало описания PR или укажите ревьюерам путь к папке изменения, чтобы они начинали оттуда.
Когда архивировать
Архивация вливает дельты изменения в общий openspec/specs/ и переносит папку в openspec/changes/archive/YYYY-MM-DD-<name>/. Поскольку specs/ - общий источник истины, в команде важен момент архивации. Два рабочих варианта:
- Архивировать после merge PR (рекомендуется). Ветка несет активное изменение; после merge в main архивация происходит там - обычно маленьким follow-up-коммитом или плановой уборкой. Так общий
specs/движется вперед только вместе с реально доехавшей работой. - Архивировать внутри PR. Проще для маленьких команд: тот же PR, который добавляет код, синхронизирует и архивирует. Плата - diff спецификаций и diff кода приземляются вместе, PR становится шумнее.
Выберите один вариант и держитесь его. В любом случае /opsx:archive проверяет завершенность задач и предлагает сначала синхронизировать - случайно «недоделанное» не смержится.
Два человека, параллельные изменения
Изменения - отдельные папки, поэтому не конфликтуют:
- Разные изменения, разные люди - без проблем.
add-dark-modeиrate-limit-login- разные папки на разных ветках; они не пересекаются, пока оба не заархивируются. - Одно изменение - один владелец. Два человека, правящие одну папку изменения, конфликтуют ровно как два человека, правящие один файл. Держите изменение за одним автором или разбейте на два (еще один аргумент за правильный размер изменения из урока 15.10).
- Единственное место конфликтов -
specs/. Если два изменения правят одно и то же требование, архивация второго конфликтует вopenspec/…/spec.md. Разрешается как любой merge-конфликт - побеждает формулировка, отражающая реальность. Это редкость, и это фича: git сообщает, что два изменения разошлись во мнениях о поведении системы.
Когда планирование вырастает из одного репозитория
Все выше предполагало, что план живет в собственной папке openspec/ кодового репозитория - и это правильный дефолт. Когда планирование реально расползается на несколько репозиториев или команд - одна фича на три сервиса, требования одной команды потребляются другими - включается beta-функциональность Stores: планирование получает собственный репозиторий, на который может указать любой кодовый репозиторий.
Часть 2. Stores (beta)
Beta. Stores, references, рабочий контекст и worksets - новое. Имена команд, флаги, форматы файлов и JSON-вывод могут еще меняться между релизами. Все примеры ниже выполнялись на текущей сборке, но после обновления перечитайте руководство.
Проблема, которую решают Stores
Обычно OpenSpec живет внутри одного кодового репозитория: папка openspec/ рядом с кодом хранит specs и changes этого репозитория.
Это перестает подходить, когда планирование больше одного репозитория:
- работа охватывает несколько репозиториев - одна фича затрагивает API-сервер, веб-приложение и общую библиотеку. В чьей папке
openspec/живет план? - команда планирует до появления кода или планирует вещи, которые никогда не станут кодом в этом репозитории;
- требования принадлежат одной команде, а потребляются другими. Версия в вики расползается с реальностью, а ваш coding-агент все равно не умеет ее читать.
Store - ответ: отдельный репозиторий, единственная работа которого - планирование. Внутри та же форма openspec/, которую вы уже знаете (specs и changes), плюс маленький файл идентичности. Один раз регистрируете его на своей машине по имени - и каждая обычная команда OpenSpec может работать в нем откуда угодно.
Устройство
team-plans (Store: планирование в отдельном репозитории)
├── .openspec-store/store.yaml идентичность: «я - team-plans»
└── openspec/
├── specs/ что истинно
└── changes/ что в движении
▲
│ зарегистрирован на каждой машине по имени;
│ расшаривается push/clone как любой репозиторий
┌─────────────┼─────────────┐
│ │ │
web-app api-server mobile-app
(кодовый репо) (кодовый репо) (кодовый репо)
Два правила удерживают модель простой:
- Store - это просто git-репозиторий. Коммитите, пушите, пуллите и ревьюите его сами. OpenSpec никогда ничего не клонирует, не синхронизирует и не пушит сам.
- Декларации вместо машинерии. Репозитории могут декларировать свою связь со Store (ниже). Декларации меняют то, что OpenSpec может вам сообщить, - но не то, где работают команды.
Пять минут до первого Store
Две команды ведут от нуля до рабочего изменения в scope Store:
openspec store setup team-plans --path ~/openspec/team-plans
Store ready: team-plans
Location: /Users/you/openspec/team-plans
OpenSpec root: ready
Registry: registered
Next: run normal OpenSpec commands against this store, for example:
openspec new change <change-id> --store team-plans
Share this store by committing and pushing it like any Git repo.
openspec new change add-login --store team-plans
Using OpenSpec root: team-plans (/Users/you/openspec/team-plans)
Created change 'add-login' at /Users/you/openspec/team-plans/openspec/changes/add-login/
Schema: spec-driven
Next: openspec status --change add-login --store team-plans
Это вся модель. Дальше жизненный цикл в точности привычный - status, instructions, validate, archive - с --store team-plans у каждой команды, и каждая подсказка CLI сама несет нужный флаг. Строка Using OpenSpec root: всегда сообщает, где команда действует.
История: одна команда, один репозиторий планирования
Команда хранит specs и changes в team-plans вместо того, чтобы раскидывать их по кодовым репозиториям.
День первый (кто настраивает):
openspec store setup team-plans --path ~/openspec/team-plans \
--remote git@github.com:acme/team-plans.git
git -C ~/openspec/team-plans push -u origin main
Флаг --remote записывает clone-URL в файл идентичности Store (.openspec-store/store.yaml) в первом коммите. Каждый будущий clone «рождается» зная, откуда он, поэтому проверки здоровья и сообщения об ошибках печатают полную копируемую команду исправления для коллег, у которых Store еще нет.
Каждый коллега (один раз на машину):
git clone git@github.com:acme/team-plans.git ~/openspec/team-plans
openspec store register ~/openspec/team-plans
Дальше все работают в общем репозитории планирования по имени:
openspec status --store team-plans --change add-login
openspec show add-login --store team-plans
Разделение работы - это git, намеренно. Созданное изменение существует только в вашем checkout, пока вы не закоммитите и не запушите - как код. Планы получают ветки, PR и ревью бесплатно, потому что Store - обычный репозиторий.
Подключение кодовых репозиториев команды. Кодовому репозиторию с полностью вынесенным планированием нужна ровно одна строка в openspec/config.yaml:
# web-app/openspec/config.yaml
store: team-plans
Теперь каждая команда OpenSpec внутри web-app работает с team-plans вообще без флагов:
cd ~/src/web-app
openspec status --change add-login
Using OpenSpec root: team-plans (/Users/you/openspec/team-plans)
...
Указатель - это fallback, а не override: явный --store всегда побеждает, а если репозиторий вырастит собственные папки планирования, побеждают они (с предупреждением убрать устаревший указатель).
Один дефолт на все репозитории машины. Если вы работаете в куче репозиториев, планирующих в один Store, задайте его один раз глобально:
openspec config set defaultStore team-plans
Теперь любая команда вне корня планирования - без --store и без проектного указателя - резолвится в team-plans. Дефолт сидит на самом дне списка приоритетов: --store, локальный корень и проектный указатель store: все равно побеждают. Баннер root и JSON-блок root сообщают source: "global_default" с id Store, так что машинный дефолт всегда отличим от собственного указателя репозитория. Сброс: openspec config unset defaultStore. Если id не зарегистрирован, команды падают с подсказкой зарегистрировать или убрать дефолт.
Пример: одна фича, два компонентных репозитория
Пусть add-checkout-promo меняет и checkout-api, и checkout-web. Команде нужен один общий продуктовый контракт, но каждому репозиторию - свои задачи реализации, ветка и ревью.
Два слоя:
- общее поведение - в
team-plans; - планы реализации - в каждом компонентном репозитории, со ссылкой на Store как на read-only upstream-контекст.
Сначала общий контракт в Store:
openspec new change add-checkout-promo --store team-plans
openspec status --change add-checkout-promo --store team-plans
Proposal и спецификации должны описывать поведение на границе компонентов - например, поля промоушена, которые возвращает сервис, и как фронтенд обрабатывает не подходящий под условия checkout. Ревью этого изменения в Store - как любой branch и pull request.
Какой контекст видит планирование?
Выбор Store меняет корень OpenSpec; он не обнаруживает и не читает все кодовые репозитории, которые этим Store пользуются. Инструкции Store видят артефакты и настроенный контекст в Store. Код компонентов они видят, только если эти папки доступны агенту или редактору и агент их читает.
Workset - удобный способ открыть Store планирования и оба кодовых репозитория вместе:
openspec workset create checkout-promo \
--member ~/openspec/team-plans \
--member ~/src/checkout-api \
--member ~/src/checkout-web \
--tool code
openspec workset open checkout-promo
Это делает папки видимыми в одном workspace IDE. Это не копирует исходный контекст в Store, не выбирает затронутые репозитории и не выдает агенту прав на их правку. Прочные межкомпонентные факты кладите в общие спецификации; не рассчитывайте, что планировщик запомнит исходники, которые он когда-то посмотрел.
Как начинается реализация в каждом репозитории?
Когда нет явного --store и более близкого корня openspec/, указатель store: team-plans направляет команды в Store. Он не делит task list Store по каталогу, из которого вызван apply. OpenSpec пока не маршрутизирует задачи по репозиториям.
Когда каждому компоненту нужен независимый цикл apply/ревью, дайте ему локальный корень OpenSpec и ссылку (reference) на центральный Store вместо указателя на него:
# checkout-api/openspec/config.yaml (и так же в checkout-web)
schema: spec-driven
references:
- team-plans
После утверждения общего контракта и его попадания в основные specs Store создайте маленькое локальное изменение для части компонента:
cd ~/src/checkout-api
openspec new change implement-checkout-promo-api
cd ~/src/checkout-web
openspec new change implement-checkout-promo-ui
Индекс ссылок в инструкциях каждого репозитория подставляет сводку спецификации Store и точную команду выборки openspec show ... --store team-plans. Каждый локальный proposal цитирует общий контракт, а его tasks описывают работу только в этом компоненте. Затем /opsx:apply запускается в каждом репозитории отдельно; резолюция корня удерживает артефакты и правки реализации в scope этого репозитория. Изменения сервиса и фронтенда можно тестировать, ревьюить, мержить и архивировать независимо.
Если реализацию нужно начинать, пока общее изменение в Store еще активно, выберите его явно: openspec show add-checkout-promo --store team-plans - индексы ссылок перечисляют канонические specs Store, а не активные изменения Store. Свяжите ветку в Store и ветки компонентов в описаниях PR, чтобы ревьюеры видели, какую версию контракта следует каждая реализация.
История: требования через границы команд
Команда платформы владеет требованиями. Продуктовые команды строят по ним - в своих репозиториях, со своими design. Reference описывает эту связь, не двигая ничью работу.
platform-reqs (Store) api-server (кодовый репозиторий)
владеет команда платформы владеет продуктовая команда
┌──────────────────────────┐ ┌──────────────────────────┐
│ openspec/specs/ │◀──────│ openspec/config.yaml │
│ payments/spec.md │ читает│ references: │
│ auth/spec.md │ │ - platform-reqs │
│ │ │ openspec/specs/ │
│ openspec/changes/ │ │ (свои design) │
│ работа платформы │ │ openspec/changes/ │
│ │ │ (своя работа) │
└──────────────────────────┘ └──────────────────────────┘
Продуктовая команда декларирует, на что опирается, в openspec/config.yaml своего репозитория:
references:
- platform-reqs
References - read-only контекст. Репозиторий сохраняет собственный корень openspec/; работа остается там. Что меняется: openspec instructions в этом репозитории теперь включает индекс specs указанного Store - каждая позиция с однострочной сводкой и точной командой выборки (openspec show <spec-id> --type spec --store platform-reqs). Агент, работающий в api-server, находит upstream-требования по платежам, цитирует их и пишет свой low-level design в собственном корне репозитория - без ручной пересылки контекста.
Reference может нести свой clone-источник, чтобы коллеги без Store получали полную команду исправления вместо тупика:
references:
- { id: platform-reqs, remote: "git@github.com:acme/platform-reqs.git" }
Когда план и код открываются вместе - сделайте workset. Это личное и явное: каждый выбирает папки, с которыми реально работает, на своей машине. Ничего о локальных путях в общий репозиторий планирования не коммитится.
openspec workset create platform \
--member ~/openspec/platform-reqs \
--member ~/src/api-server \
--member ~/src/web-app
Два вопроса, которые всегда можно задать
«Здоров ли мой setup?» - openspec doctor проверяет текущий корень и его Store-ссылки, только читая, и выдает копируемое исправление на каждую находку:
Doctor
Root
Location: /Users/you/src/api-server
OpenSpec root: ok
References
- platform-reqs: ok (/Users/you/openspec/platform-reqs)
- design-system: Referenced store 'design-system' is not registered on this machine.
Fix: git clone -- git@github.com:acme/design-system.git '/Users/you/openspec/design-system' && openspec store register '/Users/you/openspec/design-system' --id design-system
«С чем я работаю?» - openspec context собирает рабочий набор из деклараций OpenSpec: корень и Store, на которые он ссылается.
Working context for api-server (/Users/you/src/api-server)
OpenSpec root
api-server /Users/you/src/api-server
Referenced stores
platform-reqs /Users/you/openspec/platform-reqs
Fetch: openspec show <spec-id> --type spec --store platform-reqs
Обе команды поддерживают --json для агентов. openspec context --code-workspace <path> дополнительно пишет workspace-файл VS Code со всем набором - единственная запись, которую выполняет эта команда.
Worksets: снова открыть папки, с которыми работаете
Отдельно ото всего выше: большинство людей открывают одни и те же несколько папок каждую сессию - репозиторий планирования плюс два-три кодовых. Workset - личный именованный набор именно этого, открываемый одной командой в выбранном инструменте.
workset "platform" openspec workset open platform
├── team-plans ~/openspec/team-plans │
├── api-server ~/src/api-server ▼
└── web-app ~/src/web-app все три открыты в вашем инструменте
openspec workset create platform \
--member ~/openspec/team-plans --member ~/src/api-server \
--tool code
openspec workset list
platform (opens in VS Code)
team-plans /Users/you/openspec/team-plans
api-server /Users/you/src/api-server
openspec workset open platform запускает сохраненный инструмент: редакторы (VS Code, Cursor) открывают одно окно со всеми участниками и возвращают управление. Первый участник - основной. Инструмент переопределяется в любой момент флагом --tool <id>.
Worksets намеренно не общие. Они живут на вашей машине, никогда не коммитятся и ничего не утверждают о работе - только фиксируют, что вам нравится открывать вместе. Удаление workset никогда не трогает папки-участники. Новые инструменты - это конфигурация, а не код: все, что запускается через workspace-файл или флаги attach по папкам, добавляется под ключом openers в глобальной конфигурации (openspec config edit).
Как команды выбирают, где действовать
Каждая обычная команда резолвит корень одинаково, в этом порядке:
1. --store <id> явно указано → этот Store
2. ближайший openspec/ настоящий корень планирования → этот репозиторий
(подъем от cwd)
3. указатель store: config.yaml декларирует Store → этот Store
4. defaultStore глобальный машинный дефолт → этот Store
5. ничего из перечисленного
есть зарегистрированные Store на машине? → ошибка с подсказкой выбора
нет зарегистрированных Store? → текущий каталог
(классическое поведение)
Строка Using OpenSpec root: (и блок root в --json) сообщает, какой случай ваш.
Известные ограничения Stores
- Beta-форма. Все на этой странице может меняться между релизами - имена, флаги, форматы файлов, ключи JSON.
- Один checkout на id Store на машину. Регистрация второго checkout под тем же id падает с подсказкой сначала выполнить
store unregister. - Никакой синхронизации - никогда, by design. OpenSpec не клонирует, не пуллит, не пушит. Устаревший checkout показывает устаревшие specs, пока вы не сделаете pull; references индексируются live из того, что на диске.
- Пустые папки планирования могут отсутствовать. У нового Store может еще не быть
openspec/changes/,openspec/specs/илиopenspec/changes/archive/в git. В beta это допустимо; папки появляются, когда обычные команды создают в них файлы. - Репозитории-указатели остаются указателями. Репозиторий только с конфигурацией, где
openspec/config.yamlдекларируетstore: <id>, трактуется как вынесенное планирование, а не как checkout Store для регистрации. Уберите строкуstore:, если вы сознательно превращаете репозиторий в локальный корень Store. - Некоторые команды остаются на месте.
templatesи устаревшие именные формы (openspec change show, ...) работают только с текущим каталогом - без--store.schemasследует канонической схеме выбора корня и принимает--store <id>, сохраняя прежнюю форму JSON-массива. - Состояние машины остается состоянием машины. Реестр Store и worksets - локальные настройки. Ничего о вашей машине никогда не коммитится в общее планирование.
- Два стиля запуска для worksets. Инструмент, который нельзя запустить workspace-файлом или флагами attach по папкам, нельзя добавить как opener.
- У agent JSON есть известное расхождение регистра (ключи store-семейства - snake_case, workflow-семейства - camelCase). Задокументировано в agent contract (урок 15.14); объединение отложено до версионируемого релиза.
Где что лежит
| Что | Где | Общее? |
|---|---|---|
| Планирование Store | <store>/openspec/ (specs, changes) |
Да - коммитить и пушить |
| Идентичность Store | <store>/.openspec-store/store.yaml |
Да - коммитится вместе со Store |
| Реестр Store | <data dir>/openspec/stores/registry.yaml |
Нет - только эта машина |
| Worksets | <data dir>/openspec/worksets/ |
Нет - только эта машина |
<data dir> - это ~/.local/share/openspec на macOS и Linux (или $XDG_DATA_HOME/openspec, если задан) и %LOCALAPPDATA%\openspec на Windows.
Точные флаги и JSON-формы всех команд страницы - в справочнике CLI (урок 15.6) и agent contract (урок 15.14).
Практика
- Возьмите любой парный набор: «репозиторий планирования» (можно пустой) и один «кодовый» репозиторий с инициализированным OpenSpec.
- Выполните
openspec store setup demo-plans --path ~/openspec/demo-plansиopenspec new change demo --store demo-plans; убедитесь, что вывод содержит строкуUsing OpenSpec root:. - В кодовом репозитории добавьте
store: demo-plansвopenspec/config.yamlи выполнитеopenspec statusбез флагов - проверьте, что команда резолвится в Store по указателю. - Выполните
openspec doctorиopenspec contextи сравните их вывод с примерами выше. - Смоделируйте командный цикл соло: ветка →
/opsx:propose→ ревью плана →/opsx:apply→ коммит и «PR» →/opsx:archiveпосле «merge». Заметьте, какие артефакты оказались бы в PR.
Краткое резюме
- OpenSpec не трогает git:
openspec/коммитится как исходник, изменение - папка на ветке, все командные договоренности - конвенции. - Повседневный цикл команды: change ↔ branch ↔ PR; ревьюер читает
proposal.md→ дельтыspecs/→ diff кода; архивация - после merge PR (рекомендуется) или внутри него. - Параллельные изменения не конфликтуют, кроме двух правок одного требования в
specs/- и это фича, а не баг. - Когда планирование не помещается в один репозиторий, Store выносит его в отдельный git-репозиторий:
store setup,register, указательstore:в config или глобальныйdefaultStore, references как read-only upstream-контекст,doctorиcontextдля диагностики, worksets для личных наборов папок. - Stores - beta: имена и формы могут меняться, синхронизации нет by design, состояние машины не коммитится.
Вопросы для самопроверки
- Почему «OpenSpec не трогает git» - ключевое правило для командного workflow, и что из него следует для коммитов
openspec/? - В каком порядке ревьюер читает PR с дельта-спецификацией и почему дельта должна быть видна в PR до diff кода?
- Чем «архивировать после merge» отличается от «архивировать внутри PR» и почему документация рекомендует первое?
- В каком единственном месте возникают конфликты между параллельными изменениями и как их разрешать?
- Как указатель
store:вopenspec/config.yamlсоотносится по приоритету с--store, локальным корнемopenspec/иdefaultStore? - Чем reference (
references:) отличается от указателя (store:), и какую задачу каждый решает в сценарии «команда платформы владеет требованиями»? - Почему отсутствие автоматической синхронизации Store - осознанное решение, а не недоработка?
Что дальше
Урок 15.13 - кастомизация: конфигурация проекта, кастомные схемы, shared context, генерация артефактов на других языках и полный список поддерживаемых AI-инструментов с путями, куда пишет файлы OpenSpec.