Перейти к содержимому

MonoSpecs: что это и почему это является дальнейшим обновлением и расширением OpenSpec

HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

MonoSpecs: что это и почему это является дальнейшим обновлением и расширением OpenSpec

Когда продукт вырастает до 40+ независимых Git-репозиториев, где должно находиться “спецификация”? В этой статье я расскажу о двух шагах, которые HagiCode предпринял в управлении несколькими репозиториями: сначала OpenSpec был поднят в основной репозиторий, а затем поверх него был разработан MonoSpecs — решение для управления несколькими репозиториями. На самом деле, ничего особенного, просто хотело записать пройденные проблемы.

Предпосылки

Каждый, кто работал с достаточно большими продуктами, вероятно испытывал следующее: сначала код находится в одном репозитории, всё в порядке, жизнь спокойна; позже фронтенд, бэкенд, десктопное приложение, сайт документации, официальный сайт, инструменты сборки становятся независимыми репозиториями, количество репозиторий стремительно растёт, как сорняки, их не остановить. И когда вы хотите написать “документ спецификации” для некоторой функции, охватывающей несколько репозиториев, вдруг не знаете, где писать. Говорят, это похоже на детские карманные деньги — как они вдруг исчезли.

Наш собственный HagiCode — это именно такая система из 40+ независимых Git-репозиториев. В начале мы напрямую поместили директорию OpenSpec openspec/ в под-репозиторий бэкенда hagicode-core, думая, что раз бэкенд является ядром, то здесь самое безопасное место. В итоге репозиториев стало больше, и это решение выявило серию мучительных проблем. Ведь в мире кода всё никогда не будет стабильным просто потому, что вы “думали, что это безопасно”.

Первая боль: specs заперты в единственном под-репозитории. Если функция одновременно влияет на фронтенд web и бэкенд hagicode-core, мне нужно написать предложение в hagicode-core, а затем выполнить изменения кода в других под-репозиториях. К какому репозиторию принадлежит предложение, само по себе становится спорным вопросом.

Вторая боль: под-репозитории не чистые. Каждый под-репозиторий несёт свой openspec/, документы спецификации смешиваются с кодом продукта. Кто-то клонирует ваш фронтенд-репозиторий, а приносит кучу документов предложений бэкенда — полная растерянность.

Третья боль: AI Agent сложно понять отношения между репозиториями. Каждый под-репозиторий независим, нет машиночитаемого “списка”, который сказал бы AI: из каких репозиториев состоит этот продукт, за что каждый отвечает, какой можно редактировать, какой является только для чтения.

Четвёртая боль: высокая стоимость редактирования между репозиториями. Чтобы изменить спецификацию, нужно сначала cd в соответствующий под-модуль, пути постоянно прыгают, большая нагрузка на сознание при сотрудничестве.

Именно на таком фоне мы сначала сделали “OpenSpec Monorepo Migration”, подняли specs из под-репозиториев в корневой каталог monorepo. А поверх этого развилась система управления несколькими репозиториями MonoSpecs. Понимание этой последовательности — ключ к пониманию того, “почему monospec является дальнейшим обновлением и расширением openspec”.

О HagiCode

Решение, описанное в этой статье, основано на нашем практическом опыте в проекте HagiCode. HagiCode — это проект AI-помощника по коду, с большим количеством репозиториев и частым межъязыковым сотрудничеством. Такая сложность структуры вынудила нас сделать надёжными как “спецификацию”, так и “управление репозиториями”. Система MonoSpecs была打磨лена именно в такой практике с несколькими репозиториями, на самом деле ничего особенного, просто прошли ещё несколько шагов.

OpenSpec решает вопрос “как писать спецификацию, как она эволюционирует”

Чтобы чётко объяснить отношения между ними, нужно сначала рассмотреть, за что каждое отвечает.

OpenSpec по сути — это рабочий процесс управления изменениями на основе spec-driven. Его основной результат выглядит так:

openspec/
├── specs/ # Действующие спецификации возможностей (один spec.md для каждой возможности)
├── changes/ # Предложения в процессе
│ └── archive/ # Архивированные исторические предложения
└── project.md

Он отвечает на вопрос: изменение должно пройти жизненный цикл: предложение (proposal), дизайн (design), задачи (tasks), архивация (archive), и при архивации deltas объединяются в specs. Сам этот механизм не имеет отношения к “сколько репозиториев, где они, кто ими управляет”, он заботится только о том, как организовать spec-файлы.

Мы через предложение о миграции подняли 82+ spec-файлов, первоначально разбросанных в hagicode-core/openspec/, в openspec/ корневого каталога monorepo, чтобы все specs были единообразно видны и под управлением версий в одном месте.

