Community编程与开发github.com

robsonprod/portfolio

将 OpenSpec 变更中的增量规范同步到主规范。当用户想要用增量规范的更改更新主规范而不归档变更时使用。也适用于用户说“openspec sync”或“opsx sync”时。

portfolio 是什么?

此技能是一个由 AI 代理驱动的操作,旨在将 OpenSpec 变更中的增量规范智能合并到主规范中。它通过读取增量规范并直接编辑主规范来工作,从而实现智能合并,例如在不复制整个需求的情况下添加场景。该技能支持可选的存储选择(独立注册的存储库),并自动处理项目设置检查,确保用户在未设置项目时不会意外创建 OpenSpec 目录。它支持显式指定变更名称或从对话上下文中推断变更名称,并提供一个多步骤工作流程:选择变更、解析变更上下文、查找增量规范以及与需求添加、修改、删除和重命名相关的智能合并。该技能针对 AI 代理编码环境进行了优化,但也适用于其他平台。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/robsonprod/portfolio/tree/HEAD/.claude/skills/openspec-sync-specs

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

将变更中的增量规范同步到主规范。

这是一个 agent-driven 操作——您将读取增量规范并直接编辑主规范以应用更改。这允许智能合并(例如,在不复制整个需求的情况下添加场景)。

存储选择: 如果用户命名了一个存储(存储是在此机器上注册的独立 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 或编写工件文件)之前,确认项目有一个根目录:运行 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)。在解析每个增量规范的主规范时,保留其完整路径。

输入: 可选地指定一个变更名称。如果省略,检查是否可以从对话上下文中推断出来。如果模糊不清,则必须提示用户选择可用的变更。

