Skip to content

Contrato del agente de OpenSpec ​

Superficies legibles por máquina de la CLI openspec, verificadas contra src/ (auditoría de cierre, 2026-06-11). Cada forma documentada a continuación proviene del código emisor.

1. Convenciones generales ​

  • Un documento JSON por invocación. En el modo --json, stdout contiene exactamente un documento JSON (formateado con 2 espacios). El texto humano, los indicadores de progreso y el banner del almacén se envían a stderr.
  • Banner del almacén. En el modo humano, una raíz seleccionada del almacén imprime Using OpenSpec root: <id> (<path>) en stderr. Nunca se imprime en el modo JSON.
  • La capitalización de las claves depende de la superficie (véase Inconsistencias conocidas): los payloads de store/doctor/context usan snake_case; los payloads de workflow (status, instructions, new change, validate, list) usan camelCase, excepto el objeto root incrustado, que siempre usa store_id.
  • Las claves opcionales se omiten, no se establecen en null, en la mayoría de los payloads (p. ej., root.store_id, member.path). Las excepciones que usan null explícito se señalan por cada forma (store doctor git.*, payloads de falla).

2. El envoltorio de diagnóstico ​

Toda máquina legible de diagnóstico (StoreDiagnostic) comparte una misma forma de envoltorio:

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

Los diagnósticos aparecen en dos posiciones: arreglos de estado (status: StoreDiagnostic[] a nivel superior o por entrada) para hallazgos de salud, y errores lanzados convertidos en un arreglo de status de un solo elemento cuando falla un comando.

3. Selección de raíz y RootOutput ​

Todos los comandos que resuelven la raíz (list, show, validate, status, instructions, instructions apply, instructions archive, new change, archive, doctor, context, schemas) resuelven una sola raíz de OpenSpec con una precedencia:

  1. --store <id> → la raíz del almacén registrado (source: "store").
  2. De lo contrario, el ancestro más cercano con openspec/: forma de planificación → source: "nearest" (un puntero store: se ignora con una advertencia en stderr); un directorio solo de configuración con un puntero store: válido → ese almacén, source: "declared".
  3. Sin raíz más cercana + defaultStore global establecido (openspec config set defaultStore <id>) → ese almacén, source: "global_default"; un id obsoleto falla con el error del almacén subyacente y un fix que nombra openspec config unset defaultStore.
  4. Sin raíz más cercana, sin predeterminado + almacenes registrados existentes → error no_root_with_registered_stores.
  5. Sin raíz, sin predeterminado, sin almacenes: los comandos pueden tratar el cwd como source: "implicit"; doctor, context, list y validate para múltiples elementos fallan con no_openspec_root en su lugar. list conserva el fallback implícito para proyectos heredados con openspec/project.md.

Los payloads JSON exitosos normalmente incrustan la raíz; schemas --json exitoso deliberadamente permanece como el arreglo desnudo de compatibilidad documentado en §4.13:

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

Contrato de fallo de raíz: en modo JSON, una falla de resolución imprime { ...commandNullShape, "status": [diagnostic] } en stdout y sale con código 1.

4. Formas JSON de los comandos ​

4.1 list --json ​

{ "changes": [ { "name", "completedTasks", "totalTasks", "lastModified", "status": "no-tasks"|"complete"|"in-progress" } ], "root": RootOutput } — nota que el status por cambio es un enum de cadena aquí. --specs: { "specs": [ { "id", "requirementCount" } ], "root" }.

4.2 show <item> --json ​

Cambio: { "id", "title", "deltaCount", "deltas": [...], "root" }. Especificación: { "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" }. Sale con código 1 cuando falla algún elemento.

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 que todo artefacto de planificación no omitido existe; los artefactos omitidos cuentan como satisfechos sin ser creados. No significa que las tareas de implementación estén completas. isComplete se conserva como un alias de compatibilidad con el mismo valor. El requires de cada artefacto son sus ids de dependencia directa (presente para cada estado, por lo que el conjunto transitivo requerido es computable incluso cuando el artefacto está done); missingDeps aparece solo cuando está blocked. El arreglo artifacts está en orden de dependencias, con el orden de declaración artifacts: del esquema desempatando entre artefactos que se vuelven listos al mismo tiempo (nunca alfabético), por lo que la primera entrada ready es el artefacto a escribir a continuación; missingDeps usa ese mismo orden. "skipped" marca un artefacto cuyo camino generates está bajo specs/ en un cambio cuyo .openspec.yaml declara skip_specs: true; satisface dependencias pero no debe crearse. Sin cambios activos: { "changes": [], "message", "root" }, sale con código 0.

