Skip to content

OpenSpec Agent Contract ​

Machine-leesbare interfaces van de openspec CLI, geverifieerd tegen src/ (capstone-audit, 2026-06-11). Elke structuur hieronder is gedocumenteerd op basis van de code die deze genereert.

1. Algemene conventies ​

  • Eén JSON-document per aanroep. In --json-modus bevat stdout exact één JSON-document (2-spaciaal pretty-geprint). Menselijke tekst, spinners en de store-banner gaan naar stderr.
  • Store-banner. In menselijke modus print een geselecteerde store-root Using OpenSpec root: <id> (<path>) naar stderr. Wordt nooit geprint in JSON-modus.
  • Sleutelnotatie is afhankelijk van het oppervlak (zie Bekende inconsistenties): store/doctor/context-payloads gebruiken snake_case; workflow-payloads (status, instructions, new change, validate, list) gebruiken camelCase, met uitzondering van het ingebedde root-object, dat altijd store_id gebruikt.
  • Optionele sleutels worden weggelaten, niet op null gezet, in de meeste payloads (bijv. root.store_id, member.path). Uitzonderingen die expliciet null gebruiken worden per structuur vermeld (store doctor git.*, fout-payloads).

2. De diagnostische envelop ​

Eén envelopvorm wordt gedeeld door elke machine-leesbare diagnose (StoreDiagnostic):

json
{
  "severity": "error" | "warning" | "info",
  "code": "snake_case_string",
  "message": "human sentence",
  "target": "dotted.surface (optional)",
  "fix": "one actionable sentence/command (optional)"
}

Diagnoses verschijnen op twee posities: status-arrays (status: StoreDiagnostic[] op topniveau of per entry) voor gezondheidsbevindingen, en gegooide fouten omgezet naar een één-element status-array bij commandofaal.

3. Root-selectie en RootOutput ​

Alle root-bepalende commando's (list, show, validate, status, instructions, instructions apply, instructions archive, new change, archive, doctor, context, schemas) lossen één OpenSpec-root op met één prioriteit:

  1. --store <id> → de root van de geregistreerde store (source: "store").
  2. Anders, dichtstbijzijnde voorouder met openspec/: planningsvorm → source: "nearest" (een store:-pointer wordt genegeerd met een stderr-waarschuwing); alleen-config-map met een geldige store:-pointer → die store, source: "declared".
  3. Geen dichtstbijzijnde root + globale defaultStore ingesteld (openspec config set defaultStore <id>) → die store, source: "global_default"; een verouderde id faalt met de onderliggende store-fout en een fix die openspec config unset defaultStore noemt.
  4. Geen dichtstbijzijnde root, geen standaard + geregistreerde stores bestaan → fout no_root_with_registered_stores.
  5. Geen root, geen standaard, geen stores: commando's mogen de cwd behandelen als source: "implicit"; doctor, context, list en bulk validate falen in plaats daarvan met no_openspec_root. list behoudt de impliciete fallback voor legacy-projecten met openspec/project.md.

Succesvolle JSON-payloads bevatten normaal de root; succesvol schemas --json blijft bewust een compatibele kale array, gedocumenteerd in §4.13:

json
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (only when store-selected)" }

Root-foutcontract: in JSON-modus drukt een resolutiefout { ...commandNullShape, "status": [diagnostic] } uit op stdout en sluit af met exitcode 1.

4. JSON-vormen van commando's ​

4.1 list --json ​

