openspec уехал в конвейер: заводит его пайплайн, канон только высказывается

Версия 7 объявила openspec/ слотом канона: init его заводил, adopt тоже, образец
config.yaml лежал в скелетах, отсутствие каталога docs.py считал отказом. Разрез
был проведён не там. По OpenSpec работает конвейер — без каталога не запускаются
ни opsx:propose, ни ревью дизайна, ни сверка требований, — а канон документов о
нём только высказывался. Проект, которому конвейер не нужен, получал отказ за
отсутствие того, чем не пользуется.

Появился скилл av-dev-pipeline:openspec: заводит каталог, заменяет
закомментированный пример в config.yaml настройкой, объясняет разрез между
ссылкой и пересказом — утверждение, опровергаемое открытием другого файла, это
пересказ; строка, говорящая какой файл открыть, это ссылка. Образец переехал туда
же, в references/config-skeleton.md, а в скелетах канона остался указатель.

init и canon adopt OpenSpec больше не заводят, а зовут скилл конвейера через
пространство имён. Вызов не разрешился — плагина конвейера нет, и это строка
доклада, а не поломка: docs.py о каталоге тогда тоже молчит. Отсутствие openspec/
стало неприменимостью вместо отказа, остальные четыре проверки формы идут только
при живом каталоге. На фикстуре без openspec дрейф упал с 10 пунктов до 9.

Что осталось на месте и названо честно: проверка формы config.yaml и сторож
версии (docs.py openspec-form) пока живут в скрипте канона. Перенести их значит
завести в конвейере свой скрипт, а этого у него нет ни одного. У файла сейчас два
плагина — один заводит, другой проверяет, — и это временное состояние, а не
задуманное; в журнале версий оно записано так же.

Канон повышен до версии 9. Запись не двигает ни одного файла проекта: меняется
только то, кто их заводит. Но в ней названа потеря, которую легко не заметить —
проект по OpenSpec без установленного пайплайна теперь не услышит от docs.py
ничего про свою настройку, и молчание это законное.