--all (lote, mutuamente excluyente con --change — combinarlos es un error con la forma nula { "changes": [], "root": null, "status": [d] }): { "changes": [ <objeto de estado por cambio, sin raíz por cambio>, ... ], "root" }, ordenado por nombre de cambio. Un cambio que falla al cargar contribuye { "changeName", "status": [d] } en su lugar; la barrida continúa, conserva el envoltorio completo y sale con código 1 tanto en modo texto como JSON. Un --schema inválido hace fallar toda la invocación con la forma nula, incluso cuando no existen cambios.

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 lista los artefactos que este hace listos, en el orden de declaración del esquema (el mismo orden que status recomienda). "skipped": true (con "warning") aparece cuando el cambio declara skip_specs: true y este artefacto está omitido — no crees sus archivos. Una entrada de dependencia con skipped: true está satisfecha sin archivos — no intentes leer sus rutas.

ReferenceIndexEntry: { "store_id", "root"?, "specs"?: [{id,summary}], "fetch"?, "status": [] } — las entradas resueltas llevan root/specs/fetch; las no resueltas llevan store_id + estado de advertencia. Índice limitado 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 es en lo que apply se bloquea (el apply.requires del esquema); missingPrerequisites es todo lo que aún se debe construir antes de que apply pueda ejecutarse, en orden de construcción: el cierre transitivo de esos requires, por lo que puede ser la lista más larga. warnings lista problemas no bloqueantes con el cambio mismo: hoy, un cambio listo para implementarse sin especificaciones delta y sin skip_specs: true, el estado que openspec validate rechaza. Ambos campos raíz opcionales (context, operationGuidance) se leen de la raíz seleccionada en cada invocación. context es una entrada a nivel de prompt requerida cuyos hechos relevantes del proyecto, convenciones y restricciones deben aplicarse; operationGuidance es una entrada de asesoramiento cuyas entradas se siguen solo cuando son aplicables y compatibles con el flujo de trabajo integrado. Ambos permanecen separados del estado, tareas, progreso, archivos de contexto y la instrucción integrada.

4.7 instructions archive --json ​

{ "changeName", "context"?, "operationGuidance"?, "root" }. Requiere un --change válido en la raíz del repo/almacén resuelto y usa las mismas semánticas de contexto requerido/guía de asesoramiento que apply. Esta es una superficie de entrada de solo lectura en tiempo de ejecución: no devuelve el flujo de trabajo estático de archivo, no inspecciona ni fusiona especificaciones delta, no escribe especificaciones principales ni mueve el cambio.

4.8 new change <name> --json ​

Éxito: { "change": { "id", "path", "metadataPath", "schema" }, "root" }. Falla: { "change": null, "status": [d] }, sale con código 1.

4.9 archive <name> --json ​

Éxito: { "archive": { "change", "archivedAs": "YYYY-MM-DD-name", "path", "specsUpdated", "totals"?, "warnings"? }, "root" }. Falla: { "archive": null, "root"?, "status": [d] }, sale con código 1. specsUpdated es verdadero solo cuando al menos un archivo de especificación fue escrito o retirado (una capacidad cuya última requisito el cambio eliminó hace que su especificación sea eliminada, lo que requiere retire_capabilities: true en el .openspec.yaml del cambio; cada retiro se nombra en warnings, con un comando de recuperación de Git pegado solo cuando la especificación vivía en el checkout de quien invoca); un cambio ya sincronizado se archiva con totales en cero y los omisiones enumerados en warnings. El modo JSON es estrictamente no interactivo: cada punto de prompt se convierte en un código 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 para un checkout de almacén respaldado por Git que tiene una referencia de seguimiento ascendente) es conteos adelante/atrás contra el upstream obtenido por última vez, no el remoto en vivo. Hallazgos de salud de cualquier severidad salen con código 0. Payload de falla: { "root": null, "store": null, "references": [], "status": [d] }, sale con código 1.

