Agent Dev 101

Модуль 15 · Урок 15.14

Урок 15.14. Под капотом OPSX: архитектура workflow и Agent Contract

На этой странице
  1. Зачем это нужно
  2. Основная идея
  3. Что такое OPSX
  4. Почему появился OPSX
  5. Опыт пользователя: действия, а не фазы
  6. Команды OPSX
  7. Использование
  8. Обновлять или начинать заново
  9. Что изменилось относительно легаси
  10. Архитектура: глубокое погружение
  11. Agent Contract: машинночитаемые поверхности CLI
  12. Практика
  13. Краткое резюме
  14. Вопросы для самопроверки
  15. Что дальше
  16. Источники

Зачем это нужно

Уроки 15.2-15.13 описывали OpenSpec снаружи: команды, артефакты, workflow, настройка. Остались два вопроса, которые важны, когда инструмент перестает быть «просто удобным»:

  • почему workflow устроен гибким, а не phase-locked - и что под этим стоит технически: чем OPSX отличается от легаси-workflow не на уровне маркетинга, а на уровне архитектуры;
  • как именно агент читает и пишет состояние OpenSpec - какие machine-readable поверхности предоставляет CLI, по каким контрактам агент принимает решения и что означают коды ошибок.

Первый вопрос важен разработчику, который хочет доверять инструменту и настраивать его (а не пользоваться черным ящиком). Второй - тому, кто пишет собственные skills, интеграции или CI-проверки поверх OpenSpec.

Основная идея

OPSX заменяет жесткие фазы на граф действий. Артефакты изменения образуют направленный ациклический граф (DAG), где зависимости - «включатели», а не ворота: они показывают, что стало возможным, а не что «положено делать дальше». Состояние артефакта выводится из файловой системы: файл существует - артефакт готов.

Агент работает с этим графом не вслепую, а через структурированные контракты CLI: команды status и instructions в режиме --json возвращают точное состояние артефактов, готовые инструкции, зависимости и разблокировки. Это и есть Agent Contract - машинночитаемая сторона OpenSpec.

Что такое OPSX

OPSX - текущий стандартный workflow OpenSpec. Это гибкий, итеративный workflow для изменений: никаких жестких фаз, только действия, которые можно выполнять в любой момент.

Слэш-команды /opsx:*, разобранные в уроке 15.5, - пользовательская сторона этого workflow. Этот урок - про устройство.

Почему появился OPSX

Легаси-workflow OpenSpec работал, но был «закрыт»:

  • инструкции зашиты в код - спрятаны в TypeScript, их не изменить;
  • все-или-ничего - одна большая команда создает всё сразу, отдельные куски не протестировать;
  • фиксированная структура - один workflow для всех, без кастомизации;
  • черный ящик - когда вывод AI плох, промпты не подкрутить.

OPSX открывает это. Теперь каждый может:

  1. экспериментировать с инструкциями - отредактировать шаблон и посмотреть, станет ли AI лучше;
  2. тестировать гранулярно - валидировать инструкции каждого артефакта независимо;
  3. кастомизировать workflow - определять свои артефакты и зависимости;
  4. быстро итерировать - поменять шаблон, протестировать сразу, без пересборки.
