Halo 的 OpenSpec 变更修订工作流:openspec-update-change 如何保持规划工件一致性
2026/9/6 20:11:13 网站建设 项目流程

Halo 的 OpenSpec 变更修订工作流:openspec-update-change 如何保持规划工件一致性

【免费下载链接】haloHalo 是一款强大易用的开源建站工具,从个人博客、知识库,到企业官网、在线商城,Halo 都能助您轻松实现,一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo

本文以 Halo 仓库中的 openspec-update-change 技能定义 为主体,完整拆解该技能声明的六步修订流程、openspecCLI 的关键 JSON 字段(schemaNameartifactPathsexistingOutputPaths等)、逐项确认机制与护栏约束,并结合仓库中真实的 OpenSpec 规划目录(openspec/)与已归档变更示例,说明“修订计划而不触碰代码”这一工作流在 Halo 项目中如何落地。读完后你能掌握 OpenSpec 变更修订的完整操作步骤、各工件(proposal / design / specs / tasks)之间的协调关系,以及它与 propose、apply、archive 等相邻技能的衔接点。

技能定位:只改规划工件,绝不改代码

.codex/skills/目录下存放了 Halo 为 Codex Agent 准备的六个 OpenSpec 技能,每个技能是一个包含 YAML frontmatter 的SKILL.md文件:

  • openspec-explore:探索模式,只做思考与调研,明确禁止写代码;
  • openspec-propose:一步创建新变更并生成全部工件;
  • openspec-update-change:修订既有变更的规划工件(本文主题);
  • openspec-apply-change:按 tasks 逐项实现代码;
  • openspec-sync-specs:把变更下的 delta spec 同步回主 spec;
  • openspec-archive-change:归档已完成变更。

update-change 技能的核心一句话定位写在文档首段:

Revise a change's existing planning artifacts and keep them coherent. Never edit code. (修订一个变更的既有规划工件,并保持它们彼此一致。绝不编辑代码。)

其 frontmatter 元数据完整如下,体现了 Agent Skill 的声明式约定:

--- name: openspec-update-change description: Update an OpenSpec change by revising its existing planning artifacts and keeping them coherent with one another. Use when the user wants to revise a change's plan, fold new decisions into it, or reconcile its artifacts after an edit. Never edits code. allowed-tools: Bash(openspec:*) # 仅允许调用 openspec CLI license: MIT compatibility: Requires openspec CLI. metadata: author: openspec version: "1.0" generatedBy: "1.6.0" ---

两个字段值得注意:allowed-tools限定该技能只能执行openspec前缀的 Bash 命令(配合文件读写能力修订工件),description同时描述了“何时使用”(用户想修订计划、把新决策折叠进计划、或编辑后重新对齐工件),便于 Agent 路由时精准匹配。

Store 选择机制:多 OpenSpec 仓库场景下的作用域控制

技能正文在步骤之前给出了Store selection通用规则(六个技能中措辞一致):

  • “store”指一台机器上注册的独立 OpenSpec 仓库。若用户点名了某个 store,或工作发生在 store 中,先运行openspec store list --json发现已注册的 store id;
  • 之后在所有“读写 specs 与 changes”的命令上追加--store <id>,技能列出的适用命令为:new changestatusinstructionslistshowvalidatearchivedoctorcontext
  • 其他命令不接受该参数;命令打印的提示中若已带该 flag,后续跟进命令应保留它;
  • 不指定 store 时,命令作用于最近的本地openspec/根目录——这正是 Halo 仓库的场景:openspec/config.yaml 所在的openspec/目录即为规划根。

输入约定:变更名从哪来

技能对输入的处理规则是:

  1. 可显式指定变更名(change name);
  2. 未指定时,先尝试从对话上下文推断;
  3. 若含糊或存在歧义,必须提示用户从可用变更中选择(MUST prompt),不得猜测。

步骤一:变更选择(无变更名时)

执行openspec list --json获取按最近修改时间排序的可用变更列表,然后用 AskUserQuestion 工具让用户选择。展示规则有明确的 UI 约束:

  • 只呈现最近修改的3–4 个变更作为选项;
  • 每个选项展示四项信息:变更名、Schema(取schema字段,缺省则显示 "spec-driven")、状态(如"0/5 tasks""complete""no tasks")、最近修改时间(取lastModified字段);
  • 最近修改的那个变更标记为"(Recommended)"——因为它最可能是用户想更新的;
  • 严禁猜测或自动选择,始终由用户拍板。

这一约束与 openspec-archive-change 的选择逻辑呼应:归档技能同样要求“Do NOT guess or auto-select a change”。

步骤二:用openspec status解析变更状态

核心命令:

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

返回的 JSON 中,技能明确要求解析以下字段:

字段含义
schemaName正在使用的工作流 schema,例如"spec-driven"
artifacts工件数组,每个带状态:"done"/"ready"/"blocked"
isComplete布尔值,是否所有工件均已完成
planningHomechangeRootartifactPathsactionContext路径与作用域上下文

