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) utilizzanocamelCase, ad eccezione dell'oggettorootincorporato, che utilizza semprestore_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 esplicitamentenullvengono 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):
{
"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:
--store <id>→ la radice del registro store (source: "store").- Altrimenti, il più vicino antenato con
openspec/: forma di pianificazione →source: "nearest"(un puntatorestore:viene ignorato con un avviso su stderr); directory solo di configurazione con un puntatorestore:valido → quel negozio,source: "declared". - Nessuna radice vicina +
defaultStoreglobale impostato (openspec config set defaultStore <id>) → quel negozio,source: "global_default"; un id obsoleto fallisce con l'errore del negozio sottostante e unfixche nominaopenspec config unset defaultStore. - Nessuna radice vicina, nessun default + negozi registrati esistenti → errore
no_root_with_registered_stores. - Nessuna radice, nessun default, nessun negozio: i comandi possono trattare la cwd come
source: "implicit";doctor,context,listevalidatein bulk falliscono invece conno_openspec_root.listpreserva il fallback implicito per progetti legacy conopenspec/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:
"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
| Situazione | Uscita | Stdout |
|---|---|---|
| Successo, inclusi i risultati di integrità (doctor/context/store doctor) | 0 | il payload |
Fallimento del comando in modalità --json | 1 | un documento JSON con status: [d] e la forma nulla del comando |
validate con elementi non validi | 1 | report completo |
Annullamento del prompt (gruppo store, modalità umana) | 130 | solo 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:
In modalitàCorretto nel round di prova capstone: gli elementi sconosciuti e ambigui di--json, diversi percorsi di fallimento stampavano solo stderr senza alcun documento JSON.show/validateemettono{status:[{code: unknown_item | ambiguous_item, ...}]}; gli errori lanciati instatus/instructions/list/show/validatevengono instradati attraverso l'helper consapevole del JSON (la forma nulla del comando +status);store <sottocomando sconosciuto> --jsonemette{status:[{code: unknown_store_subcommand}]};listporta la sua forma nulla{changes|specs: [], root: null}in caso di fallimenti di risoluzione.store_root_missingviene emesso con due gravità (avviso nella rimozione, errore nel store doctor) — dipendente dal contesto, documentato sopra.- casing delle chiavi snake_case (famiglia store) vs camelCase (famiglia workflow);
root.store_idè in snake_case ovunque. - Esistono quattro dichiarazioni di tipi di busta parallele in src; i diagnostici di archivio non portano mai
target. list --jsonriutilizza la chiavestatuscome un'enumerazione di stringhe per ogni modifica.- Solo l'output di
validateinclude un campoversion. templatesignora la selezione della root (basata sulla directory corrente, senza--store).- Le forme nominali deprecate (sottocomandi
change/spec) emettono payload non incapsulati senzaroot/status.