Skip to content

Contratto dell'agente OpenSpec ​

Superfici leggibili dalla macchina della CLI openspec, verificate rispetto a src/ (audit capstone, 2026-06-11). Ogni struttura di seguito è documentata a partire dal codice che la genera.

1. Convenzioni generali ​

  • Un documento JSON per invocazione. In modalità --json, stdout contiene esattamente un documento JSON (formattato in modo leggibile con indentazione a 2 spazi). Il testo descrittivo per gli esseri umani, i spinner e l'intestazione dello store vengono inviati a stderr.
  • Intestazione dello store. In modalità testuale, una radice selezionata dallo store stampa Using OpenSpec root: <id> (<path>) su stderr. Non viene mai stampata in modalità JSON.
  • La convenzione delle maiuscole/minuscole delle chiavi dipende dalla superficie (vedi Incongruenze note): i payload di store/doctor/context utilizzano snake_case; i payload del workflow (status, instructions, new change, validate, list) utilizzano camelCase, ad eccezione dell'oggetto root incorporato, che utilizza sempre store_id.
  • Le chiavi opzionali sono omesse, non impostate su null, nella maggior parte dei payload (ad esempio root.store_id, member.path). Le eccezioni che utilizzano esplicitamente null vengono segnalate per ogni struttura (git.* di store doctor, payload di errore).

2. L'involucro diagnostico ​

Una forma di involucro è condivisa da ogni diagnostica leggibile da macchina (StoreDiagnostic):

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

Le diagnostiche appaiono in due posizioni: array di stato (status: StoreDiagnostic[] a livello superiore o per voce) per i risultati di salute, e errori lanciati convertiti in un array status di un singolo elemento in caso di fallimento del comando.

3. Selezione della radice e RootOutput ​

Tutti i comandi che risolvono la radice (list, show, validate, status, instructions, instructions apply, instructions archive, new change, archive, doctor, context, schemas) risolvono una radice OpenSpec con una precedenza:

  1. --store <id> → la radice del registro store (source: "store").
  2. Altrimenti, il più vicino antenato con openspec/: forma di pianificazione → source: "nearest" (un puntatore store: viene ignorato con un avviso su stderr); directory solo di configurazione con un puntatore store: valido → quel negozio, source: "declared".
  3. Nessuna radice vicina + defaultStore globale impostato (openspec config set defaultStore <id>) → quel negozio, source: "global_default"; un id obsoleto fallisce con l'errore del negozio sottostante e un fix che nomina openspec config unset defaultStore.
  4. Nessuna radice vicina, nessun default + negozi registrati esistenti → errore no_root_with_registered_stores.
  5. Nessuna radice, nessun default, nessun negozio: i comandi possono trattare la cwd come source: "implicit"; doctor, context, list e validate in bulk falliscono invece con no_openspec_root. list preserva il fallback implicito per progetti legacy con openspec/project.md.

I payload JSON di successo normalmente incorporano la radice; schemas --json di successo rimane deliberatamente il bare array di compatibilità documentato in §4.13:

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

Contratto di fallimento della radice: in modalità JSON un fallimento di risoluzione stampa { ...commandNullShape, "status": [diagnostic] } su stdout ed esce con codice 1.

4. Forme JSON dei comandi ​

4.1 list --json ​