Гейт зелёный, скиллов стало десять.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-09 14:17:19 +03:00
co-authored by Claude Opus 5
parent 1f31ac6afd
commit f0dd8f70c1
11 changed files with 278 additions and 118 deletions
+4 -1
View File
@@ -27,7 +27,10 @@
вычитывают их два отдельных прохода: `task-form` (форма записи) и вычитывают их два отдельных прохода: `task-form` (форма записи) и
`task-wording` (язык записей); `task-wording` (язык записей);
- `session` — ритуал между спринтами и ведение спринта. - `session` — ритуал между спринтами и ведение спринта.
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.** - **av-dev-pipeline** — исполнение. **Требует OpenSpec и сам его заводит.**
- `openspec` — завести и настроить `openspec/` в проекте: `openspec init`,
замена примера в `config.yaml` настройкой канонической формы. Каталог
принадлежит конвейеру, а не канону: без конвейера он проекту не нужен;
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита; - `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
- `task-batch` — несколько задач разом, каждая в своём worktree; - `task-batch` — несколько задач разом, каждая в своём worktree;
- `review-pipeline` — конвейер ревью **по темам**: документ проекта либо - `review-pipeline` — конвейер ревью **по темам**: документ проекта либо
+6 -5
View File
@@ -149,11 +149,12 @@ capability), `openspec/config.yaml`.
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть; 1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**: 2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
незаполненное — одной честной информативной строкой, а не «TBD»; незаполненное — одной честной информативной строкой, а не «TBD»;
3. **OpenSpec, если его нет**`openspec init --tools claude`, и `config.yaml` 3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
по тому же скелету. Каталог есть, а `config.yaml` из коробки — тот же случай, Skill `av-dev-pipeline:openspec`**. Каталог принадлежит конвейеру, и команда
что отсутствие: закомментированный пример выглядит настройкой и не является заведения с формой файла живут там. Пересказ инвариантов, конвенций и правил
ею. Пересказ инвариантов, конвенций и правил ревью из `context` вычисти ревью из `context` вычисти ссылкой на дом — на переводимом проекте он там
ссылкой на дом — на переводимом проекте он там почти наверняка есть; почти наверняка есть. Вызов не разрешился — плагина конвейера нет, и это
строка доклада, а не поломка: `docs.py` о каталоге тогда тоже молчит;
4. переносы содержимого; 4. переносы содержимого;
5. каталог задач — **вызови скилл `av-dev-tasks:tasks`**, сценарий адаптации: он 5. каталог задач — **вызови скилл `av-dev-tasks:tasks`**, сценарий адаптации: он
владеет форматом задач. Он же переименует транслитные слаги в английские и владеет форматом задач. Он же переименует транслитные слаги в английские и
+16 -8
View File
@@ -114,7 +114,7 @@ openspec/
| `database.*` | источник | `operations` — схема и настройки с числами | | `database.*` | источник | `operations` — схема и настройки с числами |
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные | | `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
| `openspec/specs/` | источник | `requirements` | | `openspec/specs/` | источник | `requirements` |
| `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) | | `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем; заводит конвейер) |
| `tasks/` | процессный | — (чужое владение: плагин `av-dev-tasks`) | | `tasks/` | процессный | — (чужое владение: плагин `av-dev-tasks`) |
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) | | `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
| `adr.*` | процессный | — | | `adr.*` | процессный | — |
@@ -432,11 +432,18 @@ kebab-case.** Причина не эстетическая: имя файла с
пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй
дом разойдётся на первой же правке. дом разойдётся на первой же правке.
**Каталог `openspec/` — часть канона, а не соседняя технология.** В нём дом темы **Каталог `openspec/` принадлежит конвейеру, а не канону.** В нём дом темы
`requirements`, и заводится он командой: `openspec init --tools claude`. Её `requirements`, и нужен он тому, кто по OpenSpec работает: без каталога не
выполняет `init` на новом проекте и `adopt` на переводимом; из канона она названа работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Заводит и
поимённо потому, что её печатает отказ `docs.py`, а отказ без команды заставляет настраивает его скилл `av-dev-pipeline:openspec`; `init` и `adopt` его только
искать её в другом месте. зовут. Команда (`openspec init --tools claude`) названа здесь поимённо потому,
что её печатает вывод `docs.py`, а адрес без команды заставляет искать её в
другом месте.
**Отсюда и односторонность: канон о файле высказывается, но его не требует.**
`docs.py check` проверяет форму, **если каталог есть**, и говорит
«неприменимо», если его нет. Проект без конвейера живёт без OpenSpec законно, и
отказом это быть не может.
**Файл из коробки настройкой не является.** `openspec init` кладёт `config.yaml`, **Файл из коробки настройкой не является.** `openspec init` кладёт `config.yaml`,
где и `context`, и `rules` лежат закомментированным примером. Такой файл читается где и `context`, и `rules` лежат закомментированным примером. Такой файл читается
@@ -447,7 +454,8 @@ kebab-case.** Причина не эстетическая: имя файла с
Проверяется пять вещей, и каждая — про молчащий пробел, а не про вкус: Проверяется пять вещей, и каждая — про молчащий пробел, а не про вкус:
1. **`openspec/` есть.** Нет — нет и дома темы `requirements`. 1. **`openspec/` есть.** Нет — проверка неприменима, и это не отказ: каталог
нужен конвейеру, а не канону. Остальные четыре идут только при живом каталоге.
2. **Имя файла `config.yaml`.** `config.yml` OpenSpec не читает и об этом не 2. **Имя файла `config.yaml`.** `config.yml` OpenSpec не читает и об этом не
сообщает: настройка, написанная в файл с таким именем, пропадает целиком. сообщает: настройка, написанная в файл с таким именем, пропадает целиком.
3. **`context` и `rules.specs` не остались примером.** Правила для `specs` 3. **`context` и `rules.specs` не остались примером.** Правила для `specs`
@@ -570,7 +578,7 @@ OpenSpec переименует артефакт или сменит схему
```json ```json
{ {
"canon": 8, "canon": 9,
"migrations": "internal/store/migrations" "migrations": "internal/store/migrations"
} }
``` ```
@@ -13,6 +13,44 @@ upgrade` идёт по записям снизу вверх от версии п
--- ---
## Версия 9 — 2026-08-09
OpenSpec уехал в конвейер. Каталог `openspec/` версией 7 был объявлен слотом
канона: `init` его заводил, `adopt` тоже, образец `config.yaml` лежал в скелетах,
а отсутствие каталога `docs.py` считал отказом. Разрез был проведён не там. По
OpenSpec работает конвейер — без каталога не запускаются ни `opsx:propose`, ни
ревью дизайна, ни сверка требований, — а канон документов о нём только
высказывался. Проект, которому конвейер не нужен, получал отказ за отсутствие
того, чем не пользуется.
**Что появилось.** Скилл `av-dev-pipeline:openspec`: заводит каталог, заменяет
закомментированный пример в `config.yaml` настройкой, объясняет разрез между
ссылкой и пересказом. Образец файла переехал туда же — в
`references/config-skeleton.md` того скилла.
**Что изменилось.** `init` и `canon adopt` OpenSpec больше не заводят, а **зовут
скилл конвейера**; вызов не разрешился — плагина конвейера нет, и это строка
доклада, а не поломка. Отсутствие `openspec/` для `docs.py check` стало
неприменимостью вместо отказа: остальные четыре проверки формы идут только при
живом каталоге.
**Что осталось на месте и почему.** Проверка формы `config.yaml` и сторож версии
(`docs.py openspec-form`) пока живут в скрипте канона — переносить их значит
заводить в конвейере свой скрипт, а этого у него нет ни одного. Разрез названного
это не отменяет, но и не завершает: **у файла сейчас два плагина — один заводит,
другой проверяет**, и это временное состояние, а не задуманное.
**Что сделать проекту.**
1. Ничего не переносить: файлы проекта эта версия не двигает. Меняется только то,
кто их заводит.
2. Проверить, что плагин `av-dev-pipeline` установлен, если проект работает по
OpenSpec. Без него `docs.py check` про каталог промолчит — и молчание это
законное, так что отсутствие настройки перестанет ловиться само.
3. Проект **не** работает по OpenSpec: убедиться, что `openspec/` нет, и
перестать держать его пустым ради проверки. Она больше не требует каталога.
4. `docs/.pm.json`: `"canon": 9`.
## Версия 8 — 2026-08-09 ## Версия 8 — 2026-08-09
Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs` Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs`
@@ -419,89 +419,19 @@ severity стоит здесь, а не выводится каждым прох
## `openspec/config.yaml` ## `openspec/config.yaml`
Каталог `openspec/` заводится командой — `openspec init --tools claude`, — и она **Образец переехал.** Файл заводит и заполняет плагин конвейера — скилл
кладёт `config.yaml` с закомментированным примером внутри. Пример **заменяется `av-dev-pipeline:openspec`, — потому что по OpenSpec работает он, а не канон
целиком**: нетронутый файл выглядит настроенным, а работает как пустой. документов. Проект без конвейера каталога `openspec/` не имеет вовсе, и образец
файла, которого у него нет, в скелетах канона лежал бы мёртвым грузом.
**Это маршрутизатор, а не второй дом фактов.** Сюда пишут ровно то, что нужно
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл:
язык, правила именования 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` называет отказом.
**Ключи под `rules:` — имена артефактов схемы**, а не свободные слова:
`proposal`, `specs`, `design`, `tasks`. Правило под чужим именем не применяется
и об этом не сообщает, поэтому `rules.spec` вместо `rules.specs` даёт конфиг,
выглядящий написанным и не работающий; `docs.py check` такой ключ называет.
Перечень артефактов задаёт OpenSpec, а не канон, — за его актуальностью следит
`docs.py openspec-form`.
Канон о нём всё ещё **высказывается**, но только в одну сторону: `docs.py check`
проверяет форму, **если каталог есть**, и молчит, если его нет. Что именно
проверяется — [canon.md](canon.md), раздел `openspec/config.yaml`.
## `docs/.pm.json` ## `docs/.pm.json`
```json ```json
{ {
"canon": 8 "canon": 9
} }
``` ```
+10 -4
View File
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import NoReturn from typing import NoReturn
CANON_VERSION = 8 CANON_VERSION = 9
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4 OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
@@ -528,9 +528,15 @@ def check_openspec(root: Path, rep: Report) -> None:
""" """
os_dir = root / "openspec" os_dir = root / "openspec"
if not os_dir.is_dir(): if not os_dir.is_dir():
rep.error( # Каталог принадлежит конвейеру, а не канону: там дом темы requirements
"нет openspec/ — там дом темы requirements (openspec/specs/) и " # и настройка генерации артефактов, и нужен он тому, кто по OpenSpec
f"настройка генерации артефактов; заводится `{OPENSPEC_INIT}`" # работает. Проект без конвейера живёт без него законно, поэтому здесь
# неприменимость, а не отказ. Заводит каталог скилл
# av-dev-pipeline:openspec; команда названа на случай, если плагина нет.
rep.skip(
"нет openspec/ — проверка неприменима. Каталог заводит скилл "
f"av-dev-pipeline:openspec (`{OPENSPEC_INIT}`); без конвейера "
"он проекту не нужен"
) )
return return
+16 -19
View File
@@ -1,6 +1,6 @@
--- ---
name: init name: init
description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в роадмапе и скелет остальных документов. Заводит и OpenSpec (openspec init) с настроенным openspec/config.yaml — дом темы requirements, без которого не работают ни propose, ни ревью. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon." description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в роадмапе и скелет остальных документов. OpenSpec заводит не сам, а вызовом скилла av-dev-pipeline:openspec — каталог принадлежит конвейеру; плагина конвейера нет — шаг пропускается строкой доклада. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
--- ---
# Заведение нового проекта # Заведение нового проекта
@@ -28,7 +28,6 @@ description: "Завести новый проект — сессия вопро
| `security.md` | `conventions/` | | `security.md` | `conventions/` |
| `docs/tasks/ROADMAP.md` — первые цели | `research/`, `adr/` | | `docs/tasks/ROADMAP.md` — первые цели | `research/`, `adr/` |
| `docs/.pm.json` | `review.md` — журнал пуст, настройка появится с первым ревью | | `docs/.pm.json` | `review.md` — журнал пуст, настройка появится с первым ревью |
| `openspec/config.yaml` | |
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет, Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
заводится первой задачей». Проход читает её как факт. заводится первой задачей». Проход читает её как факт.
@@ -69,30 +68,28 @@ description: "Завести новый проект — сессия вопро
1. Прочитай бриф целиком. Выпиши, на какие блоки интервью ответ уже есть. 1. Прочитай бриф целиком. Выпиши, на какие блоки интервью ответ уже есть.
2. Проведи интервью итерациями по ≤3 вопроса. 2. Проведи интервью итерациями по ≤3 вопроса.
3. **Заведи OpenSpec: `openspec init --tools claude`.** Каталог `openspec/` 3. **OpenSpec — вызови Skill `av-dev-pipeline:openspec`.** Он заводит каталог и
часть канона, а не соседняя технология: в нём дом темы `requirements`, и без заменяет пример в `config.yaml` настройкой. Делается это **до первого
него не работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. документа**: без `openspec/` не работают ни `opsx:propose`, ни ревью дизайна,
Команда кладёт ещё `.claude/skills/openspec-*` и `.claude/commands/opsx/*` ни сверка требований. Каталог принадлежит конвейеру, а не канону, поэтому
это её нормальная работа, не трогай их. здесь только вызов — ни команды, ни формы файла `init` не знает.
**Вызов не разрешился — плагина конвейера в проекте нет.** Это законный исход,
а не поломка: проект без конвейера живёт без OpenSpec. Скажи это строкой в
докладе и иди дальше; `docs.py check` о каталоге тоже промолчит.
4. Заведи `docs/.pm.json` с текущей версией канона. 4. Заведи `docs/.pm.json` с текущей версией канона.
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и 5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
отдельным файлом не остаётся: два дома для одного замысла разойдутся на отдельным файлом не остаётся: два дома для одного замысла разойдутся на
первом же уточнении. первом же уточнении.
6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) — 6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) —
каждый с честной строкой. каждый с честной строкой.
7. **Заполни `openspec/config.yaml`** по тем же скелетам. Файл из коробки — 7. Каталог задач и первые цели — **вызови скилл `av-dev-tasks:tasks`**: он владеет
закомментированный пример на английском; он **заменяется целиком**, потому что форматом целей и задач. Не разрешился — учёт задач остаётся владельцу, и это
нетронутый выглядит настроенным, а работает как пустой. Пиши туда только то, тоже строка доклада.
что нужно **в момент порождения артефакта**: язык, правила именования 8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
capability, придирки валидатора и **адреса** `docs/passport.md` и `CLAUDE.md`.
Инварианты, конвенции и правило ревью не пересказывай — у них есть дома, и
второй дом разойдётся с первым молча.
8. Каталог задач и первые цели — **вызови скилл `av-dev-tasks:tasks`**: он владеет
форматом целей и задач.
9. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа. незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа.
10. Покажи человеку, что получилось, и **отдельным списком** — что выведено из 9. Покажи человеку, что получилось, и **отдельным списком** — что выведено из
брифа, что предположено, что осталось неизвестным. Правят по этим строкам. брифа, что предположено, что осталось неизвестным. Правят по этим строкам.
## Что дальше ## Что дальше
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "av-dev-pipeline", "name": "av-dev-pipeline",
"description": "Проведение задачи через полный цикл Spec Driven Development и конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом, независимой реализацией и обязательным триажем; плюс прогон нескольких задач разом по одной в изолированном worktree. Требует OpenSpec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.", "description": "Проведение задачи через полный цикл Spec Driven Development и конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом, независимой реализацией и обязательным триажем; плюс прогон нескольких задач разом по одной в изолированном worktree. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.",
"author": { "author": {
"name": "Anton Vakhrushev", "name": "Anton Vakhrushev",
"email": "anwinged@gmail.com" "email": "anwinged@gmail.com"
+97
View File
@@ -0,0 +1,97 @@
---
name: openspec
description: "Завести и настроить OpenSpec в проекте — openspec init --tools claude, замена закомментированного примера в openspec/config.yaml на настройку канонической формы (язык, правила именования capability, придирки валидатора, адреса паспорта и CLAUDE.md), проверка, что форма не разошлась с живой версией инструмента. Использовать, когда в проекте нет каталога openspec/, когда config.yaml остался примером из коробки, когда заводят новый проект или переводят чужой и дошли до шага OpenSpec, а также когда конвейер отказался работать без источника требований. Каталог openspec нужен именно конвейеру: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований."
---
# OpenSpec в проекте
Каталог `openspec/`**предпосылка конвейера**, а не канона документов. Без него
не работают ни `opsx:propose`, ни ревью дизайна, ни `review-specs`: у требований
не остаётся дома. Поэтому заводит и настраивает его этот плагин — тот, кто по
OpenSpec и работает.
Канон документов о файле всё ещё высказывается, но односторонне: `docs.py check`
проверяет форму `config.yaml`, **если каталог есть**, и молчит, если его нет.
Проект без конвейера живёт без OpenSpec законно.
## Два шага, и второй важнее первого
**1. Завести.**
```
openspec init --tools claude
```
Команда кладёт ещё `.claude/skills/openspec-*` и `.claude/commands/opsx/*` — это
её нормальная работа, не трогай их.
**2. Заменить пример.** `openspec init` кладёт `config.yaml`, где `context` и
`rules` — закомментированный пример на английском. **Файл из коробки хуже
отсутствующего:** он есть, он валиден, имя правильное, — и читается как
настроенный, работая как пустой. Узнаётся это по уже написанному предложению: на
другом языке, с capability по имени пакета, без единого `SHALL`.
Пример **заменяется целиком** по образцу:
[references/config-skeleton.md](references/config-skeleton.md).
## Что туда пишут, а что нет
**Это маршрутизатор, а не второй дом фактов.** Внутрь идёт ровно то, что нужно
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл: язык,
правила именования capability, придирки валидатора и **адреса** документов
проекта.
Пересказ паспорта, инвариантов, конвенций и правил ревью сюда **не переносится**.
Место для второго дома здесь самое частое: `context` читается при порождении
каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии
инвариантов, состава гейта и правил выбора метки. Расходятся они молча, а
замечают это в уже написанном предложении.
Разрез, по которому отличают одно от другого: **утверждение, которое можно
опровергнуть, открыв другой файл проекта, — пересказ; строка, которая говорит,
какой файл открыть, — ссылка.** Машина этот разрез не проверяет; его смотрит
агент `doc-consistency` из плагина канона, когда тот подключён.
Два адреса обязательны — `docs/passport.md` и `CLAUDE.md`: предложение пишется до
того, как кто-либо откроет `docs/`, и без них его пишут, не зная ни границы
домена, ни инвариантов. Их отсутствие `docs.py check` называет отказом.
## Форма сверяется с живым инструментом
Схема (`spec-driven`) и перечень артефактов (`proposal`, `specs`, `design`,
`tasks`) — **состояние чужого инструмента**, а не наше решение. OpenSpec
переименует артефакт: правила под прежним именем перестанут применяться, конфиг
останется выглядеть написанным, и молчат при этом все три стороны.
Сторож — сравнение версий, и живёт он пока в `docs.py` плагина канона:
```
python3 <канон>/skills/canon/scripts/docs.py openspec-form
```
`check` каждым прогоном сравнивает `major.minor` установленного OpenSpec с той
версией, на которой форма сверялась, и при расхождении просит эту команду. Она
ничего не правит — спрашивает инструмент и печатает, что разошлось. **Чинится
расхождение в плагине, а не в проекте.**
Плагина канона в проекте нет — сторожа тоже нет, и это надо назвать строкой, а не
считать, что форма верна.
## Кто зовёт этот скилл
- `av-dev-docs:init` — шагом заведения нового проекта, до первого документа;
- `av-dev-docs:canon` в режиме `adopt` — если на переводимом проекте каталога нет
или `config.yaml` остался примером;
- человек — когда конвейер отказался работать без источника требований.
Вызов идёт **через пространство имён**, а не путём в дерево плагина. Не
разрешился — плагина конвейера в проекте нет, и тогда OpenSpec заводит человек
командой выше; скажи это строкой, а путь не выдумывай.
## Чего этот скилл не делает
- **Не пишет спеки и предложения.** Это `opsx:propose` и пайплайн задачи.
- **Не ведёт документы канона** — их дом плагин `av-dev-docs`, и адреса в
`context` только на них ссылаются.
- **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в
плагине: константы скрипта, образец здесь, запись в журнал версий канона.
@@ -0,0 +1,79 @@
# Образец `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` называет отказом.
**Ключи под `rules:` — имена артефактов схемы**, а не свободные слова:
`proposal`, `specs`, `design`, `tasks`. Правило под чужим именем не применяется
и об этом не сообщает, поэтому `rules.spec` вместо `rules.specs` даёт конфиг,
выглядящий написанным и не работающий; `docs.py check` такой ключ называет.
Перечень артефактов задаёт OpenSpec, а не канон, — за его актуальностью следит
`docs.py openspec-form`.
@@ -52,8 +52,9 @@ description: "Конвейер ревью изменения, устроенны
требований. **Проект без OpenSpec этим конвейером не проверяется** — подключай требований. **Проект без OpenSpec этим конвейером не проверяется** — подключай
OpenSpec, а не понижай прогон: ветка деградации здесь не пишется, потому что OpenSpec, а не понижай прогон: ветка деградации здесь не пишется, потому что
непроверенная ветка деградации хуже честного отказа. Заводить руками не надо: непроверенная ветка деградации хуже честного отказа. Заводить руками не надо:
`av-dev-docs:init` делает `openspec init` на новом проекте, `canon adopt` — на этим владеет скилл `av-dev-pipeline:openspec` — он заводит каталог и заменяет
переводимом, и оба кладут `openspec/config.yaml` канонической формы. пример в `config.yaml` настройкой. Его же зовут `av-dev-docs:init` на новом
проекте и `canon adopt` на переводимом.
- **Документы канона** — см. следующий раздел. - **Документы канона** — см. следующий раздел.
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в - **Проектные копии этих скиллов и агентов удаляются при установке.** Если в
проекте уже лежат свои `.claude/skills/review-pipeline`, проекте уже лежат свои `.claude/skills/review-pipeline`,