{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — let op: de per-change status is hier een string-enum. --specs: { "specs": [ { "id", "requirementCount" } ], "root" }.

4.2 show <item> --json ​

Change: { "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 1 wanneer een item faalt.

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 betekent dat elk niet-overgeslagen planningsartefact bestaat; overgeslagen artefacten tellen als voldaan zonder te worden gemaakt. Het betekent niet dat implementatietaken zijn voltooid. isComplete wordt behouden als een compatibiliteitsalias met dezelfde waarde. Elk artefact's requires is de directe afhankelijkheden (aanwezig voor elke status, zodat de transitieve vereiste set berekenbaar is zelfs wanneer het artefact done is); missingDeps verschijnt alleen wanneer blocked. De artifacts-array is in afhankelijkheidsvolgorde, met de artifacts:-declaratievolgorde van het schema als tiebreaker tussen artefacten die tegelijkertijd gereed worden (nooit alfabetisch), dus de eerste ready-entry is het artefact om als volgende te schrijven; missingDeps gebruikt dezelfde volgorde. "skipped" markeert een artefact waarvan het generates-pad onder specs/ ligt in een change waarvan .openspec.yaml skip_specs: true declareert; het voldoet aan afhankelijkheden maar mag niet worden gemaakt. Geen actieve changes: { "changes": [], "message", "root" }, exit 0.

--all (batch, onderling uitsluitend met --change — combineren is een fout met de nul-vorm { "changes": [], "root": null, "status": [d] }): { "changes": [ <per-change statusobject, geen per-change root>, ... ], "root" }, gesorteerd op changenaam. Een change die niet kan laden draagt { "changeName", "status": [d] } bij ter plaatse; de scan gaat verder, behoudt de volledige envelop en eindigt met exit 1 in zowel tekst- als JSON-modus. Een ongeldig --schema faalt de hele aanroep met de nul-vorm, zelfs wanneer er geen changes bestaan.

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 vermeldt de artefacten die dit artefact gereed maakt, in de declaratievolgorde van het schema (dezelfde volgorde die status aanbeveelt). "skipped": true (met "warning") verschijnt wanneer de change skip_specs: true declareert en dit artefact wordt overgeslagen — maak de bestanden niet aan. Een afhankelijkheidsentry met skipped: true is voldaan zonder bestanden — probeer de paden niet te lezen.

ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — opgeloste entries bevatten root/specs/fetch; niet-opgeloste bevatten store_id + waarschuwing-status. Index gemaximeerd op 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 is waarop apply blokkeert (de apply.requires van het schema); missingPrerequisites is alles wat nog gebouwd moet worden voordat apply kan draaien, in bouwvolgorde - de transitieve afsluiting van die requires, dus het kan de langere lijst zijn. warnings toont niet-blokkerende problemen met de change zelf - vandaag, een change die klaar is om te implementeren met geen delta-specs en geen skip_specs: true, de staat die openspec validate afwijst. Beide optionele root-velden (context, operationGuidance) worden bij elke aanroep gelezen van de geselecteerde root. context is een verplichte prompt-niveau-invoer waarvan relevante projectfeiten, conventies en beperkingen moeten worden toegepast; operationGuidance is adviserende invoer waarvan de vermeldingen alleen worden opgevolgd wanneer ze toepasbaar en compatibel zijn met de ingebouwde workflow. Beide blijven gescheiden van staat, taken, voortgang, contextbestanden en de ingebouwde instructie.

4.7 instructions archive --json ​

{ "changeName", "context"?, "operationGuidance"?, "root" }. Vereist een geldige --change in de opgeloste repo/store-root en gebruikt dezelfde verplichte-context/advies-guidance-semantiek als apply. Dit is een alleen-lezen runtime-invoeroppervlak: het retourneert niet de statische archiefworkflow, inspecteert of voegt delta-specs samen, schrijft hoofd-specs of verplaatst de change.

4.8 new change <name> --json ​

Succes: { "change": { "id", "path", "metadataPath", "schema" }, "root" }. Fout: { "change": null, "status": [d] }, exit 1.

4.9 archive <name> --json ​

Succes: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. Fout: { "archive": null, "root"?, "status": [d] }, exit 1. specsUpdated is alleen waar wanneer minstens één specbestand is geschreven of gepensioneerd (een capaciteit waarvan de laatste vereiste door de change is verwijderd, heeft zijn spec verwijderd, wat retire_capabilities: true vereist in de .openspec.yaml van de change; elke pensionering wordt genoemd in warnings, met een plakbaar Git-herstelcommando alleen wanneer de spec in de checkout van de aanroeper leefde); een al gesynchroniseerde change archiveert met allemaal nul totalen en de overslaan vermeld in warnings. JSON-modus is strikt niet-interactief: elk promptpunt wordt een 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 (alleen aanwezig voor een git-backed store-checkout met een upstream tracking ref) is ahead/behind-tellingen tegen de laatst opgehaalde upstream, niet de live remote. Gezondheidsbevindingen van elke ernst eindigen met exit 0. Foutpayload: { "root": null, "store": null, "references": [], "status": [d] }, exit 1.

4.11 context --json ​

{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }. BESCHIKBAAR = pad aanwezig EN status leeg. --code-workspace <path> schrijft {folders:[{name,path}]} (alleen beschikbare referentiestores, ref:-prefixen); in JSON-modus gebeurt het schrijven voordat er geprint wordt, zodat stdout precies één document bevat, zelfs bij schrijffout. Fout: { "root": null, "members": [], "status": [d] }, exit 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 = onbekend/niet onderzocht). Gezondheidsbevindingen eindigen met exit 0; fouten eindigen met exit 1 met de bijpassende nul-vorm. Prompt-annulering eindigt met exit 130.

4.13 schemas --json / templates --json ​

schemas: succes blijft een kale array [ {name, description, artifacts, source} ]; het lost de canonieke root-selectieprioriteit op en accepteert --store <id>. Root-selectiefout: { "schemas": [], "root": null, "status": [d] }, exit 1. templates: object met sleutels { "<artifactId>": {path, source} }, nog steeds gebaseerd op de cwd en zonder root/status-sleutels.

5. Exit-code-contract ​

SituatieExitStdout
Succes, incl. gezondheidsbevindingen (doctor/context/store doctor)0de payload
Commando-fout in --json-modus1één JSON-document met status: [d] en de null-shape van het commando
validate met mislukte items1volledig rapport
Prompt geannuleerd (store-groep, interactieve modus)130alleen stderr

6. Catalogus diagnostische codes ​

Resolutie ​

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; pass-through: invalid_store_id, invalid_store_registry, invalid_store_metadata.

OpenSpec-root gezondheid (fout, geen correctie) ​

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. Tijdens de stores-beta kunnen openspec/specs/, openspec/changes/ en openspec/changes/archive/ afwezig zijn in een gezonde root; ze zijn alleen gezondheidsfouten wanneer ze aanwezig zijn maar geen mappen zijn.

Store-registratie/identiteit/status ​

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-opzet/registratie/verwijdering ​

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 (waarschuwing bij verwijderen, fout bij doctor), store_root_not_directory.

Store-git ​

store_git_init_failed, store_git_identity_missing, store_git_commit_failed, store_git_no_commits (waarschuwing), store_clone_fragile_directories (waarschuwing), store_remote_divergence (info, doctor), store_checkout_drift (info, doctor).

Verwijzingen (waarschuwing) ​

reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.

Relaties (waarschuwing; doctor; context behoudt alleen de registratie) ​

relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.

Archief (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-schrijvingen ​

context_file_exists, context_output_dir_missing.

Terugvalopties ​

doctor_failed, context_failed, store_error, change_error, archive_error.

Bekende inconsistenties ​

Geregistreerd door de eindaudit; hernoemingen van gepubliceerde sleutels zijn productbeslissingen die zijn uitgesteld tot na deze release:

  1. In --json-modus gaven verschillende foutpaden alleen stderr uit zonder JSON-document. Opgelost in de eindaudit-gauntletronde: onbekende en ambiguë items in show/validate geven {status:[{code: unknown_item | ambiguous_item, ...}]} uit; gegooid fouten in status/instructions/list/show/validate gaan via de JSON-bewuste fouthelper (de null-shape van het commando + status); store <unknown subcommand> --json geeft {status:[{code: unknown_store_subcommand}]} uit; list draagt zijn {changes|specs: [], root: null} null-shape bij resolutiefouten.
  2. store_root_missing wordt uitgegeven met twee ernstniveaus (waarschuwing bij verwijderen, fout bij store doctor) — contextafhankelijk, hierboven gedocumenteerd.
  3. snake_case (store-familie) vs camelCase (workflow-familie) sleutelnotatie; root.store_id is overal snake_case.
  4. Er bestaan vier parallelle envelop-typeverklaringen in src; archiefdiagnostiek draagt nooit target.
  5. list --json hergebruikt de status-sleutel als een string-enum per wijziging.
  6. Alleen de validate-uitvoer bevat een version-veld.
  7. templates negeert rootselectie (cwd-gebaseerd, geen --store).
  8. Verouderde zelfstandignaamvormen (change/spec-ondercommando's) geven payloads zonder envelop uit zonder root/status.