text
Легаси-workflow:                       OPSX:
┌────────────────────────┐           ┌────────────────────────┐
│  Зашито в пакет         │           │  schema.yaml           │◄── это правите вы
│  (не изменить)          │           │  templates/*.md        │◄── или это
│        ↓               │           │        ↓               │
│  Ждать нового релиза   │           │  Мгновенный эффект     │
│        ↓               │           │        ↓               │
│  Надеяться, что лучше  │           │  Проверить самим       │
└────────────────────────┘           └────────────────────────┘

Это нужно всем:

  • командам - создавать workflow, совпадающие с тем, как вы реально работаете;
  • продвинутым пользователям - подстраивать промпты под свою кодовую базу;
  • контрибьюторам OpenSpec - экспериментировать с подходами без релизов.

Опыт пользователя: действия, а не фазы

Проблема линейных workflow: вы «в фазе планирования», потом «в фазе реализации», потом «готово». Но реальная работа так не устроена: вы что-то реализуете, понимаете, что design был неверный, обновляете спецификации, продолжаете реализацию. Линейные фазы борются с тем, как работа происходит на самом деле.

Подход OPSX:

  • действия, а не фазы - create, implement, update, archive - делайте любое в любой момент;
  • зависимости - включатели - они показывают, что возможно, а не что требуется дальше.
text
proposal ──→ specs ──→ design ──→ tasks ──→ implement

Команды OPSX

Команда Что делает
/opsx:propose Создает изменение и генерирует планировочные артефакты за один шаг (быстрый путь по умолчанию)
/opsx:explore Прорабатывает идеи, исследует проблемы, уточняет требования
/opsx:new Начинает scaffold нового изменения (расширенный набор)
/opsx:continue Создает следующий артефакт (расширенный набор)
/opsx:ff Быстро создает все планировочные артефакты (расширенный набор)
/opsx:apply Реализует задачи, обновляя артефакты по мере надобности
/opsx:update Пересматривает планировочные артефакты изменения, сохраняя их согласованность
/opsx:verify Проверяет реализацию против артефактов (расширенный набор)
/opsx:sync Вливает дельта-спецификации в основные specs (опционально)
/opsx:archive Архивирует по завершении
/opsx:bulk-archive Архивирует несколько завершенных изменений (расширенный набор)
/opsx:onboard Управляемое прохождение изменения end-to-end (расширенный набор)

Использование

Проработать идею:

text
/opsx:explore

Продумать идеи, исследовать проблемы, сравнить варианты. Структура не нужна - это партнер по размышлению (урок 15.7). Когда прозрение оформилось - переход к /opsx:propose (дефолт) или /opsx:new//opsx:ff (расширенный набор).

Начать изменение:

text
/opsx:propose

Создает изменение и генерирует планировочные артефакты, нужные до реализации. При включенном расширенном наборе можно вместо этого:

text
/opsx:new        # только scaffold
/opsx:continue   # по одному артефакту за раз
/opsx:ff         # все планировочные артефакты сразу

Создавать артефакты:

text
/opsx:continue

Показывает, что готово к созданию по зависимостям, затем создает один артефакт. Повторяйте, чтобы наращивать изменение инкрементально.

text
/opsx:ff add-dark-mode

Создает все планировочные артефакты сразу. Используйте, когда картина ясна.

Реализовывать (гибкая часть):

text
/opsx:apply

Проходит по задачам, отмечая их по ходу. Если вы жонглируете несколькими изменениями, можно запускать /opsx:apply <name>; иначе команда выведет из разговора или спросит.

Обновлять изменение:

text
/opsx:update add-dark-mode - we're storing the theme in a cookie now

Пересматривает существующие планировочные артефакты и держит их согласованными в любом направлении (правка design может откатиться обратно к proposal). Никогда не редактирует код. Каждая правка сначала подтверждается с вами. Если изменение уже реализовано, команда порекомендует /opsx:apply, чтобы код догнал пересмотренный план. Если правка меняет intent изменения - начните заново (см. ниже).

Синхронизировать дельта-спецификации:

text
/opsx:sync

Вливает дельта-спецификации текущего изменения в основные openspec/specs/ без архивации - изменение остается активным. Применяется вся дельта: требование под ## REMOVED удаляется из основной спецификации, переименованное переименовывается на месте; то, чего дельта не касается, остается нетронутым. Синхронизация опциональна - archive сам предложит синхронизироваться. Нужна, когда основные specs должны обновиться до архивации, когда параллельное изменение строит на добавленных спецификациях или когда вы хотите отревьюить слитый результат до архивации.

Завершить:

text
/opsx:archive   # Переместить в архив по завершении (предложит синхронизировать specs)

Обновлять или начинать заново

Править proposal или спецификации до реализации можно всегда. Но когда доработка превращается в «это уже другая работа»?

Что фиксирует proposal

Proposal определяет три вещи:

  1. Intent - какую проблему решаем?
  2. Scope - что в границах и что вне?
  3. Approach - как будем решать?

Вопрос: что изменилось и насколько?

Обновляйте существующее изменение, когда:

Тот же intent, отточенное исполнение

  • обнаружились edge cases, о которых не думали;
  • подход требует подкрутки, но цель не изменилась;
  • реализация показала, что design слегка неточен.

Scope сузился

  • полный scope оказался велик, хотите сначала MVP;
  • «добавить темную тему» → «добавить переключатель темы (системная Preference - в v2)».

Поправки от обучения

  • кодовая база устроена не так, как думалось;
  • зависимость работает не так, как ожидалось;
  • «использовать CSS-переменные» → «использовать префикс dark: в Tailwind».

Начинайте новое изменение, когда:

Intent принципиально изменился

  • сама проблема теперь другая;
  • «добавить темную тему» → «добавить полноценную систему тем с кастомными цветами, шрифтами и отступами».

Scope взорвался

  • изменение выросло настолько, что это уже другая работа;
  • исходный proposal после правок стал бы неузнаваем;
  • «починить баг логина» → «переписать систему авторизации».

Оригинал завершаем

  • исходное изменение можно пометить «готово»;
  • новая работа стоит сама, это не доработка;
  • завершить «Add dark mode MVP» → архивировать → новое изменение «Enhance dark mode».

Эвристика

text
                    ┌─────────────────────────────────┐
                    │      Это та же работа?          │
                    └───────────────┬─────────────────┘
                                    │
                 ┌──────────────────┼──────────────────┐
                 │                  │                  │
                 ▼                  ▼                  ▼
          Тот же intent?      Перекрытие >50%?   Оригинал можно
          Та же проблема?     Тот же scope?      «закрыть» без этих
                 │                  │            изменений?
                 │                  │                  │
       ┌────────┴───────┐  ┌───────┴──────┐   ┌───────┴───────┐
      ДА              НЕТ  ДА            НЕТ  НЕТ            ДА
       │               │   │              │    │              │
       ▼               ▼   ▼              ▼    ▼              ▼
    ОБНОВИТЬ        НОВОЕ ОБНОВИТЬ      НОВОЕ ОБНОВИТЬ       НОВОЕ
Тест Обновить Новое изменение
Идентичность «То же, отточено» «Другая работа»
Перекрытие scope Больше 50% совпадает Меньше 50% совпадает
Завершаемость Нельзя «закрыть» без этих правок Оригинал завершаем, новая работа стоит сама
История Цепочка правок складывается в историю Патчи запутали бы больше, чем прояснили

Принцип

Обновление сохраняет контекст. Новое изменение дает ясность.

Выбирайте обновление, когда история ваших размышлений ценна. Выбирайте новое, когда начать заново яснее, чем залатывать.

Это как ветки git: продолжайте коммитить, работая над той же фичей; начинайте новую ветку, когда это действительно новая работа; иногда вмержьте частичную фичу и начните заново для второй фазы.

Что изменилось относительно легаси

Легаси (/openspec:proposal) OPSX (/opsx:*)
Структура Один большой документ proposal Дискретные артефакты с зависимостями
Workflow Линейные фазы: plan → implement → archive Гибкие действия - что угодно когда угодно
Итерация Возвращаться неудобно Обновляйте артефакты по мере обучения
Кастомизация Фиксированная структура На схемах (определяйте свои артефакты)

Ключевое прозрение: работа нелинейна. OPSX перестает делать вид, что это не так.

Архитектура: глубокое погружение

Философия: фазы против действий

Легаси-workflow - phase-locked, все-или-ничего:

text
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   PLANNING   │ ───► │ IMPLEMENTING │ ───► │   ARCHIVING  │
│    PHASE     │      │    PHASE     │      │    PHASE     │
└──────────────┘      └──────────────┘      └──────────────┘
      │                     │                     │
      ▼                     ▼                     ▼
/openspec:proposal   /openspec:apply      /openspec:archive

• Создает ВСЕ артефакты сразу
• Нельзя вернуться обновить specs во время реализации
• Фазовые ворота форсируют линейность

OPSX - действия без фаз:

text
┌────────────────────────────────────────────┐
│           ДЕЙСТВИЯ (не фазы)               │
│                                            │
│  new ◄──► continue ◄──► apply ◄──► archive  │
│   │          │           │          │     │
│   └──────────┴───────────┴──────────┘     │
│              в любом порядке               │
└────────────────────────────────────────────┘

• Артефакты по одному ИЛИ всё сразу (ff)
• Specs/design/tasks обновляются во время реализации
• Зависимости делают возможным продвижение, фаз не существует

Архитектура компонентов

Легаси-workflow использует зашитые в TypeScript шаблоны: hardcoded-шаблоны (строки TypeScript) → специфичные для инструментов конфигураторы/адаптеры → сгенерированные файлы команд (.claude/commands/openspec/*.md). Структура фиксирована, осведомленности об артефактах нет, изменения требуют модификации кода и пересборки.

OPSX использует внешние схемы и движок графа зависимостей:

flowchart TD S["Определения схем (YAML)<br/>name: spec-driven<br/>artifacts: proposal, specs, design, tasks<br/>requires: [..._dependencies...]<br/>generates: 'specs/**/*.md' (glob)"] E["Движок графа артефактов<br/>• топологическая сортировка (порядок по зависимостям)<br/>• детекция состояния (существование файлов)<br/>• генерация инструкций (шаблоны + контекст)"] F["Файлы skills (.claude/skills/openspec-*/SKILL.md)<br/>• совместимость между редакторами (Claude Code, Cursor, Devin)<br/>• skills опрашивают CLI за структурированными данными<br/>• полная кастомизация через файлы схем"] S --> E --> F

