Kontrakt agenta OpenSpec
Interfejsy odczytywalne maszynowo narzędzia CLI openspec, zweryfikowane względem katalogu src/ (audyt wieńczący, 2026-06-11). Każdy poniższy schemat jest udokumentowany na podstawie kodu, który go generuje.
1. Ogólne konwencje
- Jeden dokument JSON na wywołanie. W trybie
--jsonstandardowe wyjście stdout zawiera dokładnie jeden dokument JSON (sformatowany z wcięciem 2 spacji). Tekst przeznaczony dla człowieka, animowane wskaźniki postępu oraz baner sklepu są wysyłane na stderr. - Baner sklepu. W trybie dla człowieka, wybrany korzeń sklepu wypisuje
Using OpenSpec root: <id> (<path>)na stderr. Nigdy nie jest wypisywany w trybie JSON. - Wielkość liter w kluczach zależy od interfejsu (zobacz Znane niespójności): ładunki store/doctor/context używają
snake_case; ładunki przepływu pracy (status,instructions,new change,validate,list) używającamelCase, z wyjątkiem osadzonego obiekturoot, który zawsze używastore_id. - Opcjonalne klucze są pomijane, a nie ustawiane na null, w większości ładunków (np.
root.store_id,member.path). Wyjątki, które jawnie używająnull, są wskazane dla każdego schematu (store doctorgit.*, ładunki błędów).
2. Otoczka diagnostyczna
Jedna forma otoczki jest współdzielona przez wszystkie maszynowo odczytywane diagnostyki (StoreDiagnostic):
{
"severity": "error" | "warning" | "info",
"code": "snake_case_string",
"message": "human sentence",
"target": "dotted.surface (optional)",
"fix": "one actionable sentence/command (optional)"
}Diagnostyki pojawiają się w dwóch miejscach: tablicach statusu (status: StoreDiagnostic[] na poziomie najwyższym lub per wpis) dla wyników dotyczących stanu zdrowia oraz rzuconych błędach konwertowanych na tablicę status z jednym elementem przy niepowodzeniu polecenia.
3. Wybór korzenia i RootOutput
Wszystkie polecenia rozwiązujące korzeń (list, show, validate, status, instructions, instructions apply, instructions archive, new change, archive, doctor, context, schemas) rozwiązują jeden korzeń OpenSpec z jedną kolejnością pierwszeństwa:
--store <id>→ korzeń zarejestrowanego magazynu (source: "store").- W przeciwnym razie najbliższy przodek z
openspec/: kształt planowania →source: "nearest"(wskaźnikstore:jest ignorowany z ostrzeżeniem na stderr); katalog tylko z konfiguracją z prawidłowym wskaźnikiemstore:→ ten magazyn,source: "declared". - Brak najbliższego korzenia + ustawione globalne
defaultStore(openspec config set defaultStore <id>) → ten magazyn,source: "global_default"; przeterminowany identyfikator kończy się błędem magazynu zfixnazywającymopenspec config unset defaultStore. - Brak najbliższego korzenia, brak domyślnego + istnieją zarejestrowane magazyny → błąd
no_root_with_registered_stores. - Brak korzenia, brak domyślnego, brak magazynów: polecenia mogą traktować bieżący katalog jako
source: "implicit";doctor,context,listoraz zbiorczevalidatekończą się natomiast błędemno_openspec_root.listzachowuje domyślny fallback dla projektów legacy zopenspec/project.md.
Pomyślne obciążenia JSON zwykle osadzają korzeń; pomyślne schemas --json celowo pozostaje nagim tablicą zgodności udokumentowaną w §4.13:
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (only when store-selected)" }Umowa o niepowodzeniu korzenia: w trybie JSON niepowodzenie rozwiązania wypisuje { ...commandNullShape, "status": [diagnostic] } na stdout i kończy się kodem 1.
4. Formy JSON poleceń
4.1 list --json
{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — zauważ, że per-zmiana status jest tutaj wyliczeniem stringowym. --specs: { "specs": [ { "id", "requirementCount" } ], "root" }.
4.2 show <item> --json
Zmiana: { "id", "title", "deltaCount", "deltas": [...], "root" }. Specyfikacja: { "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" }. Kod wyjścia 1, gdy jakikolwiek element nie przechodzi.
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 oznacza, że każdy nieominięty artefakt planowania istnieje; pominięte artefakty liczą się jako spełnione bez ich tworzenia. Nie oznacza to, że zadania implementacyjne są zakończone. isComplete jest zachowane jako alias zgodności z tą samą wartością. requires każdego artefaktu to jego bezpośrednie identyfikatory zależności (obecne dla każdego statusu, dzięki czemu zbiór wymaganych zależności transytywnych jest obliczalny nawet gdy artefakt jest done); missingDeps pojawia się tylko przy blocked. Tablica artifacts jest w kolejności zależności, z kolejnością deklaracji artifacts: schematu rozstrzygającą remisy między artefaktami, które stają się gotowe w tym samym czasie (nigdy alfabetycznie), dzięki czemu pierwszy wpis ready to artefakt do napisania jako następny; missingDeps używa tej samej kolejności. "skipped" oznacza artefakt, którego ścieżka generates znajduje się pod specs/ w zmianie, której .openspec.yaml deklaruje skip_specs: true; spełnia zależności, ale nie powinien być tworzony. Brak aktywnych zmian: { "changes": [], "message", "root" }, kod wyjścia 0.
--all (zbiorcze, wzajemnie wykluczające się z --change — łączenie ich to błąd z formą pustą { "changes": [], "root": null, "status": [d] }): { "changes": [ <obiekt statusu per-zmiana, bez per-zmiana root>, ... ], "root" }, posortowane według nazwy zmiany. Zmiana, której nie udało się załadować, dostarcza { "changeName", "status": [d] } na swoim miejscu; skanowanie kontynuuje, zachowuje kompletną otoczkę i kończy się kodem 1 zarówno w trybie tekstowym, jak i JSON. Nieprawidłowy --schema kończy całe wywołanie błędem z formą pustą, nawet gdy nie istnieją żadne zmiany.
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 wymienia artefakty, które ten czyni gotowymi, w kolejności deklaracji schematu (tej samej kolejności, w jakiej status je rekomenduje). "skipped": true (z "warning") pojawia się, gdy zmiana deklaruje skip_specs: true i ten artefakt jest pominięty — nie twórz jego plików. Wpis zależności z skipped: true jest spełniony bez plików — nie próbuj odczytywać jego ścieżek.
ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — rozwiązane wpisy zawierają root/specs/fetch; nierozwiązane zawierają store_id + status ostrzeżenia. Indeks ograniczony do 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 to to, na czym apply blokuje się (schematowe apply.requires); missingPrerequisites to wszystko, co jeszcze trzeba zbudować przed uruchomieniem apply, w kolejności budowania — zamknięcie transytzywne tych wymagań, więc może być dłuższą listą. warnings wymienia problemy nieblokujące dotyczące samej zmiany — dziś: zmiana gotowa do implementacji bez specyfikacji delta i bez skip_specs: true, stan odrzucany przez openspec validate. Oba opcjonalne pola korzenia (context, operationGuidance) są odczytywane z wybranego korzenia przy każdym wywołaniu. context to wymagane wejście poziomu promptu, którego istotne fakty projektowe, konwencje i ograniczenia muszą być zastosowane; operationGuidance to wejście doradcze, którego wpisy są stosowane tylko gdy są aktualne i zgodne z wbudowanym przepływem pracy. Oba pozostają odrębne od stanu, zadań, postępu, plików kontekstu i wbudowanej instrukcji.
4.7 instructions archive --json
{ "changeName", "context"?, "operationGuidance"?, "root" }. Wymaga prawidłowego --change w rozwiązanym korzeniu repo/magazynu i używa tych samych semantyk wymaganych kontekstu/doradczych wytycznych co apply. To jest powierzchnia wejściowa tylko do odczytu: nie zwraca statycznego przepływu archiwizacji, nie inspekcjonuje ani nie łączy specyfikacji delta, nie zapisuje specyfikacji głównych i nie przenosi zmiany.
4.8 new change <name> --json
Sukces: { "change": { "id", "path", "metadataPath", "schema" }, "root" }. Niepowodzenie: { "change": null, "status": [d] }, kod wyjścia 1.
4.9 archive <name> --json
Sukces: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. Niepowodzenie: { "archive": null, "root"?, "status": [d] }, kod wyjścia 1. specsUpdated jest prawdziwe tylko wtedy, gdy co najmniej jeden plik specyfikacji został zapisany lub wycofany (zdolność, której ostatnie wymaganie zmiana usunęła, ma swoją specyfikację usuniętą, co wymaga retire_capabilities: true w .openspec.yaml zmiany; każde wycofanie jest wymienione w warnings, z wklejamym poleceniem odzyskiwania Git tylko wtedy, gdy specyfikacja istniała w wywołaniu użytkownika); już zsynchronizowana zmiana archiwizuje się z zerowymi sumami i pominięciami wymienionymi w warnings. Tryb JSON jest ściśle nieinteraktywny: każdy punkt promptu staje się kodem 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 (obecny tylko dla magazynu opartego na git z górnym referencją śledzącą) to liczniki ahead/behind względem ostatnio pobranej górnej, nie względem żywego zdalnego. Wyniki stanu zdrowia dowolnej wagi kończą się kodem 0. Obciążenie niepowodzenia: { "root": null, "store": null, "references": [], "status": [d] }, kod wyjścia 1.
4.11 context --json
{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }. DOSTĘPNY = ścieżka obecna ORAZ status pusty. --code-workspace <path> zapisuje {folders:[{name,path}]} (tylko dostępne magazyny referencyjne, prefiksy ref:); w trybie JSON zapis wykonuje się przed wypisywaniem, więc stdout zawiera dokładnie jeden dokument nawet przy niepowodzeniu zapisu. Niepowodzenie: { "root": null, "members": [], "status": [d] }, kod wyjścia 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 = nieznane/nieprzebadane). Wyniki stanu zdrowia kończą się kodem 0; niepowodzenia kończą się kodem 1 z odpowiadającą formą pustą. Anulowanie promptu kończy się kodem 130.
4.13 schemas --json / templates --json
schemas: sukces pozostaje nagą tablicą [ {name, description, artifacts, source} ]; rozwiązuje kanoniczną kolejność pierwszeństwa wyboru korzenia i akceptuje --store <id>. Niepowodzenie wyboru korzenia: { "schemas": [], "root": null, "status": [d] }, kod wyjścia 1. templates: obiekt kluczowany { "<artifactId>": {path, source} }, nadal oparty na bieżącym katalogu bez kluczy root/status.
5. Kontrakt kodów wyjścia
| Sytuacja | Kod wyjścia | Stdout |
|---|---|---|
| Sukces, w tym wyniki kontroli stanu (doctor/context/store doctor) | 0 | dane (payload) |
Błąd polecenia w trybie --json | 1 | jeden dokument JSON z status: [d] i kształtem pustym (null-shape) polecenia |
validate z niepoprawnymi elementami | 1 | pełny raport |
Anulowanie monitu (grupa store, tryb interaktywny) | 130 | tylko stderr |
6. Katalog kodów diagnostycznych
Rozwiązywanie (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; przekazywanie dalej (pass-through): invalid_store_id, invalid_store_registry, invalid_store_metadata.
Stan zdrowia katalogu głównego OpenSpec (błąd, bez poprawy)
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. W okresie beta sklepów (stores), katalogi openspec/specs/, openspec/changes/ oraz openspec/changes/archive/ mogą być nieobecne w zdrowym katalogu głównym; są one błędami stanu zdrowia tylko wtedy, gdy występują, ale nie są katalogami.
Rejestr sklepów / tożsamość / stan
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 (informacja).
Konfiguracja sklepu / rejestracja / usuwanie
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 (ostrzeżenie przy usuwaniu, błąd w doctor), store_root_not_directory.
Git sklepu
store_git_init_failed, store_git_identity_missing, store_git_commit_failed, store_git_no_commits (ostrzeżenie), store_clone_fragile_directories (ostrzeżenie), store_remote_divergence (informacja, doctor), store_checkout_drift (informacja, doctor).
Odwołania (References) (ostrzeżenie)
reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.
Relacje (Relationships) (ostrzeżenie; doctor; kontekst zachowuje tylko wpis z rejestru)
relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.
Archiwum (tryb 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.
Zapis kontekstu
context_file_exists, context_output_dir_missing.
Mechanizmy zastępcze (Fallbacks)
doctor_failed, context_failed, store_error, change_error, archive_error.
Znane niespójności
Udokumentowane w ramach audytu końcowego (capstone audit); zmiany nazw opublikowanych kluczy to decyzje produktowe odroczone poza tę wersję:
W trybieNaprawione w rundzie końcowej (capstone gauntlet):--jsonkilka ścieżek błędów wypisywało tylko na stderr bez dokumentu JSON.show/validatez nieznanymi i niejednoznacznymi elementami emitują{status:[{code: unknown_item | ambiguous_item, ...}]}; błędy zgłaszane wstatus/instructions/list/show/validateprzechodzą przez helper obsługi błędów ze świadomością JSON (kształt pusty polecenia +status);store <nieznane podpolecenie> --jsonemituje{status:[{code: unknown_store_subcommand}]};listprzenosi swój kształt pusty{changes|specs: [], root: null}przy błędach rozwiązywania.store_root_missingjest emitowany z dwoma poziomami ważności (ostrzeżenie przy usuwaniu, błąd w doctor sklepu) — zależnie od kontekstu, udokumentowano powyżej.- Różne konwencje nazewnictwa: snake_case (rodzina
store) vs camelCase (rodzina workflow);root.store_idjest wszędzie w snake_case. - W src istnieją cztery równoległe deklaracje typu envelope; diagnostyka archiwum nigdy nie przenosi pola
target. list --jsonponownie używa kluczastatusjako wyliczenia ciągowego (string enum) dla każdej zmiany.- Tylko wynik
validatezawiera poleversion. templatesignoruje wybór katalogu głównego (oparty na cwd, bez--store).- Przestarzałe formy rzeczownikowe (podpolecenia
change/spec) emitują nieopakowane (unenveloped) dane bezroot/status.