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) gebruikencamelCase, met uitzondering van het ingebedderoot-object, dat altijdstore_idgebruikt. - Optionele sleutels worden weggelaten, niet op null gezet, in de meeste payloads (bijv.
root.store_id,member.path). Uitzonderingen die explicietnullgebruiken worden per structuur vermeld (store doctorgit.*, fout-payloads).
2. De diagnostische envelop
Eén envelopvorm wordt gedeeld door elke machine-leesbare diagnose (StoreDiagnostic):
{
"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:
--store <id>→ de root van de geregistreerde store (source: "store").- Anders, dichtstbijzijnde voorouder met
openspec/: planningsvorm →source: "nearest"(eenstore:-pointer wordt genegeerd met een stderr-waarschuwing); alleen-config-map met een geldigestore:-pointer → die store,source: "declared". - Geen dichtstbijzijnde root + globale
defaultStoreingesteld (openspec config set defaultStore <id>) → die store,source: "global_default"; een verouderde id faalt met de onderliggende store-fout en eenfixdieopenspec config unset defaultStorenoemt. - Geen dichtstbijzijnde root, geen standaard + geregistreerde stores bestaan → fout
no_root_with_registered_stores. - Geen root, geen standaard, geen stores: commando's mogen de cwd behandelen als
source: "implicit";doctor,context,listen bulkvalidatefalen in plaats daarvan metno_openspec_root.listbehoudt de impliciete fallback voor legacy-projecten metopenspec/project.md.
Succesvolle JSON-payloads bevatten normaal de root; succesvol schemas --json blijft bewust een compatibele kale array, gedocumenteerd in §4.13:
"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
| Situatie | Exit | Stdout |
|---|---|---|
| Succes, incl. gezondheidsbevindingen (doctor/context/store doctor) | 0 | de payload |
Commando-fout in --json-modus | 1 | één JSON-document met status: [d] en de null-shape van het commando |
validate met mislukte items | 1 | volledig rapport |
Prompt geannuleerd (store-groep, interactieve modus) | 130 | alleen 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:
InOpgelost in de eindaudit-gauntletronde: onbekende en ambiguë items in--json-modus gaven verschillende foutpaden alleen stderr uit zonder JSON-document.show/validategeven{status:[{code: unknown_item | ambiguous_item, ...}]}uit; gegooid fouten instatus/instructions/list/show/validategaan via de JSON-bewuste fouthelper (de null-shape van het commando +status);store <unknown subcommand> --jsongeeft{status:[{code: unknown_store_subcommand}]}uit;listdraagt zijn{changes|specs: [], root: null}null-shape bij resolutiefouten.store_root_missingwordt uitgegeven met twee ernstniveaus (waarschuwing bij verwijderen, fout bij store doctor) — contextafhankelijk, hierboven gedocumenteerd.- snake_case (store-familie) vs camelCase (workflow-familie) sleutelnotatie;
root.store_idis overal snake_case. - Er bestaan vier parallelle envelop-typeverklaringen in src; archiefdiagnostiek draagt nooit
target. list --jsonhergebruikt destatus-sleutel als een string-enum per wijziging.- Alleen de
validate-uitvoer bevat eenversion-veld. templatesnegeert rootselectie (cwd-gebaseerd, geen--store).- Verouderde zelfstandignaamvormen (
change/spec-ondercommando's) geven payloads zonder envelop uit zonderroot/status.