OpenSpec 与 Spec Kit:让需求变更留在 Git 能看见的地方
一次紧急修复把“导出最近记录”改成“导出所有匹配记录”。AI 在对话里准确复述了需求,也提交了能通过单元测试的代码;事故出在旧的分页、权限过滤和下载上限没有被写成可检查的行为。审查者只看到一个看起来合理的 diff,不知道哪些既有约束是不可回归的,测试也只覆盖了新分支。后来回滚时,团队甚至无法说明应该回滚到哪一个用户可见行为,而不只是回滚哪一个 commit。问题不是少了一份漂亮文档,而是意图、当前行为、技术取舍、任务状态和实现证据没有在同一套变更记录中相互引用。
OpenSpec 与 GitHub Spec Kit 都服务于 spec-driven development:在编码前明确行为和约束,让 AI 获得稳定上下文,而不是只根据一段对话猜测。但两者不是可互换的同名产品。OpenSpec 以 openspec/specs/ 中的当前系统行为和 openspec/changes/ 中逐项变更为中心,完成后把变更归入 archive;Spec Kit 由 specify CLI 初始化项目、安装某个 AI 编码工具的命令或 skill,并围绕 constitution、spec、plan、tasks、implement、converge 等产物组织功能交付。OpenSpec 允许按依赖与认知进展迭代产物,Spec Kit 的核心链路更强调阶段间输入输出;把前者写成固定瀑布流程,或把后者当成一次提示词生成器,都会失去工具本身的约束价值。OpenSpec 项目文档 与 Spec Kit 文档 给出了各自真实命令和生命周期。
先把事故翻译成可判断的行为
事故发生后,最容易出现的补救是写一句“恢复旧逻辑”。这不足以让下一位开发者判断什么是旧逻辑:导出的主体是谁、过滤顺序是什么、边界值如何处理、权限不足时是拒绝还是返回空集、请求中断后是否残留临时文件,都是行为的一部分。先把这些写成可观察结果,才能让规格成为代码、测试与评审的共同参照。用户故事描述价值,场景描述输入、动作与结果,不变量描述无论哪个入口都不能被打破的条件。
例如,“导出最近记录”可以被拆成:已授权用户只得到自己可见的数据;时间窗口按已定义的时区语义处理;结果总量受服务端限制;下载流中断不会遗留可访问的临时对象;没有权限的调用不以不同错误细节泄露资源是否存在。这里没有把框架、ORM 或缓存策略写进用户行为,因为它们属于后续技术计划;也没有把“模型应该聪明一点”当成需求,因为它无法被验证。
下面是一组用 Markdown 表达的行为场景。真实项目应替换实体和术语,但仍保留正例、边界例和拒绝例。
### Requirement: 受控导出
系统 MUST 仅导出调用者有权读取且落在请求时间窗口内的记录。
#### Scenario: 合法窗口内的导出
- **WHEN** 已授权用户请求一个有效时间窗口
- **THEN** 返回的记录均属于该用户可见集合
- **AND** 返回数量不超过服务端上限
#### Scenario: 不可见记录混入候选集合
- **WHEN** 查询结果包含无权读取的记录
- **THEN** 这些记录不会出现在导出文件或计数中把场景放入 Git 的目的不是让 Markdown 神奇地产生正确代码,而是让每个后续对象都有可追溯的锚点:计划解释如何实现不变量,任务指出在哪个文件和测试中兑现,PR diff 让评审者核对实现没有偷换场景含义。规格变化时要同时更新这些锚点,否则“文档已经改了”会变成另一种漂移。
OpenSpec 把当前行为与拟议变更拆开存放
OpenSpec 初始化后,openspec/specs/ 保存系统当前行为的 source of truth;openspec/changes/<change-name>/ 保存某一项尚在进行的变更。官方入门文档展示的变更目录包含 proposal.md、delta specs/、design.md 与 tasks.md。proposal 表达动机和改动,delta specs 使用 ADDED、MODIFIED、REMOVED 等要求描述行为差异,design 解释技术决策,tasks 将工作拆成可执行清单。变更完成后,delta specs 合并回主 specs,变更目录移动到 archive 留作审计历史。OpenSpec 的对象与流程 将“当前事实”和“拟议事实”分开,正好避免紧急修复把尚未确认的想法伪装成既有行为。
CLI 和 agent 集成会演进,安装时应在干净分支或隔离副本里查看生成目录与 Git diff。OpenSpec 的稳定 CLI 要求 Node.js 20.19.0 或更高版本;先检查运行时,再安装、验证 CLI 并初始化仓库:
node --version
npm install -g @fission-ai/openspec@latest
openspec --version
openspec init
openspec list
openspec view全局安装不是唯一策略。团队可采用项目本地依赖或包管理器的隔离执行方式,只要锁定来源、保留版本决策、并避免让不同成员用不兼容模板初始化同一仓库。安装命令会写入工作树或用户环境,先检查 Git 状态、Node 运行时和企业软件源策略;不要在含有未提交业务改动的目录里盲目执行初始化,以免生成文件与现有 agent 指令冲突。
一个 OpenSpec 变更从名称开始就应能表达可审查的意图,例如 limit-export-window,而不是 fix-stuff。OpenSpec 1.x 的核心 profile 使用 /opsx:propose、/opsx:explore、/opsx:apply、/opsx:sync、/opsx:archive;扩展 profile 才增加 /opsx:new、/opsx:ff、/opsx:continue、/opsx:verify 与批量归档等操作。旧的 /openspec:proposal、/openspec:apply、/openspec:archive 已不应作为新项目基线。迁移或切换 profile 时先运行 openspec init 或 openspec config profile,再用 openspec update 刷新 agent 指令;具体调用符号还会随 agent 集成变化,命令没有出现时应检查已生成的 skill 或 prompt,而不是把聊天命令当 shell 命令执行。
proposal、delta spec、design 和 tasks 各自回答不同问题
proposal 记录为什么要变、要改变什么以及影响面。它不应成为隐藏实现的长篇设计稿,也不应拿“优化体验”这种无法验证的语句代替可见行为。delta spec 写的是相对现有规范的变化:新增什么要求、修改哪条既有要求、移除什么能力;这里最需要小心 MODIFIED,通常应保留要求的完整语义而非只贴一行局部 diff,否则归并后会把未提及的旧场景误删。design 写接口、数据、错误传播、迁移、兼容与取舍;tasks 把需要完成的测试、实现、迁移和检查分解为可勾选的工作。
以导出事故为例,proposal 指出“错误扩大了导出集合且可能突破资源预算”;delta spec 约束授权过滤与数量上限;design 决定过滤在数据库还是领域层执行、如何向流式写入传播取消;tasks 先安排行为测试,再安排查询与流实现,最后安排日志字段与文档同步。若在实现中发现时间窗口必须按租户时区解释,应回写 delta spec 和 design,再调整 tasks;只在代码里加一个时区转换会让未来审查者无法判断这是业务规则还是临时补丁。
openspec/
specs/
export/
spec.md # 已接受的当前行为
changes/
limit-export-window/
proposal.md # 动机、影响与变更意图
design.md # 技术方案与取舍
tasks.md # 可完成的工作项
specs/
export/
spec.md # 当前行为的 delta正向实验是在新分支上创建一个小变更,完成 proposal 与 delta spec 后运行 openspec validate <change-name> --strict,并检查 openspec show <change-name> 让目录、要求和任务能够被独立阅读。预期不是“自动通过就能合并”,而是 validator 报告格式合法,人工仍能从每条 requirement 找到场景、从每条 task 找到预计修改位置。负向实验故意删除 MODIFIED 要求中的一个旧场景,或让 task 描述与 design 选择互相矛盾;结构错误应让 validator 非零退出,语义冲突则必须由 review 或 /opsx:verify 拦截,不能宣称格式验证能理解全部业务矛盾。
实现和测试完成后,/opsx:archive 会检查产物与任务状态,并在需要时提示先同步 delta spec;CLI 的等价入口是 openspec archive <change-name> --yes。归档会把接受后的行为并入主 specs,并把 change 移入历史目录,因此它是状态变更,不是压缩文件。任务未完成时工具可能只警告而不强制阻断,团队门禁必须在归档前要求任务、测试和 /opsx:verify 证据齐全。
Spec Kit 用 constitution 固定项目决策纪律
Spec Kit 的 specify CLI 初始化项目时,会为选定的 agent 安装相应命令、上下文规则和目录结构。其核心交付链是 specify、plan、tasks、implement、converge,constitution 则保存不应在每个功能里重新谈判的质量、测试、兼容、可观测性和用户体验原则;clarify、checklist 与 analyze 在高风险功能中承担歧义和一致性门禁。spec 描述要实现的内容和用户故事,plan 决定技术实施,tasks 把计划排成可执行步骤,implement 按任务推进,converge 再对照代码与产物寻找遗漏。官方 Spec Kit 文档 也说明,不同 AI 编码工具会生成不同形式的集成,某些环境使用 slash command,某些环境使用 skill 名称。
Spec Kit 可以通过 PyPI 发布的 specify-cli 安装,也可以从 GitHub source release 安装;团队应选定一种来源并记录升级策略。下面采用官方推荐的 uv tool 隔离安装方式,不污染系统 Python 环境。需要可复现安装时,在依赖基线中固定经过评估的 PyPI 版本;若采用 GitHub source,则固定正式 release tag,而不是跟随分支。
uv --version
uv tool install specify-cli
specify version
specify init --here --integration codex
specify integration list初始化会改动工作树,因此先建立分支并查看 diff。若目录不是空的,只有在理解生成文件与现有规则文件如何合并后才使用 --force:它可以覆盖 .specify/scripts/、.specify/templates/ 和共享 memory,升级前要单独备份并 diff constitution;specs/ 中的功能产物不会被模板包覆盖。Codex 集成以 skill 形式安装到 .agents/skills,调用形式是 $speckit-<command>;其他集成可能使用 slash command,不能混抄。specify self check 只检查更新,specify self upgrade --dry-run 预览升级动作,CLI 升级后仍需按升级指南刷新项目文件。Spec Kit 可检测可用 agent,也允许用 --ignore-agent-tools 在不检测本地 agent 的情形生成模板;这解决的是初始化集成,不是给 agent 授予代码执行、网络或凭据权限。账号、模型许可与 API 计费仍由所选 agent、IDE、平台订阅和组织政策分别决定。
constitution 的价值在于把“这次功能必须有测试、不得泄露数据、要支持回滚”从聊天中的一段临时提醒,变成所有 spec、plan 和任务都要遵守的项目原则。它也可能变成噪声:把每个现有类名、一次性实施细节或个人偏好塞进 constitution,会让每个未来功能都背负无关约束。较好的写法描述可观察的治理规则,例如“外部可见行为变更需要覆盖正常、拒绝和恢复场景”“数据库迁移必须有向后兼容说明”“不可逆操作需要人工确认”,并让这些规则在 plan 与 review 中有实际检查点。
spec、plan、tasks、implement 与 converge 必须闭环
Spec Kit 的 /speckit.specify 适合描述用户问题、用户故事、功能要求与成功标准;它不该用来拍板框架、缓存实现或表字段。/speckit.plan 再接收技术栈与架构约束,产出可供实现使用的技术计划;/speckit.tasks 从计划拆出按依赖排序的任务,通常会给出文件路径、并行标记和测试任务;/speckit.implement 按这些任务推进;/speckit.converge 对照代码与产物检查遗漏,并把缺口追加回 tasks,直到重新 implement 后不再产生新任务。生产功能还应在 implement 前使用 clarify、checklist 和只读的 analyze 暴露歧义与跨产物冲突。命令外观由集成决定:Codex 使用 $speckit-* skill,其他环境可能显示 /speckit.*。
下面的交互仅展示每个阶段要承担的语义。执行前应根据当前 agent 的命令形式确认到底使用 /speckit.* 还是相应 skill;不要在一个没有这些命令的客户端里把它们当 shell 命令运行。
/speckit.constitution
要求:外部导出必须验证授权、总量限制和取消清理;每次行为变更都要有拒绝场景测试。
/speckit.specify
为受控导出定义用户故事、窗口边界、权限语义和可观察的成功标准。
/speckit.plan
采用既有授权服务和流式导出组件;说明过滤顺序、取消传播、审计字段和回滚方式。
/speckit.tasks
/speckit.implement
/speckit.converge正向实验先跑到 tasks:检查生成任务是否包含授权拒绝、上限、取消和审计的测试工作,且任务顺序使测试能先暴露现有缺陷。实现后运行 converge,预期首次发现的遗漏会追加为未完成任务;补齐后再次运行才应报告收敛。负向实验让 spec 只写“支持导出”,plan 却选择了高权限批量查询,tasks 也没有测试;此时 analyze 应报告跨产物缺口,converge 也不能被一次“命令执行成功”替代。证据是 .specify/memory/constitution.md、active feature 目录中的 spec、plan、tasks、代码 diff 与测试报告引用同一组不变量。
Spec Kit 选择 active feature 时不再只依赖当前 Git 分支,而是读取 .specify/feature.json,也可以用 SPECIFY_FEATURE_DIRECTORY 覆盖。仅执行 git switch 不保证 plan、tasks 和 implement 指向新分支对应的功能目录。并行开发或 monorepo 中,每次执行生成命令前都应打印并核对 active feature;若环境变量指向另一目录,负向实验的预期是门禁立即失败,而不是让 agent 在错误功能目录继续写文件。
Spec Kit 的文件名与目录会随版本、模板和项目类型不同而变化。一个常见结构包含 .specify/memory/constitution.md、模板和脚本,以及 specs/<feature>/ 下的规格、计划、任务、数据模型或契约。不要为了让目录看起来像 OpenSpec 而强行移动它们:Spec Kit 的产物是在一个功能规格目录中逐层喂给 agent 的上下文,OpenSpec 的主 specs 与 change delta 则有不同的合并与归档含义。两者可以共存,但要明确谁保存当前行为的权威记录,谁保存某项功能的工作包,避免两处相同的需求悄悄分叉。
两套工具可以互补,不能强行对号入座
OpenSpec 的 openspec/specs/ 强调已接受的系统行为,changes/ 表达待完成变更并通过 archive 保留历史;Spec Kit 的 constitution 则强调跨功能原则,spec/plan/tasks/implement 是 agent 导向的功能工作流。把 OpenSpec proposal 等同于 Spec Kit spec 会丢掉二者中的一部分语义:前者天然是相对于当前 specs 的变更意图,后者可以从一个新功能的用户需求开始。把 OpenSpec archive 等同于 Spec Kit 的某个目录也不准确,Spec Kit 没有要求所有项目采用同一种 archive 行为。
在已有 OpenSpec 的仓库里引入 Spec Kit,可把 constitution 作为团队级工程原则,继续用 OpenSpec 维护当前行为和 change archive;功能实施时,让 Spec Kit plan/tasks 指向具体 OpenSpec change 的 requirement 标识。反过来,已有 Spec Kit 的仓库想使用 OpenSpec 管理长期行为时,先挑一个边界清晰的领域将稳定行为写入主 specs,再把后续改动作为 delta 进入 changes。无论采用哪种组合,都不能让 AI 同时改两份独立的“真相文件”而没有一个主导者。
Git 分支:feature/limit-export-window
OpenSpec:openspec/changes/limit-export-window/specs/export/spec.md
Spec Kit:specs/001-limit-export-window/spec.md
关联规则:两者都引用同一 requirement ID;
主行为记录由 OpenSpec specs/export/spec.md 承担;
Spec Kit 的计划和任务只引用该 ID 与实现文件,不复制整段 requirement。这个组合的正例是 PR 中能从一个 requirement ID 追到 delta、计划、任务、测试和实现 diff,归档后主 specs 仍表示当前行为。反例是两个文件分别把时间窗口写成不同单位,或一处取消上限而另一处保留上限。预期处理是暂停实现、确定权威来源并修正漂移,不是挑一个看起来更顺眼的文本继续编码。规格驱动降低误解,但不能消除真实的产品决策冲突;冲突必须由负责业务的人明确裁决。
Git 协作要把文档 diff 当成产品 diff
规格、计划和任务与代码一起提交,才能让 code review 看见“为什么改”和“改完应该怎样”。一个有意义的 PR 通常包含需求产物的 diff、代码 diff、测试变更、迁移或操作说明,以及已知限制。评审顺序也应从行为开始:先核对 requirement 与场景是否包含故障中的约束,再核对 design 是否解释关键取舍,然后看 tasks 是否覆盖测试和回滚,最后检查代码、依赖和运行证据。倒过来只看实现细节,最容易被一个整洁的重构掩盖行为回归。
下面的 Git 操作刻意把变更产物与实现放在同一分支,并在提交前检查空白与未追踪文件。实际仓库应使用团队的分支保护、签名、CI 和合并策略;不要把示例分支名、用户名或远程地址当成组织规范。
git switch -c feature/limit-export-window
git status --short
git diff --check
git add openspec/ specs/ src/ test/
git diff --cached --stat
git commit -m "限制受控导出窗口并补充规格"多人同时改同一个 requirement 时,Markdown 冲突比代码冲突更容易被草率解决。不要只选“ours”或“theirs”:逐条确认场景、MUST/SHOULD 强度、标题与 requirement ID 是否仍一致,再运行相应 validator 和测试。若团队把任务转成 issue,也要让 issue 链接回提交、规格和 PR;issue 关闭不自动证明运行结果符合行为,只有让测试、审查与部署证据相互关联才形成闭环。
AI 可以协助生成初稿、梳理不变量、拆任务和修改代码,却不能替代合并责任。它会受上下文截断、模板版本和提示差异影响,也可能把缺少信息的地方填成自信的猜测。每个 PR 要明确哪些命令已经实际执行、对应退出码与报告在哪里、哪些因为账号、许可证、网络或环境限制没有执行。未执行项目应保持未验证状态,不能写成“应该没有问题”。
账号、许可、网络与敏感数据的接入方式
OpenSpec CLI、Spec Kit CLI、agent 集成与模型提供方是不同的供应链和权限边界。能够安装 CLI 不等于能使用某个商业 agent;拥有 IDE 登录态不等于允许把私有仓库内容发送给模型;可访问公共 GitHub 不等于可以把内部 issue、日志或客户数据嵌入规格。接入前要分别确认组织许可、账户归属、网络出口、代码数据分类、agent 的上下文收集规则、扩展的发布来源和可撤销的身份机制。
规格文件本身也可能敏感。proposal 可能泄露未发布功能,design 可能暴露内部拓扑,任务路径可能暴露安全修复位置,测试夹具可能含真实账号或生产样本。用匿名实体、合成标识、脱敏日志和最小必要的接口名写示例;将真正需要受限访问的细节放在组织批准的私有系统,并只在公开规格中保留可理解但不泄密的行为描述。不要把 token、连接串、私有主机名、客户 ID、完整堆栈或工单导出塞进 agent prompt 来换取一次更快的回答。
当使用云端 agent 时,团队还需要确认供应商提供的保留、训练使用、审计和数据驻留选项;当使用本地模型时,也仍要治理模型文件来源、插件、IDE 索引、磁盘缓存和本地日志。Spec-driven 文件让上下文更结构化,但不会自动减少发送内容;反而可能让 agent 每次都读取更多项目文件。给 agent 配置最小工作区、忽略敏感目录、使用可审查的项目规则,并在离开任务后清理临时导出。
正反实验把“看起来完整”变成可复查结果
先为一个小功能建立可重复的实验矩阵。正例检查:规格有正常与拒绝场景;计划说明现有组件与迁移限制;任务列出测试先行;实现 diff 只触及声明的文件;测试覆盖行为;Git 状态干净或能解释每项变更。反例检查:故意移除一个拒绝场景、让任务跳过测试、让代码变更超出计划、让配置带入模拟 secret、或让 archive 在主 specs 未同步时执行。每个反例都有明确的失败信号,团队才不会只依赖“AI 说做完了”。
# OpenSpec 变更的格式与状态检查
openspec validate limit-export-window --strict
openspec show limit-export-window
# Spec Kit CLI 与集成状态;功能工作流仍由当前 agent 中的 skill/command 执行
specify version --features
specify integration status --json
# 项目自己的质量门禁,按真实技术栈替换
npm test
npm run lint
git diff --check预期成功证据应是具体命令的退出码、测试报告、validator 输出、已审查的 diff 和任务勾选状态。预期失败证据则包括 validator 找到缺失结构、测试断言发现未过滤数据、git diff --check 发现格式问题、secret scanner 标记出模拟之外的凭据模式。失败时先回到最早发生漂移的产物:行为误解改 spec,技术不可行改 plan 或 design,遗漏执行工作改 tasks,代码问题改实现。不要把所有失败都直接改在代码里,因为那会让生成链再次断开。
项目接入时必须将实际执行与待执行项目分开记录:前者保存命令、环境摘要、退出码和报告,后者保存阻塞原因、潜在影响和补做责任。只有真实账号、目标 agent、目标仓库和目标数据边界内产生的结果,才能作为该环境的运行证据;示例命令和预期输出不能替代这条证据链。
失败后的回滚不是删掉几个 Markdown 文件
若实现已合并但行为回归,先用 Git 与部署机制恢复已知安全版本,同时保留事故相关的 spec、plan、tasks 和测试输出。它们解释了错误如何进入系统,是修正规格和测试的重要证据。若只是功能尚未合并,回滚可以是关闭或重置该分支;若变更已经 archive,先确认主 specs 是否已同步、其他 change 是否依赖它,再决定新增一个反向 change 还是还原某个 commit。直接删除 archive 目录会损失审计历史,也可能让人误以为这个行为从未被接受过。
git log --oneline --decorate -n 12
git revert <reviewed-merge-commit>
git status --short
git diff --check
git push origin HEAD占位符 <reviewed-merge-commit> 必须替换为已经人工确认的目标提交。回滚前确认是否含数据库迁移、异步任务、缓存格式或外部契约;代码 revert 不一定能自动撤销这些状态。回滚后重新运行针对旧行为和故障场景的测试,并创建新的规格变更解释恢复后的行为。对于 OpenSpec,archive 之前确认主 specs 已反映接受的行为;对于 Spec Kit,保留该功能目录还是移入项目的历史区由团队仓库规则决定,不能假定存在统一的 archive 命令。
清理也有边界。卸载 CLI 或删除 agent 集成前,先撤销相关账号会话、短期 token、插件权限和 CI secret 引用;清理本地缓存、临时工作区和生成配置;复查 Git ignore 是否意外隐藏了规格或凭据;再确认其他开发机和自动化环境不会继续调用旧命令。没有所有者的模板、constitution 或 archive 会很快变成陈旧提示,最后被 AI 当成错误的高优先级指令。
长期治理把规格当作会变化的工程资产
长期运行后,最昂贵的不是 CLI 安装时间,而是规格漂移、重复工作流、无效上下文和无人维护的模板。为每个规格目录指定业务与技术负责人,定义评审频率、过期信号、命名方式、是否允许自动生成任务、何时必须补充性能和安全场景。跟踪的是趋势而非一组虚构绝对数字:活跃 change 数是否持续堆积、从 proposal 到合并的等待是否变长、规格与测试互相找不到链接的比例是否上升、返工是否集中在某种漏写的约束、agent 读取的上下文是否不断膨胀。
成本也要按层看。OpenSpec 的成本包括维护主 specs、审查 delta、archive 查询和 agent 集成;Spec Kit 的成本包括 CLI 与模板升级、agent 调用、生成产物审查、constitution 维护和可能的第三方模型费用;Git 协作的成本包括 CI、代码审查、冲突解决和回滚演练。不要把任意一种工具的价格、额度或模型能力写成永久事实。发生采购或配额决策时,回到供应商正式条款、组织合同和当前用量数据。
最健康的状态不是每个小改动都产生厚厚的文档,而是风险、影响面和不确定性决定产物深度:文案微调可能只需要已有行为的测试与简短说明;权限、数据迁移、外部契约和 AI 自动化改动则需要完整规格、设计、任务、反例与回滚动作。OpenSpec 适合让当前行为与变更历史持续可读,Spec Kit 适合把项目原则和 agent 任务流固定下来。它们都能让 AI 编码更少依赖瞬时对话,但真正守住系统的,是每一条变更仍能被 Git、测试、评审和回滚重新解释。