Схемы - обычные YAML-файлы в openspec/schemas/ (правьте без пересборки), движок вычисляет порядок и состояние, а на выходе - skills, которые сами опрашивают CLI.

Модель графа зависимостей

Артефакты образуют DAG. Зависимости - включатели, а не ворота:

flowchart TD P["proposal (корневая вершина)"] --> SP["specs<br/>(requires: proposal)"] P --> D["design<br/>(requires: proposal)"] SP --> T["tasks<br/>(requires: specs, design)"] T --> A["ФАЗА APPLY<br/>(requires: tasks)"]

Состояния:

text
BLOCKED ──────────────► READY ──────────────► DONE
   │                      │                     │
Отсутствуют             Все зависимости       Файл существует
зависимости             готовы (DONE)         в файловой системе

Именно поэтому /opsx:continue знает, что создавать следующим, а /opsx:apply - с чего продолжить: состояние читается из файловой системы, а не из «текущей фазы».

Поток информации

Легаси-workflow - агент получает статические инструкции:

text
User: "/openspec:proposal"
         │
         ▼
┌─────────────────────────────────────┐
│  Статические инструкции:            │
│  • создать proposal.md              │
│  • создать tasks.md                 │
│  • создать design.md                │
│  • создать файлы дельта-спек         │
│                                     │
│  Без знания о том, что существует,  │
│  и без зависимостей между           │
│  артефактами                        │
└─────────────────────────────────────┘
         │
         ▼
  Агент создает ВСЕ артефакты разом

