跳转到内容

选择语言

目前語言: 繁體中文

MonoSpecs 是什麼:為什麼說它是對 OpenSpec 的進一步升級和擴展

HagiCode for Windows Microsoft Store 宣传图
HagiCode for Windows 已上架 Microsoft Store
HagiCode for Windows 現已正式登陸 Microsoft Store,Windows 使用者可以直接從商店完成安裝並接收管道更新。現在就打開商店頁面看看吧。
打開 Microsoft Store
Awesome Site 横幅:社区精选开源合集
Awesome Site:社群精選開源合集
在開源社群精心整理的合集中,找到下一個工具、想法或靈感。從感興趣的主題出發,用熟悉的語言閱讀,再回到原始來源深入探索。
探索精選合集
Awesome Design 横幅:DESIGN.md 设计系统画廊
Awesome Design:DESIGN.md 設計系統畫廊
透過即時預覽與 README 參考瀏覽精選設計系統,再把 DESIGN.md 複製進你的專案,讓 AI 代理依它建構風格一致的頁面。
瀏覽設計畫廊
OpenSpec 横幅:先对齐规范,再用 AI 构建
OpenSpec:先對齊規範,再讓 AI 寫程式碼
OpenSpec 是面向 AI 程式開發的輕量規範層:先描述一項變更,由 AI 起草提案、規範、設計與任務,你審閱確認後再動手實作,完成後歸檔為持續更新的規範。沒有僵化的階段門檻,適用於既有程式碼庫,並能搭配你正在使用的各類 AI 助手。
了解 OpenSpec
OmniRoute 文档横幅:一个端点连接众多 AI 服务商
OmniRoute 文件:一個端點連接眾多 AI 服務商
了解 OmniRoute 如何透過同一個端點路由眾多 AI 服務商與模型,並支援免費額度與自動回退,讓你的編碼工具持續可用。
查看 OmniRoute 文件

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 就是個配置清單,其實它的價值更多在第二層——一種明確的多倉庫協作範式。其實,美的事物往往不在第一眼,得多看幾眼罷了。

為什麼說是”升級和擴展”

把兩者放在一起對比,關係就清晰了:

維度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 # 在 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 服務層

如果不抽一層抽象出來,配置解析邏輯很容易散落在 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

這裡有幾個邊界要注意,都是從 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.md 和 monospecs.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 的進一步升級和擴展”的真正含義——不是替代,而是在其之上加了一層多倉庫語義。

如果你也在做類似規模的多倉庫產品,不妨想想這兩層是不是也都鋪好了。規範寫得再漂亮,沒有清晰的倉庫治理撐著,最終還是會亂成一鍋粥…

參考資料

總結

圍繞”MonoSpecs 是什麼:為什麼說它是對 OpenSpec 的進一步升級和擴展”,更穩妥的推進方式是先把關鍵配置、依賴邊界和落地路徑逐步跑通,再補齊優化細節。

當目標、步驟和驗收點都明確之後,這類方案通常就能更順暢地進入實際交付。

开始使用 HagiCode

一次安装,几分钟上手

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

讓想法化為交付成果。

HagiCode 是面向專案開發的 AI 編碼工作台:以 OpenSpec 工作流程規劃變更,多 Agent 並行執行,再由 Hero Scenario 讓進度一目了然,將想法化為交付成果。

智慧
OpenSpec 工作流程將意圖轉化為從提案到交付的可執行路徑。
高效
多 Agent 並行推進研究、實作與審查。
有趣
豐富、便利的介面互動,讓規劃、Agent 執行與專案工作更容易跟進。