Skip to content

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 --json standardowe 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 obiektu root, który zawsze używa store_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 doctor git.*, ładunki błędów).

2. Otoczka diagnostyczna ​

Jedna forma otoczki jest współdzielona przez wszystkie maszynowo odczytywane diagnostyki (StoreDiagnostic):

json
{
  "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:

  1. --store <id> → korzeń zarejestrowanego magazynu (source: "store").
  2. W przeciwnym razie najbliższy przodek z openspec/: kształt planowania → source: "nearest" (wskaźnik store: jest ignorowany z ostrzeżeniem na stderr); katalog tylko z konfiguracją z prawidłowym wskaźnikiem store: → ten magazyn, source: "declared".
  3. 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 z fix nazywającym openspec config unset defaultStore.
  4. Brak najbliższego korzenia, brak domyślnego + istnieją zarejestrowane magazyny → błąd no_root_with_registered_stores.
  5. Brak korzenia, brak domyślnego, brak magazynów: polecenia mogą traktować bieżący katalog jako source: "implicit"; doctor, context, list oraz zbiorcze validate kończą się natomiast błędem no_openspec_root. list zachowuje domyślny fallback dla projektów legacy z openspec/project.md.

Pomyślne obciążenia JSON zwykle osadzają korzeń; pomyślne schemas --json celowo pozostaje nagim tablicą zgodności udokumentowaną w §4.13:

json
"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 ​

SytuacjaKod wyjściaStdout
Sukces, w tym wyniki kontroli stanu (doctor/context/store doctor)0dane (payload)
Błąd polecenia w trybie --json1jeden dokument JSON z status: [d] i kształtem pustym (null-shape) polecenia
validate z niepoprawnymi elementami1pełny raport
Anulowanie monitu (grupa store, tryb interaktywny)130tylko 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ę:

  1. W trybie --json kilka ścieżek błędów wypisywało tylko na stderr bez dokumentu JSON. Naprawione w rundzie końcowej (capstone gauntlet): show/validate z nieznanymi i niejednoznacznymi elementami emitują {status:[{code: unknown_item | ambiguous_item, ...}]}; błędy zgłaszane w status/instructions/list/show/validate przechodzą przez helper obsługi błędów ze świadomością JSON (kształt pusty polecenia + status); store <nieznane podpolecenie> --json emituje {status:[{code: unknown_store_subcommand}]}; list przenosi swój kształt pusty {changes|specs: [], root: null} przy błędach rozwiązywania.
  2. store_root_missing jest emitowany z dwoma poziomami ważności (ostrzeżenie przy usuwaniu, błąd w doctor sklepu) — zależnie od kontekstu, udokumentowano powyżej.
  3. Różne konwencje nazewnictwa: snake_case (rodzina store) vs camelCase (rodzina workflow); root.store_id jest wszędzie w snake_case.
  4. W src istnieją cztery równoległe deklaracje typu envelope; diagnostyka archiwum nigdy nie przenosi pola target.
  5. list --json ponownie używa klucza status jako wyliczenia ciągowego (string enum) dla każdej zmiany.
  6. Tylko wynik validate zawiera pole version.
  7. templates ignoruje wybór katalogu głównego (oparty na cwd, bez --store).
  8. Przestarzałe formy rzeczownikowe (podpolecenia change/spec) emitują nieopakowane (unenveloped) dane bez root/status.