Но по сути эта миграция просто “перенесла spec-файлы”, и не ответила на более фундаментальный вопрос: из каких под-репозиториев на самом деле состоит этот monorepo? Какие отношения между этими под-репозиториями? Вот что должен дополнить MonoSpecs.

MonoSpecs решает вопрос “как управлять самими несколькими репозиториями”

Ядром MonoSpecs является машиночитаемый файл списка: .hagicode/monospecs.yaml. Он делает четыре вещи, которые OpenSpec вообще не затрагивает.

Первая: объявить список под-репозиториев. Path, url, displayName, icon, tags, нужно ли сворачивать в “More” для каждого репозитория — всё записано в одном YAML,一目了然.

Вторая: управлять clone-скриптом. scripts/clone-repos.mjs напрямую читает этот YAML,批量 git clone, больше нет жёстко закодированного списка репозиториев. При добавлении нового репозитория нужно просто добавить строку в YAML, скрипт не требует изменений.

Третья: предоставить контекст структуры проекта для AI/IDE. Совместно с AGENTS.md, AI Agent сразу может увидеть, какой репозиторий можно редактировать, какой только для чтения, какой стек технологий.

Четвёртая: привязать результат OpenSpec к основному репозиторию. Specs больше не разбросаны по под-репозиториям, а единообразно собраны в openspec/ корневого каталога основного репозитория, поэтому под-репозитории остаются чистыми.

Два значения, не путайте

В официальном руководстве MonoSpecs чётко указано очень легко запутывающееся место: MonoSpecs на самом деле имеет два значения.

Одно — уровень системы конфигурации, относится к самому файлу конфигурации .hagicode/monospecs.yaml и сопутствующим механизмам загрузки, проверки, кеширования.

Другое — уровень типа репозитория, относится к организационному режиму репозитория “основной репозиторий + несколько под-репозиториев + централизованные specs”. Когда мы говорим, что проект “является MonoSpecs проектом”, это означает, что он采用了 такую структуру.

Только объединение этих двух слоёв составляет полный MonoSpecs. Многие при первом контакте видят только слой YAML-файла, думая, что MonoSpecs — это просто список конфигурации, на самом деле его ценность больше во втором слое — чёткой парадигме сотрудничества между несколькими репозиториями. На самом деле, красивые вещи часто не видны с первого взгляда, нужно посмотреть ещё несколько раз.

Почему говорится “обновление и расширение”

Если поставить их рядом для сравнения, отношения становятся ясными:

