OpenSpec Agent 合約
openspec CLI 的可機器讀取介面,已針對 src/ 進行驗證(2026-06-11 的 capstone 審計)。以下每個結構皆源自產生該資料的程式碼。
1. 一般慣例
- 每次執行對應一個 JSON 文件。 在
--json模式下,stdout 僅輸出一個 JSON 文件(使用兩個空白字元美化排版)。人類可讀的文字、旋轉載入動畫以及 store 橫幅則輸出至 stderr。 - Store 橫幅。 在人類模式(human mode)下,若選定了 store 根目錄,會將
Using OpenSpec root: <id> (<path>)輸出至 stderr。在 JSON 模式下不會顯示此訊息。 - 鍵名大小寫取決於介面層級(見「已知不一致之處」):store/doctor/context 有效負載使用
snake_case;workflow 有效負載(status、instructions、new change、validate、list)使用camelCase,但內嵌的root物件一律使用store_id。 - 大多數情況下,可選鍵會被省略而非設為 null(例如
root.store_id、member.path)。明確使用null的例外情況會在各結構中特別標註(如 store doctor 的git.*、失敗有效負載)。
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。 - 無根目錄、無預設值、無儲存庫:命令可將 cwd 視為
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 陣列依依賴順序排列,以 schema 的 artifacts: 宣告順序打破同時就緒的產物之間的平局(絕不按字母排序),因此第一個 ready 條目即為下一個應撰寫的產物;missingDeps 使用相同順序。"skipped" 標記 .openspec.yaml 中宣告 skip_specs: true 的變更下、generates 路徑位於 specs/ 下的產物;它滿足依賴但不得建立。無活躍變更時:{ "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 列出此產物使其就緒的產物,依 schema 的宣告順序排列(與 status 建議的順序相同)。"skipped": true(附帶 "warning")在變更宣告 skip_specs: true 且此產物被跳過時出現 — 不要建立其檔案。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 被阻擋的項目(schema 的 apply.requires);missingPrerequisites 為 apply 可執行前仍需建立的所有項目,依建立順序排列 — 即這些 requires 的傳遞閉包,因此可能較長。warnings 列出變更本身的非阻擋性問題 — 目前為:變更已就緒可實作但無 delta 規格且無 skip_specs: true,即 openspec validate 會拒絕的狀態。兩個可選根目錄欄位(context、operationGuidance)在每次呼叫時均從選定的根目錄讀取。context 為必需的提示層級輸入,其相關的專案事實、慣例和約束必須被應用;operationGuidance 為建議性輸入,其條目僅在適用且與內建工作流程相容時遵循。兩者均與 state、tasks、progress、context files 及內建 instruction 保持獨立。
4.7 instructions archive --json
{ "changeName", "context"?, "operationGuidance"?, "root" }。需要解析後的 repo/store 根目錄中存在有效的 --change,並使用與 apply 相同的必需 context/建議 guidance 語意。這是唯讀的執行時輸入介面:它不返回靜態的歸檔工作流程、不檢查或合併 delta 規格、不寫入主規格、也不移動變更。
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 中命名,僅當規格位於呼叫者的 checkout 中時才附帶可貼上的 Git 復原命令);已同步的變更歸檔時所有 totals 為零,跳過的項目列在 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 後端儲存庫 checkout 中存在)為相對於上次 fetch 的上游的 ahead/behind 計數,而非即時遠端。任何嚴重程度的健康檢查結果均以退出碼 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": [] }。AVAILABLE = path 存在且 status 為空。--code-workspace <path> 寫入 {folders:[{name,path}]}(僅包含可用的被引用儲存庫,使用 ref: 前綴);在 JSON 模式下,寫入在輸出前執行,因此即使寫入失敗 stdout 也僅包含一個文件。失敗:{ "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} },仍以 cwd 為基準,無 root/status 鍵。
5. 退出碼合約
| 情況 | 退出碼 | 標準輸出 (Stdout) |
|---|---|---|
| 成功,包含健康檢查結果 (doctor/context/store doctor) | 0 | 有效負載 (payload) |
--json 模式下的命令失敗 | 1 | 一個包含 status: [d] 和命令空形狀 (null-shape) 的 JSON 文件 |
validate 存在失敗項目時 | 1 | 完整報告 |
提示取消 (store 群組,人類模式) | 130 | 僅標準錯誤 (stderr) |
6. 診斷代碼目錄
解析 (Resolution)
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-root 健康狀態 (錯誤,無修復方案)
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 beta 期間,健康的根目錄中可能不存在 openspec/specs/、openspec/changes/ 和 openspec/changes/archive/;僅當這些路徑存在但不是目錄時,才會被視為健康錯誤。
Store 註冊表/身份/狀態
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 設置/註冊/移除
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。
Store 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; context 僅保留註冊表相關項)
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 寫入
context_file_exists, context_output_dir_missing。
後備處理 (Fallbacks)
doctor_failed, context_failed, store_error, change_error, archive_error。
已知不一致之處
由頂尖審計 (capstone audit) 記錄;發布鍵的重命名是延遲至本版本之後的產品決策:
在已在頂尖測試輪次中修復:--json模式下,多個失敗路徑僅打印 stderr,沒有 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以兩種嚴重程度發出(移除時為警告,store doctor 時為錯誤)——取決於上下文,如上所述。- snake_case(store 系列)與 camelCase(workflow 系列)的鍵大小寫;
root.store_id在所有地方均為 snake_case。 - src 中存在四個並行的信封類型聲明;歸檔診斷從不攜帶
target。 list --json重用status鍵作為每個變更的字串枚舉。- 僅
validate輸出攜帶version欄位。 templates忽略根選擇(基於當前工作目錄,無--store)。- 已棄用的名詞形式(
change/spec子命令)會發出未包裹的有效負載,且不含root/status。