Контракт агента OpenSpec
Машинозчитувані поверхні CLI openspec, перевірені щодо src/ (підсумковий аудит, 2026-06-11). Кожна структура нижче задокументована з вихідного коду.
1. Загальні домовленості
- Один JSON-документ на один виклик. У режимі
--jsonstdout містить рівно один 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 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"; застарілий ідентифікатор призводить до помилки базового сховища з полем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 помилка визначення виводить { ...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 | корисне навантаження |
Помилка команди в режимі --json | 1 | один 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.
Відомі невідповідності
Зафіксовано під час фінального аудиту; перейменування опублікованих ключів є продуктовими рішеннями, відкладеними після цього релізу:
У режиміВиправлено в останньому раунді фінальної перевірки:--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.