步骤

  1. 选择变更

    如果提供了名称,则使用它。否则:

    • 如果用户在上下文中提到了一个变更,则从对话上下文中推断出来。
    • 如果只有一个活跃变更,则自动选择。
    • 如果模糊不清,运行 openspec list --json 来获取可用的变更,并要求用户选择一个。

    在提示时,显示那些有增量规范(位于 specs/ 目录下)的变更。

    始终声明:“正在使用变更:”以及如何覆盖(例如,/opsx:sync <other>)。

  2. 解析变更上下文

    运行:

    openspec status --change "<name>" --json
    

    JSON 包括 planningHome.root。主规范位于 <planningHome.root>/openspec/specs/ 下——对所有下面的主规范路径使用该(存储感知的)根路径,而不是硬编码的仓库路径。当选择了存储时,它指向存储,而不是当前仓库。

  3. 查找增量规范

    使用 artifactPaths.specs.existingOutputPaths 来自 status JSON 作为增量规范路径的唯一来源。如果 specs 条目缺失或 existingOutputPaths 为空,则报告没有要同步的增量规范,不要从其他工件推断它们,并在不请求工件指令或写入主规范的情况下停止。

    同步 existingOutputPaths 中的每条路径,除非调用者缩小了集合。调用者通过命名一个来自 existingOutputPaths 的完整条目的显式列表来缩小集合——逐字复制那些绝对值。Archive 会内联执行此操作,用户也可以(例如,通过选择以 /specs/billing/invoices/spec.md 结尾的条目)。然后仅同步命名路径,并保持剩余的增量规范不变:批量归档排除了其实现找不到的增量,而同步它们会写入调用者故意保留的主规范。将缩小后的选择带到步骤 4;绝不要再将其扩展到完整列表。如果命名路径不在 existingOutputPaths 中,不要同步它——报告它并停止,而不是静默地丢弃它。如果命名列表为空,报告没有要同步的内容并停止,而不写入主规范。

    每个增量规范文件包含如下部分:

    • ## ADDED Requirements - 要添加的新需求
    • ## MODIFIED Requirements - 对现有需求的更改
    • ## REMOVED Requirements - 要删除的需求
    • ## RENAMED Requirements - 要重命名的需求(FROM:/TO: 格式)

    如果没有找到增量规范,通知用户并停止。

  4. 对于每个增量规范,将更改应用到主规范

    在第一次写入主规范之前,获取一个当前的规范规则快照:

    • 如果 archive 内联调用此工作流,并提供了来自 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. 智能应用更改:

    ADDED Requirements:

    • 如果主规范中不存在该需求 → 添加它
    • 如果需求已存在 → 更新它以匹配(视为隐式 MODIFIED)

    MODIFIED Requirements:

    • 在主规范中找到该需求
    • 应用更改 - 这可以是:
      • 添加主规范尚未拥有的新场景
      • 修改现有场景
      • 更改需求描述
    • 保留增量中未提及的场景/内容

    REMOVED Requirements:

    • 从主规范中删除整个需求块
    • 停用该能力。仅当 所有 以下条件都满足时,才删除整个 spec.md——以及一旦其中没有其他内容时的目录:
      1. 本次运行删除需求后没有留下任何需求块;
      2. 规范的其余部分格式良好(它仍然有 ## Purpose);
      3. 在此同步之前,主规范并非已经是空的——如果您没有删除任何内容,则不做任何更改;
      4. 整个文件中的每一个其他非空行都被计为标题、Purpose、Requirements 标题或规范需求的语句、场景或围栏示例;
      5. 变更的 .openspec.yaml 声明了 retire_capabilities: true;
      6. spec.md 解析到真实的 specs 根目录(不要跟随能力目录符号链接来删除外部文件)。 如果删除选定的需求会导致没有需求块,并且任何退役条件都不满足,请不要修改主规范。停止该能力的同步,报告阻塞条件,并告诉用户如何解决。绝不要写入或留下一个空的 ## Requirements 部分。当只有标题缺失时,也要说明这一点——这是用户添加以使退役通过的一件事。
    • 删除文件也会删除其 ## Purpose;任何其他部分都会阻止退役。在报告退役时提及 Purpose。仅当规范存在于调用者的检出中时,才包含一个可粘贴的 git checkout;否则提供检出范围恢复指南。

    RENAMED Requirements:

    • 找到 FROM 需求,重命名为 TO

    增量中的 ## Purpose:

    • 主规范已经有一个,它是权威的——保持原样(这是 openspec archive 所做的;它会警告并继续)

    d. 创建新的主规范(如果能力尚不存在):

    • 仅当增量具有要放入其中的 ADDED 需求,并且没有 MODIFIED 或 RENAMED 需求在步骤 b 中阻塞此能力时。否则不创建任何内容,并使 specs 目录保持不变。对于仅包含 REMOVED 的增量,如果变更的 .openspec.yaml 声明了 retire_capabilities: true,则报告其已退役并继续,无需重新创建规范。没有该标记,则报告同步被阻塞:openspec archive 会拒绝它,并显示“规范必须至少包含一个需求”。空的增量没有要同步的操作;也将其报告为已阻塞。绝不要写入一个空的 ## Requirements 部分。
    • 创建 <planningHome.root>/openspec/specs/<capability-path>/spec.md
    • 添加 Purpose 部分:当增量有 ## Purpose 正文时,逐字复制(这是 openspec archive 所做的);仅当没有时才编写一个简短的 TBD 占位符
    • 添加包含 ADDED 需求的 Requirements 部分
    • 遵循下面的 主规范格式参考
  5. 验证更新的主规范

    使用与之前相同的选定根标志运行 openspec validate --specs。如果验证失败,报告问题,不要声称同步成功。

  6. 显示摘要

    应用所有更改后,汇总:

    • 更新了哪些能力
    • 进行了哪些更改(添加/修改/删除/重命名的需求)
    • 任何留下了 TBD Purpose 占位符的新主规范,以便现在写入而不是拖延
    • 任何已退役的能力,命名已删除的 spec.md、其 Purpose,以及可粘贴的 git checkout 或检出范围恢复指南

增量规范格式参考

# 规范增量

## Purpose
仅用于引入全新能力的增量。为新的主规范提供种子。

## ADDED Requirements

### Requirement: New Feature
系统应当做一些新的事情。

#### Scenario: Basic case
- **WHEN** 用户做了 X
- **THEN** 系统做了 Y

## MODIFIED Requirements

### Requirement: Existing Feature
系统应当继续做现有的事情,现在也处理 A。

#### Scenario: 主规范已有的场景
- **WHEN** 用户做了 X
- **THEN** 系统做了 Y

#### Scenario: 要添加的新场景
- **WHEN** 用户做了 A
- **THEN** 系统做了 B

## REMOVED Requirements

### Requirement: Deprecated Feature

## RENAMED Requirements

- FROM: `### Requirement: Old Name`
- TO: `### Requirement: New Name`

主规范格式参考

主规范是增量合并到的目标。它们绝不能包含增量操作标题(## ADDED/MODIFIED/REMOVED/RENAMED Requirements)——同步后,每个需求都位于一个单一的 ## Requirements 部分下:

# <能力> 规范

## Purpose
此能力的简短描述及其存在的原因。

## Requirements

### Requirement: New Feature
系统应当做一些新的事情。

#### Scenario: Basic case
- **WHEN** 用户做了 X
- **THEN** 系统做了 Y

关键原则:智能合并

与编程化的合并不同,您合并而不是覆盖:

  • 一个 MODIFIED 块包含整个需求——主体以及每个在更改中存活的场景。openspec validate 和 openspec archive 都会拒绝一个丢掉了主规范仍然拥有的场景的块。
  • 保持增量未提及的任何内容,按照主规范的现有顺序。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能