{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — nota che lo status per modifica è un enum di stringhe qui. --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" }. Esce con codice 1 quando qualsiasi elemento fallisce.

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 significa che ogni artefatto di pianificazione non saltato esiste; gli artefatti saltati contano come soddisfatti senza essere creati. Non significa che i compiti di implementazione siano completi. isComplete è mantenuto come alias di compatibilità con lo stesso valore. Il campo requires di ogni artefatto è costituito dagli id delle sue dipendenze dirette (presente per ogni stato, quindi l'insieme transitivo richiesto è calcolabile anche quando l'artefatto è done); missingDeps appare solo quando è blocked. L'array artifacts è in ordine di dipendenza, con l'ordine di dichiarazione artifacts: dello schema che rompe i legami tra artefatti che diventano pronti allo stesso tempo (mai alfabetico), quindi il primo elemento ready è l'artefatto da scrivere successivamente; missingDeps usa lo stesso ordine. "skipped" segna un artefatto il cui percorso generates è sotto specs/ in una modifica il cui .openspec.yaml dichiara skip_specs: true; soddisfa le dipendenze ma non deve essere creato. Nessuna modifica attiva: { "changes": [], "message", "root" }, esce con codice 0.

--all (batch, mutuamente esclusivo con --change — combinarli è un errore con la null-shape { "changes": [], "root": null, "status": [d] }): { "changes": [ <oggetto di stato per modifica, senza radice per modifica>, ... ], "root" }, ordinato per nome di modifica. Una modifica che non riesce a caricare contribuisce con { "changeName", "status": [d] } al suo posto; la scansione continua, preserva l'involucro completo ed esce con codice 1 sia in modalità testo che JSON. Un --schema non valido fa fallire l'intera invocazione con la null-shape, anche quando non esistono modifiche.

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 elenca gli artefatti che questo rende pronti, nell'ordine di dichiarazione dello schema (lo stesso ordine che status raccomanda). "skipped": true (con "warning") appare quando la modifica dichiara skip_specs: true e questo artefatto è saltato — non crearne i file. Una voce di dipendenza con skipped: true è soddisfatta senza file — non tentare di leggere i suoi percorsi.

ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — le voci risolte portano root/specs/fetch; quelle non risolte portano store_id + stato di avviso. Indice limitato a 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 è ciò su cui apply si blocca (il apply.requires dello schema); missingPrerequisites è tutto ciò che resta da costruire prima che apply possa essere eseguito, in ordine di costruzione — la chiusura transitiva di quei requires, quindi può essere la lista più lunga. warnings elenca problemi non bloccanti con la modifica stessa — oggi, una modifica pronta per l'implementazione senza delta spec e senza skip_specs: true, lo stato che openspec validate rifiuta. Entrambi i campi root opzionali (context, operationGuidance) vengono letti dalla radice selezionata a ogni invocazione. context è un input obbligatorio a livello di prompt i cui fatti rilevanti del progetto, convenzioni e vincoli devono essere applicati; operationGuidance è input consultivo le cui voci vengono seguite solo quando applicabili e compatibili con il workflow integrato. Entrambi rimangono separati da stato, compiti, progresso, file di contesto e dall'istruzione integrata.

4.7 instructions archive --json ​

{ "changeName", "context"?, "operationGuidance"?, "root" }. Richiede un --change valido nella root del repository/store risolto e usa le stesse semantiche di contesto richiesto/guida consultiva di apply. Questa è una superficie di input runtime di sola lettura: non restituisce il workflow di archivio statico, non ispeziona o fonde delta spec, non scrive spec principali né sposta la modifica.

4.8 new change <name> --json ​

Successo: { "change": { "id", "path", "metadataPath", "schema" }, "root" }. Fallimento: { "change": null, "status": [d] }, esce con codice 1.

4.9 archive <name> --json ​

Successo: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. Fallimento: { "archive": null, "root"?, "status": [d] }, esce con codice 1. specsUpdated è vero solo quando almeno un file di spec è stato scritto o ritirato (una capacità di cui la modifica ha rimosso l'ultimo requisito ha il suo spec cancellato, il che richiede retire_capabilities: true nel .openspec.yaml della modifica; ogni ritiro è nominato in warnings, con un comando Git di recupero incollabile solo quando lo spec viveva nel checkout del chiamante); una modifica già sincronizzata archivia con totali tutti zero e i salti elencati in warnings. La modalità JSON è strettamente non interattiva: ogni punto di prompt diventa un codice 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 (presente solo per un checkout di store basato su git che ha un riferimento di tracciamento upstream) è il numero di commit avanti/indietro rispetto all'ultimo upstream recuperato, non al remote live. I risultati di salute di qualsiasi gravità escono con codice 0. Payload di fallimento: { "root": null, "store": null, "references": [], "status": [d] }, esce con codice 1.

4.11 context --json ​

{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }. DISPONIBILE = percorso presente E stato vuoto. --code-workspace <path> scrive {folders:[{name,path}]} (solo store referenziati disponibili, prefissi ref:); in modalità JSON la scrittura avviene prima della stampa così stdout contiene esattamente un documento anche in caso di fallimento di scrittura. Fallimento: { "root": null, "members": [], "status": [d] }, esce con codice 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 = sconosciuto/non sondato). I risultati di salute escono con codice 0; i fallimenti escono con codice 1 con la null-shape corrispondente. L'annullamento del prompt esce con codice 130.

4.13 schemas --json / templates --json ​

schemas: il successo rimane un array nudo [ {name, description, artifacts, source} ]; risolve la precedenza canonica di selezione della radice e accetta --store <id>. Fallimento di selezione della radice: { "schemas": [], "root": null, "status": [d] }, esce con codice 1. templates: oggetto con chiavi { "<artifactId>": {path, source} }, ancora basato sulla cwd senza chiavi root/status.

5. Contratto dei codici di uscita ​

SituazioneUscitaStdout
Successo, inclusi i risultati di integrità (doctor/context/store doctor)0il payload
Fallimento del comando in modalità --json1un documento JSON con status: [d] e la forma nulla del comando
validate con elementi non validi1report completo
Annullamento del prompt (gruppo store, modalità umana)130solo stderr

6. Catalogo dei codici diagnostici ​

Risoluzione ​

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.

Integrità della root OpenSpec (errore, nessuna correzione) ​

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. Durante la beta di stores, openspec/specs/, openspec/changes/ e openspec/changes/archive/ possono essere assenti in una root integra; sono errori di integrità solo quando presenti ma non directory.

Registro/identità/stato dello store ​

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).

