当前位置: 首页 > news >正文

MonoSpecs 是什么:为什么说它是对 OpenSpec 的进一步升级和扩展

MonoSpecs 是什么:为什么说它是对 OpenSpec 的进一步升级和扩展

当一个产品体系膨胀到 40+ 个独立 Git 仓库时,"规范"该放在哪里?这篇就聊聊 HagiCode 在多仓库治理上走过的两步棋:先把 OpenSpec 上提到主仓库,再在它之上发展出 MonoSpecs 这套多仓库管理方案。其实也没什么啦,只是走过了一些坑,想记下来罢了。

背景

做过稍微大一点产品的同学,大概都有过这样的体验吧——一开始代码就一个仓库,规规矩矩,岁月静好;后来前端、后端、桌面端、文档站、官网、构建工具各自独立成仓,仓库数量蹭蹭往上涨,像野草一样拦都拦不住。再往后你想给某个跨仓库的功能写一份"规范文档",突然就不知道该往哪写了,怎么说呢,有点像小时候的零花钱,怎么忽然就没了。

我们自己的 HagiCode 就是这样一个由 40+ 个独立 Git 仓库组成的产品体系。早期的时候,我们直接把 OpenSpec 的 openspec/ 目录塞在后端子仓库 hagicode-core 里,想着反正后端是核心,放这儿最稳。结果仓库越拆越多,这套方案就暴露出来一连串让人头疼的问题。毕竟代码世界从来不会因为你"想得稳"就真的稳。

第一个痛点:specs 被困在单一子仓库里。一个功能如果同时影响前端 web 和后端 hagicode-core,我得在 hagicode-core 里写提案,再跑去其他子仓库执行代码改动。提案该归属哪个仓库,本身就成了一个争议。

第二个痛点:子仓库不纯净。每个子仓库都背着自己的 openspec/,规范文档和产品代码混在一起。别人 clone 你一个前端仓库,结果带回来一堆后端提案文档,一脸懵。

第三个痛点:AI Agent 难以理解仓库关系。各个子仓库彼此独立,没有一个机器可读的"清单"告诉 AI:这个产品由哪些仓库组成、各自负责什么、哪个是可编辑的、哪个是只读参考。

第四个痛点:跨仓库编辑成本高。要改一份 spec,必须先 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 文件怎么组织。

我们通过一次迁移提案,把原本分散在 hagicode-core/openspec/ 的 82+ 个 spec 文件上提到 monorepo 根目录的 openspec/,让所有 spec 在一个地方统一可见、统一版本控制。

但这次迁移说白了只是"把 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 一眼就能看出哪个仓库可编辑、哪个是 reference-only、技术栈是什么。

第四件:把 OpenSpec 的产物锚定到主仓库。specs 不再散落到各子仓库,而是统一收归到主仓库根目录的 openspec/,子仓库因此保持纯净。

两层含义,别搞混了

在 MonoSpecs 的官方 guide 里,明确点出了一个非常容易混淆的地方:MonoSpecs 其实有两层含义

一层是配置系统层,指的就是 .hagicode/monospecs.yaml 这个配置文件本身,以及它配套的加载、校验、缓存机制。

另一层是仓库类型层,指的是一种"主仓库 + 多子仓库 + 集中 specs"的仓库组织模式。当我们说一个项目"是 MonoSpecs 项目",意思是它采用了这种结构。

这两层叠加在一起,才是完整的 MonoSpecs。很多人第一次接触容易只看到 YAML 文件这一层,以为 MonoSpecs 就是个配置清单,其实它的价值更多在第二层——一种明确的多仓库协作范式。其实,美的事物往往不在第一眼,得多看几眼罢了。

为什么说是"升级和扩展"

把两者放在一起对比,关系就清晰了:

维度 OpenSpec MonoSpecs
关注点 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: truerepositories:
- 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   # 在 UI 里折叠到 "More" 后面

有几个字段要特别注意:

  • 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/,主仓库成为唯一的 spec 真相源。这一步看起来简单,但带来的收益非常实在——任何一个工程师站在主仓库根目录,就能看到整个产品体系的全部规范。

第三步:让 clone 脚本读配置而非硬编码

scripts/clone-repos.mjs 的核心逻辑就是读 YAML、逐条 clone:

const CONFIG_PATH = path.join(__dirname, '..', '.hagicode', 'monospecs.yaml');
// 解析 repositories 数组
// 对每条执行 git clone <url> <path>
// 目标目录已存在则跳过或 git pull

新增仓库的时候,只需要在 YAML 里加一条,不用动脚本。这点小小的改动,省下的是无数次"忘记同步仓库列表"的扯皮。毕竟谁愿意重复劳动呢?

第四步:后端提供统一的 MonoSpecs 服务层

如果不抽一层抽象出来,配置解析逻辑很容易散落在 GitAppServiceProjectAppService 各个角落。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.yamlrepos/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

这里有几个边界要注意,都是从 spec 里抠出来的:

  • 幂等:已存在的 repos/openspec/ 目录会被保留,不会报错。
  • 不覆盖:若 monospecs.yaml 已存在且能正常解析,初始化不会动它,只补齐缺失的 .gitignore 规则和 openspec 目录。
  • 拒绝脏配置:已存在但解析不了的 monospecs.yaml 会被直接拒绝,返回可诊断的错误信息,绝对不覆盖。
  • 不自动扫描:初始化不会自作主张把磁盘目录扫描成仓库条目,repositories 默认空,需要你手工或通过 UI 填写。

配置文件位置的迁移陷阱

