Skip to content

Контракт агента OpenSpec ​

Машинно-читаемые интерфейсы CLI openspec, проверенные по коду в директории 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, указаны отдельно для каждой структуры (поля git.* в команде doctor, полезная нагрузка ошибок).

2. Диагностический конверт ​

Одна форма конверта используется всеми машиночитаемыми диагностиками (StoreDiagnostic):

json
{
  "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 по следующему приоритету:

  1. --store <id> → корень зарегистрированного хранилища (source: "store").
  2. В противном случае, ближайший родительский каталог с openspec/: планировочная структура → source: "nearest" (указатель store: игнорируется с предупреждением в stderr); каталог только с конфигурацией, содержащий валидный указатель store: → это хранилище, source: "declared".
  3. Нет ближайшего корня + установлен глобальный defaultStore (openspec config set defaultStore <id>) → это хранилище, source: "global_default"; устаревший идентификатор завершается ошибкой с базовой ошибкой хранилища и fix, указывающим openspec config unset defaultStore.
  4. Нет ближайшего корня, нет default + существуют зарегистрированные хранилища → ошибка no_root_with_registered_stores.
  5. Нет корня, нет default, нет хранилищ: команды могут рассматривать текущий каталог как source: "implicit"; doctor, context, list и пакетный validate вместо этого завершаются ошибкой no_openspec_root. list сохраняет неявный возврат для устаревших проектов с openspec/project.md.

Успешные JSON-ответы обычно включают корень; успешный schemas --json намеренно остаётся простым массивом для совместимости, как описано в §4.13:

json
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (only when store-selected)" }

Контракт при ошибке корня: в режиме JSON ошибка разрешения выводит { ...commandNullShape, "status": [diagnostic] } в stdout и завершается с кодом 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 — это идентификаторы его прямых зависимостей (присутствуют для каждого статуса, поэтому транзитивное множество необходимых артефактов вычислимо, даже когда артефакт находится в 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": [ <объект статуса изменения, без корня изменения>, ... ], "root" }, отсортировано по имени изменения. Изменение, которое не удалось загрузить, вставляет { "changeName", "status": [d] } на его место; обход продолжается, сохраняется полный конверт, и выход с кодом 1 как в текстовом, так и в JSON режиме. Некорректный --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). "skipped": true (с "warning") появляется, когда изменение объявляет skip_specs: true и этот артефакт пропущен — не создавайте его файлы. Запись зависимости с skipped: true считается удовлетворённой без файлов — не пытайтесь читать её пути.

ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — разрешённые записи содержат root/specs/fetch; неразрешённые содержат store_id + статус предупреждения. Индекс ограничен 50 КБ (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.requires схемы); missingPrerequisites — это всё, что ещё нужно построить до запуска применения, в порядке сборки — транзитивное замыкание этих требований, поэтому этот список может быть длиннее. 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 истинно, только если был записан или упразднён хотя бы один файл спецификации (возможность, чьё последнее требование удалено изменением, приводит к удалению её спецификации, для чего требуется retire_capabilities: true в .openspec.yaml изменения; каждое упразднение указывается в 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, имеющего ссылку на отслеживание upstream) — это количество коммитов впереди/позади относительно последнего загруженного upstream, а не живого удалённого репозитория. Обнаружение проблем любого уровня серьёзности завершается с кодом 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 запись выполняется до вывода, поэтому 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} }, по-прежнему основанный на текущем каталоге, без ключей root/status.

5. Контракт кодов выхода ​

СитуацияКод выходаСтандартный вывод
Успех, включая результаты проверки здоровья (doctor/context/store doctor)0полезная нагрузка
Сбой команды в режиме --json1один JSON-документ с status: [d] и нулевой формой команды
validate с отказавшими элементами1полный отчет
Отмена запроса (группа store, интерактивный режим)130только stderr

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. Во время бета-версии store, 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 (предупреждение при remove, ошибка при 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; context сохраняет только registry) ​

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.

Известные несоответствия ​

Записано аудитом capstone; переименования опубликованных ключей — продуктовые решения, отложенные после этого релиза:

  1. В режиме --json несколько путей ошибок выводили только stderr без JSON-документа. Исправлено в раунде capstone gauntlet: show/validate для неизвестных и неоднозначных элементов выдают {status:[{code: unknown_item | ambiguous_item, ...}]}; выброшенные ошибки в status/instructions/list/show/validate направляются через JSON-осведомленный помощник сбоя (нулевая форма команды + status); store <unknown subcommand> --json выдает {status:[{code: unknown_store_subcommand}]}; list несет свою нулевую форму {changes|specs: [], root: null} при ошибках разрешения.
  2. store_root_missing выдается с двумя уровнями серьезности (предупреждение при remove, ошибка в store doctor) — зависит от контекста, описано выше.
  3. snake_case (семейство store) против camelCase (семейство workflow) в регистре ключей; root.store_id всегда в snake_case.
  4. В src существуют четыре параллельных объявления типа конверта; диагностика архива никогда не содержит target.
  5. list --json повторно использует ключ status как строковый перечисление для каждого изменения.
  6. Только вывод validate содержит поле version.
  7. templates игнорирует выбор корня (основано на текущем каталоге, без --store).
  8. Устаревшие именные формы (подкоманды change/spec) выдают незавернутые полезные нагрузки без root/status.