init заводит openspec сам; конфиг стал слотом канона

Каталог openspec/ был предпосылкой, о которой канон говорил, но за которой не
следил. openspec/specs/ объявлен домом темы requirements, config.yaml описан
абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил
из init с полным каноном документов и без каталога, без которого не работают ни
opsx:propose, ни ревью дизайна, ни сверка требований.

Теперь init делает openspec init --tools claude шагом 3, до первого документа, а
adopt заводит его тем же способом, если на переводимом проекте его нет. Команда
названа поимённо в трёх местах — скилле, каноне и отказе docs.py: отказ без
команды заставляет искать её в другом месте.

Файл из коробки оказался хуже отсутствующего, и потому проверяется машиной.
openspec init кладёт config.yaml, где context и rules — закомментированный пример
на английском. Такой файл читается как настроенный: он есть, он валиден, имя
правильное. Работает он как пустой, и узнаётся это по уже написанному
предложению — на другом языке, с capability по имени пакета, без единого SHALL.
docs.py проверяет четыре вещи, каждая про молчащий пробел: каталог есть; имя
именно config.yaml (config.yml OpenSpec не читает и об этом не сообщает); context
и rules.specs не остались примером, а правила называют SHALL; context называет
passport и CLAUDE.md. Последние два обязательны по порядку работы: предложение
пишется до того, как кто-либо откроет docs/, и без этих строк его пишут, не зная
ни границы домена, ни инвариантов.

Форма конфига записана скелетом и сформулирована разрезом: утверждение, которое
можно опровергнуть, открыв другой файл проекта, — пересказ; строка, которая
говорит, какой файл открыть, — ссылка. Машина этот разрез не проверяет, отличить
одно от другого она не умеет; он отдан doc-consistency отдельным абзацем правила
«один факт — один дом», и config.yaml добавлен ему во вход. Место второго дома
там самое частое: context читается при порождении каждого артефакта, туда удобно
дописать «чтобы агент знал», и так заводятся копии инвариантов, конвенций,
состава гейта и правил ревью.

Образец лёг в канон, а не в конвейер, как планировало решение C: форма документа
принадлежит владельцу канона документов, конвейер её читатель. Иначе
av-dev-pipeline завёл бы описание файла, который заводит и проверяет av-dev-pm.

Канон повышен до версии 7 с записью, выполнимой upgrade: завести openspec,
привести config.yaml к скелету, вычистить из context пересказ, проверить имя
файла, поднять номер в .pm.json. Проверка прогнана на четырёх фикстурах — свежий
openspec init, два живых проекта и пустой каталог; отличает все четыре случая.
Решение — 47.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-07 11:59:01 +03:00
co-authored by Claude Opus 5
parent 91d4264b40
commit a79266cfcb
9 changed files with 359 additions and 26 deletions
+52
View File
@@ -3031,3 +3031,55 @@ JJJ): у профиля обязан быть один правильный от
режется, вложенное (метки) — нет: разрез вложенного даёт дублирование
общей части, а дублирование намеренно неточное машина не сверит.
## 47. OpenSpec заводится скиллом, а его конфиг — часть канона (2026-08-07)
**АЕАИА. `init` заводит OpenSpec сам, а не оставляет это человеку.** Каталог
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил:
`openspec/specs/` объявлен домом темы `requirements`, `config.yaml` описан
абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил
из `init` с полным каноном документов и без каталога, без которого не работают ни
`opsx:propose`, ни ревью дизайна, ни сверка требований. Команда названа поимённо
(`openspec init --tools claude`) в трёх местах — скилле, каноне и отказе скрипта:
отказ без команды заставляет искать её в другом месте.
**АЕАИБ. Файл из коробки хуже отсутствующего, и потому проверяется машиной.**
`openspec init` кладёт `config.yaml`, где `context` и `rules` — закомментированный
пример на английском. Такой файл читается как настроенный: он есть, он валиден,
имя правильное. Работает он как пустой, и узнаётся это по уже написанному
предложению — на другом языке, с capability по имени пакета, без единого `SHALL`.
`docs.py` проверяет четыре вещи, и каждая про молчащий пробел: каталог есть; имя
именно `config.yaml` (`config.yml` OpenSpec не читает и об этом не сообщает);
`context` и `rules.specs` не остались примером, а правила называют `SHALL`;
`context` называет `passport` и `CLAUDE.md`.
**АЕАИВ. Форма конфига — маршрутизатор, и это разрез, а не пожелание.**
Утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ;
строка, которая говорит, какой файл открыть, — ссылка. `context` читается при
порождении **каждого** артефакта, туда удобно дописать «чтобы агент знал», и
именно поэтому в нём заводятся вторые дома инвариантов, конвенций, гейта и правил
ревью. Машина этот разрез не проверяет — отличить ссылку от пересказа она не
умеет, — и он отдан `doc-consistency` отдельным абзацем правила «один факт — один
дом», с `config.yaml`, добавленным ему во вход.
**Обязательными сделаны ровно два адреса — паспорт и `CLAUDE.md`.** Причина в
порядке работы: предложение пишется **до** того, как кто-либо откроет `docs/`, и
без этих двух строк его пишут, не зная ни границы домена, ни инвариантов.
Длинный список адресов превратил бы `context` во второй дом ровно тем способом,
против которого правило и заведено.
**Образец конфига лёг в канон, а не в конвейер**, как планировалось решением C.
Форма документа принадлежит тому, кто владеет каноном документов; конвейер её
читатель. Иначе `av-dev-pipeline` завёл бы у себя описание файла, который заводит
и проверяет `av-dev-pm`, — тот же шов, что разбирали, убирая имена проходов из
канона.
### Что из этого следует
167. **Предпосылка, за которой никто не следит, — не предпосылка, а пожелание.**
Если условие названо обязательным, его должен кто-то заводить и кто-то
проверять; иначе оно живёт ровно до первого проекта, где о нём забыли.
168. **Заполненная форма и заполненный смысл — разные вещи, и первая маскирует
вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу:
это худший вид пробела, потому что выглядит он как его отсутствие.