Зачем это нужно
Уроки 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 открывает это. Теперь каждый может:
- экспериментировать с инструкциями - отредактировать шаблон и посмотреть, станет ли AI лучше;
- тестировать гранулярно - валидировать инструкции каждого артефакта независимо;
- кастомизировать workflow - определять свои артефакты и зависимости;
- быстро итерировать - поменять шаблон, протестировать сразу, без пересборки.
Легаси-workflow: OPSX:
┌────────────────────────┐ ┌────────────────────────┐
│ Зашито в пакет │ │ schema.yaml │◄── это правите вы
│ (не изменить) │ │ templates/*.md │◄── или это
│ ↓ │ │ ↓ │
│ Ждать нового релиза │ │ Мгновенный эффект │
│ ↓ │ │ ↓ │
│ Надеяться, что лучше │ │ Проверить самим │
└────────────────────────┘ └────────────────────────┘
Это нужно всем:
- командам - создавать workflow, совпадающие с тем, как вы реально работаете;
- продвинутым пользователям - подстраивать промпты под свою кодовую базу;
- контрибьюторам OpenSpec - экспериментировать с подходами без релизов.
Опыт пользователя: действия, а не фазы
Проблема линейных workflow: вы «в фазе планирования», потом «в фазе реализации», потом «готово». Но реальная работа так не устроена: вы что-то реализуете, понимаете, что design был неверный, обновляете спецификации, продолжаете реализацию. Линейные фазы борются с тем, как работа происходит на самом деле.
Подход OPSX:
- действия, а не фазы - create, implement, update, archive - делайте любое в любой момент;
- зависимости - включатели - они показывают, что возможно, а не что требуется дальше.
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 (расширенный набор) |
Использование
Проработать идею:
/opsx:explore
Продумать идеи, исследовать проблемы, сравнить варианты. Структура не нужна - это партнер по размышлению (урок 15.7). Когда прозрение оформилось - переход к /opsx:propose (дефолт) или /opsx:new//opsx:ff (расширенный набор).
Начать изменение:
/opsx:propose
Создает изменение и генерирует планировочные артефакты, нужные до реализации. При включенном расширенном наборе можно вместо этого:
/opsx:new # только scaffold
/opsx:continue # по одному артефакту за раз
/opsx:ff # все планировочные артефакты сразу
Создавать артефакты:
/opsx:continue
Показывает, что готово к созданию по зависимостям, затем создает один артефакт. Повторяйте, чтобы наращивать изменение инкрементально.
/opsx:ff add-dark-mode
Создает все планировочные артефакты сразу. Используйте, когда картина ясна.
Реализовывать (гибкая часть):
/opsx:apply
Проходит по задачам, отмечая их по ходу. Если вы жонглируете несколькими изменениями, можно запускать /opsx:apply <name>; иначе команда выведет из разговора или спросит.
Обновлять изменение:
/opsx:update add-dark-mode - we're storing the theme in a cookie now
Пересматривает существующие планировочные артефакты и держит их согласованными в любом направлении (правка design может откатиться обратно к proposal). Никогда не редактирует код. Каждая правка сначала подтверждается с вами. Если изменение уже реализовано, команда порекомендует /opsx:apply, чтобы код догнал пересмотренный план. Если правка меняет intent изменения - начните заново (см. ниже).
Синхронизировать дельта-спецификации:
/opsx:sync
Вливает дельта-спецификации текущего изменения в основные openspec/specs/ без архивации - изменение остается активным. Применяется вся дельта: требование под ## REMOVED удаляется из основной спецификации, переименованное переименовывается на месте; то, чего дельта не касается, остается нетронутым. Синхронизация опциональна - archive сам предложит синхронизироваться. Нужна, когда основные specs должны обновиться до архивации, когда параллельное изменение строит на добавленных спецификациях или когда вы хотите отревьюить слитый результат до архивации.
Завершить:
/opsx:archive # Переместить в архив по завершении (предложит синхронизировать specs)
Обновлять или начинать заново
Править proposal или спецификации до реализации можно всегда. Но когда доработка превращается в «это уже другая работа»?
Что фиксирует proposal
Proposal определяет три вещи:
- Intent - какую проблему решаем?
- Scope - что в границах и что вне?
- Approach - как будем решать?
Вопрос: что изменилось и насколько?
Обновляйте существующее изменение, когда:
Тот же intent, отточенное исполнение
- обнаружились edge cases, о которых не думали;
- подход требует подкрутки, но цель не изменилась;
- реализация показала, что design слегка неточен.
Scope сузился
- полный scope оказался велик, хотите сначала MVP;
- «добавить темную тему» → «добавить переключатель темы (системная Preference - в v2)».
Поправки от обучения
- кодовая база устроена не так, как думалось;
- зависимость работает не так, как ожидалось;
- «использовать CSS-переменные» → «использовать префикс
dark:в Tailwind».
Начинайте новое изменение, когда:
Intent принципиально изменился
- сама проблема теперь другая;
- «добавить темную тему» → «добавить полноценную систему тем с кастомными цветами, шрифтами и отступами».
Scope взорвался
- изменение выросло настолько, что это уже другая работа;
- исходный proposal после правок стал бы неузнаваем;
- «починить баг логина» → «переписать систему авторизации».
Оригинал завершаем
- исходное изменение можно пометить «готово»;
- новая работа стоит сама, это не доработка;
- завершить «Add dark mode MVP» → архивировать → новое изменение «Enhance dark mode».
Эвристика
┌─────────────────────────────────┐
│ Это та же работа? │
└───────────────┬─────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
Тот же intent? Перекрытие >50%? Оригинал можно
Та же проблема? Тот же scope? «закрыть» без этих
│ │ изменений?
│ │ │
┌────────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐
ДА НЕТ ДА НЕТ НЕТ ДА
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
ОБНОВИТЬ НОВОЕ ОБНОВИТЬ НОВОЕ ОБНОВИТЬ НОВОЕ
| Тест | Обновить | Новое изменение |
|---|---|---|
| Идентичность | «То же, отточено» | «Другая работа» |
| Перекрытие scope | Больше 50% совпадает | Меньше 50% совпадает |
| Завершаемость | Нельзя «закрыть» без этих правок | Оригинал завершаем, новая работа стоит сама |
| История | Цепочка правок складывается в историю | Патчи запутали бы больше, чем прояснили |
Принцип
Обновление сохраняет контекст. Новое изменение дает ясность.
Выбирайте обновление, когда история ваших размышлений ценна. Выбирайте новое, когда начать заново яснее, чем залатывать.
Это как ветки git: продолжайте коммитить, работая над той же фичей; начинайте новую ветку, когда это действительно новая работа; иногда вмержьте частичную фичу и начните заново для второй фазы.
Что изменилось относительно легаси
Легаси (/openspec:proposal) |
OPSX (/opsx:*) |
|
|---|---|---|
| Структура | Один большой документ proposal | Дискретные артефакты с зависимостями |
| Workflow | Линейные фазы: plan → implement → archive | Гибкие действия - что угодно когда угодно |
| Итерация | Возвращаться неудобно | Обновляйте артефакты по мере обучения |
| Кастомизация | Фиксированная структура | На схемах (определяйте свои артефакты) |
Ключевое прозрение: работа нелинейна. OPSX перестает делать вид, что это не так.
Архитектура: глубокое погружение
Философия: фазы против действий
Легаси-workflow - phase-locked, все-или-ничего:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ PLANNING │ ───► │ IMPLEMENTING │ ───► │ ARCHIVING │
│ PHASE │ │ PHASE │ │ PHASE │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
/openspec:proposal /openspec:apply /openspec:archive
• Создает ВСЕ артефакты сразу
• Нельзя вернуться обновить specs во время реализации
• Фазовые ворота форсируют линейность
OPSX - действия без фаз:
┌────────────────────────────────────────────┐
│ ДЕЙСТВИЯ (не фазы) │
│ │
│ new ◄──► continue ◄──► apply ◄──► archive │
│ │ │ │ │ │
│ └──────────┴───────────┴──────────┘ │
│ в любом порядке │
└────────────────────────────────────────────┘
• Артефакты по одному ИЛИ всё сразу (ff)
• Specs/design/tasks обновляются во время реализации
• Зависимости делают возможным продвижение, фаз не существует
Архитектура компонентов
Легаси-workflow использует зашитые в TypeScript шаблоны: hardcoded-шаблоны (строки TypeScript) → специфичные для инструментов конфигураторы/адаптеры → сгенерированные файлы команд (.claude/commands/openspec/*.md). Структура фиксирована, осведомленности об артефактах нет, изменения требуют модификации кода и пересборки.
OPSX использует внешние схемы и движок графа зависимостей:
Схемы - обычные YAML-файлы в openspec/schemas/ (правьте без пересборки), движок вычисляет порядок и состояние, а на выходе - skills, которые сами опрашивают CLI.
Модель графа зависимостей
Артефакты образуют DAG. Зависимости - включатели, а не ворота:
Состояния:
BLOCKED ──────────────► READY ──────────────► DONE
│ │ │
Отсутствуют Все зависимости Файл существует
зависимости готовы (DONE) в файловой системе
Именно поэтому /opsx:continue знает, что создавать следующим, а /opsx:apply - с чего продолжить: состояние читается из файловой системы, а не из «текущей фазы».
Поток информации
Легаси-workflow - агент получает статические инструкции:
User: "/openspec:proposal"
│
▼
┌─────────────────────────────────────┐
│ Статические инструкции: │
│ • создать proposal.md │
│ • создать tasks.md │
│ • создать design.md │
│ • создать файлы дельта-спек │
│ │
│ Без знания о том, что существует, │
│ и без зависимостей между │
│ артефактами │
└─────────────────────────────────────┘
│
▼
Агент создает ВСЕ артефакты разом
OPSX - агент опрашивает контекст:
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 - итерация естественна:
/opsx:new ──► /opsx:continue ──► /opsx:apply ──► /opsx:archive
│ │
│ ├── «Design неверный»
│ ▼
│ Просто правьте design.md
│ и продолжайте!
│ ▼
│ /opsx:apply подхватывает
│ с места, где остановились
│
└── Создает ОДИН артефакт, показывает разблокировки
Кастомные схемы
Кастомные workflow создаются командами управления схемами (подробно - урок 15.13):
# Создать схему с нуля (интерактивно)
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 теперь зависит от него:
research ──► proposal ──► tasks
Сводка архитектуры
| Аспект | Легаси | OPSX |
|---|---|---|
| Шаблоны | Зашиты в TypeScript | Внешние YAML + Markdown |
| Зависимости | Нет (всё сразу) | DAG с топологической сортировкой |
| Состояние | Ментальная модель «фаз» | Существование файлов |
| Кастомизация | Править исходники, пересборка | Создать schema.yaml |
| Итерация | Phase-locked | Гибкая, правьте что угодно |
| Поддержка редакторов | Специфичные конфигураторы/адаптеры | Один каталог skills |
Agent Contract: машинночитаемые поверхности CLI
Agent Contract - документированные формы JSON-вывода команд openspec, сверенные с исходным кодом. Это контракт, на который опираются сгенерированные skills и любые ваши интеграции: CLI обязуется держать эти формы стабильными в рамках релиза.
Общие конвенции
- Один JSON-документ на вызов. В режиме
--jsonstdout несет ровно один 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 doctorgit.*, формы ошибок).
Диагностический конверт
Все машинночитаемые диагностики делят одну форму:
{
"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):
--store <id>→ корень зарегистрированного Store (source: "store");- ближайший предок с
openspec/→ этот корень (source: "nearest"; указательstore:игнорируется с предупреждением в stderr), либо каталог только с конфигом и валидным указателемstore:→ этот Store (source: "declared"); - нет ближайшего корня + глобальный
defaultStore→ этот Store (source: "global_default"); устаревший id падает с ошибкой Store иfix, называющимopenspec config unset defaultStore; - нет корня, нет дефолта, есть зарегистрированные Store → ошибка
no_root_with_registered_stores; - нет ничего → команда может трактовать текущий каталог как
source: "implicit";doctor,context,listи пакетныйvalidateвместо этого падают сno_openspec_root.
Успешные 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и т.д.; в betaopenspec/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.
Известные несоответствия
Зафиксированы аудитом; переименования опубликованных ключей - продуктовые решения, отложенные до версионируемого релиза:
- Несколько путей провала в
--json-режиме печатали только stderr без JSON-документа - исправлено: неизвестные/неоднозначные item уshow/validateвыдаютstatusс кодамиunknown_item/ambiguous_item, выброшенные ошибки маршрутизируются через JSON-aware helper. store_root_missingвыдается с двумя уровнями (warning при remove, error в store doctor) - контекстно, задокументировано.snake_case(store-семейство) противcamelCase(workflow-семейство);root.store_idвездеsnake_case.- В исходниках существуют четыре параллельных объявления типа конверта; диагностики archive не несут
target. list --jsonпереиспользует ключstatusкак строковый enum на изменение.- Только вывод
validateнесет полеversion. templatesигнорирует выбор корня (привязка к cwd, без--store).- Устаревшие именные формы (
change/spec-подкоманды) выдают формы без конвертаroot/status.
Инженерный вывод курса: Agent Contract - образец дисциплины «CLI для человека и агента». Один и тот же инструмент имеет человеческий режим (проза, баннеры, подсказки) и агентный (--json, конверт диагностик, коды выхода, гарантии «один документ на вызов»). Для собственных CLI, с которыми работают агенты, это готовый шаблон: машинный контракт важнее человекочитаемого вывода, потому что агент не прощает неоднозначности, которые человек пропустит.
Практика
- В тестовом проекте создайте изменение и на каждом шаге сравнивайте человеческий и машинный режимы:bash
openspec status --change <name> openspec status --change <name> --json
- Выполните
openspec instructions <artifact> --change <name> --jsonдля готового артефакта и найдите в выводеtemplate,dependenciesиunlocks. - Проверьте контракт ошибок: запустите
openspec show nonexistent --jsonиopenspec status --jsonвне корня OpenSpec - убедитесь, что stdout содержит один JSON-документ сstatus: [...], а код выхода равен 1. - Смоделируйте «гибкую итерацию»: во время
/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, каталог диагностических кодов и задокументированные известные несоответствия.
Вопросы для самопроверки
- Чем «зависимости-включатели» отличаются от «фазовых ворот» и почему это принципиально для итерации посреди реализации?
- Какие три компонента образуют архитектуру OPSX и что делает движок графа артефактов?
- Как агент в OPSX определяет, какой артефакт создавать следующим? Какие две команды и в каком порядке он вызывает?
- Почему состояние
skippedудовлетворяет зависимости, но артефакт создавать нельзя, и в каком случае оно появляется? - По каким трем вопросам (intent/scope/approach) документация советует решать «обновлять или начинать заново»?
- Что гарантирует Agent Contract для режима
--jsonпо количеству документов, где размещается проза и что происходит при провале команды? - Что означают коды выхода 0, 1 и 130 в контракте CLI, и почему находки здоровья doctor завершаются нулем?
Что дальше
Урок 15.15 закрывает модуль: частые вопросы, типовые ошибки и их исправления (troubleshooting) и миграция со старого workflow /openspec:* на OPSX - включая судьбу существующих изменений и маркерных блоков в CLAUDE.md/AGENTS.md.