--- name: code-openspec description: "Завести и настроить OpenSpec в проекте — openspec init --tools claude, замена закомментированного примера в openspec/config.yaml на настройку канонической формы (язык, правила именования capability, придирки валидатора, адреса паспорта и CLAUDE.md), проверка формы своим скриптом openspec.py (имя файла, схема, незаменённый пример, адреса документов, ключи rules против артефактов схемы) и сверка слепка с живой версией инструмента. Использовать, когда в проекте нет каталога openspec/, когда config.yaml остался примером из коробки, когда заводят новый проект или переводят чужой и дошли до шага OpenSpec, а также когда конвейер отказался работать без источника требований. Каталог openspec нужен именно конвейеру: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований." --- # OpenSpec в проекте Каталог `openspec/` — **предпосылка конвейера**, а не канона документов. Без него не работают ни `opsx:propose`, ни ревью дизайна, ни `review-specs`: у требований не остаётся дома. Поэтому заводит и настраивает его этот скилл — тот, кто по OpenSpec и работает. Канон документов о файле не высказывается вовсе: `docs.py` его не открывает и об его отсутствии молчит. Проект, не ведущий задачи циклом SDD, живёт без OpenSpec законно, и проверять там нечего. За каноном остаётся одно — **единственный дом**: не пересказан ли в `context` документ, у которого есть свой файл. Это суждение, а не форма, и смотрит его агент. ## Два шага, и второй важнее первого **1. Завести.** ``` openspec init --tools claude ``` Команда кладёт ещё `.claude/skills/openspec-*` и `.claude/commands/opsx/*` — это её нормальная работа, не трогай их. **2. Заменить пример.** `openspec init` кладёт `config.yaml`, где `context` и `rules` — закомментированный пример на английском. **Файл из коробки хуже отсутствующего:** он есть, он валиден, имя правильное, — и читается как настроенный, работая как пустой. Узнаётся это по уже написанному предложению: на другом языке, с capability по имени пакета, без единого `SHALL`. Пример **заменяется целиком** по образцу: [references/config-skeleton.md](references/config-skeleton.md). ## Что туда пишут, а что нет **Это маршрутизатор, а не второй дом фактов.** Внутрь идёт ровно то, что нужно **в момент порождения артефакта** и чего в этот момент ещё никто не открыл: язык, правила именования capability, придирки валидатора и **адреса** документов проекта. Сюда же — **требования к форме `proposal` и `design`**, на которых стоит чекпоинт скилла `av-dev:code-resolve`: объяснение человеку собирается из этих двух артефактов, и требование к ним обязано применяться в момент, когда их пишут, а не вспоминаться шагом позже. Образец их содержит. Пересказ паспорта, инвариантов, конвенций и правил ревью сюда **не переносится**. Место для второго дома здесь самое частое: `context` читается при порождении каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии инвариантов, состава гейта и правил выбора метки. Расходятся они молча, а замечают это в уже написанном предложении. Разрез, по которому отличают одно от другого: **утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ; строка, которая говорит, какой файл открыть, — ссылка.** Машина этот разрез не проверяет; его смотрит агент `doc-consistency`, когда документы канона в проекте есть. Два адреса обязательны — `docs/passport.md` и `CLAUDE.md`: предложение пишется до того, как кто-либо откроет `docs/`, и без них его пишут, не зная ни границы домена, ни инвариантов. Отсутствие адреса к **существующему** документу `openspec.py check` называет отказом; документа нет в проекте — нет и требования. ## Инструмент ``` os="$CLAUDE_PLUGIN_ROOT/skills/code-openspec/scripts/openspec.py" python3 $os check --dir <корень> # форма config.yaml в проекте python3 $os form # слепок формы против живого OpenSpec ``` **Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий для всех скриптов, и ни один скилл им не владеет. Правится дом, а не этот файл. **Коды выхода — общий словарь всех скриптов `av-dev`. Ветвись на коде, а не на тексте вывода.** | Код | Что случилось | | --- | --- | | 0 | сошлось | | 1 | дрейф: рабочая ситуация, чинится | | 2 | ошибка употребления: аргументы или нарушенное правило | | 3 | окружение: не тот каталог, битый конфиг, нет инструмента | | 4 | внутренний сбой — дефект скрипта, доложить | **Различать 1 и 3 обязательно.** «Дрейф» — рабочая ситуация, и чинится она правкой предмета; «окружение» — нерабочая, и повтор той же командой не поможет. Одинаковая реакция на них неверна в обоих случаях. Здесь это значит: «форма разошлась» — рабочая ситуация, «openspec не отвечает» — нерабочая. `check` проверяет форму, и каждая проверка — про молчащий пробел, а не про вкус: каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом не сообщает); ключ `schema` называет ту схему, для которой форма описана; `context` и `rules.specs` не остались примером, **а `SHALL` назван именно внутри `rules.specs`** (в `context` он стоит и в образце, поэтому греп по файлу здесь ничего не значит); `context` называет паспорт и `CLAUDE.md`; ключи под `rules:` — имена артефактов схемы, а не свободные слова. Числа проверок здесь нет намеренно: оно протухает от каждой добавленной. **Адреса требуются только к тем документам, которые в проекте есть.** Документы канона могут быть не заведены; требовать ссылку на несуществующий файл значит требовать битую ссылку. Нет `docs/passport.md` — проверка по нему идёт строкой «не проверялось», и там же сказано, что без канона конвейер работает вслепую. ### Форма сверяется с живым инструментом Схема (`spec-driven`) и перечень артефактов (`proposal`, `specs`, `design`, `tasks`) — **состояние чужого инструмента**, а не наше решение. OpenSpec переименует артефакт: правила под прежним именем перестанут применяться, конфиг останется выглядеть написанным, и молчат при этом все три стороны. Сторож — сравнение версий. `check` каждым прогоном спрашивает `openspec --version` (десятые доли секунды) и сравнивает `major.minor` с той версией, на которой форма сверялась; разошлось — **замечание**, не отказ, с именем команды. Патч-версия в сравнение не берётся намеренно: формы она не меняет, а нагоняй на каждый багфикс приучает пролистывать весь блок. Перепроверяет `openspec.py form`: он спрашивает `openspec templates --json`, то есть перечень артефактов текущей схемы, и печатает, что разошлось с константами. Дорогой вызов вынесен из `check` сознательно — он стоит втрое дороже опроса версии, а ответ меняется только вместе с версией. **Чинится расхождение в плагине, а не в проекте:** константы скрипта, образец [references/config-skeleton.md](references/config-skeleton.md) и запись в журнал версий канона. ## Кто зовёт этот скилл - `av-dev:doc-init` — шагом заведения нового проекта, до первого документа; - `av-dev:canon` в режиме `adopt` — если на переводимом проекте каталога нет или `config.yaml` остался примером; - `av-dev:code-resolve` и `av-dev:code-review` — не вызовом по ходу, а отсылкой: OpenSpec у обоих жёсткая предпосылка, и на проекте без каталога оба посылают сюда вместо того, чтобы заводить его руками; - человек — когда конвейер отказался работать без источника требований. **Копия.** Дом правила — `shared/absence.md` в репозитории плагина. Правится дом, а не этот файл. **Скилл не вправе считать раскладку проекта полной.** Части заводятся порознь и живут порознь; каждая узнаётся своим следом: | Чего нет | Как видно | Чего теперь не делает никто | | --- | --- | --- | | настройки av-dev | нет `.av-dev.toml` в корне | проект под процесс не заводился; версии нет, настроек нет | | документы канона | нет `docs/` | проектную конкретику брать неоткуда — темы, инварианты, прецеденты | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`, `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении. **Внешний плагин может не стоять.** Их два: `opsx:*` — цикл SDD, и `av-dev-git:commit` — сообщения коммитов. Путь в дерево чужого плагина не пишется никогда: `$CLAUDE_PLUGIN_ROOT` ведёт только в своё дерево, а вычисленный от него путь к соседу либо не откроется, либо откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он прочитает его сам. **Отсутствие — исход, а не поломка.** Назови строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя: пропуск неотличим от сделанного. Выдумывать обходной путь нельзя тоже. **Присутствие узнаётся следом в проекте, а не объявлением.** Перечня того, что здесь заведено, проект не ведёт — он разошёлся бы с действительностью молча. Здесь это значит: документов канона в проекте может не быть, и тогда `context` называет только те адреса, которые есть, — строкой доклада говорится, что без паспорта предложение пишут, не зная границы домена. ## Чего этот скилл не делает - **Не пишет спеки и предложения.** Это `opsx:propose` и конвейер задачи. - **Не ведёт документы канона** — их дом скилл `av-dev:canon`, и адреса в `context` только на них ссылаются. - **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в плагине: константы скрипта, образец здесь, запись в журнал версий канона. - **Не судит, ссылается `context` на документы или пересказывает их.** Машине этот разрез не виден; его смотрит агент `doc-consistency`. Документов канона в проекте нет — сверять пересказ не с чем, и так и скажи.