валидатор config.yaml переехал в конвейер: у пайплайна свой скрипт

Решение 51 отдало OpenSpec конвейеру и честно оставило хвост: проверка формы и
сторож версии остались в docs.py, потому что своего скрипта у пайплайна не было
ни одного. Хвост не косметический — это ровно то состояние, против которого
написан весь канон: у файла два владельца, один заводит, другой проверяет, и
разойтись они могут молча.

252 строки переехали в av-dev-pipeline/skills/openspec/scripts/openspec.py: пять
проверок формы, сторож версии, сверка слепка с живым инструментом. Команды две —
check --dir <корень> и form; коды выхода общие со всеми скриптами av-dev. Из
docs.py удалены константы OPENSPEC_*, check_openspec, openspec_cli,
check_openspec_fresh, rules_keys и подкоманда openspec-form; про config.yaml он
больше не говорит ничего, кроме строки границы механизируемого — что форму
смотрит чужой скрипт. openspec/specs/ он по-прежнему знает: это дом темы
requirements и часть карты тем.

Переезд оплатился сразу, и не тем, чего ждали. Прежняя проверка требовала, чтобы
context называл docs/passport.md и CLAUDE.md, безусловно — то есть на проекте без
канона документов требовала ссылку на несуществующий файл. Пока код жил в скрипте
канона, допущение «канон есть» было незаметным: скрипт канона запускают там, где
канон есть. В скрипте конвейера то же допущение стало видно на первом прогоне.
Теперь адрес требуется только к существующему документу, отсутствие идёт строкой
«не проверялось» с названной ценой — без канона конвейер работает вслепую.

Заодно починен хвост от раскола плагинов: pyrefly project-includes в pyproject
всё ещё указывали на av-dev-pm. Линтер на явных файлах работал, а на обходе
проекта не проверял ничего.

Проверено пятью случаями: нет openspec (1), годный конфиг (0), опечатка в имени
артефакта под rules (1), проект без канона (0, с двумя строками «не
проверялось»), неизвестная команда (2).