4.11 context --json ​

{ "root": { "path", "source", "store_id"?, "role": "openspec_root" }, "members": [ { "role": "referenced_store", "id", "path"?, "remote"?, "fetch"?, "status": [] } ], "status": [] }. DISPONIBLE = ruta presente Y estado vacío. --code-workspace <path> escribe {folders:[{name,path}]} (solo almacenes referenciados disponibles, prefijos ref:); en modo JSON, la escritura se ejecuta antes de imprimir, por lo que stdout contiene exactamente un documento incluso en falla de escritura. Falla: { "root": null, "members": [], "status": [d] }, sale con código 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 = desconocido/no probado). Hallazgos de salud salen con código 0; fallas salen con código 1 y la forma nula correspondiente. Cancelación del prompt sale con código 130.

4.13 schemas --json / templates --json ​

schemas: el éxito permanece como un arreglo desnudo [ {name, description, artifacts, source} ]; resuelve la precedencia canónica de selección de raíz y acepta --store <id>. Falla de selección de raíz: { "schemas": [], "root": null, "status": [d] }, sale con código 1. templates: objeto claveado { "<artifactId>": {path, source} }, aún basado en cwd sin claves de raíz/estado.

5. Contrato de códigos de salida ​

SituaciónSalidaStdout
Éxito, incluidas las detecciones de salud (doctor/context/store doctor)0el payload
Fallo del comando en modo --json1un documento JSON con status: [d] y la forma nula del comando
validate con elementos fallidos1informe completo
Cancelación del prompt (grupo store, modo humano)130solo stderr

6. Catálogo de códigos de diagnóstico ​

Resolución ​

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; paso directo: invalid_store_id, invalid_store_registry, invalid_store_metadata.

Salud de la raíz OpenSpec (error, sin corrección) ​

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 de stores, openspec/specs/, openspec/changes/ y openspec/changes/archive/ pueden estar ausentes en una raíz saludable; solo constituyen errores de salud cuando están presentes pero no son directorios.

Registro/identidad/estado de 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).

Configuración/registro/eliminación de 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 (aviso en remove, error en doctor), store_root_not_directory.

Git de store ​

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

Referencias (aviso) ​

reference_invalid_id, reference_registry_unreadable, reference_unresolved, reference_root_unhealthy, reference_index_truncated.

Relaciones (aviso; doctor; context conserva solo la del registro) ​

relationship_registry_unreadable, root_pointer_ignored, root_pointer_invalid, pointer_declarations_inert.

Archivo (modo 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.

Escrituras de context ​

context_file_exists, context_output_dir_missing.

Respaldos ​

doctor_failed, context_failed, store_error, change_error, archive_error.

Inconsistencias conocidas ​

Registradas por la auditoría final; los renombramientos de claves publicadas son decisiones de producto aplazadas más allá de esta versión:

  1. En modo --json, varias rutas de fallo imprimían solo stderr sin documento JSON. Corregido en la ronda de gauntlet de la versión final: elementos desconocidos y ambiguos en show/validate emiten {status:[{code: unknown_item | ambiguous_item, ...}]}; los errores lanzados en status/instructions/list/show/validate se enrutan a través del helper de fallo consciente de JSON (la forma nula del comando + status); store <subcomando desconocido> --json emite {status:[{code: unknown_store_subcommand}]}; list lleva su forma nula {changes|specs: [], root: null} en fallos de resolución.
  2. store_root_missing se emite con dos severidades (aviso en remove, error en store doctor) — dependiente del contexto, documentado arriba.
  3. snake_case (familia store) vs camelCase (familia workflow) en la capitalización de claves; root.store_id es snake_case en todas partes.
  4. Existen cuatro declaraciones paralelas de tipos de sobre en src; los diagnósticos de archive nunca llevan target.
  5. list --json reutiliza la clave status como enumeración de cadena por cambio.
  6. Solo la salida de validate lleva un campo version.
  7. templates ignora la selección de raíz (basado en cwd, sin --store).
  8. Las formas nominales deprecadas (subcomandos change/spec) emiten payloads sin sobre sin root/status.