Skip to content

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) verwenden camelCase, außer dem eingebetteten root-Objekt, das immer store_id verwendet.
  • Optionale Schlüssel werden weggelassen, nicht auf null gesetzt, in den meisten Payloads (z. B. root.store_id, member.path). Ausnahmen, die explizites null verwenden, werden pro Form genannt (Store Doctor git.*, Fehler-Payloads).

2. Die Diagnose-Hülle ​

Eine Hüllenform wird von jeder maschinenlesbaren Diagnose (StoreDiagnostic) gemeinsam genutzt:

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

  1. --store <id> → die Root des registrierten Stores (source: "store").
  2. Andernfalls der nächste Vorfahre mit openspec/: Planungsform → source: "nearest" (ein store:-Zeiger wird mit einer Stderr-Warnung ignoriert); reines Konfigurationsverzeichnis mit gültigem store:-Zeiger → dieser Store, source: "declared".
  3. 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 einem fix, der openspec config unset defaultStore benennt.
  4. Kein nächster Vorfahre, keine Standardeinstellung + registrierte Stores vorhanden → Fehler no_root_with_registered_stores.
  5. Keine Root, keine Standardeinstellung, keine Stores: Befehle können das aktuelle Arbeitsverzeichnis als source: "implicit" behandeln; doctor, context, list und Bulk-validate schlagen stattdessen mit no_openspec_root fehl. list behält den impliziten Fallback für Altprojekte mit openspec/project.md bei.

Erfolgreiche JSON-Nutzlasten betten normalerweise die Root ein; erfolgreiches schemas --json bleibt bewusst das in §4.13 dokumentierte Kompatibilitäts-Basisarray:

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

SituationExit-CodeStdout
Erfolg, inkl. Gesundheitsbefunde (doctor/context/store doctor)0das Payload
Befehlsfehler im --json-Modus1ein JSON-Dokument mit status: [d] und der Null-Form des Befehls
validate mit fehlerhaften Elementen1vollständiger Bericht
Abbruch durch Eingabeaufforderung (store-Gruppe, menschlicher Modus)130nur 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:

  1. Im --json-Modus gaben mehrere Fehlerpfade nur stderr aus, ohne ein JSON-Dokument zu erzeugen. Behoben in der Capstone-Gauntlet-Runde: show/validate für unbekannte und mehrdeutige Elemente geben {status:[{code: unknown_item | ambiguous_item, ...}]} aus; geworfene Fehler in status/instructions/list/show/validate werden über den JSON-bewusste Fehler-Helfer geleitet (die Null-Form des Befehls + status); store <unbekannter Unterbefehl> --json gibt {status:[{code: unknown_store_subcommand}]} aus; list behält seine Null-Form {changes|specs: [], root: null} bei Auflösungsfehlern bei.
  2. store_root_missing wird mit zwei Schweregraden ausgegeben (Warnung beim Entfernen, Fehler im store doctor) — kontextabhängig, oben dokumentiert.
  3. snake_case (Store-Familie) vs. camelCase (Workflow-Familie) bei der Schlüsselbenennung; root.store_id ist überall snake_case.
  4. Es existieren vier parallele Envelope-Typdeklarationen im src; Archivdiagnosen tragen niemals target.
  5. list --json verwendet den status-Schlüssel als String-Enum pro Änderung.
  6. Nur die Ausgabe von validate enthält ein version-Feld.
  7. templates ignoriert die Wurzelauswahl (cwd-basiert, kein --store).
  8. Veraltete Nomenformen (change/spec-Unterbefehle) geben unumhüllte Payloads ohne root/status` aus.