OpenSpec Agent Contract
openspec CLI 的机器可读接口,已针对 src/ 完成验证(完备审计,2026-06-11)。以下每个结构均基于源代码记录。
1. 通用约定
- 每次调用输出一个 JSON 文档。 在
--json模式下,stdout 精确输出一个 JSON 文档(2 空格美化打印)。人类可读的提示、加载动画和存储区横幅会输出到 stderr。 - 存储区横幅。 在人类模式下,选定的根存储会向 stderr 打印
Using OpenSpec root: <id> (<path>)。JSON 模式下绝不会打印。 - 键名大小写取决于界面(见已知的不一致之处):store/doctor/context 载荷使用
snake_case;工作流载荷(status、instructions、new change、validate、list)使用camelCase,但嵌入的root对象除外,该对象始终使用store_id。 - 可选键省略而非设为 null,大部分载荷如此(例如
root.store_id、member.path)。明确使用显式null的情况在具体结构中会单独说明(store doctorgit.*、失败载荷)。
2. 诊断封装
所有机器可读诊断(StoreDiagnostic)共享一种封装形状:
{
"severity": "error" | "warning" | "info",
"code": "snake_case_string",
"message": "human sentence",
"target": "dotted.surface (optional)",
"fix": "one actionable sentence/command (optional)"
}诊断出现在两个位置:状态数组(顶层或每个条目的 status: StoreDiagnostic[])用于健康检查结果,以及命令失败时转换为单元素 status 数组的抛错。
3. 根选择与 RootOutput
所有根解析命令(list、show、validate、status、instructions、instructions apply、instructions archive、new change、archive、doctor、context、schemas)按以下优先级解析一个 OpenSpec 根:
--store <id>→ 已注册存储的根(source: "store")。- 否则,包含
openspec/的最近祖先:若为规划形态 →source: "nearest"(store:指针被忽略,并发出 stderr 警告);若为仅配置目录且带有有效store:指针 → 该存储,source: "declared"。 - 无最近根且设置了全局
defaultStore(openspec config set defaultStore <id>)→ 该存储,source: "global_default";过时的 id 会因底层存储错误而失败,并附带fix,提示openspec config unset defaultStore。 - 无最近根,无默认值,但有已注册存储 → 错误
no_root_with_registered_stores。 - 无根,无默认值,无存储:命令可将当前工作目录视为
source: "implicit";doctor、context、list和批量validate会因此失败并报no_openspec_root。list为遗留项目保留隐式回退,支持openspec/project.md。
成功的 JSON 负载通常内嵌根信息;但成功的 schemas --json 有意保持为 §4.13 记载的兼容性裸数组:
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (only when store-selected)" }根解析失败约定:在 JSON 模式下,解析失败会在 stdout 打印 { ...commandNullShape, "status": [diagnostic] } 并以 1 退出。
4. 命令 JSON 形状
4.1 list --json
{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — 注意此处每个变更的 status 是字符串枚举。--specs:{ "specs": [ { "id", "requirementCount" } ], "root" }。
4.2 show <item> --json
变更:{ "id", "title", "deltaCount", "deltas": [...], "root" }。规范:{ "id", "title", "overview", "requirementCount", "requirements": [...], "metadata": { "version", "format", "sourcePath"? }, "root" }。
4.3 validate --json
{ "items": [ { "id", "type": "change"|"spec", "valid", "issues": [ { "level", "path", "message", "line"?, "column"? } ], "durationMs" } ], "summary": { "totals": {items,passed,failed}, "byType": {...} }, "version": "1.0", "root" }。任何项目验证失败时退出 1。
4.4 status --json
{ "changeName", "schemaName", "planningHome"?: { "kind", "root", "changesDir", "defaultSchema" }, "changeRoot", "artifactPaths": { "<id>": {outputPath, resolvedOutputPath, existingOutputPaths} }, "nextSteps": ["..."], "actionContext": { "mode": "repo-local", "sourceOfTruth": "repo", "planningArtifacts", "linkedContext", "allowedEditRoots", "requiresAffectedAreaSelection", "constraints" }, "isPlanningComplete", "isComplete", "applyRequires", "artifacts": [ {id, outputPath, status: "done"|"skipped"|"ready"|"blocked", requires, missingDeps?} ], "root" }。isPlanningComplete 表示每个非跳过的规划产物均存在;跳过的产物视为已满足而不创建。这并不表示实现任务已完成。isComplete 为保留兼容性别名,值相同。每个产物的 requires 是其直接依赖的 id(对所有状态都存在,因此即使产物状态为 done 时也可计算传递所需集合);missingDeps 仅在 blocked 时出现。artifacts 数组依依赖顺序排列,当多个产物同时变为就绪时,按模式 artifacts: 声明顺序打破平局(从不按字母顺序),因此第一个 ready 条目即为下一个应编写的产物;missingDeps 使用相同顺序。"skipped" 标记在变更中 generates 路径位于 specs/ 下且其 .openspec.yaml 声明 skip_specs: true 的产物;它满足依赖关系但不得创建。无活跃变更时:{ "changes": [], "message", "root" },退出 0。
--all(批量,与 --change 互斥——二者结合会出错,返回 { "changes": [], "root": null, "status": [d] } 空形状):{ "changes": [ <per-change status object, no per-change root>, ... ], "root" },按变更名称排序。加载失败的变更会以 { "changeName", "status": [d] } 占位;遍历继续,保留完整封装,并在文本和 JSON 模式下均退出 1。无效的 --schema 会以空形状导致整个调用失败,即使没有变更。
4.5 instructions <artifact> --json
{ "changeName", "artifactId", "schemaName", "changeDir", "planningHome"?, "outputPath", "resolvedOutputPath", "existingOutputPaths", "description", "instruction"?, "context"?, "rules"?, "references"?: ReferenceIndexEntry[], "skipped"?, "warning"?, "template", "dependencies": [{id,done,path,description,skipped?}], "unlocks", "root" }。unlocks 列出该产物启用后就绪的产物,按模式声明顺序(即 status 建议的顺序)。当变更声明 skip_specs: true 且此产物被跳过时,会出现 "skipped": true(伴随 "warning")——请勿创建其文件。依赖条目中 skipped: true 表示无需文件即已满足——不要尝试读取其路径。
ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — 已解析的条目携带 root/specs/fetch;未解析的条目携带 store_id 和警告状态。索引上限为 50KB(reference_index_truncated)。
4.6 instructions apply --json
{ "changeName", "changeDir", "schemaName", "contextFiles": { "<artifactId>": ["/abs", ...] }, "progress": {total,complete,remaining}, "tasks": [{id,description,done}], "state": "blocked"|"all_done"|"ready", "missingArtifacts"?, "missingPrerequisites"?, "warnings"?, "instruction", "references"?, "context"?, "operationGuidance"?, "root" }。missingArtifacts 是 apply 所阻塞的内容(模式的 apply.requires);missingPrerequisites 是在 apply 运行前仍需构建的所有内容,按构建顺序——即这些所需内容的传递闭包,因此可能是较长的列表。warnings 列出变更本身的非阻塞问题——例如,当前变更已准备好实现但没有增量规范且未声明 skip_specs: true,而 openspec validate 会拒绝该状态。两个可选的根字段(context、operationGuidance)每次调用时从当前选定的根中读取。context 是必需的提示级输入,必须应用其中的相关项目事实、约定和约束;operationGuidance 是建议性输入,其条目仅在适用且与内置工作流兼容时才遵循。二者均独立于状态、任务、进度、上下文文件和内置指令。
4.7 instructions archive --json
{ "changeName", "context"?, "operationGuidance"?, "root" }。在已解析的仓库/存储根中需要有效的 --change,并使用与 apply 相同的必需上下文/建议指导语义。这是一个只读的运行时输入界面:不返回静态归档工作流、不检查或合并增量规范、不写入主规范、也不移动变更。
4.8 new change <name> --json
成功:{ "change": { "id", "path", "metadataPath", "schema" }, "root" }。失败:{ "change": null, "status": [d] },退出 1。
4.9 archive <name> --json
成功:{ "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }。失败:{ "archive": null, "root"?, "status": [d] },退出 1。specsUpdated 仅在至少一个规范文件被写入或退役时为 true(当一个能力的所有需求被变更移除时,其规范将被删除,这要求变更的 .openspec.yaml 中 retire_capabilities: true;每次退役都在 warnings 中列出名称,仅当规范存在于调用者的检出副本中时,才会提供可粘贴的 Git 恢复命令);已同步的变更归档时所有合计为零,跳过项列于 warnings 中。JSON 模式严格非交互:每个提示点都变为 archive_* 代码。
4.10 doctor --json
{ "root": { "path", "source", "store_id"?, "healthy", "status": [] }, "store": { "id", "metadata": {present,valid,remote?}, "origin_url"?, "drift"?: {ahead,behind}, "status": [] } | null, "references": [...], "status": [] }。drift(仅适用于具有上游跟踪引用的 git 备份存储检出)表示与最后获取的上游相比的领先/落后提交数,而非实时远程状态。任何级别的健康发现均退出 0。失败负载:{ "root": null, "store": null, "references": [], "status": [d] },退出 1。
4.11 context --json
{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }。可用 = 路径存在且状态为空。--code-workspace <path> 写入 {folders:[{name,path}]}(仅可用的引用存储,使用 ref: 前缀);在 JSON 模式下,写入先于打印执行,因此即使写入失败,标准输出也恰好包含一个文档。失败:{ "root": null, "members": [], "status": [d] },退出 1。
4.12 store ... --json
setup/register: { "store": {id, root, metadata_path?}, "registry": {path, registered, already_registered}, "git": {is_repository, initialized, committed}, "created_files": [], "status": [] }。unregister/remove: { "store", "registry": {path, removed}, "files": {deleted, deleted_path, left_on_disk}, "status": [] }。list: { "stores": [{id, root}], "status": [] }。doctor: { "stores": [ { id, root, metadata_path?, openspec_root: {...healthy, status}, metadata: {present, valid, id?, remote}, git: {is_repository, has_commits, has_uncommitted_changes, has_remote, origin_url}, status } ], "status": [] }(null = 未知/未探测)。健康发现退出 0;失败退出 1 并附带对应的空形状。提示取消退出 130。
4.13 schemas --json / templates --json
schemas:成功仍为裸数组 [ {name, description, artifacts, source} ];它遵循规范的根选择优先级并接受 --store <id>。根选择失败:{ "schemas": [], "root": null, "status": [d] },退出 1。templates:键值对象 { "<artifactId>": {path, source} },仍基于当前工作目录,不含 root/status 键。
5. 退出码约定
| 情况 | 退出码 | 标准输出 |
|---|---|---|
| 成功(包括健康检查发现,如 doctor/context/store doctor) | 0 | 负载 |
--json 模式下的命令失败 | 1 | 一个 JSON 文档,包含 status: [d] 和命令的空形状 |
validate 存在失败项 | 1 | 完整报告 |
提示取消(store 组,人工模式) | 130 | 仅标准错误 |
6. 诊断代码目录
解析
no_openspec_root, no_root_with_registered_stores, no_registered_stores, unknown_store, store_identity_mismatch, unhealthy_store_root, store_path_not_supported, invalid_store_pointer, initiative_option_removed, areas_option_removed;透传:invalid_store_id, invalid_store_registry, invalid_store_metadata。
OpenSpec 根健康检查(错误,无修复)
openspec_store_root_missing, openspec_store_root_not_directory, openspec_root_missing, openspec_root_not_directory, openspec_config_missing, openspec_config_not_file, openspec_specs_not_directory, openspec_changes_not_directory, openspec_archive_not_directory。在存储(stores)测试版期间,openspec/specs/、openspec/changes/ 和 openspec/changes/archive/ 在健康根目录中可能不存在;只有当它们存在但不是目录时才会成为健康错误。
存储注册表/身份/状态
invalid_store_id, invalid_store_registry, invalid_store_metadata, store_registry_busy, store_not_found, no_store_registry, store_registry_changed, store_metadata_missing, store_metadata_id_mismatch, store_metadata_invalid, store_id_conflict, store_path_conflict, store_already_registered(信息)。
存储设置/注册/移除
store_setup_id_required, store_setup_path_required, store_setup_path_not_directory, store_setup_inside_git_repo, store_setup_non_empty_directory, store_setup_cancelled, store_path_required, store_path_missing, store_path_not_directory, store_root_pointer_declared, store_register_root_unhealthy, store_register_identity_confirmation_required, store_register_cancelled, store_remote_empty, store_remote_requires_hand_edit, store_remove_confirmation_required, store_remove_cancelled, store_remove_path_not_directory, store_remove_metadata_missing, store_root_missing(在移除中为警告,在 doctor 中为错误),store_root_not_directory。
存储 Git
store_git_init_failed, store_git_identity_missing, store_git_commit_failed, store_git_no_commits(警告),store_clone_fragile_directories(警告),store_remote_divergence(信息,doctor),store_checkout_drift(信息,doctor)。
引用(警告)
reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated。
关系(警告;doctor;上下文仅保留注册表一个)
relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert。
归档(JSON 模式)
archive_change_name_required, archive_change_not_found, archive_change_symlink, archive_validation_failed, archive_confirmation_required, archive_tasks_incomplete, archive_spec_update_failed, archive_spec_validation_failed, archive_target_exists, archive_error。
上下文写入
context_file_exists, context_output_dir_missing。
回退
doctor_failed, context_failed, store_error, change_error, archive_error。
已知不一致
由顶点审计记录;已发布键的更名是产品决策,推迟到本版本之后:
在已在顶点测试轮中修复:--json模式下,多个失败路径仅输出标准错误而没有 JSON 文档。show/validate对于未知和模糊项发出{status:[{code: unknown_item | ambiguous_item, ...}]};在status/instructions/list/show/validate中抛出的错误通过 JSON 感知的失败辅助函数(命令的空形状 +status)处理;store <未知子命令> --json发出{status:[{code: unknown_store_subcommand}]};list在解析失败时携带其{changes|specs: [], root: null}空形状。store_root_missing以两种严重级别发出(在移除中为警告,在存储 doctor 中为错误)——取决于上下文,已在上面记录。- snake_case(存储系列)与 camelCase(工作流系列)的键大小写;
root.store_id在所有地方均使用 snake_case。 - src 中存在四个并行信封类型声明;归档诊断从不携带
target。 list --json将status键重用为每个变更的字符串枚举。- 只有
validate输出携带version字段。 templates忽略根选择(基于当前工作目录,无--store)。- 已弃用的名词形式(
change/spec子命令)发出未封装的负载,不包含root/status。