其中路径上下文的用法有两条硬性要求:

  1. 不要假设仓库内路径:工件 id 和路径来自当前激活的 schema,必须使用 status 返回的planningHome/changeRoot/artifactPaths/actionContext,而不是硬编码仓库局部路径;
  2. 不要对硬编码的工件名做分支判断:自定义 schema 必须“原样可用”(Custom schemas must work unchanged)。

最关键的一条规则是关于写盘目标的:

要编辑的文件是artifactPaths.<id>.existingOutputPaths——即磁盘上真实存在的具体文件,glob 类工件(如specs/**/*.md)已经完成 glob 展开。不要写resolvedOutputPath:对 glob 工件而言它仍然是 glob 模式本身,而不是真实文件。

Halo 仓库的实例正好印证这一点:归档变更 2026-05-19-issue-5634-category-post-navigation 的工件布局为proposal.mddesign.mdtasks.md三个常规文件,外加specs/category-post-navigation/spec.md(对应specs/**/*.md这类 glob 工件展开后的具体文件)。修订时应当逐个指向这些已存在的文件,而不是写回specs/**/*.md这个模式串。

步骤三:理解请求——“定向修订”还是“一致性审查”

技能把用户输入区分为两类,处理方式不同:

  • 定向修订:用户提出了具体改动(例如“设计现在改用 X 了”),这就是起始编辑点;
  • 一致性审查(coherence review):用户只说“更新一下”/“让它保持一致”,则读取全部既有工件,两两对照检查矛盾(contradictions)、缺口(gaps)与重复(duplication)

这个二分法意味着同一技能既能执行明确的小手术,也能做全文档体检,后者的检查方向是双向的(见步骤四)。

步骤四:阅读与对齐(Read and Reconcile)

这是整个流程的技术核心,包含五条细则:

  1. 读全:读被请求触及的工件,也读该变更的其他既有工件;
  2. 应用编辑后反向核查所有其他工件,方向任意:修改后置工件(如 tasks)可能需要回过头修订前置工件(如 design 或 proposal)——构建顺序只是“有用的阅读顺序”,不是“哪些工件可被修订”的约束;
  3. 记录一切:把现在不一致、缺失或矛盾的地方全部记下来;
  4. 只改已存在的文件existingOutputPaths):不创建尚不存在的工件,也不在 glob 工件下发明新文件;遇到这种情况,记录下来并指向/opsx:continue去创建;
  5. 如果变更本已一致,明说并零编辑——不做无意义的“润色式”写入。

步骤五:逐工件确认后再落盘

  • 每一处拟议修订都要先展示改什么、为什么改,用户确认后才写盘;
  • 用户拒绝某条修订,则该工件保持原样,不写入;
  • 当某工件需要大幅重写时,先取回该工件的规则与模板:
openspec instructions <artifact-id> --change "<name>" --json

对照 openspec-propose 中对openspec instructions返回值的说明,该 JSON 至少包含context(项目背景)、rules(工件专属规则)、template(输出文件结构)、instruction(schema 特定指导)、resolvedOutputPathdependencies,并且强调contextrules对 Agent 的约束,不得被复制进工件文件本身。Halo 的 openspec/config.yaml 正是这些规则的项目侧来源:

schema: spec-driven context: | Tech stack: Backend: Java 21, Gradle (Groovy DSL), Spring Boot 4.x, WebFlux/Reactor, R2DBC Frontend: Vue 3, TypeScript, Vite (vite-plus), pnpm workspaces, TailwindCSS ... rules: proposal: - Evaluate impact on existing plugin/theme APIs for compatibility - Database schema changes must include a migration strategy ... tasks: - Backend changes must pass `./gradlew spotlessCheck` - Frontend changes must pass `pnpm lint` and `pnpm typecheck` - API changes require updating OpenAPI docs and regenerating api-client ...

也就是说,一次“大幅重写 tasks 工件”时,openspec instructions tasks --change ...注入的 rules 会强制要求后端任务包含spotlessCheck通过项、API 变更需更新 OpenAPI 文档并重新生成 api-client——这解释了为什么 归档变更的 tasks.md 中能看到Run ./gradlew spotlessApplycheck OpenAPI spec generation这类验收项。

步骤六:指引下一步(只指引,绝不代执行)

修订完成后,技能要求按变更所处阶段给出且仅给出下一步建议,明确标注 “guidance only - NEVER act on it”:

变更状态建议命令对应技能
仍有缺失的工件/opsx:continue继续创建工件(update-change 明确不得越权代劳)
变更已实现(tasks 已勾掉/代码已应用)/opsx:applyopenspec-apply-change——因为代码可能已不匹配修订后的计划,apply 负责把 delta 带进代码
全部完成且已实现/opsx:archiveopenspec-archive-change——将changeRoot移入archive/YYYY-MM-DD-<name>/

