Skip to content

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

Машинозчитувані поверхні CLI openspec, перевірені щодо src/ (підсумковий аудит, 2026-06-11). Кожна структура нижче задокументована з вихідного коду.

1. Загальні домовленості ​

  • Один JSON-документ на один виклик. У режимі --json stdout містить рівно один JSON-документ (відформатований з відступом у 2 пробіли). Текст для людини, анімація завантаження та банер сховища виводяться у stderr.
  • Банер сховища. У режимі для людини вибраний корінь сховища виводить Using OpenSpec root: <id> (<path>) у stderr. Ніколи не виводиться в режимі JSON.
  • Регістр ключів залежить від поверхні (див. Відомі невідповідності): корисні навантаження store/doctor/context використовують snake_case; корисні навантаження робочого процесу (status, instructions, new change, validate, list) використовують camelCase, за винятком вбудованого об’єкта root, який завжди використовує store_id.
  • Необов’язкові ключі опускаються, а не встановлюються в null, у більшості корисних навантажень (наприклад, root.store_id, member.path). Винятки, які використовують явний null, зазначаються для кожної структури (store doctor git.*, корисні навантаження помилок).

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. Немає найближчого кореня, немає типового + існують зареєстровані сховища → помилка no_root_with_registered_stores.
  5. Немає кореня, немає типового, немає сховищ: команди можуть вважати поточний каталог коренем із 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 та попередження в status. Індекс обмежено 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 — усе, що ще потрібно побудувати перед виконанням 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 лише тоді, коли принаймні один файл специфікації було записано або вилучено (можливість, останню вимогу якої зміна видалила, призводить до видалення її специфікації, що вимагає 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": [] }. ДОСТУПНИЙ = шлях присутній І 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. Контракт кодів виходу ​

СитуаціяКод виходуStdout
Успіх, включаючи висновки про стан (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. Під час бета-версії сховищ каталоги 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.

Відомі невідповідності ​

Зафіксовано під час фінального аудиту; перейменування опублікованих ключів є продуктовими рішеннями, відкладеними після цього релізу:

  1. У режимі --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} у разі помилок вирішення.
  2. store_root_missing виводиться з двома рівнями серйозності (попередження при видаленні, помилка в 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.