валидатор 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
+16 -55
View File
@@ -427,62 +427,23 @@ kebab-case.** Причина не эстетическая: имя файла с
### `openspec/config.yaml`
**Только нужды генерации артефактов** — язык, правила именования capability,
придирки валидатора RFC 2119 — плюс **адреса** документов канона. Правило ревью,
пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй
дом разойдётся на первой же правке.
**Файл канону не принадлежит, и проверяет его тоже не канон.** Каталог
`openspec/` — предпосылка конвейера: без него не работают ни `opsx:propose`, ни
ревью дизайна, ни сверка требований. Заводит его, настраивает и **проверяет
форму** плагин `av-dev-pipeline`, скилл `openspec`: там образец файла, там же
скрипт `openspec.py check`. `docs.py` о файле не говорит ничего.
**Каталог `openspec/` принадлежит конвейеру, а не канону.** В нём дом темы
`requirements`, и нужен он тому, кто по OpenSpec работает: без каталога не
работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Заводит и
настраивает его скилл `av-dev-pipeline:openspec`; `init` и `adopt` его только
зовут. Команда (`openspec init --tools claude`) названа здесь поимённо потому,
что её печатает вывод `docs.py`, а адрес без команды заставляет искать её в
другом месте.
Канон называет его здесь по одной причине: `openspec/specs/` — **дом темы
`requirements`**, и без этой строки карта тем неполна. На форму самого
`config.yaml` канон не высказывается.
**Отсюда и односторонность: канон о файле высказывается, но его не требует.**
`docs.py check` проверяет форму, **если каталог есть**, и говорит
«неприменимо», если его нет. Проект без конвейера живёт без OpenSpec законно, и
отказом это быть не может.
**Файл из коробки настройкой не является.** `openspec init` кладёт `config.yaml`,
где и `context`, и `rules` лежат закомментированным примером. Такой файл читается
как настроенный — он есть, он валиден, у него правильное имя, — а работает как
пустой: предложение пишется без языка, без правил именования capability и без
знания, где лежит граница домена. Это ровно тот класс, против которого написан
весь канон, и потому здесь он проверяется машиной, а не чтением.
Проверяется пять вещей, и каждая — про молчащий пробел, а не про вкус:
1. **`openspec/` есть.** Нет — проверка неприменима, и это не отказ: каталог
нужен конвейеру, а не канону. Остальные четыре идут только при живом каталоге.
2. **Имя файла `config.yaml`.** `config.yml` OpenSpec не читает и об этом не
сообщает: настройка, написанная в файл с таким именем, пропадает целиком.
3. **`context` и `rules.specs` не остались примером.** Правила для `specs`
обязаны называть `SHALL`: требование без этого литерала валидатор отвергает.
4. **`context` называет `passport` и `CLAUDE.md`.** Предложение пишется **до**
того, как кто-либо откроет `docs/`; без этих двух адресов его пишут, не зная
ни границы домена, ни инвариантов.
5. **Ключи под `rules:` — имена артефактов схемы** (`proposal`, `specs`,
`design`, `tasks`). Правило, адресованное несуществующему артефакту, не
применяется и об этом молчит: `rules.spec` вместо `rules.specs` — конфиг,
выглядящий написанным и не работающий.
**Схема и перечень артефактов — слепок чужого инструмента, и он стареет.**
OpenSpec переименует артефакт или сменит схему — правила под прежним именем
перестанут действовать молча, а канон будет продолжать требовать прежнее.
Поэтому за свежестью слепка следит машина: `check` сравнивает `major.minor`
установленного OpenSpec с версией, на которой форма сверялась, и при расхождении
даёт **замечание** (не отказ: патч-версии формы не меняют, а нагоняй на каждый
багфикс приучает пролистывать блок). Перепроверяет `docs.py openspec-form` — он
спрашивает сам инструмент и печатает, что разошлось. **Чинится это в плагине, а
не в проекте:** константы скрипта, скелет и запись в журнал версий канона.
Шестого — «нет ли здесь пересказа» — машина не проверяет: отличить ссылку от
пересказа она не умеет. Это работа `doc-consistency`, и раздел «Что проверяет
машина, а что человек» называет её строкой.
Форма — [skeletons.md](skeletons.md).
**Одно за каноном всё же остаётся, и это не форма, а единственный дом.** Блок
`context` — самое частое место для второго дома: он читается при порождении
каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии
инвариантов, конвенций, состава гейта и правил ревью. Расходятся они молча.
Разрез: **утверждение, которое можно опровергнуть, открыв другой файл проекта, —
пересказ; строка, которая говорит, какой файл открыть, — ссылка.** Машина этого
не различает; судит агент `doc-consistency`, и `config.yaml` у него во входе.
## Правило единственного дома
@@ -578,7 +539,7 @@ OpenSpec переименует артефакт или сменит схему
```json
{
"canon": 9,
"canon": 10,
"migrations": "internal/store/migrations"
}
```
@@ -13,6 +13,46 @@ upgrade` идёт по записям снизу вверх от версии п
---
## Версия 10 — 2026-08-09
Проверка формы `config.yaml` ушла к тому, кто файл заводит. Версия 9 перенесла в
конвейер настройку OpenSpec и честно назвала остаток: форма и сторож версии
остались в `docs.py`, то есть у файла было два плагина — один заводит, другой
проверяет. Остаток закрыт.
**Что появилось.** Скрипт `openspec.py` в скилле `av-dev-pipeline:openspec`, две
команды: `check --dir <корень>` — форма в проекте, `form` — сверка слепка с живым
OpenSpec. Коды выхода те же, что у `docs.py` и `tasks.py`.
**Что удалено из `docs.py`.** Константы `OPENSPEC_*`, проверка формы, сторож
версии и подкоманда `openspec-form` — 252 строки. Скрипт канона про
`openspec/config.yaml` не говорит теперь ничего; `openspec/specs/` он по-прежнему
знает, потому что это дом темы `requirements` и часть карты тем.
**Что стало лучше по дороге.** Адреса `docs/passport.md` и `CLAUDE.md` требуются
теперь **только к тем документам, которые в проекте есть**. Прежняя проверка
требовала их безусловно, то есть на проекте без канона документов требовала
битую ссылку. Теперь отсутствие документа — строка «не проверялось» с указанием,
что без канона конвейер работает вслепую.
**Что осталось за каноном.** Один вопрос, и это не форма: не пересказан ли в
`context` документ, у которого есть свой дом. Разрез — утверждение, опровергаемое
открытием другого файла, против строки «открой такой-то файл»; машине он не
виден, судит агент `doc-consistency`, и `config.yaml` у него во входе.
**Что сделать проекту.**
1. Заменить в гейте и в скриптах `docs.py openspec-form` на `openspec.py form`.
Подкоманды больше нет: прежний вызов упадёт ошибкой употребления (код 2), а не
промолчит.
2. **Добавить в гейт шаг `openspec.py check`, если проект работает по OpenSpec.**
Форму раньше проверял `docs.py check` заодно; теперь он о ней молчит, и без
отдельного шага незаменённый пример в `config.yaml` перестанет ловиться. Это
главная потеря этого повышения, и она тихая.
3. Проект по OpenSpec без установленного `av-dev-pipeline` — форму не проверяет
никто. Либо поставить плагин, либо назвать это принятым риском вслух.
4. `docs/.pm.json`: `"canon": 10`.
## Версия 9 — 2026-08-09
OpenSpec уехал в конвейер. Каталог `openspec/` версией 7 был объявлен слотом
@@ -431,7 +431,7 @@ severity стоит здесь, а не выводится каждым прох
```json
{
"canon": 9
"canon": 10
}
```