РазмерностьOpenSpecMonoSpecs
ФокусСодержание и жизненный цикл spec-файловОрганизационная структура и список репозиториев
Основной результатopenspec/specs/*/spec.md.hagicode/monospecs.yaml
Зависит ли от другогоНе зависит от MonoSpecsЗависит от OpenSpec, повторно использует его openspec/ для управления изменениями
Решённая больКак писать спецификацию, как она эволюционируетКак объявлять несколько репозиториев, как clone, как понимает AI
Область действияМожет использоваться в любом репозиторииСпециально разработан для структуры “один основной, несколько под-”

Проще говоря, MonoSpecs не заменяет OpenSpec, а добавляет поверх него слой “управления репозиториями”. С помощью monospecs.yaml описывается топология репозиториев, с помощью централизованного openspec/ spec декомпозируется от под-репозиториев, с помощью commit_when_archive архивация автоматически фиксируется в основном репозитории.

Если использовать аналогию: OpenSpec предоставляет “синтаксис изменения”, MonoSpecs предоставляет “семантику нескольких репозиториев”. Первый является предпосылкой второго, второй является расширением первого. Все дороги ведут в Рим, только на этот раз дорога длиннее, чем казалось.

Как внедрить: четыре шага

Первый шаг: определить основной репозиторий и файл конфигурации

Поместите файл конфигурации в корневой каталог monorepo, объявите все под-репозитории. Возьмём наш собственный проект, структура примерно такая:

.hagicode/monospecs.yaml
version: "1.0"
commit_when_archive: true
repositories:
- path: "repos/web"
url: "https://github.com/HagiCode-org/web.git"
displayName: "Фронтенд"
tags: [frontend, react, pcode-client]
- path: "repos/hagicode-core"
url: "https://github.com/newbe36524/pcode"
displayName: "Бэкенд"
tags: [backend, dotnet, orleans]
- path: "repos/docs"
url: "https://github.com/HagiCode-org/docs.git"
displayName: "Документация"
tags: [docs, astro, starlight]
ui:
collapseToMore: true # Сворачивать в "More" в UI

Есть несколько полей, которые нужно特别注意:

  • path — локальный путь относительно корня основного репозитория, также уникальный ключ каждой записи.
  • url — адрес Git-удалённого репозитория, clone-скрипт использует его для получения кода.
  • displayName / icon / tags влияют только на отображение в UI и контекст AI, не влияют на поведение clone.
  • commit_when_archive: true — при архивации предложения OpenSpec автоматически commit в основной репозиторий.

Второй шаг: поднять OpenSpec в корневой каталог основного репозитория

Сравнение до и после миграции:

До миграции (specs в под-репозитории) После миграции (specs в основном репозитории)
hagicode-core/ . (корень основного репозитория)
└── openspec/ ├── .hagicode/monospecs.yaml
└── specs/ (82+ specs) ├── openspec/
│ ├── specs/ (централизованное управление)
│ └── changes/
└── repos/
├── hagicode-core/ (чистый, без openspec)
├── web/
└── docs/

Под-репозитории больше не несут openspec/, основной репозиторий становится единственным источником истинности specs. Этот шаг кажется простым, но приносит очень реальную выгоду — любой инженер, стоя в корневом каталоге основного репозитория, может увидеть все спецификации всей системы продукта.

Третий шаг: заставить clone-скрипт читать конфигурацию, а не жёстко кодировать

Основная логика scripts/clone-repos.mjs — чтение YAML и клонирование каждой записи:

const CONFIG_PATH = path.join(__dirname, '..', '.hagicode', 'monospecs.yaml');
// Разбор массива repositories
// Для каждой записи выполнить git clone <url> <path>
// Если целевой каталог существует, пропустить или git pull

При добавлении нового репозитория нужно только добавить строку в YAML, не нужно трогать скрипт. Это небольшое изменение экономит бесчисленные споры о “забыл синхронизировать список репозиториев”. Ведь кто хочет повторять labour?

Четвёртый шаг: бэкенд предоставляет унифицированный сервисный слой MonoSpecs

Если не вытащить абстракцию, логика разбора конфигурации легко разбросается по GitAppService, ProjectAppService и другим углам. HagiCode в модуле ClaudeHelper вытащил IMonoSpecsService, предоставляя наружу чёткий набор возможностей:

public interface IMonoSpecsService
{
Task<MonoSpecsConfigDto> GetConfigAsync(string projectPath);
Task<List<RepositoryInfoDto>> GetSubRepositoriesAsync(string projectPath);
Task<MonoSpecsDataDto> GetMonoSpecsDataAsync(string projectPath);
Task<MonoSpecsManagementDto> GetManagementDocumentAsync(string projectPath);
Task<MonoSpecsManagementDto> InitializeManagementDocumentAsync(string projectPath);
Task ValidateManagementDocumentAsync(string projectPath, UpdateMonoSpecsManagementRequestDto request);
Task SaveManagementDocumentAsync(string projectPath, UpdateMonoSpecsManagementRequestDto request);
}

Этот сервис отвечает за загрузку, проверку, кеширование конфигурации и предоставляет возможность “инициализировать минимальный шаблон” — для пустого проекта одним кликом генерировать скелет monospecs.yaml, repos/, openspec/changes/archive/, openspec/specs/, и автоматически дополнить .gitignore. Кеш, как память, запомнил, в следующий раз не нужно так напряжённо думать.

Несколько проблем на практике

Инициализация совершенно нового проекта MonoSpecs

После вызова InitializeManagementDocumentAsync на диске появится такая структура:

my-project/
├── .gitignore # Добавлены правила игнорирования repos/ (идемпотентно, не добавляется повторно)
├── .hagicode/
│ └── monospecs.yaml # Минимальный шаблон: version / commit_when_archive / repositories: []
├── openspec/
│ ├── changes/archive/
│ └── specs/
└── repos/ # Пустой каталог, ожидает clone

Есть несколько границ, на которые нужно обратить внимание, всё вытащено из спецификации:

  • Идемпотентность: существующие repos/, openspec/ каталоги будут сохранены, не будет ошибок.
  • Не перезаписывать: если monospecs.yaml уже существует и может быть нормально разобран, инициализация не будет трогать его, только дополнятся отсутствующие правила .gitignore и каталог openspec.
  • Отклонять грязную конфигурацию: существующий но не разбираемый monospecs.yaml будет прямо отклонён, возвращается диагностируемая информация об ошибке, абсолютно не перезаписывается.
  • Не сканировать автоматически: инициализация не будет по собственной инициативе сканировать дисковые каталоги в записи репозиториев, repositories по умолчанию пуст, нужно заполнить вручную или через UI.

Ловушка миграции местоположения файла конфигурации

Исторически monospecs.yaml когда-то находился в корневом каталоге проекта, позже принудительно мигрировал в .hagicode/monospecs.yaml. Это в спецификации написано очень чётко:

monospecs.yaml в корневом каталоге больше не обнаруживается и не используется как совместимый откат. Clone-скрипт признаёт только .hagicode/monospecs.yaml.

Поэтому при обновлении старого проекта нужно вручную выполнить mv monospecs.yaml .hagicode/monospecs.yaml, нет пути молчаливой совместимости. На первый взгляд немного немилосердно, но подумайте, это для полного устранения двусмысленности “обе позиции могут действовать” — если эта двусмысленность существует, при поиске проблемы можно сойти с ума,毕竟 никто не хочет来回 искать ответ между двумя файлами.

Сохранение проверки: не пишите недействительную конфигурацию

Перед записью обратно через SaveManagementDocumentAsync сервис сделает проверку полей. Несколько типичных сценариев отклонения:

  • Две записи репозиториев с повторяющимся path → отклонить, вернуть конфликтующие поля.
  • Любая запись без path → отклонить, вернуть обязательную ошибку.
  • url не пустой, но не является легальным абсолютным URL → отклонить.

Только после прохождения проверки будет сериализовано в YAML и записано на диск, одновременно недействительный кеш конфигурации пути этого проекта, гарантируя, что при следующем чтении получается самое новое содержание. Этот шаг кажется мелким, но может избежать бесчисленных тикетов “почему я изменил конфигурацию, она не вступила в силу”,毕竟 этих тикетов много, никто не выдержит.

Режим workspace vs режим ручных repositories

Файл конфигурации поддерживает два способа получения списка репозиториев.

Один — режим ручных repositories, напрямую перечисляет каждый репозиторий в YAML, документ управления помечается как редактируемый.

Другой — режим workspace, объявляет файл .code-workspace, от него получается список репозиториев. В этом режиме документ управления помечается как только для чтения, запрещается напрямую переписывать массив репозиториев, можно изменять только поддерживаемые верхние поля.

Наш собственный HagiCode Mono в данный момент закомментировал режим workspace, использует ручной режим. Причина проста: в ручном режиме можно точно контролировать icon и tags каждого репозитория, эффект отображения в UI более контролируем. Как говорится, то, что можно контролировать, всегда будет спокойнее.

Практические рекомендации для AI Agent

Сейчас AI-программирование становится всё более популярным, система MonoSpecs на самом деле имеет ещё одну неявную ценность: она предоставляет структурированную карту проекта для AI.

При сотрудничестве между несколькими репозиториями, AGENTS.md и monospecs.yaml — это два ключевых контекста для AI. Рекомендуемый рабочий процесс такой:

  1. Сначала прочитать monospecs.yaml, получить топологию репозиториев, понять, какой можно редактировать, какой только для чтения.
  2. Затем прочитать “Active Edit Scope” в корневом AGENTS.md, подтвердить текущий разрешённый диапазон модификаций.
  3. Для изменений между репозиториями единообразно писать предложения в openspec/changes/ корня основного репозитория, не начинать отдельный openspec в каждом под-репозитории.

Это соглашение позволяет AI стабильно понимать разделение труда “основной репозиторий управляет specs, под-репозитории управляют кодом”, а не ошибочно записать spec в под-репозитории — с этой ошибкой мы уже наступали несколько раз. На самом деле не вините AI,毕竟 под-репозитории и основной репозиторий выглядят так похоже, кто сразу различит?

Заключение

Одно предложение для резюме: OpenSpec определяет “как писать изменения”, MonoSpecs определяет “как размещать репозитории”.

Первый является синтаксической основой второго, второй расширяет первый из контекста одиночного репозитория в контекст нескольких репозиториев, и с одним YAML-списком единовременно собирает топологию репозиториев, процесс clone, контекст AI, принадлежность specs. Это и есть истинное значение “monospec является дальнейшим обновлением и расширением openspec” — не замена, а поверх него добавлен слой семантики нескольких репозиториев.

Если вы также делаете продукт с несколькими репозиториями подобного масштаба,不妨 подумать, оба ли этих уровня подготовлены. Спецификацию как бы красиво написали, без надёжного управления репозиториями в итоге всё равно будет混乱成一锅粥…

Справочные материалы

Заключение

Вокруг “MonoSpecs: что это и почему это является дальнейшим обновлением и расширением OpenSpec”, более надёжный способ продвижения — сначала постепенно запустить ключевые конфигурации, границы зависимостей и пути внедрения, а затем дополнить детали оптимизации.

Когда цели, шаги и точки приёма чётко определены, такие решения обычно могут более плавно войти в фактическую доставку.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。