OPSX - агент опрашивает контекст:

text
User: "/opsx:continue"
         │
         ▼
Шаг 1: опросить текущее состояние
  $ openspec status --change "add-auth" --json

  {
    "artifacts": [
      {"id": "proposal", "status": "done"},
      {"id": "specs",   "status": "ready"},   ◄── первый готовый
      {"id": "design",  "status": "ready"},
      {"id": "tasks",   "status": "blocked",
       "missingDeps": ["specs", "design"]}
    ]
  }
         │
         ▼
Шаг 2: получить инструкции для готового артефакта
  $ openspec instructions specs --change "add-auth" --json

  {
    "template": "# Specification\n\n## ADDED Requirements...",
    "dependencies": [{"id": "proposal", "path": "...", "done": true}],
    "unlocks": ["tasks"]
  }
         │
         ▼
Шаг 3: прочитать зависимости → создать ОДИН артефакт → показать разблокировки

Модель итерации

Легаси-workflow - итерироваться неудобно: «стоп, design неверный» во время /apply оставлял три плохих варианта - править файлы вручную (ломая контекст), бросить и начать заново или продавить и чинить потом. Официального механизма «вернуться назад» не было.

OPSX - итерация естественна:

text
/opsx:new ──► /opsx:continue ──► /opsx:apply ──► /opsx:archive
                 │                   │
                 │                   ├── «Design неверный»
                 │                   ▼
                 │            Просто правьте design.md
                 │            и продолжайте!
                 │                   ▼
                 │          /opsx:apply подхватывает
                 │          с места, где остановились
                 │
                 └── Создает ОДИН артефакт, показывает разблокировки

Кастомные схемы

Кастомные workflow создаются командами управления схемами (подробно - урок 15.13):

bash
# Создать схему с нуля (интерактивно)
openspec schema init my-workflow

# Или форкнуть существующую как отправную точку
openspec schema fork spec-driven my-workflow

# Провалидировать структуру
openspec schema validate my-workflow

