在实验性工作流中归档一个已完成的变更。
存储选择: 如果用户指定了一个存储(存储是一个在此机器上注册的独立 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获取可用变更并让用户选择一个
提示时,只显示活跃的变更(尚未归档的)。 如果可用,包含每个变更使用的模式。
始终宣布:“使用变更:”以及如何覆盖(例如,
/opsx:archive <other>)。在现有归档检查之前加载当前归档输入:
解析所选变更并规划根目录后,运行:
openspec instructions archive --change "<name>" --json在此命令上保持相同的选定根目录标志。此查找是建议性的且可选的:它只提供额外的提示输入,因此绝不能阻止归档。 如果它退出非零或返回无效的 JSON — 例如在尚不支持此命令的旧版 CLI 上 — 继续不带任何上下文和操作指导的归档工作流。不要报告错误也不要停止。
成功的响应可能省略两个可选字段。将
context视为必需的提示级输入:阅读并考虑它,并应用相关的项目事实、约定和约束。将operationGuidance视为可选的补充建议:阅读并考虑每个条目,并遵循适用且与内置归档工作流兼容的条目。将这两个字段与内置步骤、显式用户选择、解析的路径、CLI 检查和命令合约分开。如果 context 与其中一个控制输入冲突,报告冲突并保留控制值。如果 guidance 不适用或与某个控制输入冲突,不要遵循它并解释原因。不要从任一字段推断替换路径、跳过的提示或标志,也不要将其文本逐字复制到规格、变更工件或归档摘要中,除非用户单独要求。这些是提示级行为合约,而不是可强制执行的检查。
-
检查工件完成状态
运行
openspec status --change "<name>" --json检查工件完成情况。解析 JSON 以了解:
schemaName:使用的工作流planningHome、changeRoot、artifactPaths和actionContext:路径和作用域上下文artifacts:工件列表及其状态(done、skipped或其他)
如果有任何工件既不是
done也不是skipped(跳过的工件满足要求——该变更声明了 skip_specs):- 显示警告,列出未完成的工件
- 询问用户是否确认要继续
- 如果用户确认,则继续
-
检查任务完成状态
使用相同的选定根目录标志运行
openspec list --json,并在changes中找到name与所选变更完全匹配的条目。 要求只有一个匹配项并且totalTasks和completedTasks为非负整数,且completedTasks <= totalTasks。CLI 解析模式跟踪的任务文件,包括自定义工件名称、输出路径和 glob 模式。 未完成任务 =totalTasks - completedTasks。不要从工件状态或缺少顶级
tasks.md推断任务完成。如果查找失败、返回无效 JSON、省略或重复所选变更,或返回无效计数,报告问题并在同步或归档之前停止。 CLI 仅将x/X复选框标记视为完成;其他标记,包括不熟悉的标记,保持未完成。如果找到未完成任务:
- 显示警告,显示未完成的任务数量
- 询问用户是否确认要继续
- 如果用户确认,则继续
如果
totalTasks为零: 继续而不发出与任务相关的警告。 -
评估增量规格同步状态
使用来自 status JSON 的
artifactPaths.specs.existingOutputPaths作为唯一的增量规格来源。如果specs条目丢失或existingOutputPaths为空,继续而不提示同步,也不要从其他工件推断增量规格。如果增量规格存在:
- 将每个增量规格与其对应的主规格进行比较,路径为
<planningHome.root>/openspec/specs/<capability-path>/spec.md(使用步骤 2 中存储感知的planningHome.root,而不是硬编码的仓库路径) - 缺少主规格不自动意味着“已同步”。对于新能力,主规格是同步的输出,而不是输入:
- 如果增量具有已修改或重命名的需求,报告只有已添加的需求才能创建新的主规格,并将该能力标记为同步阻塞。永远不要发明一个没有当前版本的需求。
- 否则,如果增量只有已移除的需求且该变更的
.openspec.yaml声明了retire_capabilities: true,则该能力已退役:将其计为已同步,警告没有剩余内容要删除,并且不要重新创建主规格。现在和验证同步完成时均应用此规则。 - 否则,如果增量没有已添加的需求,报告无法进行同步并将该能力标记为同步阻塞。对于仅移除的情况,警告没有主规格可从中删除,并保持主规格树不变。
openspec archive拒绝未标记的仅移除情况,并显示“Spec must have at least one requirement”。 - 否则,将该能力计为需要同步并在摘要中命名(
<capability-path>: new main spec will be created)。如果增量也有已移除的需求,警告它们将被忽略,因为没有主规格可从中删除。同步仅从增量的已添加需求创建主规格,与openspec archive所做的一致。
- 确定将应用哪些更改(添加、修改、移除、重命名)
- 即使一个能力被同步阻塞,也继续评估其余能力。在提示之前显示组合摘要。
提示选项:
- 如果有任何能力被同步阻塞:解释原因,仅提供“不进行同步而归档”、“取消”
- 否则,如果需要更改:“立即同步(推荐)”、“不进行同步而归档”
- 否则,如果已同步:“立即归档”、“仍然同步”、“取消”
根据回答路由:
- “取消”——停止,不归档
- “不进行同步而归档”或“立即归档”——继续归档
- “立即同步”或“仍然同步”——同步,然后验证(见下文)。如果有能力被同步阻塞,不要启动任何同步;解释阻塞原因并重复可用选项。
- 其他任何回答——再次询问,而不是归档
在选定同步写入任何主规格之前,使用相同的选定根目录标志运行
openspec instructions specs --change "<name>" --json一次。要求零退出状态和有效的工件指令 JSON。如果查找失败或返回无效 JSON,报告错误并在写入任何主规格或移动变更之前停止。有效的响应中省略了rules表示无规则情况。仅将返回的rules应用于此合并产生的主规格的内容和形式;不要将其用作归档指导、变更 CLI 行为,或将规则文本复制到任何输出文件中。然后内联运行
openspec-sync-specs工作流(代理驱动的智能合并)用于变更 '',传递增量规格分析和上面获取的规格规则快照,并等待其完成。内联同步必须重用该快照,而不再获取specs指令。不要将其委托给后台任务——步骤 5 会将changeRoot移出仍在读取它的同步之外,导致变更被归档且主规格从未更新。如果你的代理只能通过委托运行它,则同步委托并等待结果。如果同步报告任何停止或阻塞条件,将同步视为失败。立即停止归档。不要执行同步后的内容比较,也不要移动其
changeRoot。没有任何内容已移动,因此用户可以修复阻塞条件或重新运行同步。在同步写入每个主规格后,根据规范同步合约验证其结构:
- 新的主规格以
# <capability> 规格标题开头。现有的主规格保持其标题完全不变。 - 对于已建立的主规格,完全保留现有的
## 用途部分,不做任何改动。 - 对于新的主规格,逐字复制增量中的
## 用途。仅当用途文本短于标准验证预期时发出警告。不要重新生成或重写现有的已编写用途。如果没有提供可用的## 用途,则使用现有的待定用途行为和警告。 - 验证主规格中没有保留增量风格的节标题(
## 已添加的需求、## 已修改的需求、## 已移除的需求、## 已重命名的需求),严格遵守同步工作流格式化规则。 - 同步写入或更改的需求块使用
### 需求:标题,其场景使用#### 场景:标题,位于规格的## 需求部分下。对于增量未提及的内容,保持原样不变。
然后,对整个步骤顶部的每个具有增量规格在
artifactPaths.specs.existingOutputPaths中的能力重新运行比较,包括显式退役的、缺少规格的情况——不仅仅是同步报告接触到的那些。成功的同步不会留下任何需要应用的内容,因此每个能力现在必须读作已同步:- 已添加的需求存在
- 已修改的需求携带增量中提到的场景和描述更改,其其他场景保持不变
- 已移除的需求已消失——并且当此次同步退役了一个能力(移除了其最后一个需求,使
## 需求为空)时,其主规格被删除,而不是留空。 - 已重命名的需求以新名称存在,并且旧名称下不存在
如果同步失败,或者任何能力不匹配,报告差异并停止——不要归档。没有任何内容已移动,且
changeRoot完好,因此用户可以修复不匹配并重新运行同步,然后再次开始归档。 - 将每个增量规格与其对应的主规格进行比较,路径为
-
执行归档
在
planningHome.changesDir下创建一个archive目录(如果不存在):mkdir -p "<planningHome.changesDir>/archive"生成目标名称:当变更名称已经以
YYYY-MM-DD-前缀开头时,按原样使用;否则在当前日期前添加YYYY-前缀。