历史上 monospecs.yaml 曾经放在项目根目录,后来强制迁移到 .hagicode/monospecs.yaml。这一点 spec 里写得很明确:

根目录的 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.mdmonospecs.yaml 是给 AI 的两份关键上下文。建议的工作流是这样的:

  1. 先读 monospecs.yaml 拿到仓库拓扑,搞清楚谁可编辑、谁是 reference-only。
  2. 再读根 AGENTS.md 的 "Active Edit Scope",确认当前允许修改的范围。
  3. 跨仓库变更统一在主仓库根的 openspec/changes/ 写提案,不要在各子仓库里另起 openspec。

这套约定让 AI 能稳定地理解"主仓库管 specs、子仓库管代码"的分工,而不会误把 spec 写进子仓库——这种误操作我们之前踩过好几次坑。其实也不怪 AI,毕竟子仓库和主仓库长得那么像,谁能一眼分清呢?

总结

一句话总结:OpenSpec 定义了"变更怎么写",MonoSpecs 定义了"仓库怎么摆"

前者是后者的语法基础,后者把前者从单仓库语境扩展到多仓库语境,并用一个 YAML 清单把仓库拓扑、clone 流程、AI 上下文、specs 归属一次性收敛起来。这就是"monospec 是对 openspec 的进一步升级和扩展"的真正含义——不是替代,而是在其之上加了一层多仓库语义。

如果你也在做类似规模的多仓库产品,不妨想想这两层是不是也都铺好了。规范写得再漂亮,没有清晰的仓库治理撑着,最终还是会乱成一锅粥......

参考资料

  • HagiCode 官网
  • HagiCode-org/site GitHub 仓库
  • OpenSpec 工作流文档
  • MonoSpecs 相关 spec:monospecs-guidemonospecs-repository-configmonospec-config-management

总结

围绕“MonoSpecs 是什么:为什么说它是对 OpenSpec 的进一步升级和扩展”,更稳妥的推进方式是先把关键配置、依赖边界和落地路径逐步跑通,再补齐优化细节。

当目标、步骤和验收点都明确之后,这类方案通常就能更顺畅地进入实际交付。

原文与版权说明

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。
本内容采用人工智能辅助协作,最终内容由作者审核并确认。

  • 本文作者: newbe36524
  • 原文链接: https://docs.hagicode.com/go?platform=cnblogs&target=%2Fblog%2F2026-06-20-monospecs-openspec-upgrade-and-extension%2F
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
http://www.jsqmd.com/news/1048657/

相关文章:

  • 3步掌握yuzu模拟器:从零开始畅玩Switch游戏
  • 嵌入式GUI开发:emWin EDIT控件从入门到精通
  • 萧邦 2026 官方售后全新门店地址正式落地,迭代升级版全国统一售后咨询热线同步面向公众公示 - 亨得利中国服务中心
  • [特殊字符] AI大模型+知识图谱=?这个智慧教学平台太超前了!
  • 【新】5p235基于spark的猫眼电影数据分析与推荐系统-django3(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 嵌入式GUI显示驱动开发:emWin GUIDRV_SPage配置与实战指南
  • 2026煲仔饭机直供选型指南:解析行业代表性直供厂家 - 速递信息
  • TensorFlow Estimator训练报错怎么办?教你一招避坑
  • 5分钟打造你的私人游戏云:Sunshine游戏串流服务器完全指南
  • 信创AI模型适配模盒:从GLM-5部署看国产算力全栈落地
  • 内蒙古四季旅游导游推荐|春夏秋冬专属路线、持证资深导游全程适配(2026四季攻略) - 纯玩旅游分享
  • 3步搞定抖音无水印视频下载:完整指南让你永久保存高清原创内容
  • 3个实用技巧彻底优化《鸣潮》体验:从帧率解锁到抽卡分析的完整指南
  • Switch破解终极指南:5步掌握大气层完整自定义功能
  • 影刀RPA实战教程:手把手教你搭建电商商品数据采集机器人
  • 黄金市场智能分析:Multi-Agent架构与双模型协同实战
  • 2026-06-20 闲话
  • 2026济宁本地正规瓷砖空鼓维修服务商盘点|无损免拆砖修复,全域上门售后有保障 - 宅安选房屋修缮
  • 华为MetaERP 面向落地的“xxxx↔SAP 集成点切换 → Oracle EBS”方案。它的核心目的只有一个:把 xxxx对 SAP 的“硬绑定”拆成可替换通道(Adapter/Connecto
  • 卡地亚中国区 2026 售后网点优化工程:全部维修门店新址完成更新升级,新版官方全国服务电话同步全域启用 - 卡地亚中国服务中心
  • 5个步骤掌握Source Han Serif CN:免费开源中文字体完全指南
  • 拒绝加盟外包!2026合扬直营黄金回收服务全城统一标准 - 奢侈品交易观察员
  • 2026 上新:宁波除甲醛公司 7 大排名(全民选票・客户真实口碑版)权威票选结果发布 - 专注室内空气检测治理
  • ARM中断与VIC控制器实战:从原理到配置与避坑指南
  • 嵌入式GUI开发中emWin位图资源优化:颜色转换、抖动技术与设备相关位图实战
  • LPC210x ARM7 ADC与定时器实战:从寄存器配置到驱动代码
  • AI编程已转向本地化智能体工作流
  • 合光影像和观喜摄影是什么关系?一句话说清楚 - eee888
  • 嵌入式GUI字体系统实战:从emWin字体类型、抗锯齿到字符集全解析
  • 2026 上新:宁波高品质甲醛治理公司推荐:头部公司综合实力与口碑大赏 - 专注室内空气检测治理