# Посмотреть, откуда резолвится схема (отладка)
openspec schema which my-workflow

Схемы хранятся в openspec/schemas/ (проектно, под версионным контролем) или в ~/.local/share/openspec/schemas/ (глобально). Пример схемы research-first - артефакт research добавлен перед proposal, и proposal теперь зависит от него:

text
research ──► proposal ──► tasks

Сводка архитектуры

Аспект Легаси OPSX
Шаблоны Зашиты в TypeScript Внешние YAML + Markdown
Зависимости Нет (всё сразу) DAG с топологической сортировкой
Состояние Ментальная модель «фаз» Существование файлов
Кастомизация Править исходники, пересборка Создать schema.yaml
Итерация Phase-locked Гибкая, правьте что угодно
Поддержка редакторов Специфичные конфигураторы/адаптеры Один каталог skills

Agent Contract: машинночитаемые поверхности CLI

Agent Contract - документированные формы JSON-вывода команд openspec, сверенные с исходным кодом. Это контракт, на который опираются сгенерированные skills и любые ваши интеграции: CLI обязуется держать эти формы стабильными в рамках релиза.

Общие конвенции

  • Один JSON-документ на вызов. В режиме --json stdout несет ровно один JSON-документ (pretty-print с отступом 2). Человеческая проза, спиннеры и баннер Store уходят в stderr.
  • Баннер Store. В человеческом режиме корень, выбранный через Store, печатает Using OpenSpec root: <id> (<path>) в stderr. В JSON-режиме не печатается никогда.
  • Регистр ключей зависит от семейства команд: полезная нагрузка store/doctor/context использует snake_case; workflow-команды (status, instructions, new change, validate, list) - camelCase, кроме вложенного объекта root, который всегда использует store_id.
  • Опциональные ключи опускаются, а не обнуляются (null), в большинстве форм; исключения с явным null оговорены по формам (store doctor git.*, формы ошибок).

Диагностический конверт

Все машинночитаемые диагностики делят одну форму:

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

Диагностики появляются в двух позициях: массивы status (находки о здоровье - на верхнем уровне или по элементу) и ошибки выполнения, превращаемые в одноэлементный массив status при падении команды.

Выбор корня и RootOutput

Все команды, резолвящие корень (list, show, validate, status, instructions, new change, archive, doctor, context, schemas), используют один приоритет (он же из урока 15.12):

  1. --store <id> → корень зарегистрированного Store (source: "store");
  2. ближайший предок с openspec/ → этот корень (source: "nearest"; указатель store: игнорируется с предупреждением в stderr), либо каталог только с конфигом и валидным указателем store: → этот Store (source: "declared");
  3. нет ближайшего корня + глобальный defaultStore → этот Store (source: "global_default"); устаревший id падает с ошибкой Store и fix, называющим openspec config unset defaultStore;
  4. нет корня, нет дефолта, есть зарегистрированные Store → ошибка no_root_with_registered_stores;
  5. нет ничего → команда может трактовать текущий каталог как source: "implicit"; doctor, context, list и пакетный validate вместо этого падают с no_openspec_root.

Успешные JSON-формы обычно встраивают корень:

json
"root": { "path": "/abs/path", "source": "store" | "declared" | "global_default" | "nearest" | "implicit", "store_id": "id (только при выборе Store)" }

Контракт ошибки корня: в JSON-режиме провал резолюции печатает { ...nullShape, "status": [diagnostic] } в stdout и завершается с кодом 1.

Формы JSON основных команд

Точные формы всех команд - в справочнике CLI (урок 15.6) и оригинале контракта. Здесь - сводка по ключевым командам:

