OpenSpec-Agentenvertrag
Maschinenlesbare Oberflächen der openspec-CLI, verifiziert gegen src/ (Capstone-Audit, 2026-06-11). Jede unten beschriebene Form wird aus dem emittierenden Code dokumentiert.
1. Allgemeine Konventionen
- Ein JSON-Dokument pro Aufruf. Im
--json-Modus schreibt stdout genau ein JSON-Dokument (2 Leerzeichen schön formatiert). Menschlicher Text, Spinner und das Store-Banner gehen nach stderr. - Store-Banner. Im menschlichen Modus gibt ein Store-ausgewählter Root
Using OpenSpec root: <id> (<path>)an stderr aus. Wird niemals im JSON-Modus ausgegeben. - Schlüssel-Schreibweise ist oberflächenabhängig (siehe Bekannte Inkonsistenzen): Store/Doctor/Context-Payloads verwenden
snake_case; Workflow-Payloads (status,instructions,new change,validate,list) verwendencamelCase, außer dem eingebettetenroot-Objekt, das immerstore_idverwendet. - Optionale Schlüssel werden weggelassen, nicht auf null gesetzt, in den meisten Payloads (z. B.
root.store_id,member.path). Ausnahmen, die explizitesnullverwenden, werden pro Form genannt (Store Doctorgit.*, Fehler-Payloads).
2. Die Diagnose-Hülle
Eine Hüllenform wird von jeder maschinenlesbaren Diagnose (StoreDiagnostic) gemeinsam genutzt:
{
"severity": "error" | "warning" | "info",
"code": "snake_case_string",
"message": "human sentence",
"target": "dotted.surface (optional)",
"fix": "one actionable sentence/command (optional)"
}Diagnosemeldungen erscheinen an zwei Positionen: Status-Arrays (status: StoreDiagnostic[] auf oberster Ebene oder pro Eintrag) für Gesundheitsbefunde und geworfene Fehler, die bei Befehlsfehlern in ein status-Array mit einem Element umgewandelt werden.
3. Root-Auswahl und RootOutput
Alle Root-auflösenden Befehle (list, show, validate, status, instructions, instructions apply, instructions archive, new change, archive, doctor, context, schemas) lösen eine OpenSpec-Root mit einer Priorität auf:
--store <id>→ die Root des registrierten Stores (source: "store").- Andernfalls der nächste Vorfahre mit
openspec/: Planungsform →source: "nearest"(einstore:-Zeiger wird mit einer Stderr-Warnung ignoriert); reines Konfigurationsverzeichnis mit gültigemstore:-Zeiger → dieser Store,source: "declared". - Kein nächster Vorfahre + globale
defaultStore-Einstellung (openspec config set defaultStore <id>) → dieser Store,source: "global_default"; eine veraltete ID schlägt mit dem zugrunde liegenden Store-Fehler fehl und einemfix, deropenspec config unset defaultStorebenennt. - Kein nächster Vorfahre, keine Standardeinstellung + registrierte Stores vorhanden → Fehler
no_root_with_registered_stores. - Keine Root, keine Standardeinstellung, keine Stores: Befehle können das aktuelle Arbeitsverzeichnis als
source: "implicit"behandeln;doctor,context,listund Bulk-validateschlagen stattdessen mitno_openspec_rootfehl.listbehält den impliziten Fallback für Altprojekte mitopenspec/project.mdbei.
Erfolgreiche JSON-Nutzlasten betten normalerweise die Root ein; erfolgreiches schemas --json bleibt bewusst das in §4.13 dokumentierte Kompatibilitäts-Basisarray:
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (only when store-selected)" }Root-Fehlervertrag: Im JSON-Modus gibt ein Auflösungsfehler { ...commandNullShape, "status": [diagnostic] } auf stdout aus und beendet sich mit Exit-Code 1.
4. JSON-Formen der Befehle
4.1 list --json
{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — beachten Sie, dass status pro Änderung hier eine String-Enumeration ist. --specs: { "specs": [ { "id", "requirementCount" } ], "root" }.
4.2 show <item> --json
Änderung: { "id", "title", "deltaCount", "deltas": [...], "root" }. Spec: { "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" }. Exit-Code 1, wenn ein Element fehlschlägt.
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 bedeutet, dass jedes nicht übersprungene Planungsartefakt existiert; übersprungene Artefakte gelten als erfüllt, ohne erstellt zu werden. Es bedeutet nicht, dass Implementierungsaufgaben abgeschlossen sind. isComplete bleibt als Kompatibilitätsalias mit demselben Wert erhalten. Das requires-Feld jedes Artefakts enthält die IDs seiner direkten Abhängigkeiten (bei jedem Status vorhanden, sodass die transitive Abhängigkeitsmenge auch bei done-Artefakten berechenbar ist); missingDeps erscheint nur bei blocked. Das artifacts-Array ist in Abhängigkeitsreihenfolge, wobei die artifacts:-Deklarationsreihenfolge des Schemas Gleichstände zwischen gleichzeitig bereiten Artefakten auflöst (niemals alphabetisch). Der erste ready-Eintrag ist also das als Nächstes zu schreibende Artefakt; missingDeps verwendet dieselbe Reihenfolge. "skipped" markiert ein Artefakt, dessen generates-Pfad unter specs/ in einer Änderung liegt, deren .openspec.yaml skip_specs: true deklariert; es erfüllt Abhängigkeiten, darf aber nicht erstellt werden. Keine aktiven Änderungen: { "changes": [], "message", "root" }, Exit-Code 0.
--all (Stapelmodus, gegenseitig ausschließend mit --change — die Kombination ist ein Fehler mit der Null-Form { "changes": [], "root": null, "status": [d] }): { "changes": [ <Statusobjekt pro Änderung, ohne Root pro Änderung>, ... ], "root" }, sortiert nach Änderungsname. Eine Änderung, die nicht geladen werden kann, trägt { "changeName", "status": [d] } an ihrer Stelle bei; der Durchlauf wird fortgesetzt, bewahrt die vollständige Hülle und beendet sich in Text- und JSON-Modus mit Exit-Code 1. Ein ungültiges --schema lässt den gesamten Aufruf mit der Null-Form fehlschlagen, auch wenn keine Änderungen existieren.
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 listet die Artefakte auf, die dieses bereit macht, in der Deklarationsreihenfolge des Schemas (derselben Reihenfolge, die status empfiehlt). "skipped": true (mit "warning") erscheint, wenn die Änderung skip_specs: true deklariert und dieses Artefakt übersprungen wird — erstellen Sie seine Dateien nicht. Ein Abhängigkeitseintrag mit skipped: true ist ohne Dateien erfüllt — versuchen Sie nicht, seine Pfade zu lesen.
ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — aufgelöste Einträge tragen root/specs/fetch; nicht aufgelöste tragen store_id + Warnstatus. Index auf 50KB begrenzt (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 ist das, worauf apply blockiert (das apply.requires des Schemas); missingPrerequisites ist alles, was vor der Ausführung von apply noch zu erstellen ist, in Erstellungsreihenfolge — die transitive Hülle dieser requires, daher kann es die längere Liste sein. warnings listet nicht blockierende Probleme mit der Änderung selbst auf — derzeit eine Änderung, die implementierungsbereit ist, aber keine Delta-Specs und kein skip_specs: true hat, der Zustand, den openspec validate ablehnt. Beide optionalen Root-Felder (context, operationGuidance) werden bei jedem Aufruf aus der ausgewählten Root gelesen. context ist eine erforderliche Eingabe auf Prompt-Ebene, deren relevante Projektfakten, Konventionen und Einschränkungen angewendet werden müssen; operationGuidance ist eine beratende Eingabe, deren Einträge nur befolgt werden, wenn sie anwendbar und mit dem eingebauten Workflow kompatibel sind. Beide bleiben getrennt von Zustand, Aufgaben, Fortschritt, Kontextdateien und der eingebauten Anweisung.
4.7 instructions archive --json
{ "changeName", "context"?, "operationGuidance"?, "root" }. Erfordert eine gültige --change in der aufgelösten Repo-/Store-Root und verwendet dieselbe Semantik für erforderlichen Kontext/beratende Anleitung wie apply. Dies ist eine schreibgeschützte Oberfläche für Laufzeiteingaben: Sie gibt nicht den statischen Archiv-Workflow zurück, prüft oder führt Delta-Specs nicht zusammen, schreibt keine Haupt-Specs und verschiebt die Änderung nicht.
4.8 new change <name> --json
Erfolg: { "change": { "id", "path", "metadataPath", "schema" }, "root" }. Fehler: { "change": null, "status": [d] }, Exit-Code 1.
4.9 archive <name> --json
Erfolg: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. Fehler: { "archive": null, "root"?, "status": [d] }, Exit-Code 1. specsUpdated ist nur wahr, wenn mindestens eine Spec-Datei geschrieben oder zurückgezogen wurde (eine Fähigkeit, deren letzte Anforderung die Änderung entfernt hat, erhält eine gelöschte Spec, was retire_capabilities: true in der .openspec.yaml der Änderung erfordert; jeder Rückzug wird in warnings benannt, mit einem einfügbaren Git-Wiederherstellungsbefehl nur, wenn die Spec im Checkout des Aufrufers lebte); eine bereits synchronisierte Änderung archiviert mit allen Null-Totals und den in warnings aufgeführten Überspringungen. Der JSON-Modus ist strikt nicht-interaktiv: jeder Eingabeaufforderungspunkt wird zu einem archive_*-Code.
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 (nur bei einem Git-gestützten Store-Checkout mit einem Upstream-Tracking-Ref vorhanden) sind Ahead-/Behind-Zähler gegenüber dem zuletzt abgerufenen Upstream, nicht dem Live-Remote. Gesundheitsbefunde beliebiger Schweregrade beenden mit Exit-Code 0. Fehler-Nutzlast: { "root": null, "store": null, "references": [], "status": [d] }, Exit-Code 1.
4.11 context --json
{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }. VERFÜGBAR = Pfad vorhanden UND Status leer. --code-workspace <path> schreibt {folders:[{name,path}]} (nur verfügbare referenzierte Stores, ref:-Präfixe); im JSON-Modus erfolgt das Schreiben vor dem Drucken, sodass stdout selbst bei Schreibfehlern genau ein Dokument enthält. Fehler: { "root": null, "members": [], "status": [d] }, Exit-Code 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 = unbekannt/nicht geprüft). Gesundheitsbefunde beenden mit Exit-Code 0; Fehler beenden mit Exit-Code 1 und der passenden Null-Form. Abgebrochene Eingabeaufforderung beendet mit Exit-Code 130.
4.13 schemas --json / templates --json
schemas: Erfolg bleibt ein bloßes Array [ {name, description, artifacts, source} ]; es löst die kanonische Root-Auswahl-Priorität auf und akzeptiert --store <id>. Root-Auswahl-Fehler: { "schemas": [], "root": null, "status": [d] }, Exit-Code 1. templates: Schlüsselobjekt { "<artifactId>": {path, source} }, weiterhin cwd-basiert ohne Root-/Status-Schlüssel.
5. Exit-Code-Vertrag
| Situation | Exit-Code | Stdout |
|---|---|---|
| Erfolg, inkl. Gesundheitsbefunde (doctor/context/store doctor) | 0 | das Payload |
Befehlsfehler im --json-Modus | 1 | ein JSON-Dokument mit status: [d] und der Null-Form des Befehls |
validate mit fehlerhaften Elementen | 1 | vollständiger Bericht |
Abbruch durch Eingabeaufforderung (store-Gruppe, menschlicher Modus) | 130 | nur stderr |
6. Katalog der Diagnosecodes
Auflösung
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; Durchreiche: invalid_store_id, invalid_store_registry, invalid_store_metadata.
OpenSpec-Wurzel-Gesundheit (Fehler, keine Behebung)
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ährend der Beta-Phase der Stores können openspec/specs/, openspec/changes/ und openspec/changes/archive/ in einer gesunden Wurzel fehlen; sie sind nur dann Fehler bei der Gesundheitsprüfung, wenn sie vorhanden sind, aber keine Verzeichnisse darstellen.
Store-Registry/Identität/Zustand
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 (Info).
Store-Einrichtung/Registrierung/Entfernung
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 (Warnung beim Entfernen, Fehler beim doctor), store_root_not_directory.
Store-git
store_git_init_failed, store_git_identity_missing, store_git_commit_failed, store_git_no_commits (Warnung), store_clone_fragile_directories (Warnung), store_remote_divergence (Info, doctor), store_checkout_drift (Info, doctor).
Referenzen (Warnung)
reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.
Beziehungen (Warnung; doctor; Context behält nur den Registry-Eintrag)
relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.
Archiv (JSON-Modus)
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-Schreibvorgänge
context_file_exists, context_output_dir_missing.
Fallbacks
doctor_failed, context_failed, store_error, change_error, archive_error.
Bekannte Inkonsistenzen
Aufgezeichnet durch die Capstone-Audit; Umbenennungen veröffentlichter Schlüssel sind Produktentscheidungen, die bis nach dieser Veröffentlichung verschoben wurden:
ImBehoben in der Capstone-Gauntlet-Runde:--json-Modus gaben mehrere Fehlerpfade nur stderr aus, ohne ein JSON-Dokument zu erzeugen.show/validatefür unbekannte und mehrdeutige Elemente geben{status:[{code: unknown_item | ambiguous_item, ...}]}aus; geworfene Fehler instatus/instructions/list/show/validatewerden über den JSON-bewusste Fehler-Helfer geleitet (die Null-Form des Befehls +status);store <unbekannter Unterbefehl> --jsongibt{status:[{code: unknown_store_subcommand}]}aus;listbehält seine Null-Form{changes|specs: [], root: null}bei Auflösungsfehlern bei.store_root_missingwird mit zwei Schweregraden ausgegeben (Warnung beim Entfernen, Fehler im store doctor) — kontextabhängig, oben dokumentiert.- snake_case (Store-Familie) vs. camelCase (Workflow-Familie) bei der Schlüsselbenennung;
root.store_idist überall snake_case. - Es existieren vier parallele Envelope-Typdeklarationen im src; Archivdiagnosen tragen niemals
target. list --jsonverwendet denstatus-Schlüssel als String-Enum pro Änderung.- Nur die Ausgabe von
validateenthält einversion-Feld. templatesignoriert die Wurzelauswahl (cwd-basiert, kein--store).- Veraltete Nomenformen (
change/spec-Unterbefehle) geben unumhüllte Payloads ohneroot/status` aus.