валидатор 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:
@@ -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"
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user