Команда Ключевые поля формы
list --json changes[] с name, completedTasks/totalTasks, lastModified, status (строковый enum: no-tasks/complete/in-progress), nested?; warnings? с кодом nested_change_directory - namespace-папки под changes/, не являющиеся изменениями: не трактовать их как изменения; --specs - specs[] с id, requirementCount
show <item> --json изменение: id, title, deltaCount, deltas[]; спецификация: id, title, overview, requirementCount, requirements[], metadata; требование - {name, text, scenarios[{name, rawText}]}; name требования - заголовок без Requirement: и замыкающих #, точное имя, по которому archive сопоставляет MODIFIED/REMOVED/RENAMED
validate --json items[] с valid, issues[{level, path, message, line?, column?}], summary.totals; код выхода 1, если провален хоть один item
status --json changeName, schemaName, artifactPaths по id артефакта, actionContext, isPlanningComplete, artifacts[{id, outputPath, status: done/skipped/ready/blocked, requires, missingDeps?}] - в порядке зависимостей (порядок декларации схемы рушит ничьи); первый ready - артефакт, который писать следующим; skipped помечает артефакт под skip_specs: true - он удовлетворяет зависимости, но создаваться не должен; --all - пакетная форма по всем изменениям
instructions <artifact> --json template, instruction?, context?, rules?, references?, dependencies[{id, done, path, description, skipped?}], unlocks[] (в порядке декларации схемы); зависимость с skipped: true удовлетворена без файлов - не пытайтесь читать ее пути
instructions apply --json contextFiles по артефактам, progress{total, complete, remaining}, tasks[], state: blocked/all_done/ready, missingArtifacts (то, на чем apply блокируется), missingPrerequisites (транзитивное замыкание - полный список недостроенного), warnings?; context - обязательный вход уровня промпта, operationGuidance - советы, применяемые только если совместимы со встроенным workflow
instructions archive --json только context?, operationGuidance?, root - поверхность только для чтения: не возвращает workflow архивации, не мержит дельты, не пишет specs
new change <name> --json успех: change{id, path, metadataPath, schema}; провал: change: null, status: [d], код 1
archive <name> --json успех: archive{change, archivedAs: "YYYY-MM-DD-name", path, specsUpdated, totals?, warnings?}; specsUpdated истиннен, только если записан или удален хоть один файл спецификации (удаление последнего требования возможности требует retire_capabilities: true в .openspec.yaml); JSON-режим строго неинтерактивен: каждая точка промпта становится кодом archive_*
doctor --json root{path, source, store_id?, healthy, status[]}, store{...drift?...} (drift - ahead/behind от последнего fetch, не живого remote), references[]; находки здоровья любого уровня завершаются кодом 0
context --json root{...role}, members[{role: referenced_store, id, path?, remote?, fetch?, status[]}]; элемент ДОСТУПЕН = путь есть И status пуст; --code-workspace <path> пишет workspace-файл из доступных Store
store ... --json setup/register/unregister/remove/list/doctor - формы с store, registry, git, created_files/files, status[]; отмена промпта - код 130
schemas --json успех остается совместимым голым массивом [{name, description, artifacts, source}]; templates --json - объект по id артефакта, остается привязанным к cwd без --store

Контракт кодов выхода

Ситуация Код Stdout
Успех, включая находки здоровья (doctor/context/store doctor) 0 полезная нагрузка
Провал команды в --json-режиме 1 один JSON-документ с status: [d] и null-формой команды
validate с проваленными item 1 полный отчет
Отмена промпта (группа store, человеческий режим) 130 только stderr

Каталог диагностических кодов

Коды сгруппированы по семействам (полный список - в оригинале контракта):

  • Резолюция: no_openspec_root, no_root_with_registered_stores, unknown_store, store_identity_mismatch, invalid_store_pointer и другие;
  • Здоровье корня OpenSpec (ошибки без fix): openspec_root_missing, openspec_config_not_file, openspec_specs_not_directory и т.д.; в beta openspec/specs/, openspec/changes/ и openspec/changes/archive/ могут отсутствовать в здоровом корне - ошибки только если они есть, но не каталоги;
  • Реестр и идентичность Store: store_not_found, store_id_conflict, store_path_conflict, store_already_registered (info) и другие;
  • Setup/register/remove Store: store_setup_path_required, store_register_root_unhealthy, store_remove_confirmation_required и другие;
  • git Store: store_git_init_failed, store_git_no_commits (warning), store_checkout_drift (info, doctor);
  • References (warnings): reference_unresolved, reference_index_truncated (индекс ссылок ограничен 50KB) и другие;
  • Archive (JSON-режим): archive_change_not_found, archive_tasks_incomplete, archive_spec_update_failed, archive_retirement_cleanup_failed, archive_error; после archive: null нет гарантии, что файлы не изменились - инспектируйте изменение, архив и specs перед повтором;
  • Контекстные записи: context_file_exists, context_output_dir_missing;
  • Fallback-диагностики: doctor_failed, context_failed, store_error, change_error, archive_error.