Канон повышен до версии 10. Главное в записи — тихая потеря: форму раньше
проверял docs.py check заодно, теперь нужен отдельный шаг openspec.py check в
гейте, иначе незаменённый пример в config.yaml перестанет ловиться. Решение — 52.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-09 14:28:12 +03:00
co-authored by Claude Opus 5
parent 9453a218d1
commit fdadfb65ac
11 changed files with 534 additions and 358 deletions
+50 -18
View File
@@ -1,6 +1,6 @@
---
name: openspec
description: "Завести и настроить OpenSpec в проекте — openspec init --tools claude, замена закомментированного примера в openspec/config.yaml на настройку канонической формы (язык, правила именования capability, придирки валидатора, адреса паспорта и CLAUDE.md), проверка, что форма не разошлась с живой версией инструмента. Использовать, когда в проекте нет каталога openspec/, когда config.yaml остался примером из коробки, когда заводят новый проект или переводят чужой и дошли до шага OpenSpec, а также когда конвейер отказался работать без источника требований. Каталог openspec нужен именно конвейеру: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований."
description: "Завести и настроить OpenSpec в проекте — openspec init --tools claude, замена закомментированного примера в openspec/config.yaml на настройку канонической формы (язык, правила именования capability, придирки валидатора, адреса паспорта и CLAUDE.md), проверка формы своим скриптом openspec.py (имя файла, схема, незаменённый пример, адреса документов, ключи rules против артефактов схемы) и сверка слепка с живой версией инструмента. Использовать, когда в проекте нет каталога openspec/, когда config.yaml остался примером из коробки, когда заводят новый проект или переводят чужой и дошли до шага OpenSpec, а также когда конвейер отказался работать без источника требований. Каталог openspec нужен именно конвейеру: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований."
---
# OpenSpec в проекте
@@ -10,9 +10,11 @@ description: "Завести и настроить OpenSpec в проекте
не остаётся дома. Поэтому заводит и настраивает его этот плагин — тот, кто по
OpenSpec и работает.
Канон документов о файле всё ещё высказывается, но односторонне: `docs.py check`
проверяет форму `config.yaml`, **если каталог есть**, и молчит, если его нет.
Проект без конвейера живёт без OpenSpec законно.
Канон документов о файле не высказывается вовсе: `docs.py` его не открывает и об
его отсутствии молчит. Проект без конвейера живёт без OpenSpec законно, и
проверять там нечего. За каноном остаётся одно — **единственный дом**: не
пересказан ли в `context` документ, у которого есть свой файл. Это суждение, а не
форма, и смотрит его агент.
## Два шага, и второй важнее первого
@@ -54,28 +56,55 @@ openspec init --tools claude
Два адреса обязательны — `docs/passport.md` и `CLAUDE.md`: предложение пишется до
того, как кто-либо откроет `docs/`, и без них его пишут, не зная ни границы
домена, ни инвариантов. Их отсутствие `docs.py check` называет отказом.
домена, ни инвариантов. Отсутствие адреса к **существующему** документу
`openspec.py check` называет отказом; документа нет в проекте — нет и требования.
## Форма сверяется с живым инструментом
## Инструмент
```
os="$CLAUDE_PLUGIN_ROOT/skills/openspec/scripts/openspec.py"
python3 $os check --dir <корень> # форма config.yaml в проекте
python3 $os form # слепок формы против живого OpenSpec
```
**Коды выхода — общий словарь скриптов av-dev:** 0 сошлось, 1 дрейф, 2 ошибка
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте.
Различать 1 и 3 обязательно: «форма разошлась» — рабочая ситуация, «openspec не
отвечает» — нерабочая.
`check` проверяет пять вещей, и каждая — про молчащий пробел, а не про вкус:
каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом
не сообщает); `context` и `rules.specs` не остались примером, а правила называют
`SHALL`; `context` называет паспорт и `CLAUDE.md`; ключи под `rules:` — имена
артефактов схемы, а не свободные слова.
**Адреса требуются только к тем документам, которые в проекте есть.** Канон
документов ставится отдельным плагином и может быть не подключён; требовать
ссылку на несуществующий файл значит требовать битую ссылку. Нет
`docs/passport.md` — проверка по нему идёт строкой «не проверялось», и там же
сказано, что без канона конвейер работает вслепую.
### Форма сверяется с живым инструментом
Схема (`spec-driven`) и перечень артефактов (`proposal`, `specs`, `design`,
`tasks`) — **состояние чужого инструмента**, а не наше решение. OpenSpec
переименует артефакт: правила под прежним именем перестанут применяться, конфиг
останется выглядеть написанным, и молчат при этом все три стороны.
Сторож — сравнение версий, и живёт он пока в `docs.py` плагина канона:
Сторож — сравнение версий. `check` каждым прогоном спрашивает `openspec
--version` (десятые доли секунды) и сравнивает `major.minor` с той версией, на
которой форма сверялась; разошлось — **замечание**, не отказ, с именем команды.
Патч-версия в сравнение не берётся намеренно: формы она не меняет, а нагоняй на
каждый багфикс приучает пролистывать весь блок.
```
python3 <канон>/skills/canon/scripts/docs.py openspec-form
```
`check` каждым прогоном сравнивает `major.minor` установленного OpenSpec с той
версией, на которой форма сверялась, и при расхождении просит эту команду. Она
ничего не правит — спрашивает инструмент и печатает, что разошлось. **Чинится
расхождение в плагине, а не в проекте.**
Плагина канона в проекте нет — сторожа тоже нет, и это надо назвать строкой, а не
считать, что форма верна.
Перепроверяет `openspec.py form`: он спрашивает `openspec templates --json`, то
есть перечень артефактов текущей схемы, и печатает, что разошлось с константами.
Дорогой вызов вынесен из `check` сознательно — он стоит втрое дороже опроса
версии, а ответ меняется только вместе с версией. **Чинится расхождение в
плагине, а не в проекте:** константы скрипта, образец
[references/config-skeleton.md](references/config-skeleton.md) и запись в журнал
версий канона.
## Кто зовёт этот скилл
@@ -95,3 +124,6 @@ python3 <канон>/skills/canon/scripts/docs.py openspec-form
`context` только на них ссылаются.
- **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в
плагине: константы скрипта, образец здесь, запись в журнал версий канона.
- **Не судит, ссылается `context` на документы или пересказывает их.** Машине
этот разрез не виден; его смотрит агент `doc-consistency` из плагина канона.
Плагина нет — эту проверку не делает никто, и так и скажи.