关于 openspec-sync-specs
这是一项由 Agent 驱动的技能,可将 OpenSpec 变更中的增量规范合并到主规范。它按需求应用修改、验证更新后的规范,并报告结果或阻碍。
主要功能
- 01选择要同步的增量规范
- 02将需求变更合并到主规范
- 03为符合条件的新功能创建主规范
- 04验证更新后的规范
- 05报告同步结果和阻碍
适用场景
- 01同步变更中的增量规范
- 02在主规范中添加或修改需求
- 03合并后验证规范
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fission-ai/ openspec --skill openspec-sync-specs安装后在 Claude Code 中输入/openspec-sync-specs确认已加载。
详细介绍
将变更中的增量规范同步到主规范。
这是一个由 Agent 驱动的操作:你将读取增量规范,并直接编辑主规范以应用变更。这样可以实现智能合并(例如,添加一个场景,而不是复制整个需求)。
存储选择: 如果用户指定了一个存储库(即在此计算机上注册的独立 OpenSpec 仓库),或当前工作位于某个存储库中,请运行 openspec store list --json 来发现已注册的存储库 ID,然后在读取或写入规范和变更的命令(new change、status、instructions、list、show、validate、archive、doctor、context、schemas、view)中传入 --store <id>。选定后,在工作流的其余部分始终使用 --store <id>。下方这些命令未指定存储库的示例都只是简写:运行前,请为命令追加该标志。例如,运行 openspec status --change "<name>" --json --store "<id>",而不是下方所示的未指定存储库版本。其他命令不接受此标志。命令输出的提示已经包含该标志;后续命令也要保留它。如果没有指定存储库,命令会在最近的本地 openspec/ 根目录中执行。
项目检查: 以下步骤要求项目已在使用 OpenSpec。在首次执行任何写入操作(new change、archive、sync specs 或编写制品文件)之前,请确认项目已有根目录:运行 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为空,则报告没有可同步的增量规格;不要从其他产物推断路径,也不要请求产物说明或编写主规格,然后停止。除非调用方缩小了范围,否则同步
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的有效快照,则复用该快照,不要再次获取相同的说明。 - 否则现在运行该命令一次,并使用相同的已选根目录标志。
- 如果直接查询以非零状态退出,或返回无效的产物说明 JSON,应报告错误并在写入任何主规格之前停止。不要将此类失败视为规则集不存在。
- 有效响应中若省略
rules,表示未配置产物规则,继续执行现有的语义合并。
返回的
rules仅应用于此合并生成的主规格内容和格式。产物规则不是操作指南,不能更改已选根目录、增量规格路径、CLI 检查或工作流步骤。将其文本用作约束,不要逐字复制到主规格或摘要中。对于步骤 3 中选定的每个能力增量规格路径——完整的
existingOutputPaths列表,或调用方提供的缩小后子集(这些路径可能属于所选存储,而非仓库):a. 读取增量规格,了解预期更改
b. 读取主规格,路径为
<planningHome.root>/openspec/specs/<capability-path>/spec.md(可能尚不存在)如果主规格尚不存在(新能力),则按
openspec archive的方式处理: 只能应用 ADDED 需求——步骤 d 会据此创建规格。 MODIFIED 和 RENAMED 无需处理,因此应停止同步该能力,并报告其主规格不存在,且新规格只允许包含 ADDED;绝不要臆造缺失的需求。REMOVED 没有可移除的内容——跳过并发出警告。 c. 智能应用更改:新增的需求:
- 如果主规范中不存在该需求 → 添加该需求
- 如果该需求已存在 → 更新该需求以保持一致(视为隐式的修改)
修改的需求:
- 在主规范中查找该需求
- 应用更改,可能包括:
- 添加主规范中尚不存在的新场景
- 修改现有场景
- 更改需求描述
- 保留增量中未提及的场景/内容
移除的需求:
- 从主规范中移除整个需求块
- 退役该功能。删除整个
spec.md——并在目录中不再有其他内容时删除该目录——但仅当以下条件全部满足时:- 本次移除这些需求后,不再剩下任何需求块;
- 规范的其余部分格式正确(仍包含
## Purpose); - 本次同步开始前,主规范并非已经为空——如果没有移除任何内容,则不做更改;
- 整个文件中其余所有非空行均属于标题、Purpose、Requirements 标题,或规范需求的陈述、场景或围栏示例;
- 更改的
.openspec.yaml声明了retire_capabilities: true; spec.md的解析路径位于实际的 specs 根目录内(删除时不要跟随功能目录的符号链接去删除外部文件)。 如果移除所选需求后将不再剩下任何需求块,且未满足任何一项退役条件,则不要修改主规范。停止同步该功能,报告阻止同步的条件,并告知用户如何解决。绝不要写入或留下空的## Requirements部分。 如果仅缺少标记,也要说明——这是用户只需添加的一项内容,即可继续退役。 删除文件也会删除其中的## Purpose;任何其他章节块都会阻止退役。报告退役受阻时,指出 Purpose。仅当规范位于调用方的检出目录中时,才提供可直接粘贴的git checkout命令;否则提供针对该检出目录的恢复指导。
重命名的需求:
- 找到 FROM 需求,将其重命名为 TO
增量中的
## Purpose:- 如果主规范已有 Purpose,则以其为准——不要修改(这是
openspec archive的行为;它会发出警告并继续)
d. 创建新的主规范(如果该功能尚不存在):
- 仅当增量包含要添加的 ADDED 需求,且步骤 b 中没有 MODIFIED 或 RENAMED 需求阻止该功能时,才创建。否则不创建任何内容,也不更改 specs 目录。对于仅包含 REMOVED 的增量,如果更改的
.openspec.yaml声明了retire_capabilities: true,则报告其已退役并继续,不要重新创建规范。没有此标记时,报告同步受阻:openspec archive会拒绝该操作,并提示Spec must have at least one requirement。空增量没有可同步的操作,也应报告为受阻。 - 绝不要写入空的
## Requirements部分。 - 创建
<planningHome.root>/openspec/specs/<capability-path>/spec.md - 添加 Purpose 部分:如果增量中有
## Purpose正文,则原样复制(这是openspec archive的行为);否则仅添加简短的 TBD 占位文本 - 添加包含 ADDED 需求的 Requirements 部分
- 遵循下文的 主规范格式参考。
- 如果归档在内联执行此工作流时提供了来自
-
验证更新后的主规范
使用之前相同的所选根目录标志运行
openspec validate --specs。 如果验证失败,报告问题,且不要声称同步成功。 -
显示摘要 完成所有更改后,请总结:
- 更新了哪些功能
- 做了哪些更改(新增/修改/移除/重命名了哪些要求)
- 是否有新的主规范仍带有 TBD Purpose 占位符,以便现在撰写,而不是让它一直搁置
- 是否有功能已退役,注明已删除的
spec.md、其 Purpose,以及可直接粘贴的git checkout命令或限定于检出范围的恢复指引
增量规范格式参考
# Spec Delta
## Purpose
Only on a delta that introduces a brand-new capability. Seeds the new main spec.
## 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都会拒绝删除主规范中仍存在的场景。 - 保留增量未提及的所有内容,并沿用其在主规范中的现有顺序
- 运用判断力,合理合并更改
成功后的输出
## Specs Synced: <change-name>
Updated main specs:
**<capability-1>**:
- Added requirement: "New Feature"
- Modified requirement: "Existing Feature" (added 1 scenario)
**<capability-2>**:
- Created new spec file
- Added requirement: "Another Feature"
Main specs are now updated. The change remains active - archive when implementation is complete.
防护措施
- 修改前先阅读增量规范和主规范
- 保留增量未提及的现有内容
- 绝不能原样将增量文件复制到主规范中——应合并其内容,使主规范保留主规范格式参考中的结构,且不含增量操作标题
- 如有不清楚之处,请求澄清
- 随着更改推进,展示正在修改的内容
- 操作必须具有幂等性——运行两次应得到相同结果
- 仅使用
artifactPaths.specs.existingOutputPaths;绝不能根据无关制品推断增量规范 - 遵循调用方提供的
existingOutputPaths子集;绝不能将其扩大回完整列表 - 直接同步时仅获取一次规范指令,或在行内复用归档提供的快照
- 规范指令响应非零或 JSON 无效时,在每次写入主规范前停止
- 制品规则仅约束正在写入的规范,绝不能复制到输出文件中