Известные несоответствия

Зафиксированы аудитом; переименования опубликованных ключей - продуктовые решения, отложенные до версионируемого релиза:

  1. Несколько путей провала в --json-режиме печатали только stderr без JSON-документа - исправлено: неизвестные/неоднозначные item у show/validate выдают status с кодами unknown_item/ambiguous_item, выброшенные ошибки маршрутизируются через JSON-aware helper.
  2. store_root_missing выдается с двумя уровнями (warning при remove, error в store doctor) - контекстно, задокументировано.
  3. snake_case (store-семейство) против camelCase (workflow-семейство); root.store_id везде snake_case.
  4. В исходниках существуют четыре параллельных объявления типа конверта; диагностики archive не несут target.
  5. list --json переиспользует ключ status как строковый enum на изменение.
  6. Только вывод validate несет поле version.
  7. templates игнорирует выбор корня (привязка к cwd, без --store).
  8. Устаревшие именные формы (change/spec-подкоманды) выдают формы без конверта root/status.

Инженерный вывод курса: Agent Contract - образец дисциплины «CLI для человека и агента». Один и тот же инструмент имеет человеческий режим (проза, баннеры, подсказки) и агентный (--json, конверт диагностик, коды выхода, гарантии «один документ на вызов»). Для собственных CLI, с которыми работают агенты, это готовый шаблон: машинный контракт важнее человекочитаемого вывода, потому что агент не прощает неоднозначности, которые человек пропустит.

Практика

  1. В тестовом проекте создайте изменение и на каждом шаге сравнивайте человеческий и машинный режимы:
    bash
    openspec status --change <name>
    openspec status --change <name> --json
    
  2. Выполните openspec instructions <artifact> --change <name> --json для готового артефакта и найдите в выводе template, dependencies и unlocks.
  3. Проверьте контракт ошибок: запустите openspec show nonexistent --json и openspec status --json вне корня OpenSpec - убедитесь, что stdout содержит один JSON-документ с status: [...], а код выхода равен 1.
  4. Смоделируйте «гибкую итерацию»: во время /opsx:apply исправьте design.md вручную, перезапустите apply и убедитесь, что работа продолжилась с места остановки, а не началась заново.

Краткое резюме

  • OPSX - текущий стандартный workflow: гибкие действия вместо фаз; зависимости - включатели, а не ворота; состояние артефактов - существование файлов.
  • Легаси был закрыт: инструкции зашиты в TypeScript, всё-или-ничего, без кастомизации. OPSX выносит определения в schema.yaml + шаблоны, а движок строит DAG артефактов с топологической сортировкой.
  • Агент в OPSX не получает статические инструкции, а опрашивает CLI: status --json (что готово) → instructions --json (шаблон, зависимости, разблокировки) → один артефакт за шаг.
  • «Обновлять или начинать заново» решается по intent/scope/approach: обновление сохраняет контекст, новое изменение дает ясность.
  • Agent Contract - машинночитаемые поверхности CLI: один JSON-документ на вызов, конверт диагностик StoreDiagnostic, единый приоритет выбора корня, коды выхода 0/1/130, каталог диагностических кодов и задокументированные известные несоответствия.

Вопросы для самопроверки

  1. Чем «зависимости-включатели» отличаются от «фазовых ворот» и почему это принципиально для итерации посреди реализации?
  2. Какие три компонента образуют архитектуру OPSX и что делает движок графа артефактов?
  3. Как агент в OPSX определяет, какой артефакт создавать следующим? Какие две команды и в каком порядке он вызывает?
  4. Почему состояние skipped удовлетворяет зависимости, но артефакт создавать нельзя, и в каком случае оно появляется?
  5. По каким трем вопросам (intent/scope/approach) документация советует решать «обновлять или начинать заново»?
  6. Что гарантирует Agent Contract для режима --json по количеству документов, где размещается проза и что происходит при провале команды?
  7. Что означают коды выхода 0, 1 и 130 в контракте CLI, и почему находки здоровья doctor завершаются нулем?

Что дальше

Урок 15.15 закрывает модуль: частые вопросы, типовые ошибки и их исправления (troubleshooting) и миграция со старого workflow /openspec:* на OPSX - включая судьбу существующих изменений и маркерных блоков в CLAUDE.md/AGENTS.md.

Источники

Обновлено 03.10.2026