“已实现再修订计划”是这条分支存在的意义所在:plan 与 code 之间的偏差要靠 apply 去追平,而不是在 update 阶段顺手改代码。

输出契约化的输出格式

技能规定每次调用结束后必须展示三件事:

  1. 哪些工件被修订了(以及哪些拟议修订被用户拒绝);
  2. 哪些内容被推迟给/opsx:continue(尚未创建的工件或文件);
  3. 变更当前处于什么位置、推荐的下一条命令是什么。

这使每次修订会话都有可审计的收尾,Agent 与人类都能据此判断后续动作。

护栏(Guardrails)逐条解读

原文档末尾的 Guardrails 是该技能的行为边界,逐条对应到具体工程考量:

  1. 只碰规划工件,绝不改实现代码;若修订后的计划隐含代码变更,停下并指向/opsx:apply——这划清了“计划面”与“实现面”的职责边界,与 explore 技能“never write code”、apply 技能“只做代码”形成三段式分工;
  2. 使用openspec status报告的工件 id 与路径,永不基于硬编码工件名分支——保证自定义 schema 可插拔;
  3. 只编辑existingOutputPaths里的具体文件,永不写 glob 的resolvedOutputPath——防止把specs/**/*.md当文件名写出损坏产物;
  4. 不推进构建前沿(build frontier):不新建工件、不在 glob 工件下新建文件——那是/opsx:continue的职责。这实际上把一个“计划演进”的过程拆成了两个幂等的角色:continue 只增,update 只改;
  5. 每次写入前与用户确认——规划工件是决策记录,误写成本高;
  6. “Update vs. Start Fresh”启发式:如果请求改变的是变更的**意图(intent)**而非细化(refining),建议用/opsx:new重新开一个变更,而不是把新意图硬塞进旧计划。这条规则防止语义漂移被伪装成普通编辑。

在 Halo 仓库中看真实工件长什么样

结合openspec/changes/archive/下的归档变更,可以看清 update-change 技能“修订对象”的实际形态。以 2026-05-19-issue-5634-category-post-navigation 为例,其工件集合完整展示了 spec-driven schema 的三类文件:

  • proposal.md:What & Why——为何文章页“上一篇/下一篇”需要按分类作用域导航(对应 issue halo-dev/halo#5634),以及变更点清单(新增PostFinder.cursorByCategory、扩展GET /posts/{name}/navigation?scope=category、无分类时返回空NavigationPostVo、既有cursor()行为不变);
  • design.md:How——包含 Goals/Non-Goals、四条编号决策(精确匹配主分类且不下钻子分类、新增方法而非修改cursor()、复用端点加查询参数而非新路径、不做控制台配置)以及风险/权衡表;
  • tasks.md:实施步骤——按 Finder 接口与实现、REST 端点、测试、验证四个小节编号,全部以- [x]勾选收尾,并包含./gradlew spotlessApply./gradlew test等可执行验收项;
  • specs/category-post-navigation/spec.md:delta spec——以## ADDED Requirements+### Requirement+#### Scenario(WHEN/THEN 式)描述新增行为契约,例如“文章无分类时cursorByCategory返回空NavigationPostVo”。

对照 update-change 的四步流程:若此时有人提出“现在导航要支持子分类下钻”这种修订,正确的动作是改 design.md 的 Decision 1(该决策明确记录了“用户选择了精确匹配”),再反向核查 delta spec 中 “Category scope uses exact match (no subcategory cascade)” 这条 Requirement 与 tasks 第 1.2 项是否需要同步调整——这正是步骤四“ANY direction”规则要覆盖的场景。而 delta spec 到主 spec 的合并(openspec/specs/ 等 18 个 capability 目录)则由 openspec-sync-specs 负责,归档时的mv操作由 archive 技能执行,update-change 一概不越权。

小结:一个“只改计划”的幂等修订闭环

openspec-update-change 的设计可以概括为四个工程决策:

  1. 状态驱动而非假设驱动:一切工件 id、路径、状态以openspec status --json的返回为准,天然兼容任意自定义 schema;
  2. 读写分离:只写existingOutputPaths中已存在的文件,glob 模式永远只读,创建新内容交给 continue;
  3. 人工确认门:逐工件展示 diff 意图、拒绝即回退,保证规划记录不被 Agent 悄悄改写;
  4. 意图变更熔断:触及变更意图时建议/opsx:new重来,避免旧计划与新目标混杂。

在 Halo 这样的多模块单体仓库(api、application、platform、ui)中,这种“计划先行、修订受控、实现与归档各司其职”的 OpenSpec 工作流,使得从 proposal 的 What/Why 到 tasks 的勾选收尾 的每个阶段都有可追溯的文件载体,而 update-change 技能正是保证这些载体在决策演进中始终自洽的那一环。

【免费下载链接】haloHalo 是一款强大易用的开源建站工具,从个人博客、知识库,到企业官网、在线商城,Halo 都能助您轻松实现,一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询