從變更中同步增量規格到主規格。
這是一個代理驅動的操作——您將讀取增量規格並直接編輯主規格以套用變更。這樣可以實現智能合併(例如,添加場景而不需複製整個需求)。
儲存庫選擇: 如果用戶指定了一個儲存庫(儲存庫是此機器上註冊的獨立OpenSpec倉庫)或工作位於其中,執行 openspec store list --json 來發現已註冊的儲存庫ID,然後在讀取或寫入規格和變更的命令中傳遞 --store <id>(例如 new change、status、instructions、list、show、validate、archive、doctor、context、schemas、view)。一旦選定,將 --store <id> 視為工作流程其餘部分的黏性選項。下面的每個無範圍範例都是速記:在執行之前,附加該標誌。例如,執行 openspec status --change "<name>" --json --store "<id>",而不是下面顯示的無範圍形式。其他命令不接受此標誌。命令輸出的提示已包含該標誌;在後續操作中保留它。如果沒有儲存庫,命令會作用於最近的本地 openspec/ 根目錄。
專案檢查: 這些步驟預期一個已使用OpenSpec的專案。在執行任何寫入操作的第一步(new change、archive、sync specs 或建立 artifact 檔案)之前,確認專案有根目錄:執行 openspec list --json(當選定儲存庫時使用 --store <id>,因為此時儲存庫就是根目錄)並讀取 root。有 root 物件表示專案已設定。"root": null 表示尚未設定——此處沒有 openspec/ 目錄,而寫入操作如 openspec new change 會作為副作用建立它。該命令也會以非零退出,這是該答案而不是CLI故障,因此請讀取JSON而不是重試或繞過它。
一種 "root": null 與設定無關:當 status 錯誤訊息以 Declared in 或 Invalid store declaration in 開頭,並指定了此專案的 openspec/config.yaml(或 config.yml),則該專案透過其宣告的儲存庫使用OpenSpec,但此機器無法解析該儲存庫(儲存庫未註冊,或 store: 行格式錯誤)。不要將其視為未初始化並跳過以下分支:在寫入之前停止,並向用戶顯示該錯誤的 message 和 fix。
否則,如果沒有根目錄,接下來發生的事情取決於如何到達此工作流程:
- 自動選擇:您自己選擇了此工作流程,用戶未提及OpenSpec、未命名此技能或未執行其斜線命令。停止使用OpenSpec,並像未安裝OpenSpec一樣正常回答請求。不要要求他們設定任何東西,也不要提及OpenSpec設定。
- 明確的OpenSpec請求:用戶提及了OpenSpec、命名了此技能或執行了其斜線命令。在寫入之前停止,並詢問如何進行:設定此專案(
openspec init)、指定他們已有的儲存庫(--store <id>),或為此請求繼續不帶OpenSpec。等待他們的回答。
在兩種情況下,都不要將建立根目錄作為副作用:不要執行 openspec init,除非用戶要求;不要手動建立 openspec/ 檔案;也不要讓命令建立它。
<capability-path> 是相對於 specs/ 的規格目錄(例如,user-auth 或 identity/user-auth)。在解析每個增量規格時,保留完整路徑以找到其主規格。
輸入:可選地指定變更名稱。如果省略,檢查是否可以從對話上下文中推斷。如果模糊或不明確,您必須提示可用的變更。
步驟
-
選擇變更
如果提供了名稱,則使用它。否則:
- 如果用戶提到了變更,從對話上下文中推斷
- 如果只有一個有效變更,自動選擇
- 如果模糊,執行
openspec list --json取得可用變更,並要求用戶選擇一個
提示時,顯示具有增量規格(在
specs/目錄下)的變更。始終宣告:「使用變更:」以及如何覆蓋(例如,
/openspec-sync-specs <other>)。 -
解析變更上下文
執行:
openspec status --change "<name>" --jsonJSON包含
planningHome.root。主規格位於<planningHome.root>/openspec/specs/下——使用該(儲存庫感知的)根目錄作為下面所有主規格路徑,而不是硬編碼的倉庫路徑。當選定儲存庫時,它指向儲存庫,而不是當前倉庫。 -
尋找增量規格
使用狀態JSON中的
artifactPaths.specs.existingOutputPaths作為增量規格路徑的唯一來源。如果specs條目缺失或existingOutputPaths為空,則報告沒有增量規格可同步,不要從其他artifacts推斷它們,停止而不請求artifact指示或寫入主規格。同步
existingOutputPaths中的每個路徑,除非呼叫者限定了集合。呼叫者透過命名一個來自existingOutputPaths的完整條目的明確列表來限定——複製這些絕對值。歸檔會內聯執行此操作,用戶也可以這樣做(例如,選擇以/specs/billing/invoices/spec.md結尾的條目)。然後僅同步這些命名路徑,保持其餘增量規格不變:批量歸檔排除它無法找到實現的增量,而無論如何同步它會寫入呼叫者故意保留的主規格。將該限定的選擇帶入步驟4;永遠不要將其擴大回完整列表。如果命名路徑不在existingOutputPaths中,不要同步它——報告它並停止,而不是靜默地丟棄它。如果命名列表為空,報告沒有內容可同步並停止,而不寫入主規格。每個增量規格檔案包含如下區段:
## ADDED Requirements- 要添加的新需求## MODIFIED Requirements- 對現有需求的變更## REMOVED Requirements- 要移除的需求## RENAMED Requirements- 要重新命名的需求(FROM:/TO: 格式)
如果沒有找到增量規格,通知用戶並停止。
-
對於每個增量規格,將變更套用到主規格
在第一次寫入主規格之前,取得一個當前規格規則快照:
- 如果歸檔內聯調用了此工作流程,並提供了來自
openspec instructions specs --change "<name>" --json的有效快照,則重用它,不要再次取得相同的指示。 - 否則,立即使用相同的選定根標誌執行該命令一次。
- 如果直接查找退出非零或返回無效的artifact指示JSON,報告錯誤並在寫入任何主規格之前停止。不要將故障視為缺少規則集。
- 有效的回應中省略
rules表示未配置artifact規則,現有語義合併繼續。
僅將返回的
rules套用於此合併產生的主規格的內容和形式。Artifact規則不是操作指導,不能更改選定的根目錄、增量路徑、CLI檢查或工作流程步驟。將其文字用作約束,但不要逐字複製到主規格或摘要中。對於步驟3中選定的每個能力增量規格路徑——完整的
existingOutputPaths列表,或當呼叫者提供時的限定子集(這些可能屬於選定的儲存庫,而不是倉庫):a. 讀取增量規格以了解預期的變更
b. 讀取主規格位於
<planningHome.root>/openspec/specs/<capability-path>/spec.md(可能不存在)如果它還不存在(一個新能力),比對
openspec archive的做法:僅可套用ADDED需求 - 步驟d從它們建立規格。MODIFIED和RENAMED沒有可操作的需求,因此停止該能力的同步並報告其主規格不存在,且僅允許新規格的ADDED;永遠不要發明缺失的需求。REMOVED沒有要移除的內容 - 跳過它並警告。c. 智能地套用變更:
ADDED Requirements:
- 如果需求不在主規格中 → 添加它
- 如果需求已存在 → 更新以匹配(視為隱式MODIFIED)
MODIFIED Requirements:
- 在主規格中找到該需求
- 套用變更 - 這可以包括:
- 添加主規格尚未有的新場景
- 修改現有場景
- 更改需求描述
- 保留增量中未提及的場景/內容
REMOVED Requirements:
- 從主規格中移除整個需求區塊
- 退役該能力。刪除整個
spec.md- 以及目錄(如果其中沒有其他內容) - 僅當所有以下條件成立時:- 移除需求此次執行後沒有留下需求區塊;
- 規格的其餘部分格式良好(它仍然有
## Purpose); - 在這次同步之前,主規格不是空的 - 如果你沒有移除任何內容,什麼都不改變;
- 整個檔案中的每個其他非空白行都被計為標題、Purpose、Requirements標題或規範需求的陳述、場景或代碼範例;
- 變更的
.openspec.yaml宣告retire_capabilities: true; spec.md解析到真實規格根目錄內(不要跟隨能力目錄的符號連結來刪除外部檔案)。 如果移除選定的需求後不會留下需求區塊,且任何退役條件不滿足,則不要修改主規格。停止該能力的同步,報告阻塞條件,並告訴用戶如何解決。永遠不要寫入或留下空的## Requirements區段。當只有標記缺失時,也要說明——這是用戶可以添加以使退役通過的唯一東西。
- 刪除檔案也會刪除其
## Purpose;任何其他區段都會阻塞退役。當你報告退役時,命名Purpose。僅當規格存在於呼叫者的檢出中時,才包含可貼上的git checkout;否則提供檢出範圍的恢復指導。
RENAMED Requirements:
- 找到FROM需求,重新命名為TO
增量中的
## Purpose:- 主規格已經有一個且它是權威的 - 保留它不變(這是
openspec archive的做法;它會警告並繼續)
d. 建立新主規格如果能力尚不存在:
- 僅當增量有要放入的ADDED需求,且步驟b中沒有MODIFIED或RENAMED需求阻塞此能力時。否則不建立任何內容,保持規格目錄不變。對於僅REMOVED的增量,如果變更的
.openspec.yaml宣告retire_capabilities: true,則報告它已退役並繼續而不重新建立規格。沒有該標記時,報告同步被阻塞:openspec archive會拒絕它,並顯示「規格必須至少有一個需求」。空的增量沒有可同步的操作;也報告它被阻塞。永遠不要寫入空的## Requirements區段。 - 建立
<planningHome.root>/openspec/specs/<capability-path>/spec.md - 添加Purpose區段:當增量有
## Purpose時逐字複製其主體(這是openspec archive的做法);如果沒有,僅寫入一個簡短的TBD佔位符 - 添加Requirements區段,包含ADDED需求
- 遵循下面的主規格格式參考
- 如果歸檔內聯調用了此工作流程,並提供了來自
-
驗證更新的主規格
使用與之前相同的選定根標誌執行
openspec validate --specs。 如果驗證失敗,報告問題,不要聲稱同步成功。 -
顯示摘要
套用所有變更後,總結:
- 哪些能力被更新
- 進行了哪些變更(需求添加/修改/移除/重新命名)
- 任何帶有TBD Purpose佔位符的新主規格,以便立即撰寫而不是擱置
- 任何退役的能力,命名已刪除的
spec.md、其Purpose,以及可貼上的git checkout或檢出範圍的恢復指導
增量規格格式參考
# Spec Delta
## Purpose
僅在引入全新能力的增量上。為新主規格播種。
## ADDED Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
## MODIFIED Requirements
### Requirement: Existing Feature
The system SHALL keep doing the existing thing, now also handling A.
#### Scenario: Scenario the main spec already has
- **WHEN** user does X
- **THEN** system does Y
#### Scenario: New scenario to add
- **WHEN** user does A
- **THEN** system does B
## REMOVED Requirements
### Requirement: Deprecated Feature
## RENAMED Requirements
- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`
主規格格式參考
主規格是增量合併到的目標。它們永遠不應包含增量操作標題(## ADDED/MODIFIED/REMOVED/RENAMED Requirements)——同步後,每個需求都位於單一的 ## Requirements 區段下:
# <capability> Specification
## Purpose
Short description of what this capability does and why it exists.
## Requirements
### Requirement: New Feature
The system SHALL do something new.
#### Scenario: Basic case
- **WHEN** user does X
- **THEN** system does Y
關鍵原則:智能合併
與程式化合併不同,您是合併而不是覆蓋:
- MODIFIED區塊攜帶整個需求——主體加上在變更中存活的每個場景。
openspec validate和openspec archive都會拒絕丟棄主規格仍有的場景的合併。 - 保留增量中未提及的任何內容,以主規格現有的順序爲準。