OpenSpec 에이전트 계약
src/ 기준으로 검증된 openspec CLI의 기계 가독 표면입니다 (최종 감사, 2026-06-11). 아래 모든 형상은 출력 코드를 기반으로 문서화되었습니다.
1. 일반 규칙
- 호출당 JSON 문서 1개.
--json모드에서 stdout에는 정확히 하나의 JSON 문서(2공백 정렬 출력)만 출력됩니다. 인간용 텍스트, 스피너, 스토어 배너는 stderr로 출력됩니다. - 스토어 배너. 인간 모드에서 스토어 선택 루트는
Using OpenSpec root: <id> (<path>)를 stderr로 출력합니다. JSON 모드에서는 절대 출력되지 않습니다. - 키 대소문자는 표면에 따라 다릅니다 (알려진 불일치 항목 참조): 스토어/doctor/context 페이로드는
snake_case를 사용하고, 워크플로 페이로드(status,instructions,new change,validate,list)는camelCase를 사용합니다. 단, 임베디드root객체는 항상store_id를 사용합니다. - 대부분의 페이로드에서 선택적 키는 null이 아닌 생략됩니다 (예:
root.store_id,member.path). 명시적null을 사용하는 예외는 형상별로 명시합니다 (스토어 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[], 최상위 또는 항목별)로 건강 상태를 보고하고, throw된 오류는 명령 실패 시 단일 요소 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가 있는 레거시 프로젝트를 위해 implicit 폴백을 유지합니다.
성공한 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] }를 출력하고 exit code 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" }. 항목 중 하나라도 실패하면 exit 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는 모든 비-skip 계획 아티팩트가 존재함을 의미합니다. skip된 아티팩트는 생성되지 않아도 충족된 것으로 간주됩니다. 구현 작업이 완료되었음을 의미하지 않습니다. isComplete는 동일한 값으로 유지되는 호환성 별칭입니다. 각 아티팩트의 requires는 직접 의존 ID 목록입니다(모든 status에 존재하므로 아티팩트가 done 상태여도 전이적 필수 집합을 계산할 수 있습니다). missingDeps는 blocked일 때만 나타납니다. artifacts 배열은 의존 순서로 정렬되며, 동시에 ready가 되는 아티팩트 간에는 스키마의 artifacts: 선언 순서가 동점을 해결합니다(아بجد순이 아님). 따라서 첫 번째 ready 항목이 다음에 작성할 아티팩트입니다. missingDeps도 동일한 순서를 사용합니다. "skipped"는 .openspec.yaml에 skip_specs: true가 선언된 변경사항에서 generates 경로가 specs/ 아래에 있는 아티팩트를 표시합니다. 의존성은 충족하지만 파일을 생성해서는 안 됩니다. 활성 변경사항이 없는 경우: { "changes": [], "message", "root" }, exit 0.
--all(일괄 처리, --change와 상호 배타적 — 함께 사용하면 { "changes": [], "root": null, "status": [d] } null-shape 오류): { "changes": [ <변경사항별 status 객체, 변경사항별 root 없음>, ... ], "root" }, 변경사항 이름순 정렬. 로드 실패한 변경사항은 { "changeName", "status": [d] }로 대체됩니다. 스윕은 계속 진행되며 완전한 엔벨로프를 유지하고 텍스트 모드와 JSON 모드 모두 exit 1로 종료합니다. 잘못된 --schema는 변경사항이 없더라도 전체 호출을 null-shape로 실패시킵니다.
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는 이 아티팩트가 ready로 만드는 아티팩트 목록이며 스키마 선언 순서입니다(status가 권장하는 순서와 동일). "skipped": true("warning"와 함께)는 변경사항이 skip_specs: true를 선언하고 이 아티팩트가 skip된 경우 나타납니다 — 파일을 생성하지 마세요. skipped: true인 의존 항목은 파일 없이 충족됩니다 — 해당 경로를 읽으려 하지 마세요.
ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — 해석된 항목은 root/specs/fetch를 포함하고, 해석되지 않은 항목은 store_id + 경고 status를 포함합니다. 인덱스는 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 실행 전에 아직 구축해야 할 모든 항목이며, build 순서로 정렬됩니다 — 해당 requires의 전이적 폐쇄집합이므로 더 긴 목록일 수 있습니다. warnings는 변경사항 자체의 비차단 문제를 나열합니다 — 현재는 delta 스펙이 없고 skip_specs: true도 없는 상태에서 구현 준비가 된 변경사항, 즉 openspec validate가 거부하는 상태를 의미합니다. 두 선택 루트 필드(context, operationGuidance)는 모든 호출 시 선택된 루트에서 읽힙니다. context는 관련 프로젝트 사실, 관례, 제약사항을 적용해야 하는 필수 프롬프트 수준 입력입니다. operationGuidance는 적용 가능하고 내장 워크플로우와 호환될 때만 따르는 조언 입력입니다. 둘 다 state, tasks, progress, context 파일, 내장 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] }, exit 1.
4.9 archive <name> --json
성공: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. 실패: { "archive": null, "root"?, "status": [d] }, exit 1. specsUpdated는 최소 한 개의 스펙 파일이 작성되거나 폐기된 경우에만 true입니다(변경사항이 마지막 요구사항을 제거한 기능의 스펙은 삭제되며, 변경사항의 .openspec.yaml에 retire_capabilities: true가 필요합니다. 모든 폐기는 warnings에 명시되며, 스펙이 호출자의 체크아웃에 존재한 경우에만 붙여넣을 수 있는 Git 복구 명령이 포함됩니다). 이미 동기화된 변경사항은 모든 totals가 0으로 아카이브되며 skip 항목이 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(upstream tracking ref가 있는 git 기반 스토어 체크아웃에서만 존재)는 라이브 원격이 아닌 마지막 fetch한 upstream 대비 앞/뒤 카운트입니다. 모든 심각도의 건강 발견은 exit 0. 실패 페이로드: { "root": null, "store": null, "references": [], "status": [d] }, exit 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 존재 AND status 비어있음. --code-workspace <path>는 {folders:[{name,path}]}를 작성합니다(사용 가능한 참조 스토어만, ref: 접두사). JSON 모드에서는 출력을 쓰기 전에 작성하므로 작성 실패 시에도 stdout에 정확히 하나의 문서만 포함됩니다. 실패: { "root": null, "members": [], "status": [d] }, exit 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 = 미확인/미탐색). 건강 발견은 exit 0. 실패는 대응하는 null-shape와 함께 exit 1. 프롬프트 취소는 exit 130.
4.13 schemas --json / templates --json
schemas: 성공 시 맨 배열 [ {name, description, artifacts, source} ] 그대로 유지됩니다. 정통 루트 선택 우선순위를 해석하고 --store <id>를 지원합니다. 루트 선택 실패: { "schemas": [], "root": null, "status": [d] }, exit 1. templates: 키 객체 { "<artifactId>": {path, source} }, 여전히 cwd 기반이며 root/status 키가 없습니다.
5. Exit-code 계약
| 상황 | Exit | Stdout |
|---|---|---|
| 성공 (건강 진단 포함: doctor/context/store doctor) | 0 | 페이로드 |
--json 모드에서 명령 실패 | 1 | status: [d]와 명령의 null-shape를 포함한 JSON 문서 1개 |
실패 항목이 있는 validate | 1 | 전체 보고서 |
프롬프트 취소 (store 그룹, human 모드) | 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; pass-through: invalid_store_id, invalid_store_registry, invalid_store_metadata.
OpenSpec-root health (error, no fix)
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/는 건강한 루트에서 존재하지 않을 수 있습니다. 이들은 존재하지만 디렉터리가 아닌 경우에만 건강 오류로 간주됩니다.
Store registry/identity/state
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 (info).
Store setup/register/remove
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 (remove에서는 warning, doctor에서는 error), store_root_not_directory.
Store git
store_git_init_failed, store_git_identity_missing, store_git_commit_failed, store_git_no_commits (warning), store_clone_fragile_directories (warning), store_remote_divergence (info, doctor), store_checkout_drift (info, doctor).
References (warning)
reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.
Relationships (warning; doctor; context는 registry 항목만 유지)
relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.
Archive (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 writes
context_file_exists, context_output_dir_missing.
Fallbacks
doctor_failed, context_failed, store_error, change_error, archive_error.
알려진 불일치 사항
Capstone 감사에서 기록된 내용입니다. 공개 키 이름 변경은 제품 결정 사항으로 이번 릴리스 이후로 연기되었습니다.
Capstone gauntlet 라운드에서 수정됨:--json모드에서 여러 실패 경로가 JSON 문서 없이 stderr만 출력했습니다.show/validate의 unknown 및 ambiguous 항목은{status:[{code: unknown_item | ambiguous_item, ...}]}를 출력합니다;status/instructions/list/show/validate에서 발생한 예외는 JSON 인식 실패 헬퍼를 통해 라우팅됩니다(명령의 null-shape +status);store <unknown subcommand> --json은{status:[{code: unknown_store_subcommand}]}를 출력합니다;list는 resolution 실패 시{changes|specs: [], root: null}null-shape를 유지합니다.store_root_missing는 두 가지 심각도로 출력됩니다(remove에서는 warning, store doctor에서는 error) — 컨텍스트에 따라 달라지며, 위 문서에 명시되어 있습니다.- snake_case(store 계열)와 camelCase(workflow 계열) 키 대소문자 혼용;
root.store_id는 모든 곳에서 snake_case입니다. - src에 4개의 병렬 envelope 타입 선언이 존재합니다; archive 진단에는
target이 포함되지 않습니다. list --json은 변경 사항별로status키를 문자열 enum으로 재사용합니다.version필드를 포함하는 출력은validate만 있습니다.templates는 루트 선택을 무시합니다(cwd 기반,--store없음).- 비추천 명사 형식(
change/spec하위 명령)은root/status없이 envelope 없는 페이로드를 출력합니다.