如果面试中只说“我用 OpenSpec 生成了几个 Markdown 文件”,信息量其实很低。真正值得讲的是:为什么要把需求、行为契约、技术设计和执行任务拆开;这些文件怎样按依赖关系逐步收敛不确定性;实现完成后又怎样把增量规范合入主规格、保存审计轨迹,并通过 OncallAgent 自己的 wiki-sync 机制变成可浏览的工程知识。
📷 [图片 token=H12vbb6jYotQADxFtYCcYtzdnVh(未能下载,见飞书原文)]
OpenSpec 在 OncallAgent 中承担的是变更控制面。它不负责替代 Git,不负责运行产品,也不等于一份需求文档。它把一次功能、缺陷修复或工程改造组织成一个独立 change,让 Codex 在写第一行代码之前先知道“为什么做、做什么、系统应表现成什么样、准备怎样实现、按什么顺序交付”。
📷 [图片 token=YIMKb6seaoSNZYxXiGbc5EyXnQd(未能下载,见飞书原文)]
先给出一段面试可用的完整回答
可以这样概括:
我在 OncallAgent 中使用 OpenSpec 管理变更。每个 change 采用 spec-driven schema,先用 proposal 对齐问题、范围和影响,再用 delta spec 定义可验收的行为变化,用 design 记录技术决策与取舍,最后用 tasks 把实现和验证拆成可追踪步骤。Codex 按这些产物实施后,我会对照规格验证实现,把 delta spec 同步到主规格,再将完整 change 移入带日期的 archive。OncallAgent 还增加了仓库级 wiki-sync:它不复制 OpenSpec 内容,而是用符号链接和 VitePress 的 @include 生成聚合页面,同时校验主规格同步状态、include 目标、索引、Sidebar 和文档构建,从而把一次对话式开发变成可审查、可追溯、可交接的工程记录。
这段回答里最重要的不是术语数量,而是把四层关系说清楚:proposal 管意图,spec 管行为,design 管实现决策,tasks 管执行;archive 保存历史,main specs 保存当前事实,wiki-sync 负责展示和导航。
📷 [图片 token=WEmEbfJRcoYAk2xJSkncGZtrnSc(未能下载,见飞书原文)]
一个 change 通常会生成什么
OncallAgent 当前的 OpenSpec Skills 由 OpenSpec 1.5.0 生成,默认使用 spec-driven schema。一个完整活动变更通常位于:
openspec/changes/<change-name>/
├── .openspec.yaml
├── proposal.md
├── design.md
├── tasks.md
└── specs/
└── <capability>/
└── spec.md
<change-name> 是本次变化的名称,通常采用 kebab-case,例如 limit-qwen-embedding-batch-size。<capability> 不是变更名,而是被新增或修改的长期能力名称,例如 qwen-openai-provider。一次 change 可以涉及多个 capability,因此 specs/ 下可能有多个目录。
📷 [图片 token=AUyEb3612oUhJwxAVsHckbCtngb(未能下载,见飞书原文)]
.openspec.yaml 保存 change 的工作流元数据,例如采用哪个 schema、何时创建。它告诉工具“怎样组织产物”,不承载业务需求。
proposal.md 回答为什么做、准备改变什么、涉及哪些能力、影响哪些模块。它负责边界对齐,避免 Codex 在错误问题上高速执行。
specs/<capability>/spec.md 是 delta spec,只描述相对当前主规格发生的变化。它用 Requirement 和 Scenario 定义可观察行为,而不是写类名、函数名或库的调用方式。
design.md 回答怎样实现,记录上下文、目标与非目标、关键决策、风险和取舍。这里才适合解释为什么把约束放在 Provider 层、为什么不修改业务层、为什么选择某种测试边界。
tasks.md 把设计转换成带复选框的实施清单。任务需要覆盖代码、测试和验证,而且每一项都应能判断是否完成。
实现代码、测试、数据库迁移和共享契约并不生成在 change 目录里,它们仍然位于正常的工程目录。OpenSpec 产物负责说明和约束变化,代码负责兑现变化,两者通过验证阶段建立对应关系。
📷 [图片 token=WVuFbYUl4oyjXbxqMaXcn3uYn8f(未能下载,见飞书原文)]
产物为什么有先后依赖
默认 spec-driven schema 的依赖关系不是为了制造流程负担,而是为了避免下游文件建立在未经确认的假设上:
proposal
├──> delta specs
└──> design
delta specs + design
└──> tasks
└──> apply
└──> verify
└──> sync / archive
proposal 先确定问题和能力边界。delta specs 与 design 都依赖 proposal:前者把“做什么”写成行为契约,后者把“怎么做”写成技术决策。tasks 必须等二者都清楚后再生成,否则任务清单很容易只覆盖代码改动,却漏掉验收场景、权限边界、契约同步或失败处理。
📷 [图片 token=IEa9bNl3eoJ4Njxy5BGcscrAndg(未能下载,见飞书原文)]
OncallAgent 中的完整流程
需求模糊时先探索,不急着建 change
当目标只有一句话,或者需要在多个方案间取舍时,先让 Codex 阅读 README、主规格、相关实现和测试,进入探索阶段。这个阶段要解决的是“问题到底在哪里、哪些行为已经存在、改变会影响哪些边界”。探索可以输出结论,但不应把未经确认的猜测直接写成正式规格。
📷 [图片 token=Y2IKbVVdroIvxwxhwChcskHOnAb(未能下载,见飞书原文)]
创建产物有快速和渐进两种方式
需求已经清楚时,OncallAgent 最稳妥的快速路径是直接使用 openspec-propose,或在支持快捷命令的环境中使用 /opsx:propose <change-name>。该 Skill 会创建 change,并按依赖顺序补齐 proposal、delta specs、design 和 tasks,直到达到 apply-ready。
需要逐步审查时,可以使用 openspec-new-change 只创建脚手架,再反复使用 openspec-continue-change,每次只生成一个当前 ready 的产物。当前仓库的 openspec-ff-change 自己也会先执行 openspec new change,因此不要机械照抄参考站点的“new 后再 ff”;同名 change 已存在时,应继续它,而不是再次创建。
还有一个仓库细节:裸 new 只生成脚手架和元数据,而 wiki-sync 的 active 页面要求 proposal、design、tasks 及 delta specs 已经存在。实际应在 propose 完成后,或 new + continue 补齐全部产物后,再同步 active WIKI 页面。
📷 [图片 token=UDyBbLVYRowvSmxi8LzcAIzZnKe(未能下载,见飞书原文)]
实现前先审查四件事
进入 apply 前,至少检查:proposal 的范围是否聚焦;delta spec 是否写成可验证行为而不是实现细节;design 是否说明关键决策和非目标;tasks 是否同时覆盖实现、测试和验证。任何一项不清楚,都应该先改产物。OpenSpec 是流动式工作流,产物不是签字后永远不能改的瀑布文档。
📷 [图片 token=G1eEbaH8uo2K6jxNj4ic5y2Zn9v(未能下载,见飞书原文)]
apply 按任务实施,但不能只会勾框
openspec-apply-change 会读取状态返回的 contextFiles,再按 tasks 中未完成项逐一实现。OncallAgent 要求修改前先查相关主规格、实现和测试,API 或 SSE 变化先更新 packages/api-contracts,数据库变化增加 Alembic migration,用户数据操作显式带 owner/tenant scope。每完成一项都要先验证,再把 - [ ] 改成 - [x]。
📷 [图片 token=EJxcbRHWZoCm4kxfSarcVDlenxf(未能下载,见飞书原文)]
verify 证明产物、代码和测试互相一致
验证不能只看 tasks 是否全部打勾。它要回答三个问题:完整性——所有需求和任务是否有实现;正确性——实现是否符合 Scenario;一致性——代码结构是否兑现 design 中的决策。还应运行与影响范围匹配的 Ruff、Pyright、Pytest、前端类型检查与测试、共享契约检查,以及 openspec validate --all。没有运行的检查必须如实说明,不能用“应该能过”代替证据。
📷 [图片 token=IegXbVgS2oOOnZxRFntcbXeNn4e(未能下载,见飞书原文)]
sync 把变化合入当前事实
活动 change 下的 delta spec 只代表“准备发生什么”。完成后,openspec-sync-specs 会把 ADDED、MODIFIED、REMOVED、RENAMED 操作智能合并到 openspec/specs/<capability>/spec.md。主规格是当前系统行为的权威基准;delta spec 是一次变更的意图记录。两者不能混为一谈。
📷 [图片 token=JbAgbbqmqoLr9fxrQKWcqRVVnZe(未能下载,见飞书原文)]
archive 保存完整审计轨迹
归档前,Skill 会检查产物完成度、未完成任务和 delta spec 同步状态。确认后,把整个 change 目录移动到:
openspec/changes/archive/YYYY-MM-DD-<change-name>/
proposal、design、tasks、delta specs 和 .openspec.yaml 都会一起保留。归档目录用于追溯历史,不应在里面继续开发。如果需要调整已经生效的行为,应创建新的 change;如果需要撤销代码,应使用 Git 回滚或建立反向变更,而不是修改旧档案来改写历史。
📷 [图片 token=AhpabZJhSoSYTCxngaWcbUcnnpX(未能下载,见飞书原文)]
归档之后为什么还要 wiki-sync
OpenSpec 归档和 OncallAgent 的 wiki-sync 是两个独立动作。前者维护规格和变更历史;后者把这些仓库文件组织成 VitePress 可浏览页面。归档后执行:
python3 .codex/skills/wiki-sync/scripts/sync_wiki.py archive <change-name-or-archive-name>
npm run docs:build
wiki-sync 会扫描全部 active 和 archive,而不只是机械更新一页。它要求 docs/openspec 是指向仓库 openspec 的符号链接,验证每个归档至少有 delta spec,核对 ADDED/MODIFIED 已存在于主规格、REMOVED 已从主规格消失,然后生成或覆盖 docs/changes/ 下的聚合页、总索引和 VitePress Sidebar。
每个聚合页只保存 frontmatter、状态和 @include。proposal、design、tasks、delta specs 并不会复制一份到 docs;页面通过符号链接引用原始文件。这样 OpenSpec 始终是唯一事实来源,避免“规格改了,Wiki 忘记改”的双写漂移。
wiki-sync 的结构校验与 npm run docs:build 是两道不同门禁。前者检查 delta/main spec、include 真实路径、页面集合、索引和 Sidebar 顺序;后者检查 VitePress 是否能生产构建。构建成功不等于 include 一定有效,所以两种检查都不能省略。
📷 [图片 token=NJJbboqkSoodVHxciOUcPbA2nkf(未能下载,见飞书原文)]
小而完整的真实案例
主案例采用已归档 change:2026-07-11-limit-qwen-embedding-batch-size。问题是阿里云百炼 text-embedding-v4 的 OpenAI-compatible 接口单次最多接受 10 条文本,而较大文档可能产生 11 个以上 chunk,默认客户端会触发 HTTP 400。
proposal 把范围限定为 Provider 构造、Provider 测试和文档索引回归测试,并明确不修改 HTTP/SSE、Milvus schema 和前端。delta spec 定义三个场景:不超过 10 条时单批处理;超过 10 条时拆成兼容批次并保持顺序;大文档最终要写入全部 chunk 且任务为 succeeded。
📷 [图片 token=U9tfbEczboUtOUxoKRCcDFNin7e(未能下载,见飞书原文)]
design 的关键判断是把 chunk_size=10 放在 OpenAIEmbeddings Provider 层,而不是让文档索引业务代码理解厂商限制。这样索引服务仍然一次提交完整列表,LangChain 客户端透明拆成 10+1 请求,未来其他调用方也自动复用同一安全行为。
tasks 将工作拆成实现限制、验证客户端配置与拆批、验证 11 个 chunk 完整落库、运行质量门禁。当前实现中 QWEN_EMBEDDING_BATCH_SIZE = 10,Provider 测试验证 11 条输入被拆为前 10 条和后 1 条且向量顺序不变;索引测试验证业务层仍提交 11 个 chunk,并最终全部写入。两类测试验证不同层次,不是重复测试。
📷 [图片 token=SbEtbyKnWoVSTKxss77cOOudnZc(未能下载,见飞书原文)]
归档 delta 已合入 openspec/specs/qwen-openai-provider/spec.md,对应 VitePress 聚合页也已生成。
面试中容易说错的地方
**不要说 OpenSpec 会自动保证代码正确。**它提供结构和可追踪性,正确性仍依赖规格质量、工程判断、测试和真实验证。
**不要把 proposal、spec 和 design 写成三份重复需求。**proposal 管意图与范围,spec 管外部可观察行为,design 管内部实现决策。
**不要说归档就是删除。**归档保留全部产物,只是把活动工作区移动到带日期的历史目录,并把 delta 的最终状态沉淀进主规格。
**不要把 VitePress WIKI 和主规格当成两个事实源。**WIKI 页面通过 include 展示 OpenSpec,主规格仍在 openspec/specs/。
📷 [图片 token=XFxzbyYE2ogICWxfUDdcjpKlnRf(未能下载,见飞书原文)]
深入阅读
下面的子页面分别拆解每种文件的组织方式、为什么需要它、常见误区、真实案例和可直接使用的面试表达。