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:
@@ -1,6 +1,6 @@
|
||||
# Канон документов проекта
|
||||
|
||||
**Версия 6.**
|
||||
**Версия 7.**
|
||||
|
||||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||||
@@ -106,6 +106,7 @@ openspec/
|
||||
| `database.*` | источник | `operations` — схема и настройки с числами |
|
||||
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
|
||||
| `openspec/specs/` | источник | `requirements` |
|
||||
| `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) |
|
||||
| `tasks/` | процессный | — |
|
||||
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
||||
| `adr.*` | процессный | — |
|
||||
@@ -411,10 +412,40 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
### `openspec/config.yaml`
|
||||
|
||||
**Только нужды генерации артефактов** — язык, правила именования capability,
|
||||
придирки валидатора RFC 2119 — плюс ссылки на документы канона. Правило ревью,
|
||||
придирки валидатора RFC 2119 — плюс **адреса** документов канона. Правило ревью,
|
||||
пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй
|
||||
дом разойдётся на первой же правке.
|
||||
|
||||
**Каталог `openspec/` — часть канона, а не соседняя технология.** В нём дом темы
|
||||
`requirements`, и заводится он командой: `openspec init --tools claude`. Её
|
||||
выполняет `init` на новом проекте и `adopt` на переводимом; из канона она названа
|
||||
поимённо потому, что её печатает отказ `docs.py`, а отказ без команды заставляет
|
||||
искать её в другом месте.
|
||||
|
||||
**Файл из коробки настройкой не является.** `openspec init` кладёт `config.yaml`,
|
||||
где и `context`, и `rules` лежат закомментированным примером. Такой файл читается
|
||||
как настроенный — он есть, он валиден, у него правильное имя, — а работает как
|
||||
пустой: предложение пишется без языка, без правил именования capability и без
|
||||
знания, где лежит граница домена. Это ровно тот класс, против которого написан
|
||||
весь канон, и потому здесь он проверяется машиной, а не чтением.
|
||||
|
||||
Проверяется четыре вещи, и каждая — про молчащий пробел, а не про вкус:
|
||||
|
||||
1. **`openspec/` есть.** Нет — нет и дома темы `requirements`.
|
||||
2. **Имя файла `config.yaml`.** `config.yml` OpenSpec не читает и об этом не
|
||||
сообщает: настройка, написанная в файл с таким именем, пропадает целиком.
|
||||
3. **`context` и `rules.specs` не остались примером.** Правила для `specs`
|
||||
обязаны называть `SHALL`: требование без этого литерала валидатор отвергает.
|
||||
4. **`context` называет `passport` и `CLAUDE.md`.** Предложение пишется **до**
|
||||
того, как кто-либо откроет `docs/`; без этих двух адресов его пишут, не зная
|
||||
ни границы домена, ни инвариантов.
|
||||
|
||||
Пятого — «нет ли здесь пересказа» — машина не проверяет: отличить ссылку от
|
||||
пересказа она не умеет. Это работа `doc-consistency`, и раздел «Что проверяет
|
||||
машина, а что человек» называет её строкой.
|
||||
|
||||
Форма — [skeletons.md](skeletons.md).
|
||||
|
||||
## Правило единственного дома
|
||||
|
||||
Факт живёт ровно в одном файле; остальные ссылаются. Карта на случай спора:
|
||||
@@ -482,6 +513,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
| маркеры долга — числом | **протухший факт, разошедшийся с кодом** | `doc-code-drift` |
|
||||
| миграция изменена, а `database.md` нет | зависимость в манифесте, не названная в обзоре | `doc-code-drift` |
|
||||
| capability без упоминания в `architecture.md` | второй способ там, где обзор обещал единственный | `doc-code-drift` |
|
||||
| `openspec/config.yaml`: имя, `schema`, незаменённый пример, адреса паспорта и `CLAUDE.md` | **пересказ документа канона в `context` вместо ссылки** | `doc-consistency` |
|
||||
| | связность и читаемость | `doc-wording` |
|
||||
|
||||
**Агентов двое, и разведены они по глубине, а не по охвату.** `doc-consistency`
|
||||
@@ -507,7 +539,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
|
||||
```json
|
||||
{
|
||||
"canon": 6,
|
||||
"canon": 7,
|
||||
"migrations": "internal/store/migrations",
|
||||
"tasks": {
|
||||
"backlog": "INDEX.md"
|
||||
|
||||
Reference in New Issue
Block a user