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
+74 -1
View File
@@ -417,11 +417,84 @@ severity стоит здесь, а не выводится каждым прох
тронуть рабочие данные, без третьего вся шкала ранжирования триажа держится на
догадке.
## `openspec/config.yaml`
Каталог `openspec/` заводится командой — `openspec init --tools claude`, — и она
кладёт `config.yaml` с закомментированным примером внутри. Пример **заменяется
целиком**: нетронутый файл выглядит настроенным, а работает как пустой.
**Это маршрутизатор, а не второй дом фактов.** Сюда пишут ровно то, что нужно
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл:
язык, правила именования capability, придирки валидатора и **адреса** документов
канона. Пересказ паспорта, инвариантов, конвенций и правил ревью сюда не
переносится: расходится он молча, а замечают это в уже написанном предложении.
```yaml
schema: spec-driven
context: |
Language: Russian
Пиши на русском, но:
- Структурные заголовки оставляй на английском:
## ADDED/MODIFIED/REMOVED Requirements, ### Requirement:, #### Scenario:
- Ключевые слова GIVEN/WHEN/THEN и RFC 2119 (SHALL/MUST/SHOULD) — на английском
- Технические термины, пути и код — на английском
Имена capabilities:
- Capability — это ПОВЕДЕНИЕ или домен системы, а не пакет кода (совпадение с
именем пакета допустимо, но не критерий).
- Существительное, понятное без знания кода: ingest, parsing, storage,
read-api. НЕ store/httpapi — это реализация.
- Гранулярность по принципу «требования меняются вместе». Дробить, когда в
одной спеке смешиваются разные заботы. Переименовать дёшево (RENAMED
Requirements) — не дроби преждевременно в маленьком проекте.
RFC 2119 — требование валидатора, не стиль:
- Каждое ### Requirement ОБЯЗАНО содержать литерал SHALL или MUST, иначе
`openspec validate` падает. Поэтому эти слова и WHEN/THEN не русифицируем.
Что это за проект — читай перед предложением, а не отсюда:
- docs/passport.md — цель, её граница (чем проект НЕ является), потребители,
типовые сценарии, референсы;
- CLAUDE.md — инварианты с severity и семантика гейта;
- docs/architecture.md — устройство; docs/security.md — периметр;
docs/adr/ — почему решено так; docs/research/ — что уже измерено.
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
первым молча, и заметно это становится в предложении, которое уже написано.
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом
скилл av-dev-pipeline:review-pipeline, проектная настройка — docs/review.md.
Конвенции кода: механизированное проверяет гейт, прозой остаётся
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
пересказываем: и то и другое растёт по ходу задач.
Развилка или блокер — сперва prior art. Готовые решения смотрим в референсах
паспорта, отвергаем — с названной причиной, и причина идёт в design.md этого
же изменения.
rules:
proposal:
- Capabilities называй по поведению или домену системы, не по пакету кода
specs:
# Кавычки обязательны: без них YAML обрежет строку на первом '#'.
- "Каждое ### Requirement обязано содержать SHALL или MUST (иначе валидация падает)"
- "Сценарий — ровно #### (четыре решётки); три или список молча теряются"
- "SHALL/MUST должно стоять в ПЕРВОМ абзаце требования: валидатор смотрит только его"
- "Заголовки и WHEN/THEN/GIVEN — на английском, остальной текст на русском"
```
**Четыре правила для `specs` сняты отказами валидатора, а не выведены из
документации** — потому и записаны дословно: без них каждое второе предложение
узнаёт их падением `openspec validate --strict`. Блок `context` проект
дополняет своим (стек, разведка, особенности домена), но **адреса паспорта и
`CLAUDE.md` обязательны** — их отсутствие `docs.py check` называет отказом.
## `docs/.pm.json`
```json
{
"canon": 6
"canon": 7
}
```