Configurazione/registrazione/rimozione dello store ​

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 (avviso nella rimozione, errore nel doctor), store_root_not_directory.

Git dello store ​

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

Riferimenti (avviso) ​

reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.

Relazioni (avviso; doctor; context conserva solo quella del registro) ​

relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.

Archivio (modalità 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.

Scritture del contesto ​

context_file_exists, context_output_dir_missing.

Fallback ​

doctor_failed, context_failed, store_error, change_error, archive_error.

Incoerenze note ​

Registrate dall’audit capstone; le ridenominazioni delle chiavi pubblicate sono decisioni di prodotto rimandate dopo questa release:

  1. In modalità --json, diversi percorsi di fallimento stampavano solo stderr senza alcun documento JSON. Corretto nel round di prova capstone: gli elementi sconosciuti e ambigui di show/validate emettono {status:[{code: unknown_item | ambiguous_item, ...}]}; gli errori lanciati in status/instructions/list/show/validate vengono instradati attraverso l'helper consapevole del JSON (la forma nulla del comando + status); store <sottocomando sconosciuto> --json emette {status:[{code: unknown_store_subcommand}]}; list porta la sua forma nulla {changes|specs: [], root: null} in caso di fallimenti di risoluzione.
  2. store_root_missing viene emesso con due gravità (avviso nella rimozione, errore nel store doctor) — dipendente dal contesto, documentato sopra.
  3. casing delle chiavi snake_case (famiglia store) vs camelCase (famiglia workflow); root.store_id è in snake_case ovunque.
  4. Esistono quattro dichiarazioni di tipi di busta parallele in src; i diagnostici di archivio non portano mai target.
  5. list --json riutilizza la chiave status come un'enumerazione di stringhe per ogni modifica.
  6. Solo l'output di validate include un campo version.
  7. templates ignora la selezione della root (basata sulla directory corrente, senza --store).
  8. Le forme nominali deprecate (sottocomandi change/spec) emettono payload non incapsulati senza root/status.